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 ;)).