trailblazer 2.0.7 → 2.1.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/CHANGES.md +35 -1
- data/Gemfile +6 -12
- data/README.md +3 -1
- data/Rakefile +6 -17
- data/lib/trailblazer.rb +7 -4
- data/lib/trailblazer/deprecation/call.rb +46 -0
- data/lib/trailblazer/deprecation/context.rb +43 -0
- data/lib/trailblazer/operation/contract.rb +40 -9
- data/lib/trailblazer/operation/deprecations.rb +21 -0
- data/lib/trailblazer/operation/guard.rb +5 -5
- data/lib/trailblazer/operation/model.rb +15 -10
- data/lib/trailblazer/operation/nested.rb +56 -85
- data/lib/trailblazer/operation/persist.rb +4 -2
- data/lib/trailblazer/operation/policy.rb +16 -7
- data/lib/trailblazer/operation/pundit.rb +3 -3
- data/lib/trailblazer/operation/representer.rb +5 -0
- data/lib/trailblazer/operation/rescue.rb +12 -9
- data/lib/trailblazer/operation/validate.rb +36 -29
- data/lib/trailblazer/operation/wrap.rb +49 -11
- data/lib/trailblazer/task.rb +20 -0
- data/lib/trailblazer/version.rb +1 -1
- data/test/benchmark.rb +63 -0
- data/test/deprecation/call_test.rb +42 -0
- data/test/deprecation/context_test.rb +19 -0
- data/test/docs/contract_test.rb +73 -53
- data/test/docs/dry_test.rb +2 -2
- data/test/docs/fast_test.rb +133 -13
- data/test/docs/guard_test.rb +28 -35
- data/test/docs/macro_test.rb +1 -1
- data/test/docs/model_test.rb +13 -13
- data/test/docs/nested_test.rb +54 -122
- data/test/docs/operation_test.rb +42 -43
- data/test/docs/pundit_test.rb +16 -16
- data/test/docs/representer_test.rb +18 -18
- data/test/docs/rescue_test.rb +29 -29
- data/test/docs/trace_test.rb +82 -0
- data/test/docs/wrap_test.rb +59 -26
- data/test/module_test.rb +75 -75
- data/test/nested_test.rb +293 -0
- data/test/operation/contract_test.rb +23 -153
- data/test/operation/dsl/contract_test.rb +9 -9
- data/test/operation/dsl/representer_test.rb +169 -169
- data/test/operation/model_test.rb +15 -21
- data/test/operation/persist_test.rb +18 -11
- data/test/operation/pundit_test.rb +25 -23
- data/test/operation/representer_test.rb +254 -254
- data/test/test_helper.rb +5 -2
- data/test/variables_test.rb +158 -0
- data/trailblazer.gemspec +1 -1
- data/untitled +33 -0
- metadata +25 -27
- data/lib/trailblazer/operation/callback.rb +0 -35
- data/lib/trailblazer/operation/procedural/contract.rb +0 -15
- data/lib/trailblazer/operation/procedural/validate.rb +0 -22
- data/test/operation/callback_test.rb +0 -70
- data/test/operation/dsl/callback_test.rb +0 -106
- data/test/operation/params_test.rb +0 -36
- data/test/operation/pipedream_test.rb +0 -59
- data/test/operation/pipetree_test.rb +0 -104
- data/test/operation/present_test.rb +0 -24
- data/test/operation/resolver_test.rb +0 -47
- data/test/operation_test.rb +0 -143
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c82134611daa3efe9309eebb4a283585b4095428
         | 
| 4 | 
            +
              data.tar.gz: cc121cdb03c9ea4da12b9b2713150e50d18ebac9
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 992b2af33d7b5de098b59c2eb9a75b6dd4854d0a580613b6938c6ae44f5ec2d048f4686cfecb23c4daf5673c78b24aa90286b50e5df24bc856134155f7e9d7f4
         | 
| 7 | 
            +
              data.tar.gz: 96912060658256a931dec0a8951c61fb3bc1eb62521a4730a2458258f2e1e7c145adad129820c19149d578213c45c81673587f1f1afe16bec9c1a189ad32c914
         | 
    
        data/CHANGES.md
    CHANGED
    
    | @@ -1,3 +1,38 @@ | |
| 1 | 
            +
            # 2.1
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Macros now always have to provide an `:id`. This was a bit fuzzy in 2.0.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * Nested
         | 
