README.md in atacama-0.1.11 vs README.md in atacama-0.2.0

- old
+ new

@@ -28,11 +28,11 @@ ## Usage The basic object is `Contract`. It enforces type contracts by utilizing `dry-types`. -``` +```ruby class UserFetcher < Atacama::Contract option :id, Types::Strict::Number.gt(0) returns Types.Instance(User) def call @@ -41,36 +41,55 @@ end UserFetcher.call(id: 1) ``` -With the use of two classes, we can compose together multiple Contracts to yield a pipeline -of changes to execute. +With the use of two classes, we can compose together multiple Contracts to yield a pipeline of changes to execute. -``` +Steps contain two flow control objects: +* `Option(key: value)` which informs the Transformer to take this value and yield it to the subsequent steps in the chain. +* `Return(value)` halts execution and early returns from the pipeline. Useful for things like validation and error handling. + +The `Transformer` always returns a value object. + +```ruby class UserFetcher < Atacama::Step option :id, type: Types::Strict::Number.gt(0) returns Types.Option(model: Types.Instance(User)) + # Both #Option and #Return are flow control values that tell the transaction what is a + # value object and what should halt execution and return. def call - Option(model: User.find(id)) + Option(model: User.find!(id)) + rescue ActiveRecord::RecordNotFound + Return(Error.new('Not found')) end end +# Around steps allow for yielding to child steps for things like instrumentation or +# ActiveRecord::Transactions. class Duration < Atacama::Step def call start = Time.now yield $redis.avg('duration', Time.now - start) end end +# The transaction class descends the queue of steps, yielding options to each step +# defined. +# +# Steps can be defined with: +# * Procs +# * Class references +# * Instance methods +# class UpdateUser < Atacama::Transformer - option :model, type: Types.Instance(User) + option :id, type: Types::Strict::Number.gt(0) option :attributes, type: Types::Strict::Hash - returns_option :model, Types.Instance(User) + returns_option :model, Types.Instance(User) | Types.Instance(Error) step :duration, with: Duration do step :find, with: UserFetcher step :save end @@ -88,15 +107,48 @@ ``` Any step can be mocked out without the need for a third party library. Just pass any object that responds to `#call` in the class initializer. -``` +```ruby UpdateUser.new(steps: { - save: lambda do |**| + save: lambda do puts "skipping save" end }) +``` + +Sometimes you need to compose these objects together and inject dependencies. Those injected values +will be passed in to the object when it's later invoked with `#call`. + +```ruby +UpdateUser.inject(id: 1).call(attributes: { email: 'hello@world.com' }) +``` + +Injected contracts can then be used inside of a Contract. Useful for Polymorphic objects. + +```ruby +class HistoryCreate < Atacama::Step + option :history_class, type: Types::Strict::Class + option :model, type: Types.Instance(ActiveRecord::Base) + + def call + history_class.from_model(model) + end +end + +class UpdateUser < Atacama::Transformer + option :id, type: Types::Strict::Number.gt(0) + option :attributes, type: Types::Strict::Hash + + returns_option :model, Types.Instance(User) | Types.Instance(Error) + + step :duration, with: Duration do + step :find, with: UserFetcher + step :save, with: Saver + step :history, with: HistoryCreate.inject(history_class: UserHistory) + end +end ``` ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.