trailblazer 1.1.2 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +10 -7
- data/CHANGES.md +108 -0
- data/COMM-LICENSE +91 -0
- data/Gemfile +18 -4
- data/LICENSE.txt +7 -20
- data/README.md +55 -15
- data/Rakefile +21 -2
- data/draft-1.2.rb +7 -0
- data/lib/trailblazer.rb +17 -4
- data/lib/trailblazer/dsl.rb +47 -0
- data/lib/trailblazer/operation/auto_inject.rb +47 -0
- data/lib/trailblazer/operation/builder.rb +18 -18
- data/lib/trailblazer/operation/callback.rb +31 -38
- data/lib/trailblazer/operation/contract.rb +46 -0
- data/lib/trailblazer/operation/controller.rb +45 -27
- data/lib/trailblazer/operation/guard.rb +24 -0
- data/lib/trailblazer/operation/model.rb +41 -33
- data/lib/trailblazer/operation/nested.rb +43 -0
- data/lib/trailblazer/operation/params.rb +13 -0
- data/lib/trailblazer/operation/persist.rb +13 -0
- data/lib/trailblazer/operation/policy.rb +26 -72
- data/lib/trailblazer/operation/present.rb +19 -0
- data/lib/trailblazer/operation/procedural/contract.rb +15 -0
- data/lib/trailblazer/operation/procedural/validate.rb +22 -0
- data/lib/trailblazer/operation/pundit.rb +42 -0
- data/lib/trailblazer/operation/representer.rb +25 -92
- data/lib/trailblazer/operation/rescue.rb +23 -0
- data/lib/trailblazer/operation/resolver.rb +18 -24
- data/lib/trailblazer/operation/validate.rb +50 -0
- data/lib/trailblazer/operation/wrap.rb +37 -0
- data/lib/trailblazer/version.rb +1 -1
- data/test/{operation/controller_test.rb → controller_test.rb} +8 -4
- data/test/docs/auto_inject_test.rb +30 -0
- data/test/docs/contract_test.rb +429 -0
- data/test/docs/dry_test.rb +31 -0
- data/test/docs/guard_test.rb +143 -0
- data/test/docs/nested_test.rb +117 -0
- data/test/docs/policy_test.rb +2 -0
- data/test/docs/pundit_test.rb +109 -0
- data/test/docs/representer_test.rb +268 -0
- data/test/docs/rescue_test.rb +153 -0
- data/test/docs/wrap_test.rb +174 -0
- data/test/gemfiles/Gemfile.ruby-1.9 +3 -0
- data/test/gemfiles/Gemfile.ruby-2.0 +12 -0
- data/test/gemfiles/Gemfile.ruby-2.3 +12 -0
- data/test/module_test.rb +22 -15
- data/test/operation/builder_test.rb +66 -18
- data/test/operation/callback_test.rb +70 -0
- data/test/operation/contract_test.rb +385 -15
- data/test/operation/dsl/callback_test.rb +18 -30
- data/test/operation/dsl/contract_test.rb +209 -19
- data/test/operation/dsl/representer_test.rb +42 -15
- data/test/operation/guard_test.rb +1 -147
- data/test/operation/model_test.rb +105 -0
- data/test/operation/params_test.rb +36 -0
- data/test/operation/persist_test.rb +44 -0
- data/test/operation/pipedream_test.rb +59 -0
- data/test/operation/pipetree_test.rb +104 -0
- data/test/operation/present_test.rb +24 -0
- data/test/operation/pundit_test.rb +104 -0
- data/test/{representer_test.rb → operation/representer_test.rb} +58 -42
- data/test/operation/resolver_test.rb +34 -70
- data/test/operation_test.rb +57 -189
- data/test/test_helper.rb +23 -3
- data/trailblazer.gemspec +8 -7
- metadata +91 -59
- data/gemfiles/Gemfile.rails.lock +0 -130
- data/gemfiles/Gemfile.reform-2.0 +0 -6
- data/gemfiles/Gemfile.reform-2.1 +0 -7
- data/lib/trailblazer/autoloading.rb +0 -15
- data/lib/trailblazer/endpoint.rb +0 -31
- data/lib/trailblazer/operation.rb +0 -175
- data/lib/trailblazer/operation/collection.rb +0 -6
- data/lib/trailblazer/operation/dispatch.rb +0 -3
- data/lib/trailblazer/operation/model/dsl.rb +0 -29
- data/lib/trailblazer/operation/model/external.rb +0 -34
- data/lib/trailblazer/operation/policy/guard.rb +0 -35
- data/lib/trailblazer/operation/uploaded_file.rb +0 -77
- data/test/callback_test.rb +0 -104
- data/test/collection_test.rb +0 -57
- data/test/model_test.rb +0 -148
- data/test/operation/external_model_test.rb +0 -71
- data/test/operation/policy_test.rb +0 -97
- data/test/operation/reject_test.rb +0 -34
- data/test/rollback_test.rb +0 -47
| @@ -0,0 +1,153 @@ | |
| 1 | 
            +
            require "test_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class NestedRescueTest < Minitest::Spec
         | 
