Sustainable Development Campaign

ROM has been in development for a couple of years already, in fact, its early prototype was built in 2012, and has gone through multiple phases before the final architecture was introduced in October last year. This has been a huge effort which resulted in the creation of many smaller libraries and, of course, ROM itself.

One of the main reasons that this project was started was to discover a better way of writing Ruby code that would result in a cleaner architecture for the systems we're building. The Ruby ecosystem has mostly been shaped by the Active Record pattern introduced in the Rails framework, but ROM tries to move away from the common approach, so that it's easier to build and grow systems built in Ruby.

It's been an interesting journey with lots of experimentation, and we are getting there, as ROM is approaching 1.0.0.

There are a few mind-shifting aspects of ROM which are worth consideration as general programming guidelines in Ruby:

  • Prefer simple objects that don't change (mutate) and have a single purpose to exist
  • Prefer interfaces that have no side-effects
  • Embrace simple data structures rather than complicated objects that mix data and behavior
  • Prefer objects over classes, and always treat dependencies explicitly
  • Pay attention to boundaries and proper separation of concerns

Those guideliness have led to the decision that ROM will not be a typical Object Relational Mapper which tries to hide your database behind walls of complicated and leaky abstractions and pretend that "you can just use objects". This is the reason why ROM gives you a powerful interface to use your database effectively; which can be levaraged to design your systems with simpler data structures in mind and decouple your application layer from persistence concerns.

Such an approach has a significant impact on lowering complexity of your application layer.

Growing The Ecosystem

ROM is already a big project; if you consider its lower level libraries, over 14 adapters and integration with 3 frameworks. But it goes beyond that - other libraries are being developed that are based the on same programming principles. It is absolutely amazing to see that happening. The ecosystem is growing.

We have reached a point where ROM needs a sustainable pace of development, so that following things can happen:

  • Reach 1.0.0 in September
  • Extract re-usable APIs into separate libraries so that other projects can benefit from them
  • Improve existing adapters to be production-ready (the more the merrier)
  • Address all known issues in a timely fashion
  • Support users in multiple channels
  • Establish a solid release process with a CI setup that can help in developing adapters and extensions

In order to be able to do all of that we need your support.

Campaign on Bountysource

Bountysource is a service that helps in raising funds for Open Source projects. Its latest feature, called Salt, allows sustainable fundraising on a monthly basis. This makes it possible to support a steady pace of development - which is crucial for a complex project like ROM.

If you'd like to see ROM grow faster, please consider supporting the project through the campaign.

We want to take the project to the next level with this campaign and hope to expand the team so that more people can work continuously on ROM. We are happy to use the funds to sponsor work on ROM itself and also on any other library that the project could benefit from.

UPDATE

We moved our donation campaign to opencollective.com/rom

What's in it for you?

There are many ways in which you and your company can benefit from ROM today, but there's still a lot to be done to make ROM simpler to use for the common application use-case. This includes adding convenient, high-level abstractions as well as providing great documentation and other resources that would teach people how to use ROM.

If you're interested in the project but it still feels "weird" or "too complicated", this is exactly the reason that the campaign was started. ROM should be accessible for everybody, including less experienced developers. Not only do we want to promote ROM, but also, the general approach to writing Ruby code, which we believe results in a better and more maintainable code-base.

For any questions or concerns, please do not hesitate to get in touch.


ROM 0.8.0 Released

We're very happy to announce the release of Ruby Object Mapper 0.8.0. This release ships with the support for nested input for commands and many improvements in mappers. You can look at the changelog for the full overview.

Apart from ROM 0.8.0 release there are also updates of the following gems:

  • rom-sql 0.5.2 which comes with improved migration tasks that no longer require env finalization CHANGELOG
  • rom-rails 0.4.0 with the support for embedded validators CHANGELOG

There are 2 new adapters added to the rom-rb organization so check them out:

Support For Nested Input

ROM commands are now even more powerful by allowing composition of multiple commands into one that can receive a nested input which will be used to insert data into multiple relations. This feature is compatible with combined relation mapping which means you can pipe results from a combined command through mappers just like in case of combined relations.

When do you want to use this feature? Every time you want to persist entire object graph, in example a post with its tags or a user with an address.

Here's a complete example of using combined commands with a mapper:

ROM.setup(:sql, 'postgres://localhost/rom')

ROM::SQL.gateway.connection.create_table :posts do
  primary_key :id
  column :title, String
end