| 6 | 
            +
              if Nested( Edit ), outputs will automatically be connected, see editor.
         | 
| 7 | 
            +
            * Wrap
         | 
| 8 | 
            +
              dropped the `pipe` option. This is now `options, flow_options, *`
         | 
| 9 | 
            +
              `false` is now automatically connected to End.failure.
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * `operation.new` step removed.
         | 
| 12 | 
            +
            * Undocumented step behavior removed. You can't write to `self` anymore.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    ```ruby
         | 
| 15 | 
            +
                    step :process
         | 
| 16 | 
            +
                    def process(*)
         | 
| 17 | 
            +
                      self["x"] = true
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                    ```
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                Always write to `options`.
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            * self[] removed
         | 
| 24 | 
            +
            * Fixed `Guard` where procs could receive one argument, only. Guards follow the step interface: `Policy::Guard( ->(options, **) { .. } )
         | 
| 25 | 
            +
            * Removed `Operation::Callback` which was a poor idea and luckily no one was using it.
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            TODO:
         | 
| 28 | 
            +
            document Task API and define step API
         | 
| 29 | 
            +
            deprecate step->(options) ?
         | 
| 30 | 
            +
            injectable, per-operation step arguments strategy?
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            # 2.1.0.beta1
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            * Add `deprecation/call` and `deprecation/context` that help with the new `call` API and symbols for `options` keys.
         | 
| 35 | 
            +
             | 
| 1 36 | 
             
            # 2.0.7
         | 
| 2 37 |  | 
| 3 38 | 
             
            * Allow to use any method with the Model macro, e.g.
         | 
| @@ -8,7 +43,6 @@ | |
| 8 43 |  | 
| 9 44 | 
             
              will now invoke `Comment[ params[:id] ]`, which makes using Sequel a breeze.
         | 
| 10 45 |  | 
| 11 | 
            -
             | 
| 12 46 | 
             
            # 2.0.6
         | 
| 13 47 |  | 
| 14 48 | 
             
            * Fix what we broke in 2.0.5, where `Wrap` would always use the current operation subclass and not the empty `Trailblazer::Operation`. Thanks to @mensfeld.
         | 
    
        data/Gemfile
    CHANGED
    
    | @@ -3,29 +3,23 @@ source 'https://rubygems.org' | |
| 3 3 | 
             
            # Specify your gem's dependencies in trailblazer.gemspec
         | 
| 4 4 | 
             
            gemspec
         | 
| 5 5 |  | 
| 6 | 
            -
            # gem "representable", path: "../representable"
         | 
| 7 | 
            -
            # gem "disposable", path: "../disposable"
         | 
| 8 | 
            -
            # gem "reform", github: "apotonick/reform"
         | 
| 9 6 | 
             
            gem "reform-rails"
         | 
| 10 | 
            -
            gem "activesupport" | 
| 7 | 
            +
            gem "activesupport"#, "~> 4.2.0"
         | 
| 11 8 |  | 
| 12 | 
            -
            gem "roar", github: "apotonick/roar"
         | 
| 13 9 | 
             
            # gem "reform", "~> 2.0.0"
         | 
| 14 10 | 
             
            gem "reform"#, path: "../reform"
         | 
| 15 | 
            -
            # gem "roar", path: "../roar"
         | 
| 16 11 | 
             
            gem "multi_json"
         | 
| 17 12 |  | 
| 18 13 | 
             
            gem "dry-auto_inject"
         | 
| 19 14 | 
             
            gem "dry-matcher"
         | 
| 20 15 | 
             
            gem "dry-validation"
         | 
| 21 16 |  | 
| 22 | 
            -
             | 
| 23 | 
            -
            # gem "trailblazer-operation", path: "../operation"
         | 
| 24 | 
            -
            # gem "pipetree", path: "../pipetree"
         | 
| 17 | 
            +
            gem "trailblazer-operation", path: "../operation"
         | 
| 25 18 | 
             
            # gem "trailblazer-operation", github: "trailblazer/trailblazer-operation"
         | 
| 26 | 
            -
            # gem "pipetree", github: "apotonick/pipetree"
         | 
| 27 | 
            -
             | 
| 28 19 |  | 
| 29 20 | 
             
            gem "minitest-line"
         | 
| 30 21 |  | 
| 31 | 
            -
             | 
| 22 | 
            +
            gem "trailblazer-activity", path: "../trailblazer-circuit"
         | 