| 4 | 
            +
              #---
         | 
| 5 | 
            +
              # nested raise (i hope people won't use this but it works)
         | 
| 6 | 
            +
              A = Class.new(RuntimeError)
         | 
| 7 | 
            +
              Y = Class.new(RuntimeError)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              class NestedInsanity < Trailblazer::Operation
         | 
| 10 | 
            +
                step Rescue {
         | 
| 11 | 
            +
                  step ->(options) { options["a"] = true }
         | 
| 12 | 
            +
                  step Rescue {
         | 
| 13 | 
            +
                    step ->(options) { options["y"] = true }
         | 
| 14 | 
            +
                    step ->(options) { raise Y if options["raise-y"] }
         | 
| 15 | 
            +
                    step ->(options) { options["z"] = true }
         | 
| 16 | 
            +
                  }
         | 
| 17 | 
            +
                  step ->(options) { options["b"] = true }
         | 
| 18 | 
            +
                  step ->(options) { raise A if options["raise-a"] }
         | 
| 19 | 
            +
                  step ->(options) { options["c"] = true }
         | 
| 20 | 
            +
                  self.< ->(options) { options["inner-err"] = true }
         | 
| 21 | 
            +
                }
         | 
| 22 | 
            +
                step ->(options) { options["e"] = true }
         | 
| 23 | 
            +
                self.< ->(options) { options["outer-err"] = true }
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              it { NestedInsanity["pipetree"].inspect.must_equal %{[>>operation.new,&Rescue:10,>:22,<NestedRescueTest::NestedInsanity:23]} }
         | 
| 27 | 
            +
              it { NestedInsanity.({}).inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:true [true, true, true, true, true, true, nil, nil] >} }
         | 
| 28 | 
            +
              it { NestedInsanity.({}, "raise-y" => true).inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:false [true, true, nil, nil, nil, nil, true, true] >} }
         | 
| 29 | 
            +
              it { NestedInsanity.({}, "raise-a" => true).inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:false [true, true, true, true, nil, nil, nil, true] >} }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              #-
         | 
| 32 | 
            +
              # inheritance
         | 
| 33 | 
            +
              class UbernestedInsanity < NestedInsanity
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              it { UbernestedInsanity.({}).inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:true [true, true, true, true, true, true, nil, nil] >} }
         | 
| 37 | 
            +
              it { UbernestedInsanity.({}, "raise-a" => true).inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:false [true, true, true, true, nil, nil, nil, true] >} }
         | 
| 38 | 
            +
            end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            class RescueTest < Minitest::Spec
         | 
| 41 | 
            +
              RecordNotFound = Class.new(RuntimeError)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              Song = Struct.new(:id, :title) do
         | 
| 44 | 
            +
                def self.find(id)
         | 
