Nina

Gem Version Maintainability Test Coverage

DSL for simplifying complex objects compositions. Also it reduce biolerplate code when you need to create complex OOD compositions. It's based on https://github.com/andriy-baran/toritori so please check it first

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add nina

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install nina

Usage

Let's define two builders: main and secondary

Params = Class.new
Query = Class.new
Command = Class.new

A = Struct.new(:a)
B = Struct.new(:b)
C = Struct.new(:c)

class Flow
  include Nina

  builder :main do
    factory :params, produces: Params
    factory :query, produces: Query
    factory :command, produces: Command
  end

  builder :secondary do
    factory :params, produces: A
    factory :query, produces: B
    factory :command, produces: C
  end
end

Each builder has three factories: params, query, and command. Please check https://github.com/andriy-baran/toritori for related documentation.

With this setup we are able to compose objects in two different ways. Taking the definition block as an ordered list we can traverse it top->bottom or bottom->top connecting objects at each step

Wrapping top->bottom

graph TD;
  query-->params;
  command-->query;

Nesting bottom->top

graph TD;
  query-->command;
  params-->query;

Lets explore what we have as a result

# Wrapping strategy
builder = abstract_factory.main_builder
instance = builder.wrap do |build|
  # This block controlls order of building process steps
  # Allows you to provide initialization attributes
  # And specify only things you need to be added
  build.params                  # The most nested object;
  q = build.query               # query will get a reader to params
  build.command if i_need_this? # Top level object; command will get a reader to query
  build.query == q              # memoization do not allow creation of objects multiple times
  build.query(1, 2, 3)          # instead it returns first object no matter what parameters you provided later
end
instance # => #<Command>
instance.query # => #<Query>
instance.query.params # => #<Params>

# Nesting strategy
builder = abstract_factory.secondary_builder
instance = builder.nest do |build|
  build.params  # Top level object
  build.query   # query will get a reader to params
  build.command # The most nested object; query will get a reader to command
end
instance # => #<A>
instance.query # => #<B>
instance.query.command # => #<C>

Delegation

We may apply delegation techique from OOD to expose methods of deeper layers

builder = abstract_factory.secondary_builder
instance = builder.nest(delegate: true) do |build|
  build.params
  build.query
  build.command
end
instance.a # => nil
instance.b # => nil
instance.c # => nil
instance.query.c # => nil

If you need provide an initalization parameters for the objects

instance = builder.wrap(delegate: true) do |b|
  # b.params(1, 2) => ArgumentError
  b.params(1)
  b.query(2)
  b.command(3)
end
instance.a # => 1
instance.b # => 2
instance.c # => 3

Top level API

If you have some objects and just want to link them you can use following methods

setup = { params: params, query: query, command: command }
Nina.link(setup, delegate: true) do |name, object| # => params.query.command
  # optionally do something
end

Nina.reverse_link(setup, delegate: false) do |name, object| # => command.query.params
  # optionally do something
end

Callbacks

To do something between stages (after creation of object)

builder_with_callbacks = builder.with_callbacks do |c|
  c.params { _1.a = 1 }
  c.params { _1.a += 3 }
  c.params { _1.a += 2 }
  c.query { _1.b = 2 }
end
instance = builder_with_callbacks.wrap
instance.query.params.a # => 6
instance.query.b # => 2
instance.c # => nil

We are copying callbacks from builder if had some defined

builder_with_callbacks = builder.with_callbacks do |c|
  c.params { _1.a = 1 }
end
builder_with_callbacks_with_callbacks = builder_with_callbacks.with_callbacks do |c|
  c.params { _1.a += 1 }
end
instance = builder_with_callbacks.wrap
instance.a # => 2

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/nina. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Nina project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.