Advanced » Explicit Setup

Block style setup is suitable for simple, quick'n'dirty scripts that need to access databases, in a typical application setup, you want to break down individual component definitions, like relations or commands, into separate files and define them as explicit classes.

ROM & Frameworks

Framework integrations take care of the setup for you. If you want to use ROM with a framework, please refer to specific instructions under Getting Started section

Setup

To do setup in flat style, create a ROM::Configuration object. This is the same object that gets yielded into your block in block-style setup, so the API is identical.

configuration = ROM::Configuration.new(:memory, 'memory://test')
configuration.relation(:users)
# ... etc

When you’re finished configuring, pass the configuration object to ROM.container to generate the finalized container. There are no differences in the internal semantics between block-style and flat-style setup.

Registering Components

ROM components need to be registered with the ROM configuration in order to be used.

configuration = ROM::Configuration.new(:memory, 'memory://test')

# Declare Relations, Commands, and Mappers here

If you prefer to create explicit classes for your components you must register them with the configuration directly:

configuration = ROM::Configuration.new(:memory, 'memory://test')

configuration.register_relation(OneOfMyRelations)
configuration.register_relation(AnotherOfMyRelations)
configuration.register_command(User::CreateCommand)
configuration.register_mapper(User::UserMapper)

You can pass multiple components to each register call, as a list of arguments.

Auto-registration

ROM provides auto_registration as a convenience method for automatically require-ing and registering components that are not declared with the DSL. At a minimum, auto_registration requires a base directory. By default, it will load relations from <base>/relations, commands from <base>/commands, and mappers from <base>/mappers.

Namespaces inferred from directory structure

By default, auto-registration assumes that the directory structure reflects your module/class organization, for example:

# lib/relations/users.rb
module Relations
  class Users < ROM::Relation[:memory]
    schema(infer: true)
  end
end

# lib/relations

Then to set up auto-registration simply provide the root path to your components directory:

configuration = ROM::Configuration.new(:memory)
configuration.auto_registration('/path/to/lib')
container = ROM.container(configuration)

In special cases where the components are located deeper in the file structure all you have to do is provide the path to the outer most module and the rest will be inferred from the directory structure:

# lib/persistence/relations/users.rb
module Persistence
  module Relations
    class Users < ROM::Relation[:memory]
      schema(:users, infer: true) # Notice the dataset name is set explicitly
    end
  end
end

configuration = ROM::Configuration.new(:memory)
configuration.auto_registration('root_dir/lib/persistence/')
container = ROM.container(configuration)

In this scenario the Dataset name will need to be set explicitly otherwise the fully qualified relation name will be used, in this case :persistence_relations_users.

Explicit namespace name

If your directory structure doesn't reflect module/class organization but you do namespace components, then you can set up auto-registration via :namespace option:

# lib/relations/users.rb
module Persistence
  module Relations
    class Users < ROM::Relation[:sql]
      schema(infer: true)
    end
  end
end

Notice that the directory structure is different from our module structure. Since we use Persistence as our namespace, we need to set it explicitly so ROM can locate our relation after loading:

configuration = ROM::Configuration.new(:memory)
configuration.auto_registration('/path/to/lib', namespace: 'Persistence')
container = ROM.container(configuration)

Keep in mind with this namespace strategy, each component must be located under a module matching the components name:

# Commands
# lib/commands/update_user_command.rb
module Persistence
  module Commands
    class UpdateUserCommand < ROM::SQL:Commands::Create
      relation :users
      register_as :update_user_command

      def execute(tuple); end
    end
  end
end

# Mappers
# lib/mappers/user_mapper.rb
module Persistence
  module Mappers
    class UserMapper < ROM::Transformer
      relation :users
      register_as :user_mapper

      map_array do; end
    end
  end
end

Turning namespace off

If you keep all components under {path}/(relations|commands|mappers) directories and don't namespace them, then you can simply turn namespacing off:

# lib/relations/users.rb
class Users < ROM::Relation[:sql]
  schema(infer: true)
end
configuration = ROM::Configuration.new(:memory)
configuration.auto_registration('/path/to/lib', namespace: false)
container = ROM.container(configuration)

Relations

Relations can be defined with a class extending ROM::Relation from the appropriate adapter.

# Defines a Users relation for the SQL adapter
class Users < ROM::Relation[:sql]

end

# Defines a Posts relation for the HTTP adapter
class Posts < ROM::Relation[:http]

end

Relations can declare the specific gateway and dataset it takes data from, as well as the registered name of the relation. The following example sets the default options explicitly:

class Users < ROM::Relation[:sql]
  register_as :users    # the registered name; eg. for use in Repository’s relations(...) method
  gateway :default      # the gateway name, as defined in setup
  dataset :users        # eg. in sql, this is the table name
end

Commands

Just like Relations, Commands can be defined as explicit classes:

class CreateUser < ROM::Commands::Create[:memory]

end

Commands have three settings: their relation, which takes the registered name of a relation; their result type, either :one or :many; and their registered name.

class CreateUser < ROM::Commands::Create[:memory]
   register_as :create
   relation :users
   result :one
end

Typically, you're going to use repository command interface and changesets; custom command classes are useful when the built-in command support in repositories doesn't meet your requirements