| 45 | 
            +
                  raise if id == "RuntimeError!"
         | 
| 46 | 
            +
                  id.nil? ? raise(RecordNotFound) : new(id)
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def lock!
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              #:simple
         | 
| 54 | 
            +
              class Create < Trailblazer::Operation
         | 
| 55 | 
            +
                class MyContract < Reform::Form
         | 
| 56 | 
            +
                  property :title
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                step Rescue {
         | 
| 60 | 
            +
                  step Model(Song, :find)
         | 
| 61 | 
            +
                  step Contract::Build( constant: MyContract )
         | 
| 62 | 
            +
                }
         | 
| 63 | 
            +
                step Contract::Validate()
         | 
| 64 | 
            +
                step Persist( method: :sync )
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
              #:simple end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              it { Create.( id: 1, title: "Prodigal Son" )["contract.default"].model.inspect.must_equal %{#<struct RescueTest::Song id=1, title="Prodigal Son">} }
         | 
| 69 | 
            +
              it { Create.( id: nil ).inspect("model").must_equal %{<Result:false [nil] >} }
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              #-
         | 
| 72 | 
            +
              # Rescue ExceptionClass, handler: ->(*) { }
         | 
| 73 | 
            +
              class WithExceptionNameTest < Minitest::Spec
         | 
| 74 | 
            +
              #
         | 
| 75 | 
            +
              class MyContract < Reform::Form
         | 
| 76 | 
            +
                property :title
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
              #:name
         | 
| 79 | 
            +
              class Create < Trailblazer::Operation
         | 
| 80 | 
            +
                step Rescue( RecordNotFound, KeyError, handler: :rollback! ) {
         | 
| 81 | 
            +
                  step Model( Song, :find )
         | 
| 82 | 
            +
                  step Contract::Build( constant: MyContract )
         | 
| 83 | 
            +
                }
         | 
| 84 | 
            +
                step Contract::Validate()
         | 
| 85 | 
            +
                step Persist( method: :sync )
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def rollback!(exception, options)
         | 
| 88 | 
            +
                  options["x"] = exception.class
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
              #:name end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                it { Create.( id: 1, title: "Prodigal Son" )["contract.default"].model.inspect.must_equal %{#<struct RescueTest::Song id=1, title="Prodigal Son">} }
         | 
| 94 | 
            +
                it { Create.( id: 1, title: "Prodigal Son" ).inspect("x").must_equal %{<Result:true [nil] >} }
         | 
| 95 | 
            +
                it { Create.( id: nil ).inspect("model", "x").must_equal %{<Result:false [nil, RescueTest::RecordNotFound] >} }
         | 
| 96 | 
            +
                it { assert_raises(RuntimeError) { Create.( id: "RuntimeError!" ) } }
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
             | 
| 100 | 
            +
              #-
         | 
| 101 | 
            +
              # cdennl use-case
         | 
| 102 | 
            +
              class CdennlRescueAndTransactionTest < Minitest::Spec
         | 
| 103 | 
            +
                module Sequel
         | 
| 104 | 
            +
                  cattr_accessor :result
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  def self.transaction
         | 
| 107 | 
            +
                    yield.tap do |res|
         | 
| 108 | 
            +
                      self.result = res
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
              #:example
         | 
| 114 | 
            +
              class Create < Trailblazer::Operation
         | 
| 115 | 
            +
                class MyContract < Reform::Form
         | 
| 116 | 
            +
                  property :title
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                step Rescue( RecordNotFound, handler: :rollback! ) {
         | 
| 120 | 
            +
                  step Wrap ->(*, &block) { Sequel.transaction do block.call end } {
         | 
| 121 | 
            +
                    step Model( Song, :find )
         | 
| 122 | 
            +
                    step ->(options) { options["model"].lock! } # lock the model.
         | 
| 123 | 
            +
                    step Contract::Build( constant: MyContract )
         | 
| 124 | 
            +
                    step Contract::Validate( )
         | 
| 125 | 
            +
                    step Persist( method: :sync )
         | 
| 126 | 
            +
                  }
         | 
| 127 | 
            +
                }
         | 