ROM::SQL.gateway.connection.create_table :tags do
  primary_key :id
  foreign_key :post_id, :posts, null: false
  column :name, String
end

class Posts < ROM::Relation[:sql]
end

class Tags < ROM::Relation[:sql]
end

class CreatePost < ROM::Commands::Create[:sql]
  relation :posts
  result :one
  register_as :create
  input Transproc(:accept_keys, [:title]) # filters out `:tags` key
end

class CreateTag < ROM::Commands::Create[:sql]
  relation :tags
  register_as :create
  input Transproc(:accept_keys, [:name, :post_id])
  associates :post, key: [:post_id, :id] # automatically sets FK value
end

class PostMapper < ROM::Mapper
  relation :posts
  register_as :entity

  combine :tags, on: { id: :post_id }
end

rom = ROM.finalize.env

create_post_with_tags = rom
  .command([{ post: :posts }, [:create, [:tags, [:create]]]])
  .as(:entity)

create_post_with_tags.call(
  post: { title: 'Hello World', tags: [{ name: 'red' }, { name: 'green' }] }
).to_a
# [
#   {
#     :id => 1,
#     :title => "Hello World",
#     :tags => [
#       { :id=>1, :post_id=>1, :name=>"red" },
#       { :id=>2, :post_id=>1, :name=>"green" }
#      ]
#   }
# ]

Mapper Steps

Transforming data with mappers can be really complex and sometimes you may want to define multiple mapping steps. That's why we introduced a new interface in Mapper DSL where you can do just that:

class UserMapper < ROM::Mapper
  step do
    attribute :id, from: :user_id
    attribute :name, from: :user_name
  end

  step do
    wrap :details do
      attribute :name
    end
  end
end

mapper = UserMapper.build

mapper.call([{ user_id: 1, user_name: 'Jane' }])
# [{ :id => 1, :details => { :name => "Jane" } }]

Typically you want to use this feature when mapping logic is too complex to be expressed using nested blocks. It's especially useful when dealing with multiple group/ungroup/wrap/unwrap/fold/unfold operations that simply cannot be defined as a deeply nested mapping definition block.

New Mapping Transformations

We have 3 new transformations fold, unfold and ungroup which makes mappers even more powerful.

Folding can be used to collapse values from multiple tuples under a single array attribute:

class PostFoldMapper < ROM::Mapper
  fold tag_list: [:tag_name]
end

mapper = PostFoldMapper.build

puts mapper.call([
  { title: 'Hello World', tag_name: 'red' },
  { title: 'Hello World', tag_name: 'green' }
]).inspect
# [{:title=>"Hello World", :tag_list=>["red", "green"]}]

Unfolding is, unsurprisingly, an inversion of folding:

class PostUnfoldMapper < ROM::Mapper
  unfold :tag_name, from: :tag_list
end

mapper = PostUnfoldMapper.build

puts mapper.call([{ title: 'Hello World', tag_list: ['red', 'green'] }]).inspect
# [{:tag_name=>"red", :title=>"Hello World"}, {:tag_name=>"green", :title=>"Hello World"}]

Now you can also ungroup tuples:

class PostUngroupMapper < ROM::Mapper
  ungroup :tags do
    attribute :tag_name, from: :name
  end
end

mapper = PostUngroupMapper.build

puts mapper.call([
  { title: 'Hello World', tags: [{ name: 'red' }, { name: 'green' }] }
]).inspect
# [{:tag_name=>"red", :title=>"Hello World"}, {:tag_name=>"green", :title=>"Hello World"}]

Guides

ROM is growing really fast and there's a lot of functionality that is difficult to describe in API documentation. That's why we started a new Guides section on the official rom-rb.org website.

You can already find a lot of information about ROM setup, adapters, relations, commands and mappers. We'll be adding more content and improving existing documentation based on the feedback so please check them out and let us know what you think.

In the upcoming weeks you should also see new tutorials covering topics like building your own persistence layer with ROM, handling data import with ROM or how to use ROM with various JSON serializers like Roar or Yaks, so stay tuned!

Next Release

We have a pretty good understanding of what we want to achieve with the next 0.9.0 release which will improve the internal architecture of ROM. We're planning to split rom gem into smaller pieces and introduce cleaner and more explicit interfaces for setting up ROM.

Another planned change is introducing Policy Over Configuration API which should improve ROM configuration and handling various conventions.

This release will be a big step towards 1.0.0 which is scheduled for September (yes, this year ;)).


  • 7 of 9