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
    
        data/Rakefile
    CHANGED
    
    | @@ -5,6 +5,25 @@ task :default => [:test] | |
| 5 5 |  | 
| 6 6 | 
             
            Rake::TestTask.new(:test) do |test|
         | 
| 7 7 | 
             
              test.libs << 'test'
         | 
| 8 | 
            -
              test.test_files = FileList['test/**/*_test.rb']
         | 
| 8 | 
            +
              # test.test_files = FileList['test/**/*_test.rb']
         | 
| 9 | 
            +
              test_files = FileList[%w{
         | 
| 10 | 
            +
                test/operation/guard_test.rb
         | 
| 11 | 
            +
                test/operation/pundit_test.rb
         | 
| 12 | 
            +
                test/operation/builder_test.rb
         | 
| 13 | 
            +
                test/operation/model_test.rb
         | 
| 14 | 
            +
                test/operation/contract_test.rb
         | 
| 15 | 
            +
                test/operation/persist_test.rb
         | 
| 16 | 
            +
                test/operation/callback_test.rb
         | 
| 17 | 
            +
                test/operation/resolver_test.rb
         | 
| 18 | 
            +
                test/operation/dsl/contract_test.rb
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                test/docs/*_test.rb
         | 
| 21 | 
            +
                }]
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              if RUBY_VERSION == "1.9.3"
         | 
| 24 | 
            +
                test_files = test_files - %w{test/docs/dry_test.rb test/docs/auto_inject_test.rb}
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              test.test_files = test_files
         | 
| 9 28 | 
             
              test.verbose = true
         | 
| 10 | 
            -
            end
         | 
| 29 | 
            +
            end
         | 
    
        data/draft-1.2.rb
    ADDED
    
    
    
        data/lib/trailblazer.rb
    CHANGED
    
    | @@ -1,7 +1,20 @@ | |
| 1 1 | 
             
            require "trailblazer/operation"
         | 
| 2 | 
            +
            require "trailblazer/operation/pipetree"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require "trailblazer/dsl"
         | 
| 2 5 | 
             
            require "trailblazer/version"
         | 
| 3 | 
            -
            require "uber/inheritable_attr"
         | 
| 4 6 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 7 | 
            +
            require "trailblazer/operation/builder"
         | 
| 8 | 
            +
            require "trailblazer/operation/model"
         | 
| 9 | 
            +
            require "trailblazer/operation/contract"
         | 
| 10 | 
            +
            require "trailblazer/operation/validate"
         | 
| 11 | 
            +
            require "trailblazer/operation/representer"
         | 
| 12 | 
            +
            require "trailblazer/operation/present"
         | 
| 13 | 
            +
            require "trailblazer/operation/policy"
         | 
| 14 | 
            +
            require "trailblazer/operation/pundit"
         | 
| 15 | 
            +
            require "trailblazer/operation/guard"
         | 
| 16 | 
            +
            require "trailblazer/operation/persist"
         | 
| 17 | 
            +
            # require "trailblazer/operation/callback"
         | 
| 18 | 
            +
            require "trailblazer/operation/nested"
         | 
| 19 | 
            +
            require "trailblazer/operation/wrap"
         | 
| 20 | 
            +
            require "trailblazer/operation/rescue"
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            module Trailblazer
         | 
| 2 | 
            +
              module DSL
         | 
| 3 | 
            +
                # Boring DSL code that allows to set a skill class, or define it ad-hoc using a block.
         | 
| 4 | 
            +
                # passing a constant always wipes out the existing class.
         | 
| 5 | 
            +
                #
         | 
| 6 | 
            +
                # Used in Contract, Representer, Callback, ..
         | 
| 7 | 
            +
                class Build
         | 
| 8 | 
            +
                  # options[:prefix]
         | 
| 9 | 
            +
                  # options[:class]
         | 
| 10 | 
            +
                  # options[:container]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # Currently, adds .class only to classes. this could break builder instances?
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def call(options, name=nil, constant=nil, dsl_block, &block)
         | 
| 15 | 
            +
                    # contract MyForm
         | 
| 16 | 
            +
                    if name.is_a?(Class)
         | 
| 17 | 
            +
                      constant = name
         | 
| 18 | 
            +
                      name     = :default
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    is_instance = !(constant.kind_of?(Class) || dsl_block) # i don't like this magic too much, but since it's the only DSL method in TRB, it should be ok. # DISCUSS: options[:is_instance]
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    path = path_name(options[:prefix], name, is_instance ? nil : "class") # "contract.default.class"
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    if is_instance
         | 
| 26 | 
            +
                      skill = constant
         | 
| 27 | 
            +
                    else
         | 
| 28 | 
            +
                      extended = options[:container][path] # Operation["contract.default.class"]
         | 
| 29 | 
            +
                      extended = yield extended if extended && block_given?
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      # only extend an existing skill class when NO constant was passed.
         | 
| 32 | 
            +
                      constant = (extended || options[:class]) if constant.nil?# && block_given?
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      skill = Class.new(constant)
         | 
| 35 | 
            +
                      skill.class_eval(&dsl_block) if dsl_block
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    [path, skill]
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                private
         | 
| 42 | 
            +
                  def path_name(prefix, name, suffix)
         | 
| 43 | 
            +
                    [prefix, name, suffix].compact.join(".") # "contract.class" for default, otherwise "contract.params.class" etc.
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require "dry/auto_inject"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Trailblazer::Operation
         | 
| 4 | 
            +
              # Thanks, @timriley! <3
         | 
| 5 | 
            +
              # https://gist.github.com/timriley/d314a58da9784912159006e208ba8ea9
         | 
| 6 | 
            +
              module AutoInject
         | 
| 7 | 
            +
                class InjectStrategy < Module
         | 
| 8 | 
            +
                  ClassMethods = Class.new(Module)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  attr_reader :container
         | 
| 11 | 
            +
                  attr_reader :dependency_map
         | 
| 12 | 
            +
                  attr_reader :class_mod
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def initialize(container, *dependency_names)
         | 
| 15 | 
            +
                    @container = container
         | 
| 16 | 
            +
                    @dependency_map = Dry::AutoInject::DependencyMap.new(*dependency_names)
         | 
| 17 | 
            +
                    @class_mod = ClassMethods.new
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def included(klass)
         | 
| 21 | 
            +
                    define_call
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    klass.singleton_class.prepend @class_mod
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    super
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def define_call
         | 
| 31 | 
            +
                    class_mod.class_exec(container, dependency_map) do |container, dependency_map|
         | 
| 32 | 
            +
                      define_method :call do |params={}, options={}, *dependencies|
         | 
| 33 | 
            +
                        options_with_deps = dependency_map.to_h.each_with_object({}) { |(name, identifier), obj|
         | 
| 34 | 
            +
                          obj[name] = options[name] || container[identifier]
         | 
| 35 | 
            +
                        }.merge(options)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                        super(params, options_with_deps, *dependencies)
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              def self.AutoInject(container)
         | 
| 45 | 
            +
                Dry::AutoInject(container, strategies: {default: AutoInject::InjectStrategy})
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -1,26 +1,26 @@ | |
| 1 1 | 
             
            require "uber/builder"
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 4 | 
            -
             | 
| 5 | 
            -
               | 
| 6 | 
            -
                 | 
| 7 | 
            -
             | 
| 3 | 
            +
            # http://trailblazer.to/gems/operation/2.0/builder.html
         | 
| 4 | 
            +
            class Trailblazer::Operation
         | 
| 5 | 
            +
              module Builder
         | 
| 6 | 
            +
                def self.import!(operation, import, user_builder)
         | 
| 7 | 
            +
                  import.(:>>, user_builder,
         | 
| 8 | 
            +
                    name:   "builder.call",
         | 
| 9 | 
            +
                    before: "operation.new")
         | 
| 8 10 |  | 
| 9 | 
            -
             | 
| 10 | 
            -
                 | 
| 11 | 
            -
              end
         | 
| 11 | 
            +
                  false # suppress inheritance. dislike. FIXME at some point.
         | 
| 12 | 
            +
                end
         | 
| 12 13 |  | 
| 13 | 
            -
             | 
| 14 | 
            -
                 | 
| 15 | 
            -
             | 
| 14 | 
            +
                # Include this when you want the ::builds DSL.
         | 
| 15 | 
            +
                def self.included(includer)
         | 
| 16 | 
            +
                  includer.extend DSL # ::builds, ::builders
         | 
| 17 | 
            +
                  includer.| includer.Builder( includer.builders ) # pass class Builders object to our ::import!.
         | 
| 18 | 
            +
                end
         | 
| 16 19 |  | 
| 17 | 
            -
             | 
| 18 | 
            -
              # Runs the builders for this operation class to figure out the actual class.
         | 
| 19 | 
            -
              def build_operation_class(*args)
         | 
| 20 | 
            -
                class_builder(self).(*args) # Uber::Builder::class_builder(context)
         | 
| 20 | 
            +
                DSL = Uber::Builder::DSL
         | 
| 21 21 | 
             
              end
         | 
| 22 22 |  | 
| 23 | 
            -
              def  | 
| 24 | 
            -
                 | 
| 23 | 
            +
              def self.Builder(*args, &block)
         | 
| 24 | 
            +
                [ Builder, args, block ]
         | 
| 25 25 | 
             
              end
         | 
| 26 | 
            -
            end
         | 
| 26 | 
            +
            end
         | 
| @@ -1,53 +1,46 @@ | |
| 1 1 | 
             
            require "declarative"
         | 
| 2 2 | 
             
            require "disposable/callback"
         | 
| 3 3 |  | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
                config  = self.class.callbacks.fetch(name) # TODO: test exception
         | 
| 14 | 
            -
                group   = config[:group].new(contract)
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                options[:context] ||= (config[:context] == :operation ? self : group)
         | 
| 17 | 
            -
                group.(options)
         | 
| 4 | 
            +
            # Needs #[], #[]= skill dependency.
         | 
| 5 | 
            +
            class Trailblazer::Operation
         | 
| 6 | 
            +
              module Callback
         | 
| 7 | 
            +
                def self.import!(operation, import, group)
         | 
| 8 | 
            +
                  import.(:&, ->(input, options) { input.callback!(group) },
         | 
| 9 | 
            +
                    name: "callback.#{group}")
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  operation.send :include, self
         | 
| 12 | 
            +
                end
         | 
| 18 13 |  | 
| 19 | 
            -
                 | 
| 20 | 
            -
               | 
| 14 | 
            +
                def callback!(name=:default, options=self) # FIXME: test options.
         | 
| 15 | 
            +
                  config  = self["callback.#{name}.class"] || raise #.fetch(name) # TODO: test exception
         | 
| 16 | 
            +
                  group   = config[:group].new(self["contract.default"])
         | 
| 21 17 |  | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
              end
         | 
| 18 | 
            +
                  options[:context] ||= (config[:context] == :operation ? self : group)
         | 
| 19 | 
            +
                  group.(options)
         | 
| 25 20 |  | 
| 26 | 
            -
             | 
| 27 | 
            -
                @invocations ||= {}
         | 
| 28 | 
            -
              end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
              module ClassMethods
         | 
| 31 | 
            -
                def callbacks
         | 
| 32 | 
            -
                  @callbacks ||= {}
         | 
| 21 | 
            +
                  invocations[name] = group
         | 
| 33 22 | 
             
                end
         | 
| 34 23 |  | 
| 35 | 
            -
                def  | 
| 36 | 
            -
                   | 
| 37 | 
            -
             | 
| 38 | 
            -
                  add_callback(name, constant, &block)
         | 
| 24 | 
            +
                def invocations
         | 
| 25 | 
            +
                  @invocations ||= {}
         | 
| 39 26 | 
             
                end
         | 
| 40 27 |  | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 28 | 
            +
                module DSL
         | 
| 29 | 
            +
                  def callback(name=:default, constant=nil, &block)
         | 
| 30 | 
            +
                    heritage.record(:callback, name, constant, &block)
         | 
| 44 31 |  | 
| 45 | 
            -
             | 
| 46 | 
            -
                     | 
| 47 | 
            -
                     | 
| 48 | 
            -
                  }
         | 
| 32 | 
            +
                    # FIXME: make this nicer. we want to extend same-named callback groups.
         | 
| 33 | 
            +
                    # TODO: allow the same with contract, or better, test it!
         | 
| 34 | 
            +
                    extended = self["callback.#{name}.class"] && self["callback.#{name}.class"]
         | 
| 49 35 |  | 
| 50 | 
            -
             | 
| 36 | 
            +
                    path, group_class = Trailblazer::DSL::Build.new.({ prefix: :callback, class: Disposable::Callback::Group, container: self }, name, constant, block) { |extended| extended[:group] }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    self[path] = { group: group_class, context: constant ? nil : :operation }
         | 
| 39 | 
            +
                  end
         | 
| 51 40 | 
             
                end
         | 
| 52 41 | 
             
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              def self.Callback(*args, &block)
         | 
| 44 | 
            +
                [ Callback, args, block ]
         | 
| 45 | 
            +
              end
         | 
| 53 46 | 
             
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            # Best practices for using contract.
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # * inject contract instance via constructor to #contract
         | 
| 4 | 
            +
            # * allow contract setup and memo via #contract(model, options)
         | 
| 5 | 
            +
            # * allow implicit automatic setup via #contract and class.contract_class
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # Needs Operation#model.
         | 
| 8 | 
            +
            # Needs #[], #[]= skill dependency.
         | 
| 9 | 
            +
            module Trailblazer::Operation::Contract
         | 
| 10 | 
            +
              module Build
         | 
| 11 | 
            +
                # bla build contract at runtime.
         | 
| 12 | 
            +
                def self.build_contract!(operation, options, name:"default", constant:nil, builder: nil)
         | 
| 13 | 
            +
                  # TODO: we could probably clean this up a bit at some point.
         | 
| 14 | 
            +
                  contract_class = constant || options["contract.#{name}.class"]
         | 
| 15 | 
            +
                  model          = operation["model"] # FIXME: model.default
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  return operation["contract.#{name}"] = Uber::Option[builder].(operation, constant: contract_class, model: model) if builder
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  operation["contract.#{name}"] = contract_class.new(model)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def self.import!(operation, import, **args)
         | 
| 23 | 
            +
                  import.(:>, ->(operation, options) { build_contract!(operation, options, **args) },
         | 
| 24 | 
            +
                    name: "contract.build")
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def self.Build(*args, &block)
         | 
| 29 | 
            +
                [ Build, args, block ]
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              module DSL
         | 
| 33 | 
            +
                # This is the class level DSL method.
         | 
| 34 | 
            +
                #   Op.contract #=> returns contract class
         | 
| 35 | 
            +
                #   Op.contract do .. end # defines contract
         | 
| 36 | 
            +
                #   Op.contract CommentForm # copies (and subclasses) external contract.
         | 
| 37 | 
            +
                #   Op.contract CommentForm do .. end # copies and extends contract.
         | 
| 38 | 
            +
                def contract(name=:default, constant=nil, base: Reform::Form, &block)
         | 
| 39 | 
            +
                  heritage.record(:contract, name, constant, &block)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  path, form_class = Trailblazer::DSL::Build.new.({ prefix: :contract, class: base, container: self }, name, constant, block)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  self[path] = form_class
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end # Contract
         | 
| 46 | 
            +
            end
         | 
| @@ -1,19 +1,17 @@ | |
| 1 | 
            -
            require "trailblazer/endpoint"
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            module Trailblazer::Operation::Controller
         | 
| 4 2 | 
             
            private
         | 
| 5 3 | 
             
              def form(operation_class, options={})
         | 
| 6 | 
            -
                res,  | 
| 7 | 
            -
                 | 
| 4 | 
            +
                res, options = operation_for!(operation_class, options) { |params| { operation: operation_class.present(params) } }
         | 
| 5 | 
            +
                res.contract.prepopulate!(options) # equals to @form.prepopulate!
         | 
| 8 6 |  | 
| 9 | 
            -
                 | 
| 7 | 
            +
                res.contract
         | 
| 10 8 | 
             
              end
         | 
| 11 9 |  | 
| 12 10 | 
             
              # Provides the operation instance, model and contract without running #process.
         | 
| 13 11 | 
             
              # Returns the operation.
         | 
| 14 12 | 
             
              def present(operation_class, options={})
         | 
| 15 | 
            -
                res,  | 
| 16 | 
            -
                 | 
| 13 | 
            +
                res, options = operation_for!(operation_class, options.merge(skip_form: true)) { |params| { operation: operation_class.present(params) } }
         | 
| 14 | 
            +
                res # FIXME.
         | 
| 17 15 | 
             
              end
         | 
| 18 16 |  | 
| 19 17 | 
             
              def collection(*args)
         | 
| @@ -23,11 +21,11 @@ private | |
| 23 21 | 
             
              end
         | 
| 24 22 |  | 
| 25 23 | 
             
              def run(operation_class, options={}, &block)
         | 
| 26 | 
            -
                res | 
| 24 | 
            +
                res = operation_for!(operation_class, options) { |params| operation_class.(params) }
         | 
| 27 25 |  | 
| 28 | 
            -
                yield  | 
| 26 | 
            +
                yield res if res[:valid] and block_given?
         | 
| 29 27 |  | 
| 30 | 
            -
                 | 
| 28 | 
            +
                res # FIXME.
         | 
| 31 29 | 
             
              end
         | 
| 32 30 |  | 
| 33 31 | 
             
              # The block passed to #respond is always run, regardless of the validity result.
         | 
| @@ -40,33 +38,53 @@ private | |
| 40 38 | 
             
              end
         | 
| 41 39 |  | 
| 42 40 | 
             
            private
         | 
| 43 | 
            -
              def operation!(operation_class, options={}) # or #model or #setup.
         | 
| 44 | 
            -
                operation_for!(operation_class, options) { |params| [true, operation_class.present(params)] }
         | 
| 45 | 
            -
              end
         | 
| 46 | 
            -
             | 
| 47 41 | 
             
              def process_params!(params)
         | 
| 48 42 | 
             
              end
         | 
| 49 43 |  | 
| 50 | 
            -
              # Override and return arbitrary params object.
         | 
| 51 | 
            -
              def params!(params)
         | 
| 52 | 
            -
                params
         | 
| 53 | 
            -
              end
         | 
| 54 | 
            -
             | 
| 55 44 | 
             
              # Normalizes parameters and invokes the operation (including its builders).
         | 
| 56 45 | 
             
              def operation_for!(operation_class, options, &block)
         | 
| 57 46 | 
             
                params = options[:params] || self.params # TODO: test params: parameter properly in all 4 methods.
         | 
| 58 | 
            -
                params = params!(params)
         | 
| 59 47 | 
             
                process_params!(params) # deprecate or rename to #setup_params!
         | 
| 60 48 |  | 
| 61 | 
            -
                res | 
| 62 | 
            -
                setup_operation_instance_variables!( | 
| 49 | 
            +
                res = Endpoint.new(operation_class, params, request, options).(&block)
         | 
| 50 | 
            +
                setup_operation_instance_variables!(res, options)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                [res, options.merge(params: params)]
         | 
| 53 | 
            +
              end
         | 
| 63 54 |  | 
| 64 | 
            -
             | 
| 55 | 
            +
              def setup_operation_instance_variables!(result, options)
         | 
| 56 | 
            +
                @operation = result # FIXME: remove!
         | 
| 57 | 
            +
                @model     = result["model"]
         | 
| 58 | 
            +
                @form      = result["contract"] unless options[:skip_form]
         | 
| 65 59 | 
             
              end
         | 
| 66 60 |  | 
| 67 | 
            -
               | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 61 | 
            +
              # Encapsulates HTTP-specific logic needed before running an operation.
         | 
| 62 | 
            +
              # Right now, all this does is #document_body! which figures out whether or not to pass the request body
         | 
| 63 | 
            +
              # into params, so the operation can use a representer to deserialize the original document.
         | 
| 64 | 
            +
              # To be used in Hanami, Roda, Rails, etc.
         | 
| 65 | 
            +
              class Endpoint
         | 
| 66 | 
            +
                def initialize(operation_class, params, request, options)
         | 
| 67 | 
            +
                  @operation_class = operation_class
         | 
| 68 | 
            +
                  @params          = params
         | 
| 69 | 
            +
                  @request         = request
         | 
| 70 | 
            +
                  @is_document     = options[:is_document]
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def call
         | 
| 74 | 
            +
                  document_body! if @is_document
         | 
| 75 | 
            +
                  yield @params# Create.run(params)
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              private
         | 
| 79 | 
            +
                attr_reader :params, :operation_class, :request
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def document_body!
         | 
| 82 | 
            +
                  # this is what happens:
         | 
| 83 | 
            +
                  # respond_with Comment::Update::JSON.run(params.merge(comment: request.body.string))
         | 
| 84 | 
            +
                  concept_name = operation_class.model_class.to_s.underscore # this could be renamed to ::concept_class soon.
         | 
| 85 | 
            +
                  request_body = request.body.respond_to?(:string) ? request.body.string : request.body.read
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  params.merge!(concept_name => request_body)
         | 
| 88 | 
            +
                end
         | 
| 71 89 | 
             
              end
         | 
| 72 90 | 
             
            end
         |