| 128 | 
            +
                failure :error! # handle all kinds of errors.
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                def rollback!(exception, options)
         | 
| 131 | 
            +
                  #~ex
         | 
| 132 | 
            +
                  options["x"] = exception.class
         | 
| 133 | 
            +
                  #~ex end
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def error!(options)
         | 
| 137 | 
            +
                  #~ex
         | 
| 138 | 
            +
                  options["err"] = true
         | 
| 139 | 
            +
                  #~ex end
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
              #:example end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                it { Create.( id: 1, title: "Pie" ).inspect("model", "x", "err").must_equal %{<Result:true [#<struct RescueTest::Song id=1, title=\"Pie\">, nil, nil] >} }
         | 
| 145 | 
            +
                # raise exceptions in Model:
         | 
| 146 | 
            +
                it { Create.( id: nil ).inspect("model", "x").must_equal %{<Result:false [nil, RescueTest::RecordNotFound] >} }
         | 
| 147 | 
            +
                it { assert_raises(RuntimeError) { Create.( id: "RuntimeError!" ) } }
         | 
| 148 | 
            +
                it do
         | 
| 149 | 
            +
                  Create.( id: 1, title: "Pie" )
         | 
| 150 | 
            +
                  Sequel.result.first.must_equal Pipetree::Flow::Right
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
              end
         | 
| 153 | 
            +
            end
         | 
| @@ -0,0 +1,174 @@ | |
| 1 | 
            +
            require "test_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class WrapTest < Minitest::Spec
         | 
| 4 | 
            +
              Song = Struct.new(:id, :title) do
         | 
| 5 | 
            +
                def self.find(id)
         | 
| 6 | 
            +
                  id.nil? ? raise : new(id)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              class Create < Trailblazer::Operation
         | 
| 11 | 
            +
                class MyContract < Reform::Form
         | 
| 12 | 
            +
                  property :title
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                step Wrap ->(options, *, &block) {
         | 
| 16 | 
            +
                  begin
         | 
| 17 | 
            +
                    block.call
         | 
| 18 | 
            +
                  rescue => exception
         | 
| 19 | 
            +
                    options["result.model.find"] = "argh! because #{exception.class}"
         | 
| 20 | 
            +
                    false
         | 
| 21 | 
            +
                  end } {
         | 
| 22 | 
            +
                  step Model( Song, :find )
         | 
| 23 | 
            +
                  step Contract::Build( constant: MyContract )
         | 
| 24 | 
            +
                }
         | 
| 25 | 
            +
                step Contract::Validate()
         | 
