Hexx::Storage

Gem Version Build Status Dependency Status Code Climate Coverage Inline docs

The module provides additional sugar for setting sweety ROM from yml.

Synopsis

Providing some settings exist:

# application_path/config/rom/my_gem.yml
---
test: # required environment level
  foo: # required repository name level
    adapter: :memory
  bar:
    adapter: :memory
    log:     "tmp/logs/my_gem/test/bar.log"
production:
  foo:
    adapter:  :memory
  bar:
    adapter:  :sql
    uri:      "postgres://localhost/rom"
    user:     "foo"
    password: "bar"
    log:      "tmp/logs/my_gem/bar.log"

Load them to the storage:

require "hexx-storage"

storage = Hexx::Storage.setup

# root path of the application than carries settings
storage.root = "application_path"
# load settings from file, relative to that root
storage.load "config/rom/my_gem.yml", env: :production
# add migrations paths to some repositories
storage[:bar].migrations = "my_gem/db/migrations/bar"

And check the results:

# final settings for the ROM.setup
storage.settings
# => {
#      foo: [:memory, "foo"],
#      bar: [
#        :sql,
#        [
#          #<Sequel::PG::Database: ...>,
#          migrator: #<ROM::SQL::Migration::Migrator ...>
#        ]
#      ]
#    }

Some more details:

# names of repositories:
storage.keys # => [:foo, :bar]

# uri-s
storage.values.map(&:uri)
# => [nil, "postgres://localhost/rom"]

# additional options (except for adapter, uri and log)
storage[:bar].options
# => { user: "foo", password: "bar" }

# loggers (from established root and log)
storage[:foo].logger
# => nil (no logger is set)
storage[:bar].logger
# => #<Logger
#      @logdev= <Logger::LogDevice
#        @filename="application_path/tmp/logs/my_gem/bar.log"
#      >
#    >

Settings specific for :sql adapter:

# established Sequel connections
storage[:foo].conn
# => nil (adapter is not the :sql)
storage[:bar].conn
# => #<Sequel::PG::Database: "postgres://localhost/rom">

# if the logger is set, it is authomatically added to the connection
storage[:bar].conn.loggers
# => [#<Logger ...>]

# migrators
storage[:foo].migrator
# => nil (adapter is not the :sql)
storage[:bar].migrator
# => #<ROM::SQL::Migration::Migrator 
#      @connection=#<Sequel::PG::Database: "postgres://localhost/rom">
#      @path="my_gem/db/migrations/bar"
#    >

Extended Example

Below is example of setings for the my_gem gem used inside the application.

The example consists of 3 parts:

MyGem

The loader:

# my_gem_path/lib/my_gem.rb
module MyGem
  # loads patches (refinements) to external classes (Ruby core, stdlib etc.)
  require_relative "my_gem/patches"
  # loads and initializes the gem's settings
  require_relative "my_gem/settings"
  # loads the core part of the domain (value objects, entities, policies etc.),
  # that don't use the persistency layer
  require_relative "my_gem/core"
  # loads the ROM-based persistency layer
  # (sets ROM up with relations, mappers, and commands)
  require_relative "my_gem/storage"
  # loads the domain API (service objects that backs on both the domain core,
  # and the persistency layer)
  require_relative "my_gem/api"
end

The Settings provides a singleton object to collect all module settings, including the storage and other external dependencies.

After definition it loads the initializer, provided by an application.

# my_gem_path/lib/my_gem/settings.rb
require "hexx-storage"

module MyGem
  module Settings
    include Singleton

    def self.configure
      yield self if block_given?
    end

    def self.[](name)
      send_public name
    end

    def storage
      @storage ||= Hexx::Storage.setup
    end
  end

  # Initializes the dependencies from the initializer, provided by application.
  require File.join ENV["PATH_TO_INITIALIZERS"], "my_gem.rb"
end

The persistency layer backs on the ROM module, whose settings has been initialized before.

# my_gem_path/lib/my_gem/storage.rb

require "rom"

module MyGem
  # Takes storage from the initialized settings
  storage = Settings[:storage]

  # Sets path to local migrations (that's why they are assigned separately)
  # (no need to copy migrations to the application, like Rails does)
  storage[:default].migrations = File.join "storage/migrations"

  # Sets ROM up with the initialized storage settings
  ROM.setup(storage.settings)

  # Loads ROM relations, commands and mappers, that depends from
  # value objects and entities, that are loaded as parts of the
  # core part of the module (see the loading sequence in the loader).

  # ...

  # Finalizes the ROM and sets established loggers to its repositories
  ROM.finalize
  ROM.env.repositories[:default].use_logger storage[:default].logger

  # Memoizes the ROM environment that can be reset by the application
  STORAGE = ROM.env
end

The RSpec helper loads my_gem inside the dummy application:

# my_gem_path/spec/spec_helper.rb
require "dummy/lib/dummy"

Application

The loader:

# application_path/lib/application.rb

# The application knows the :sql adapter will be used
require "rom-sql"

# Sets path to initializers
ENV["PATH_TO_INITIALIZERS"] = File.expand_path "../config/initializers"

# Gives control to the gem loader
require "my_gem"

The my_gem initializer inside the application:

# application_path/config/initializers/my_gem.rb
MyGem::Settings.configure do |config|
  config.storage.root = File.join ".."
  config.storage.load "config/rom/my_gem.yml", env: ENV["CURRENT_ENV"]
end

The my_gem storage's settings inside the application:

# application_path/config/rom/my_gem.yml
---
test:
  default:
    adapter: :memory
    uri:     "my_gem"
    log:     "tmp/logs/test/my_gem.test.log"  # relative to application root
production:
  default:
    adapter: :sql
    uri:     "postgres://localhost/rom" # whatever
    log:     "tmp/logs/production/my_gem.log" # relative to application root

Dummy App

The loader:

# my_gem_path/spec/dummy/lib/dummy.rb

# Sets initializers folder relative to the Dummy
ENV["PATH_TO_INITIALIZERS"] = File
  .expand_path "../spec/dummy/config/initializers"

require "my_gem"

The MyGem initializer:

# my_gem_path/spec/dummy/config/initializers/my_module.rb
MyGem::Settings.configure do |config|
  config.storage.root = File.join "../spec/dummy"
  config.storage.load "config/rom/my_gem.yml", env: :test
end

The ROM storage settings for MyGem:

# my_gem_path/spec/dummy/config/rom/my_gem.yml
---
test:
  default:
    adapter: :memory
    log:     "../../tmp/logs/test.log" # relative to the Dummy root

Installation

Add this line to your application's Gemfile:

# Gemfile
gem "hexx-storage", git: "https://github.com/nepalez/hexx-storage"

Then execute:

bundle

Compatibility

Tested under MRI 2.1+

Limitations:

  • MRI 2.0 doesn't support syntax def foo(bar:)
  • both JRuby and Rubinius don't support refinements

Uses RSpec 3.0+ for testing and hexx-suit for dev/test tools collection.

License

See the MIT LICENSE.