| 23 | 
            +
            # gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            # gem "trailblazer-context", path: "../trailblazer-context"
         | 
    
        data/README.md
    CHANGED
    
    | @@ -6,7 +6,9 @@ _Trailblazer provides new high-level abstractions for Ruby frameworks. It gently | |
| 6 6 | 
             
            [](http://trailblazer.to/newsletter/)
         | 
| 7 7 | 
             
            [](http://badge.fury.io/rb/trailblazer)
         | 
| 8 8 |  | 
| 9 | 
            -
            **This document discusses Trailblazer 2. | 
| 9 | 
            +
            **This document discusses Trailblazer 2.1.** An overview about the additions are [on our website](http://trailblazer.to/blog/2017-12-trailblazer-2-1-what-you-need-to-know.html).
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            The [1.x documentation is here](http://trailblazer.to/gems/operation/1.1/).
         | 
| 10 12 |  | 
| 11 13 | 
             
            ## Trailblazer In A Nutshell
         | 
| 12 14 |  | 
    
        data/Rakefile
    CHANGED
    
    | @@ -5,23 +5,12 @@ task :default => [:test] | |
| 5 5 |  | 
| 6 6 | 
             
            Rake::TestTask.new(:test) do |test|
         | 
| 7 7 | 
             
              test.libs << 'test'
         | 
| 8 | 
            -
               | 
| 9 | 
            -
               | 
| 10 | 
            -
             | 
| 11 | 
            -
                test/operation/model_test.rb
         | 
| 12 | 
            -
                test/operation/contract_test.rb
         | 
| 13 | 
            -
                test/operation/persist_test.rb
         | 
| 14 | 
            -
                test/operation/callback_test.rb
         | 
| 15 | 
            -
                test/operation/resolver_test.rb
         | 
| 16 | 
            -
                test/operation/dsl/contract_test.rb
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                test/docs/*_test.rb
         | 
| 19 | 
            -
                }]
         | 
| 20 | 
            -
             | 
| 21 | 
            -
              if RUBY_VERSION == "1.9.3"
         | 
| 22 | 
            -
                test_files = test_files - %w{test/docs/dry_test.rb test/docs/auto_inject_test.rb}
         | 
| 23 | 
            -
              end
         | 
| 8 | 
            +
              test.test_files = FileList['test/**/*_test.rb'] - FileList["test/deprecation/*_test.rb"]
         | 
| 9 | 
            +
              test.verbose = true
         | 
| 10 | 
            +
            end
         | 
| 24 11 |  | 
| 25 | 
            -
             | 
| 12 | 
            +
            Rake::TestTask.new(:testdep) do |test|
         | 
| 13 | 
            +
              test.libs << 'test'
         | 
| 14 | 
            +
              test.test_files = FileList["test/deprecation/*_test.rb"]
         | 
| 26 15 | 
             
              test.verbose = true
         | 
| 27 16 | 
             
            end
         | 
    
        data/lib/trailblazer.rb
    CHANGED
    
    | @@ -1,8 +1,11 @@ | |
| 1 | 
            -
            require "trailblazer/ | 
| 2 | 
            -
            require "trailblazer/operation/pipetree"
         | 
| 1 | 
            +
            require "trailblazer/version"
         | 
| 3 2 |  | 
| 3 | 
            +
            require "trailblazer/operation"
         | 
| 4 4 | 
             
            require "trailblazer/dsl"
         | 
| 5 | 
            -
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            require "trailblazer/task"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            require "trailblazer/operation/deprecations"
         | 
| 6 9 |  | 
| 7 10 | 
             
            require "trailblazer/operation/model"
         | 
| 8 11 | 
             
            require "trailblazer/operation/contract"
         | 
| @@ -12,7 +15,7 @@ require "trailblazer/operation/policy" | |
| 12 15 | 
             
            require "trailblazer/operation/pundit"
         | 
| 13 16 | 
             
            require "trailblazer/operation/guard"
         | 
| 14 17 | 
             
            require "trailblazer/operation/persist"
         | 
| 15 | 
            -
            # require "trailblazer/operation/callback"
         | 
| 16 18 | 
             
            require "trailblazer/operation/nested"
         | 
| 17 19 | 
             
            require "trailblazer/operation/wrap"
         | 
| 18 20 | 
             
            require "trailblazer/operation/rescue"
         | 
| 21 | 
            +
            require "trailblazer/operation/inject"
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            module Trailblazer
         | 
| 2 | 
            +
              module Deprecation
         | 
| 3 | 
            +
                module Operation
         | 
| 4 | 
            +
                  # This is super hacky. Fix you call calls everywhere (shouldn't take too long) and never load this file, again.
         | 
| 5 | 
            +
                  module Call
         | 
| 6 | 
            +
                    def self.options_for_call(params, *containers)
         | 
| 7 | 
            +
                      if containers == []
         | 
| 8 | 
            +
                        if params.is_a?(Hash) # this means we assume everything is cool. Create.( {...} )
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                        else # this means someone did Create.( #<WeirdParamsObject> )
         | 
| 11 | 
            +
                          deprecate_positional_params(params, *containers)
         | 
| 12 | 
            +
                          return { params: params }, *containers
         | 
| 13 | 
            +
                        end
         | 
| 14 | 
            +
                      else # Create.( params, "current_user" => ... )
         | 
| 15 | 
            +
                        options, containers = containers[0], (containers[1..-1] || [])
         | 
| 16 | 
            +
                        if options.is_a?(Hash) # old API
         | 
| 17 | 
            +
                          warn "[Trailblazer] Please don't pass the `params` object as a positional argument into `Operation.()`, use the `:params` key and one hash for all: `Operation.( params: my_params, current_user: ... )` ."
         | 
| 18 | 
            +
                          return options.merge( params: params ), *containers
         | 
| 19 | 
            +
                        end
         | 
| 20 | 
            +
                      end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      return params, *containers
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def self.deprecate_positional_params(params, *containers)
         | 
| 26 | 
            +
                      warn "[Trailblazer] Please don't pass the `params` object as a positional argument into `Operation.()`, use the `:params` key: `Operation.( params: my_params )` ."
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            Trailblazer::Operation.module_eval do
         | 
| 34 | 
            +
              # this sucks:
         | 
| 35 | 
            +
              def self.call(options={}, *containers)
         | 
| 36 | 
            +
                options, *containers = Trailblazer::Deprecation::Operation::Call.options_for_call(options, *containers)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                ctx = Trailblazer::Operation::PublicCall.options_for_public_call(options, *containers)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                # call the activity.
         | 
| 41 | 
            +
                last_signal, (options, flow_options) = __call__( [ctx, {}] ) # Railway::call # DISCUSS: this could be ::call_with_context.
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                # Result is successful if the activity ended with an End event derived from Railway::End::Success.
         | 
| 44 | 
            +
                Trailblazer::Operation::Railway::Result(last_signal, options, flow_options)
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            module Trailblazer
         | 
| 2 | 
            +
              module Deprecation
         | 
| 3 | 
            +
                class ContextWithIndifferentAccess < Trailblazer::Context
         | 
| 4 | 
            +
                  def [](key)
         | 
| 5 | 
            +
                    return super unless Trailblazer::Operation::PublicCall.deprecatable?(key)
         | 
| 6 | 
            +
                    key, _ = Trailblazer::Operation::PublicCall.deprecate_string(key, nil)
         | 
| 7 | 
            +
                    super(key)
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def []=(key, value)
         | 
| 11 | 
            +
                    return super unless Trailblazer::Operation::PublicCall.deprecatable?(key)
         | 
| 12 | 
            +
                    key, _ = Trailblazer::Operation::PublicCall.deprecate_string(key, nil)
         | 
| 13 | 
            +
                    super(key, value)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            Trailblazer::Operation::PublicCall.module_eval do
         | 
| 20 | 
            +
              def self.options_for_public_call(options={}, *containers)
         | 
| 21 | 
            +
                hash_transformer = ->(containers) { containers[0].to_hash } # FIXME: don't transform any containers into kw args.
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                options = deprecate_strings(options)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                immutable_options = Trailblazer::Context::ContainerChain.new( [options, *containers], to_hash: hash_transformer ) # Runtime options, immutable.
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                Trailblazer::Deprecation::ContextWithIndifferentAccess.new(immutable_options, {})
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def self.deprecatable?(key)
         | 
| 31 | 
            +
                key.is_a?(String) && key.split(".").size == 1
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def self.deprecate_strings(options)
         | 
| 35 | 
            +
                ary = options.collect { |k,v| deprecatable?(k) ? deprecate_string(k, v) : [k,v]  }
         | 
| 36 | 
            +
                Hash[ary]
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def self.deprecate_string(key, value)
         | 
| 40 | 
            +
                warn "[Trailblazer] Using a string key for non-namespaced keys is deprecated. Please use `:#{key}` instead of `#{key.inspect}`."
         | 
| 41 | 
            +
                [ key.to_sym, value ]
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -8,27 +8,58 @@ | |
| 8 8 | 
             
            # Needs #[], #[]= skill dependency.
         | 
| 9 9 | 
             
            class Trailblazer::Operation
         | 
| 10 10 | 
             
              module Contract
         | 
| 11 | 
            -
                def self.Build(name:"default", constant:nil, builder: nil)
         | 
| 12 | 
            -
                  step = ->( | 
| 11 | 
            +
                def self.Build(name: "default", constant: nil, builder: nil)
         | 
| 12 | 
            +
                  step = ->((options, flow_options), **circuit_options) { Build.(options, circuit_options, name: name, constant: constant, builder: builder) }
         | 
| 13 13 |  | 
| 14 | 
            -
                   | 
| 14 | 
            +
                  task = Trailblazer::Activity::Task::Binary( step )
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  { task: task, id: "contract.build" }
         | 
| 15 17 | 
             
                end
         | 
| 16 18 |  | 
| 17 19 | 
             
                module Build
         | 
| 18 | 
            -
                  #  | 
| 19 | 
            -
                  def self. | 
| 20 | 
            +
                  # Build contract at runtime.
         | 
| 21 | 
            +
                  def self.call(options, circuit_options, name: "default", constant: nil, builder: nil)
         | 
| 20 22 | 
             
                    # TODO: we could probably clean this up a bit at some point.
         | 
| 21 | 
            -
                    contract_class = constant || options["contract.#{name}.class"]
         | 
| 22 | 
            -
                    model          = options[ | 
| 23 | 
            +
                    contract_class = constant || options["contract.#{name}.class"] # DISCUSS: Injection possible here?
         | 
| 24 | 
            +
                    model          = options[:model]
         | 
| 23 25 | 
             
                    name           = "contract.#{name}"
         | 
| 24 26 |  | 
| 25 | 
            -
                     | 
| 27 | 
            +
                    options[name] =
         | 
| 28 | 
            +
                      if builder
         | 
| 29 | 
            +
                        call_builder( options, circuit_options, builder: builder, constant: contract_class, name: name )
         | 
| 30 | 
            +
                      else
         | 
| 31 | 
            +
                        contract_class.new(model)
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def self.call_builder(options, circuit_options, builder:raise, constant:raise, name:raise)
         | 
| 36 | 
            +
                    # builder_options = Trailblazer::Context( options, constant: constant, name: name ) # options.merge( .. )
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    # Trailblazer::Option::KW(builder).(builder_options, circuit_options)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
             | 
| 41 | 
            +
             | 
| 26 42 |  | 
| 27 | 
            -
             | 
| 43 | 
            +
             | 
| 44 | 
            +
             | 
| 45 | 
            +
             | 
| 46 | 
            +
             | 
| 47 | 
            +
                    # FIXME: almost identical with Option::KW.
         | 
| 48 | 
            +
                    # FIXME: see Nested::Options::Dynamic, the same shit
         | 
| 49 | 
            +
                    tmp_options =  options.to_hash.merge(
         | 
| 50 | 
            +
                      constant: constant,
         | 
| 51 | 
            +
                      name:     name
         | 
| 52 | 
            +
                    )
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    Trailblazer::Option(builder).( options, tmp_options, circuit_options )
         | 
| 28 55 | 
             
                  end
         | 
| 29 56 | 
             
                end
         | 
| 30 57 |  | 
| 31 58 | 
             
                module DSL
         | 
| 59 | 
            +
                  def self.extended(extender)
         | 
| 60 | 
            +
                    extender.extend(ClassDependencies)
         | 
| 61 | 
            +
                    warn "[Trailblazer] Using `contract do...end` is deprecated. Please use a form class and the Builder( constant: <Form> ) option."
         | 
| 62 | 
            +
                  end
         | 
| 32 63 | 
             
                  # This is the class level DSL method.
         | 
| 33 64 | 
             
                  #   Op.contract #=> returns contract class
         | 
| 34 65 | 
             
                  #   Op.contract do .. end # defines contract
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Trailblazer
         | 
| 2 | 
            +
              class Operation
         | 
| 3 | 
            +
                class DeprecatedOptions < Option
         | 
| 4 | 
            +
                  def self.call!(proc, direction, options, flow_options, *args)
         | 
| 5 | 
            +
                    if proc.is_a?(Proc) && proc.arity == 1
         | 
| 6 | 
            +
                      deprecate(proc)
         | 
| 7 | 
            +
                      proc.(options)
         | 
| 8 | 
            +
                    elsif proc.method(:call).arity == 1
         | 
| 9 | 
            +
                      deprecate(proc)
         | 
| 10 | 
            +
                      proc.(options)
         | 
| 11 | 
            +
                    else
         | 
| 12 | 
            +
                      super
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def self.deprecate(proc)
         | 
| 17 | 
            +
                    warn "[Trailblazer] Please use the step API `def my_step!(options, **)` for your step: #{proc}"
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end # DeprecatedOptions
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -1,17 +1,17 @@ | |
| 1 | 
            -
            require "trailblazer/operation/policy"
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            class Trailblazer::Operation
         | 
| 4 2 | 
             
              module Policy
         | 
| 5 3 | 
             
                def self.Guard(proc, name: :default, &block)
         | 
| 6 | 
            -
                  Policy.step(Guard.build(proc), name: name)
         | 
| 4 | 
            +
                  Policy.step( Guard.build(proc), name: name )
         | 
| 7 5 | 
             
                end
         | 
| 8 6 |  | 
| 9 7 | 
             
                module Guard
         | 
| 10 8 | 
             
                  def self.build(callable)
         | 
| 11 | 
            -
                     | 
| 9 | 
            +
                    option = Trailblazer::Option::KW(callable)
         | 
| 12 10 |  | 
| 13 11 | 
             
                    # this gets wrapped in a Operation::Result object.
         | 
| 14 | 
            -
                    ->( | 
| 12 | 
            +
                    ->( (options, *), circuit_args ) do
         | 
| 13 | 
            +
                      Result.new( !!option.(options, circuit_args), {} )
         | 
| 14 | 
            +
                    end
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 | 
             
                end # Guard
         | 
| 17 17 | 
             
              end
         | 
| @@ -1,23 +1,28 @@ | |
| 1 1 | 
             
            class Trailblazer::Operation
         | 
| 2 2 | 
             
              def self.Model(model_class, action=nil)
         | 
| 3 | 
            -
                step =  | 
| 3 | 
            +
                # step = Pipetree::Step.new(step, "model.class" => model_class, "model.action" => action)
         | 
| 4 4 |  | 
| 5 | 
            -
                 | 
| 5 | 
            +
                task = Railway::TaskBuilder.( Model.new )
         | 
| 6 6 |  | 
| 7 | 
            -
                 | 
| 7 | 
            +
                runner_options = {
         | 
| 8 | 
            +
                  merge: Wrap::Inject::Defaults(
         | 
| 9 | 
            +
                    "model.class"  => model_class,
         | 
| 10 | 
            +
                    "model.action" => action
         | 
| 11 | 
            +
                  )
         | 
| 12 | 
            +
                }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                { task: task, id: "model.build", runner_options: runner_options }
         | 
| 8 15 | 
             
              end
         | 
| 9 16 |  | 
| 10 | 
            -
               | 
| 11 | 
            -
                def  | 
| 17 | 
            +
              class Model
         | 
| 18 | 
            +
                def call(options, params:,  **)
         | 
| 12 19 | 
             
                  builder = Model::Builder.new
         | 
| 13 20 |  | 
| 14 | 
            -
                   | 
| 15 | 
            -
                    options["model"] = model = builder.(options, options["params"])
         | 
| 21 | 
            +
                  options[:model] = model = builder.(options, params)
         | 
| 16 22 |  | 
| 17 | 
            -
             | 
| 23 | 
            +
                  options["result.model"] = result = Result.new(!model.nil?, {})
         | 
| 18 24 |  | 
| 19 | 
            -
             | 
| 20 | 
            -
                  end
         | 
| 25 | 
            +
                  result.success?
         | 
| 21 26 | 
             
                end
         | 
| 22 27 |  | 
| 23 28 | 
             
                class Builder
         | 
| @@ -1,113 +1,84 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
              # WARNING: this is experimental API, but it will end up with something like that.
         | 
| 9 | 
            -
              module Element
         | 
| 10 | 
            -
                # DISCUSS: add builders here.
         | 
| 11 | 
            -
                def initialize(wrapped=nil)
         | 
| 12 | 
            -
                  @wrapped = wrapped
         | 
| 13 | 
            -
                end
         | 
| 1 | 
            +
            # per default, everything we pass into a circuit is immutable. it's the ops/act's job to allow writing (via a Context)
         | 
| 2 | 
            +
            module Trailblazer
         | 
| 3 | 
            +
              class Operation
         | 
| 4 | 
            +
                def self.Nested(callable, input:nil, output:nil, id: "Nested(#{callable})")
         | 
| 5 | 
            +
                  task_wrap_wirings = []
         | 
| 6 | 
            +
                  task, operation = Nested.build(callable, input, output)
         | 
| 14 7 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
                  def initialize(wrapped)
         | 
| 17 | 
            -
                    @wrapped = Option::KW.(wrapped)
         | 
| 18 | 
            -
                  end
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
              end
         | 
| 8 | 
            +
                  # @needs operation#outputs
         | 
| 21 9 |  | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
                # superinternal API is not entirely decided, yet.
         | 
| 25 | 
            -
                # @api private
         | 
| 26 | 
            -
                def self.for(step, input, output, is_nestable_object=method(:nestable_object?)) # DISCUSS: use builders here?
         | 
| 27 | 
            -
                  invoker            = Caller::Dynamic.new(step)
         | 
| 28 | 
            -
                  invoker            = Caller.new(step) if is_nestable_object.(step)
         | 
| 10 | 
            +
                  # TODO: move this to the generic step DSL
         | 
| 11 | 
            +
                  task_wrap_extensions = []
         | 
| 29 12 |  | 
| 30 | 
            -
                   | 
| 31 | 
            -
             | 
| 13 | 
            +
                  if input || output
         | 
| 14 | 
            +
                    default_input_filter  = ->(options, *) { ctx = options }
         | 
| 15 | 
            +
                    default_output_filter = ->(options, *) { options }
         | 
| 32 16 |  | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 17 | 
            +
                    input  ||= default_input_filter
         | 
| 18 | 
            +
                    output ||= default_output_filter
         | 
| 35 19 |  | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
                    result = invoker.(operation, options, options_for_nested.(operation, options)) # TODO: what about containers?
         | 
| 20 | 
            +
                    input_filter  = Activity::Wrap::Input.new(input)
         | 
| 21 | 
            +
                    output_filter = Activity::Wrap::Output.new(output)
         | 
| 39 22 |  | 
| 40 | 
            -
                     | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 23 | 
            +
                    task_wrap_extensions = Activity::Magnetic::Builder::Path.plan do
         | 
| 24 | 
            +
                      task input_filter,  id: ".input",  before: "task_wrap.call_task"
         | 
| 25 | 
            +
                      task output_filter, id: ".output", before: "End.success", group: :end # DISCUSS: position
         | 
| 26 | 
            +
                    end
         | 
| 43 27 | 
             
                  end
         | 
| 44 | 
            -
             | 
| 28 | 
            +
                    # Default {Output} copies the mutable data from the nested activity into the original.
         | 
| 45 29 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
                  # interestingly, with < we get a weird nil exception. bug in Ruby?
         | 
| 48 | 
            -
                  object.is_a?(Class) && object <= Trailblazer::Operation
         | 
| 30 | 
            +
                  { task: task, id: id, runner_options: { merge: task_wrap_extensions }, plus_poles: Activity::Magnetic::DSL::PlusPoles.from_outputs(operation.outputs) }
         | 
| 49 31 | 
             
                end
         | 
| 50 32 |  | 
| 51 | 
            -
                #  | 
| 52 | 
            -
                 | 
| 53 | 
            -
                   | 
| 33 | 
            +
                # @private
         | 
| 34 | 
            +
                module Nested
         | 
| 35 | 
            +
                  def self.build(nested_operation, input, output) # DISCUSS: use builders here?
         | 
| 36 | 
            +
                    return dynamic = Dynamic.new(nested_operation), dynamic unless nestable_object?(nested_operation)
         | 
| 54 37 |  | 
| 55 | 
            -
             | 
| 56 | 
            -
                     | 
| 38 | 
            +
                    # The returned {Nested} instance is a valid circuit element and will be `call`ed in the circuit.
         | 
| 39 | 
            +
                    # It simply returns the nested activity's `signal,options,flow_options` return set.
         | 
| 40 | 
            +
                    # The actual wiring - where to go with that - is done by the step DSL.
         | 
| 41 | 
            +
                    return Trailblazer::Activity::Subprocess(nested_operation, call: :__call__), nested_operation
         | 
| 57 42 | 
             
                  end
         | 
| 58 43 |  | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
                    operation._call(options)
         | 
| 44 | 
            +
                  def self.nestable_object?(object)
         | 
| 45 | 
            +
                    object.is_a?( Trailblazer::Activity::Interface )
         | 
| 62 46 | 
             
                  end
         | 
| 63 47 |  | 
| 64 | 
            -
                  def  | 
| 65 | 
            -
             | 
| 66 | 
            -
                  class Dynamic < Caller
         | 
| 67 | 
            -
                    include Element::Dynamic
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                    def nested(input, options)
         | 
| 70 | 
            -
                      @wrapped.(input, options)
         | 
| 71 | 
            -
                    end
         | 
| 48 | 
            +
                  def self.operation_class
         | 
| 49 | 
            +
                    Operation
         | 
| 72 50 | 
             
                  end
         | 
| 73 | 
            -
                end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                class Options
         | 
| 76 | 
            -
                  include Element
         | 
| 77 51 |  | 
| 78 | 
            -
                   | 
| 79 | 
            -
                  def call(input, options)
         | 
| 80 | 
            -
                    options.to_runtime_data[0]
         | 
| 81 | 
            -
                  end
         | 
| 52 | 
            +
                  private
         | 
| 82 53 |  | 
| 54 | 
            +
                  # For dynamic `Nested`s that do not expose an {Activity} interface.
         | 
| 55 | 
            +
                  # Since we do not know its outputs, we have to map them to :success and :failure, only.
         | 
| 56 | 
            +
                  #
         | 
| 57 | 
            +
                  # This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
         | 
| 83 58 | 
             
                  class Dynamic
         | 
| 84 | 
            -
                     | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 59 | 
            +
                    def initialize(wrapped)
         | 
| 60 | 
            +
                      @wrapped = Trailblazer::Option::KW(wrapped)
         | 
| 61 | 
            +
                      @outputs = {
         | 
| 62 | 
            +
                        :success => Activity::Output( Railway::End::Success.new(:success), :success ),
         | 
| 63 | 
            +
                        :failure => Activity::Output( Railway::End::Failure.new(:failure), :failure ),
         | 
| 64 | 
            +
                      }
         | 
| 88 65 | 
             
                    end
         | 
| 89 | 
            -
                  end
         | 
| 90 66 |  | 
| 91 | 
            -
             | 
| 92 | 
            -
                    include Element
         | 
| 67 | 
            +
                    attr_reader :outputs
         | 
| 93 68 |  | 
| 94 | 
            -
                    def call( | 
| 95 | 
            -
                       | 
| 96 | 
            -
                    end
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                    def mutable_data_for(result)
         | 
| 99 | 
            -
                      result.instance_variable_get(:@data).to_mutable_data
         | 
| 100 | 
            -
                    end
         | 
| 69 | 
            +
                    def call( (options, flow_options), **circuit_options )
         | 
| 70 | 
            +
                      activity = @wrapped.(options, circuit_options) # evaluate the option to get the actual "object" to call.
         | 
| 101 71 |  | 
| 102 | 
            -
             | 
| 103 | 
            -
                      include Element::Dynamic
         | 
| 72 | 
            +
                      signal, args = activity.__call__( [options, flow_options], **circuit_options )
         | 
| 104 73 |  | 
| 105 | 
            -
                       | 
| 106 | 
            -
             | 
| 107 | 
            -
                       | 
| 74 | 
            +
                      # Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
         | 
| 75 | 
            +
                      # Note that here we lose information about what specific event was emitted.
         | 
| 76 | 
            +
                      [
         | 
| 77 | 
            +
                        signal.kind_of?(Railway::End::Success) ? @outputs[:success].signal : @outputs[:failure].signal,
         | 
| 78 | 
            +
                        args
         | 
| 79 | 
            +
                      ]
         | 
| 108 80 | 
             
                    end
         | 
| 109 81 | 
             
                  end
         | 
| 110 82 | 
             
                end
         | 
| 111 | 
            -
              end
         | 
| 83 | 
            +
              end # Operation
         | 
| 112 84 | 
             
            end
         | 
| 113 | 
            -
             |