| 26 | 
            +
                step Persist( method: :sync )
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              it { Create.( id: 1, title: "Prodigal Son" )["contract.default"].model.inspect.must_equal %{#<struct WrapTest::Song id=1, title="Prodigal Son">} }
         | 
| 30 | 
            +
              it { Create.( id: nil ).inspect("result.model.find").must_equal %{<Result:false [\"argh! because RuntimeError\"] >} }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              #-
         | 
| 33 | 
            +
              # Wrap return
         | 
| 34 | 
            +
              class WrapReturnTest < Minitest::Spec
         | 
| 35 | 
            +
                class Create < Trailblazer::Operation
         | 
| 36 | 
            +
                  step Wrap ->(options, *, &block) { options["yield?"] ? block.call : false } {
         | 
| 37 | 
            +
                    step ->(options) { options["x"] = true }
         | 
| 38 | 
            +
                    step :noop!
         | 
| 39 | 
            +
                    # ...
         | 
| 40 | 
            +
                  }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def noop!(options)
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                it { Create.().inspect("x").must_equal %{<Result:false [nil] >} }
         | 
| 47 | 
            +
                # returns falsey means deviate to left.
         | 
| 48 | 
            +
                it { Create.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              class WrapWithCallableTest < Minitest::Spec
         | 
| 52 | 
            +
                class MyWrapper
         | 
| 53 | 
            +
                  extend Uber::Callable
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def self.call(options, *, &block)
         | 
| 56 | 
            +
                    options["yield?"] ? yield : false
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                class Create < Trailblazer::Operation
         | 
| 61 | 
            +
                  step Wrap( MyWrapper ) {
         | 
| 62 | 
            +
                    step ->(options) { options["x"] = true }
         | 
| 63 | 
            +
                    # ...
         | 
| 64 | 
            +
                  }
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                it { Create.().inspect("x").must_equal %{<Result:false [nil] >} }
         | 
| 68 | 
            +
                # returns falsey means deviate to left.
         | 
| 69 | 
            +
                it { Create.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              #-
         | 
| 73 | 
            +
              # arguments for Wrap
         | 
| 74 | 
            +
              class Update < Trailblazer::Operation
         | 
| 75 | 
            +
                step Wrap ->(options, operation, pipe, &block) { operation["yield?"] ? block.call : false } {
         | 
| 76 | 
            +
                  step ->(options) { options["x"] = true }
         | 
| 77 | 
            +
                }
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              it { Update.().inspect("x").must_equal %{<Result:false [nil] >} }
         | 
| 81 | 
            +
              it { Update.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              class WrapExampleProcTest < Minitest::Spec
         | 
| 84 | 
            +
                module Sequel
         | 
| 85 | 
            +
                  def self.transaction
         | 
| 86 | 
            +
                    yield
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              #:sequel-transaction
         | 
| 91 | 
            +
              class Create < Trailblazer::Operation
         | 
| 92 | 
            +
                #~wrap-only
         | 
| 93 | 
            +
                class MyContract < Reform::Form
         | 
| 94 | 
            +
                  property :title
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                #~wrap-only end
         | 
| 98 | 
            +
                step Wrap ->(*, &block) { Sequel.transaction do block.call end } {
         | 
| 99 | 
            +
                  step Model( Song, :new )
         | 
| 100 | 
            +
                  #~wrap-only
         | 
| 101 | 
            +
                  step Contract::Build( constant: MyContract )
         | 
| 102 | 
            +
                  step Contract::Validate( )
         | 
| 103 | 
            +
                  step Persist( method: :sync )
         | 
| 104 | 
            +
                  #~wrap-only end
         | 
| 105 | 
            +
                }
         | 
| 106 | 
            +
                failure :error! # handle all kinds of errors.
         | 
| 107 | 
            +
                #~wrap-only
         | 
| 108 | 
            +
                step :notify!
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def error!(options)
         | 
| 111 | 
            +
                  # handle errors after the wrap
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def notify!(options)
         | 
| 115 | 
            +
                  # send emails, because success...
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
                #~wrap-only end
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
              #:sequel-transaction end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                it { Create.( title: "Pie" ).inspect("model", "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
         | 
| 122 | 
            +
              end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
              class WrapExampleCallableTest < Minitest::Spec
         | 
| 125 | 
            +
                module Sequel
         | 
| 126 | 
            +
                  def self.transaction
         | 
| 127 | 
            +
                    yield
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
              #:callable-t
         | 
| 132 | 
            +
              class MyTransaction
         | 
| 133 | 
            +
                extend Uber::Callable
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                def self.call(options, *)
         | 
| 136 | 
            +
                  Sequel.transaction { yield } # yield runs the nested pipe.
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
              end
         | 
| 139 | 
            +
              #:callable-t end
         | 
| 140 | 
            +
              #:sequel-transaction-callable
         | 
| 141 | 
            +
              class Create < Trailblazer::Operation
         | 
| 142 | 
            +
                #~wrap-onlyy
         | 
| 143 | 
            +
                class MyContract < Reform::Form
         | 
| 144 | 
            +
                  property :title
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                #~wrap-onlyy end
         | 
| 148 | 
            +
                step Wrap( MyTransaction ) {
         | 
| 149 | 
            +
                  step Model( Song, :new )
         | 
| 150 | 
            +
                  #~wrap-onlyy
         | 
| 151 | 
            +
                  step Contract::Build( constant: MyContract )
         | 
| 152 | 
            +
                  step Contract::Validate( )
         | 
| 153 | 
            +
                  step Persist( method: :sync )
         | 
| 154 | 
            +
                  #~wrap-onlyy end
         | 
| 155 | 
            +
                }
         | 
| 156 | 
            +
                failure :error! # handle all kinds of errors.
         | 
| 157 | 
            +
                #~wrap-onlyy
         | 
| 158 | 
            +
                step :notify!
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                def error!(options)
         | 
| 161 | 
            +
                  # handle errors after the wrap
         | 
| 162 | 
            +
                end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                def notify!(options)
         | 
| 165 | 
            +
                  # send emails, because success...
         | 
| 166 | 
            +
                end
         | 
| 167 | 
            +
                #~wrap-onlyy end
         | 
| 168 | 
            +
              end
         | 
| 169 | 
            +
              #:sequel-transaction-callable end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                it { Create.( title: "Pie" ).inspect("model", "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
         | 
| 172 | 
            +
              end
         | 
| 173 | 
            +
            end
         | 
| 174 | 
            +
             | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            source 'https://rubygems.org'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Specify your gem's dependencies in trailblazer.gemspec
         | 
| 4 | 
            +
            gemspec path: "../../"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            gem "multi_json"
         | 
| 7 | 
            +
            gem "trailblazer-operation"
         | 
| 8 | 
            +
            gem "minitest-line"
         | 
| 9 | 
            +
            gem "benchmark-ips"
         | 
| 10 | 
            +
            gem "activesupport", "~> 4.2.0"
         | 
| 11 | 
            +
            gem "reform-rails"
         | 
| 12 | 
            +
            gem "dry-validation"
         | 
    
        data/test/module_test.rb
    CHANGED
    
    | @@ -1,13 +1,15 @@ | |
| 1 1 | 
             
            require 'test_helper'
         | 
| 2 2 | 
             
            require "trailblazer/operation/module"
         | 
| 3 | 
            -
            require "trailblazer/operation/ | 
| 3 | 
            +
            require "trailblazer/operation/callback"
         | 
| 4 | 
            +
            require "trailblazer/operation/contract"
         | 
| 4 5 |  | 
| 5 6 | 
             
            class OperationModuleTest < MiniTest::Spec
         | 
| 6 7 | 
             
              Song = Struct.new(:name, :artist)
         | 
| 7 8 | 
             
              Artist = Struct.new(:id, :full_name)
         | 
| 8 9 |  | 
| 9 10 | 
             
              class Create < Trailblazer::Operation
         | 
| 10 | 
            -
                include Trailblazer::Operation:: | 
| 11 | 
            +
                include Trailblazer::Operation::Callback
         | 
| 12 | 
            +
                include Contract::Explicit
         | 
| 11 13 |  | 
| 12 14 | 
             
                contract do
         | 
| 13 15 | 
             
                  property :name
         | 
| @@ -20,18 +22,21 @@ class OperationModuleTest < MiniTest::Spec | |
| 20 22 | 
             
                  on_change :notify_me!
         | 
| 21 23 | 
             
                end
         | 
| 22 24 |  | 
| 23 | 
            -
                 | 
| 24 | 
            -
             | 
| 25 | 
            +
                attr_reader :model
         | 
| 26 | 
            +
                def call(params)
         | 
| 27 | 
            +
                  self["model"] = Song.new
         | 
| 25 28 |  | 
| 26 | 
            -
                  validate(params,  | 
| 29 | 
            +
                  validate(params, model: self["model"]) do
         | 
| 27 30 | 
             
                    contract.sync
         | 
| 28 31 |  | 
| 29 32 | 
             
                    dispatch!
         | 
| 30 33 | 
             
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  self
         | 
| 31 36 | 
             
                end
         | 
| 32 37 |  | 
| 33 38 | 
             
                def dispatched
         | 
| 34 | 
            -
                   | 
| 39 | 
            +
                  self["dispatched"] ||= []
         | 
| 35 40 | 
             
                end
         | 
| 36 41 |  | 
| 37 42 | 
             
              private
         | 
| @@ -47,6 +52,8 @@ class OperationModuleTest < MiniTest::Spec | |
| 47 52 | 
             
                contract do
         | 
| 48 53 | 
             
                  property :artist, inherit: true do
         | 
| 49 54 | 
             
                    property :full_name
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    puts definitions.inspect
         | 
| 50 57 | 
             
                  end
         | 
| 51 58 | 
             
                end
         | 
| 52 59 |  | 
| @@ -76,18 +83,18 @@ class OperationModuleTest < MiniTest::Spec | |
| 76 83 | 
             
              it do
         | 
| 77 84 | 
             
                op = Create.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
         | 
| 78 85 |  | 
| 79 | 
            -
                op | 
| 80 | 
            -
                op | 
| 81 | 
            -
                op | 
| 82 | 
            -
                op | 
| 86 | 
            +
                op["dispatched"].must_equal [:notify_me!]
         | 
| 87 | 
            +
                op["model"].name.must_equal "Feelings"
         | 
| 88 | 
            +
                op["model"].artist.id.must_equal 1
         | 
| 89 | 
            +
                op["model"].artist.full_name.must_equal nil # property not declared.
         | 
| 83 90 | 
             
              end
         | 
| 84 91 |  | 
| 85 92 | 
             
              it do
         | 
| 86 93 | 
             
                op = Update.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
         | 
| 87 94 |  | 
| 88 | 
            -
                op | 
| 89 | 
            -
                op | 
| 90 | 
            -
                op | 
| 91 | 
            -
                op | 
| 95 | 
            +
                op["dispatched"].must_equal [:notify_me!, :notify_them!, :notify_you!]
         | 
| 96 | 
            +
                op["model"].name.must_equal "Feelings"
         | 
| 97 | 
            +
                op["model"].artist.id.must_equal 1
         | 
| 98 | 
            +
                op["model"].artist.full_name.must_equal "The Offspring" # property declared via Module.
         | 
| 92 99 | 
             
              end
         | 
| 93 | 
            -
            end
         | 
| 100 | 
            +
            end
         | 
| @@ -1,41 +1,89 @@ | |
| 1 1 | 
             
            require "test_helper"
         | 
| 2 | 
            +
            require "trailblazer/operation/builder"
         | 
| 2 3 |  | 
| 3 | 
            -
            class  | 
| 4 | 
            -
               | 
| 5 | 
            -
             | 
| 4 | 
            +
            class BuilderTest < MiniTest::Spec
         | 
| 5 | 
            +
              #---
         | 
| 6 | 
            +
              # pass proc to Builder[]
         | 
| 7 | 
            +
              # this is the puristic way.
         | 
| 8 | 
            +
              class A < Trailblazer::Operation
         | 
| 9 | 
            +
                builds = ->(klass, options) do
         | 
| 10 | 
            +
                  return B if options["params"][:sub]
         | 
| 11 | 
            +
                  klass
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                self.| Builder( builds )
         | 
| 15 | 
            +
                self.| :process
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                class B < A
         | 
| 6 18 | 
             
                end
         | 
| 7 19 |  | 
| 20 | 
            +
                def process(*); self["x"] = self.class end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              it { A.()["x"].must_equal A }
         | 
| 24 | 
            +
              it { A.({ sub: true })["x"].must_equal A::B }
         | 
| 25 | 
            +
              it { A::B["builder"].must_equal nil }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              #---
         | 
| 28 | 
            +
              # use manual Builders object
         | 
| 29 | 
            +
              MyBuilders = Uber::Builder::Builders.new
         | 
| 30 | 
            +
              MyBuilders << ->(options) { return self::B if options["params"][:sub] }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              class Create < Trailblazer::Operation
         | 
| 33 | 
            +
                self.| Builder( MyBuilders )
         | 
| 34 | 
            +
                self["pipetree"].> ->(input, options) { options["x"] = input.class }
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              it { Create.()["x"].must_equal Create }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              #---
         | 
| 40 | 
            +
              #- Builder inheritance
         | 
| 41 | 
            +
              class B < A
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              it { B["pipetree"].inspect.must_equal %{[>>operation.new,>process]} }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              #---
         | 
| 47 | 
            +
              # use Builder DSL
         | 
| 48 | 
            +
              # you don't need to include Builder in the pipetree
         | 
| 49 | 
            +
              class ParentOperation < Trailblazer::Operation
         | 
| 8 50 | 
             
                class Sub < self
         | 
| 9 51 | 
             
                end
         | 
| 10 52 |  | 
| 11 | 
            -
                 | 
| 12 | 
            -
             | 
| 53 | 
            +
                include Builder
         | 
| 54 | 
            +
                builds -> (options) do
         | 
| 55 | 
            +
                  return Sub if options["params"][:sub]
         | 
| 13 56 | 
             
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def process(*); self["x"] = self.class end
         | 
| 59 | 
            +
                self.| :process
         | 
| 14 60 | 
             
              end
         | 
| 15 61 |  | 
| 16 | 
            -
              it { ParentOperation. | 
| 17 | 
            -
              it { ParentOperation. | 
| 18 | 
            -
              it { ParentOperation.({}).class.must_equal ParentOperation }
         | 
| 19 | 
            -
              it { ParentOperation.({sub: true}).class.must_equal ParentOperation::Sub }
         | 
| 62 | 
            +
              it { ParentOperation.({})["x"].must_equal ParentOperation }
         | 
| 63 | 
            +
              it { ParentOperation.({ sub: true })["x"].must_equal ParentOperation::Sub }
         | 
| 20 64 | 
             
            end
         | 
| 21 65 |  | 
| 66 | 
            +
            #---
         | 
| 67 | 
            +
            # copying via Operation.builders
         | 
| 22 68 | 
             
            class OperationBuilderClassTest < MiniTest::Spec
         | 
| 23 69 | 
             
              class SuperOperation < Trailblazer::Operation
         | 
| 24 | 
            -
                 | 
| 25 | 
            -
             | 
| 70 | 
            +
                include Builder
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                builds do |options|
         | 
| 73 | 
            +
                  self::Sub if options["params"][:sub] # Sub is defined in ParentOperation.
         | 
| 26 74 | 
             
                end
         | 
| 27 75 | 
             
              end
         | 
| 28 76 |  | 
| 29 77 | 
             
              class ParentOperation < Trailblazer::Operation
         | 
| 30 | 
            -
                def process(params)
         | 
| 31 | 
            -
                end
         | 
| 32 | 
            -
             | 
| 33 78 | 
             
                class Sub < self
         | 
| 34 79 | 
             
                end
         | 
| 35 80 |  | 
| 36 | 
            -
                self | 
| 81 | 
            +
                self.| Builder( SuperOperation.builders )
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def process(*); self["x"] = self.class end
         | 
| 84 | 
            +
                self.| :process
         | 
| 37 85 | 
             
              end
         | 
| 38 86 |  | 
| 39 | 
            -
              it { ParentOperation.({}). | 
| 40 | 
            -
              it { ParentOperation.({sub: true}). | 
| 41 | 
            -
            end
         | 
| 87 | 
            +
              it { ParentOperation.({})["x"].must_equal ParentOperation }
         | 
| 88 | 
            +
              it { ParentOperation.({ sub: true })["x"].must_equal ParentOperation::Sub }
         | 
| 89 | 
            +
            end
         |