Exactly a year ago ROM 0.3.0 was released after the reboot of the project was announced, it was a complete rewrite that introduced the new and simplified adapter interface. Since then we've come a long way, the community has been growing, rom-rb organization on Github has now 28 projects including 16 adapters.
The core API, which also includes adapter interface, is stabilizing. This means we are coming very close to releasing the final version of rom 1.0.0 and today I'm very happy to announce that its first beta1 was released. Please notice this is the release of the core rom gem - adapters and other extensions remain unstable (in the sem-ver sense).
To ease testing we also released beta versions of minor upgrades of other rom gems so that you can install them from rubygems rather than relying on github sources. Overall the release includes following updates:
Setting up ROM has been refactored and specific responsibilites have been broken down into smaller, and explicit, objects. Unfortunately it is a breaking change as ROM.setup method is gone. If you're using ROM with rails, things should continue to work, but if you have a custom rom setup additional actions are required.
This was an important change that resulted in a cleaner API, removed complex logic that used to rely on inheritance hooks to automatically register components and reduced the amount of global state that ROM relies on.
We kept convenience in mind though and introduced a feature that uses dir/file structure to infer components and register them automatically but without the complexity of relying on inheritance hooks.
Here's an example of a step-by-step setup with explicit registration:
# instead of ROM.setup:config=ROM::Configuration.new(:sql,'postgres://localhost/rom')classUsers<ROM::Relation[:sql]endconfig.register_relation(Users)# creates rom container with registered componentscontainer=ROM.container(config)
Here's an example of a setup that infers components from dir/file names:
config=ROM::Configuration.new(:sql,'postgres://localhost/rom')# configure auto-registration by providing root path to your components# namespacing is turned offconfig.auto_registration("/path/to/components",namespace: false)# assuming there's `/path/to/components/relations/users.rb`# which defines `Users` relation class it will be automatically registered# creates rom container with registered componentscontainer=ROM.container(config)
You can also use auto-registration with namespaces, which is turned on by default:
config=ROM::Configuration.new(:sql,'postgres://localhost/rom')# configure auto-registration by providing root path to your components# namespacing is turned on by defaultconfig.auto_registration("/path/to/components")# assuming there's `/path/to/components/relations/users.rb`# which defines `Relations::Users` class it will be automatically registered# creates rom container with registered componentscontainer=ROM.container(config)
For a quick-start you can use an in-line style setup DSL:
Probably the most noticable improvement/feature is the addition of the command graph DSL. The command graph was introduced in 0.9.0 and it allowed you to compose a single command that will be able to persist data coming in a nested structure, similar to nested_attributes_for in ActiveRecord, but more flexible.
This release introduces support for update and delete commands in the graph as well as a new DSL for graph definitions. Here's an example:
# assuming `rom` is your rom container and you have `create` commands# for :users and :books relationscommand=rom.command# returns command builder# define a command that will persist user data with its book datacreate_command=command.create(user: :users)do|user|user.create(:books)end# call it with a nested inputcreate_command.call(user: {name: "Jane",books: [{title: "Book 1"},{title: "Book 2"}]})
It also supports update (delete works in the same way):
# assuming `rom` is your rom container and you have `update` commands# for :users and :books relationscommand=rom.command# returns command builder# define a command that will restrict user by its id and update ituser_update=command.restrict(:users){|users,user|users.by_id(user[:id])}update_command=command.update(user: user_update)do|user|# define an inner update command for booksbooks_update=user.restrict(:books)do|books,user,book|books.by_user(user).by_id(book[:id])enduser.update(books: books_update)end# call it with a nested inputupdate_command.call(user: {id: 1,name: "Jane Doe",books: [{id: 1,title: "Book 1"},{id: 2,title: "Book 2"}]})
As a bonus, you are free to use all types of commands in the same graph and have complete freedom in defining how specific relations must be restricted for a given command.
New Command Result API
Starting from 1.0.0 you can check whether a command result was successful or not:
create_command=command.create(user: :users)do|user|user.create(:books)endresult=create_command.call(user: {name: "Jane",books: [{title: "Book 1"},{title: "Book 2"}]})result.success?# true if everything went fine, false otherwiseresult.failure?# true if it failed, false otherwise
Relation API Extensions
Early version of rom-repository introduced a couple of plugins that now have become part of the core rom gem. They are opt-in and the adapter developers must decide whether or not it makes sense to enable them for adapter relations.
View
Relation view plugin is a DSL for defining relation views with an explicit header definition. It is typically useful for reusable relation projections that you can easily compose together in repositories.
This plugin plays major role in relation composition as it defines the header up-front, which allows repositories to generate mappers automatically, which is very convenient. It is also a nice way of specifying re-usable relation projections which some times may indicate where using an actual database view (assuming your db supports it) could simplify your queries.
Key Inference
This simple plugin provides default value for a foreign-key in a relation. It is used for generating relation views used for composition in the repositories.
You can use it too:
rom.relation(:users).foreign_key# => `:user_id`
Defining Default Datasets
It is now possible to not only specify the name of a relation dataset, but also configure it using a block, when you do that your relation will be initialized with whatever that blocks returns, it is executed in the context of the dataset object:
Please try out the beta releases and provide feedback. Once we are sure that it works for everybody we'll be able to push the first RC and hopefully follow-up with the final 1.0.0 release shortly after the RC. Other gems that are now released as betas will be bumped to final versions and depend on rom 1.0.0 final.
The final release also means a major update of rom-rb.org along with a new set of documentation, guides and tutorials. This is still a work in progress and needs help, please get in touch if you're interested in helping out.
Once rom 1.0.0 is out there will be major focus on rom-sql and rom-repository. There's a plan to improve query DSL in rom-sql and provide full CRUD interface for repositories that should be handy for simple applications.
If you see any issues, please report them in the individual issue trackers on Github or main rom if you are not sure which gem it relates to.
We are pleased to announce the release of ROM 0.9.0! This is a big release which focuses on internal clean-up in the core library as a preparation for 1.0.0. For those of you hungry for new features - you won't be disappointed. As part of this release we are introducing new adapters, new gems extracted from rom and the long awaited high-level interface for ROM called rom-repository.
For notes about upgrading to ROM 0.9.0 please refer to Upgrade Guides.
Probably the most significant addition coming with this release is rom-repository. Using lower-level APIs and configuring mappers manually is tedious in most of the cases that's why Repository was introduced.
Repository interface is very simple and built on top of Relation and Mapper API. It allows you to easily work with relations and have results automatically mapped to struct-like objects. There are a couple of neat plugins that ship with this gem which make relation composition ridiculously simple.
Repositories work with all adapters which means you can combine data from different data sources.
Please refer to Repository Guide for the rationale and more information.
Multi-Environment Support
Initially, ROM supported its setup process through a global environment object. This was a good start that worked well with frameworks like Rails that expect globally accessible objects; however, we're pushing towards removing global state as much as possible.
For that reason in ROM 0.9.0 you can configure the environment as a standalone object, which comes with the benefit of being able to have more than one environment. Why would you want to have many environments? For example for database sharding, or separating components within your application where data comes from different sources and you want to keep them isolated.
Here's an example of a multi-environment setup:
classPersistence::Command::CreateUser<ROM::Commands::Create[:sql]relation:usersregister_as:createendclassPersistence::Query::Users<ROM::Relation[:sql]dataset:usersendcommand_env=ROM::Environment.newcommand_env.setup(:sql,[:postgres,'postgres://command_host/my_db')command_env.register_relation(Persistence::Command::CreateUser)command_container=command_env.finalize.envcommand_container.command(:users)# access to defined commandsquery_env=ROM::Environment.newquery_env.setup(:sql,[:postgres,'postgres://query_host/my_db')query_env.register_relation(Persistence::Query::Users)query_container=query_env.finalize.envquery_container.relation(:users)# access to defined relations
Global setup process still works, but please refer to upgrade guide if you are using ROM standalone without any framework integration.
Gateway Configuration Support
A new interface for configuring individual adapter gateways has been added. For now the only customization you can make is configuring how relation inferrence should work:
# disable inferring relations from schemaROM.setup(:sql,[:postgres,'postgres://localhost/db',infer_relations: false])# cherry-pick which relations should be inferredROM.setup(:sql,[:postgres,'postgres://localhost/db',inferrable_relations: [:users,:tasks]])# disallow inferrence for specific relationsROM.setup(:sql,[:postgres,'postgres://localhost/db',not_inferrable_relations: [:some_table]])
This feature is useful when you have a big database and you don't want to use ROM to deal with all of your relations.
Extracted Standalone Mappers
You can now install rom-mapper as a standalone gem and use the powerful mapping DSL:
Mappers are very powerful, make sure to check out the Mapper Guides.
All Relations Are Lazy
Before 0.9.0, ROM had a separate layer for decorating your relations with a lazy-proxy wrapper. This has caused some confusion and unnecessary complexity, as the relations you defined were not the same thing that the #relation() method returned. It also turned out that implementing rom-repository was more difficult than it should have been.
That's why in ROM 0.9.0 all relations have lazy interface. It means that every relation method you define is auto-curried:
classUsers<ROM::Relation[:sql]defby_name(name)where(name: name)endend# assuming your container is called `rom`users=rom.relation(:users)user_by_name=users.by_name# returns auto-curried relationuser_by_name['Jane'].one!# call later on to apply the required argument
Adapter Query DSL is Public
Starting from ROM 0.9.0, the query interface exposed by individual adapters is public, but it is not recommended to use it directly in your application. Relations should be used to encapsulate data access properly and query DSLs should not leak to the application layer.
# this is considered as a smellusers.where(name: "Jane")# that's the proper way™users.by_name("Jane")
Extracted Model Extensions
A couple of useful extensions have been extracted from the rom-rails gem into rom-model. These are standalone components that are based on Virtus and ActiveModel. We have an ambitious plan to rewrite it in the future on top of more powerful tools. Please refer to rom-model README for more information.
Right now you can use Attributes and enhanced Validator objects with nice support for embedded validations:
The new, abstract rom-http adapter is a fantastic addition to the growing list of ROM adapters. It gives you a solid foundation for building a custom adapter which needs to talk via HTTP protocol. It's pretty flexible, and works like any other rom adapter - which means that you can use either the lower-level relation and mapping APIs or set it up with rom-repository and auto-mapping.
With ROM 0.9.0 we're close to the first stable 1.0.0 release, but there's still a lot to be done. Please consider supporting this great effort.
Please also remember that ROM is a project open for contributions and currently we have 24 repositories under our GitHub organization. There are many adapters looking for maintainers, there are many smaller tasks to do in core libraries, framework integrations and other extensions. Please get in touch if you're interested in contributing <3.
Reporting Issues and Support
All repositories now have their own issue trackers enabled on GitHub. If you find a bug, or have problems using ROM, please report an issue for a specific project. If you're not sure which project it relates to, just report it in the main rom issue tracker, and we'll move it to the right place if needed.
For any random questions and support requests you can talk to us on zulip.
Last but not least - we're looking for help in setting up a Discourse instance on DigitalOcean to make it simpler for people to discuss things as an alternative to gitter.
In case you missed it, ROM is part of the second edition of ROSSConf in Berlin, where you'll have a chance to contribute to the project. We have a crazy plan to release 1.0.0 during the event or at least close all the remaining issues and get an RC out of the door. :)
We'll be working on the list of issues scheduled for 1.0.0, thus it is important to get as much feedback as possible from you.
Please try out ROM 0.9.0. Let us know your thoughts. Report issues, ideas, comments, anything that can help in specifying what should be done for 1.0.0 will be grately appreciated.