Project

jet_set

0.0
No commit activity in last 3 years
No release in over 3 years
JetSet is a microscopic ORM for DDD projects.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 2.1
>= 10.0
>= 3.0
>= 0.16
>= 1.3

Runtime

>= 1.0.0
>= 5.4.0
 Project Readme

JetSet Build Status

JetSet is a data mapping framework for domain-driven developers who think that SQL is the best tool for data querying. JetSet is built on top of Sequel ORM and it's just an abstraction for making the persistence of mapped objects invisible.

Installation

Add this line to your application's Gemfile:

gem 'jet_set'

And then execute:

$ bundle

Or install it yourself as:

$ gem install jet_set

Usage

Initialization

Open DB connection, see Sequel docs:

 @connection = Sequel.connect('sqlite:/') # you can connect to any DB supported by Sequel

Create a mapping of your model, details described [here]:

class Mapping
  def self.load_mapping
    JetSet::map do
      entity User do
        field :first_name # reqular field
        collection :invoices, type: Invoice # "has many" association
        reference :plan, type: Plan, weak: true # "belongs to" association
      end
    end
  end
end

Init JetSet environment on start of your application:

JetSet::init(Mapping.load_mapping, @container)

Open JetSet session:

@jet_set = JetSet::open_session(@connection)

For web-applications it's reasonable to bind JetSet session to request lifetime - all modification operations in an MVC action can represent a "Unit of Work".

Object model

Using JetSet you can wrap an application domain model and purely implement "Persistence Ignorance" approach. The model objects are pure Ruby objects without any noisy stuff like annotations, inline mapping, etc:

class User
  attr_reader :invoices

  def initialize(attrs = {})
    @first_name = attrs[:first_name]
    @last_name = attrs[:last_name]
    @invoices = []
  end

  def add_invoice(invoice)
    @invoices << invoice
  end
end

class Invoice
  attr_reader :created_at, :amount

  def initialize(attrs = {})
    @created_at = DateTime.now
    @amount = attrs[:amount] || 0
  end
end

Object model tracking and saving

Create an objects which is described in the mapping:

user = User.new(first_name: 'Ivan', last_name: 'Ivanov')
invoice = Invoice.new(created_at: DateTime.now, user: user, amount: 100.0)

Attach them to the session:

 @session.attach(invoice, user)

It makes the objects tracked by JetSet.

Finalize the session:

 @session.finalize

It saves all added/changed objects to the database.

Object model loading

user_query = <<~SQL
  SELECT
    u.* AS ENTITY user
  FROM users u
  LIMIT 1
SQL

invoices_sql = <<~SQL
  SELECT
    i.* AS ENTITY invoice
  WHERE i.user_id = :user_id
SQL

customer = @session.fetch(User, user_query) do |user|
  preload(user, :invoices, invoices_sql, user_id: user.id)
end

All loaded objects are already attached to the session and you can perform a changes which will be saved after the session finalization:

customer.invoices[0].apply # changes invoice state
@session.finalize

Do not load your object model just for drawing a views. For showing a results just use Sequel without any object mappings:

result = @connection[:user].where(role: 'admin').to_a
json = JSON.generate(data: result)

In other words, following CQS approach you can load your model for a command but not for a query.

Validation

Simple validation is optional feature provided by JetSet out of the box. To add this to your domain object you just need to include module JetSet::Validations and use validate statements:

require 'jet_set/validations'

class User
  include JetSet::Validations
  validate :name, 'cannot be empty', -> (value) {!value.nil? && !value.empty?}
  validate :email, type: :string, presence: true
  validate :email, 'should be valid email address', -> (value) {value.match(...)}

  def initialize(attrs = {})
    @name = attrs[:name]
  end
end

JetSet uses such validations automatically on saving objects in the database. Also you can invoke validation manually, i.e. in unit tests, using validate! method:

user = User.new(name: nil)
user.validate! # raises JetSet::ValidationError

JetSet::ValidationError contains details regarding invalid items like:

error.invalid_items # => {name: 'cannot be empty'}

You can find more interesting examples in JetSet integration tests.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jet_set.

License

The gem is available as open source under the terms of the MIT License.