trailblazer 1.0.4 → 1.1.0.rc1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed93c4cc7b13ae2e7ae67009df6080abb36eb352
4
- data.tar.gz: 4fcf7486acf1eb5e33e1d5cb8da05f9d47a973cf
3
+ metadata.gz: c06b905e00be850d576dedb8139524da3f0f5f1d
4
+ data.tar.gz: f04942ee37e3c73ffc098bd5531fde00e293c566
5
5
  SHA512:
6
- metadata.gz: b33013263162958cb68fe02e4d2db68a4990104d29c380fb678db5eed73e9860a9b4d9b15060e69a7fd8c81796e323c317dbb8c926ded0d0cb6945bb2d0a6f8b
7
- data.tar.gz: 3098ba9e9c415cf53b9acf6f1e14cd904d0b5680fa76bcdd2ba6686f607bd73697aa223adfec4bc25f70928cc55f61436e64584de7b009acff73ac22f8654ad1
6
+ metadata.gz: f81051754c0537ed2c9c924a386274ed4c04c987968a8039da48318ac2ea565ce1a91f1edc5c433f7c08157a47e65319e3b32e711b5be979a235916548f70880
7
+ data.tar.gz: 5673d4ab693361e964212ec2d60624f69a3c79031cb062cd40dbf47540b9d528c7c3b215e59bd4b9fc6ab134af5b58496bdf45b4306864227f6419ac30cec33b
data/.travis.yml CHANGED
@@ -3,4 +3,8 @@ rvm:
3
3
  - 2.2.0
4
4
  - 2.1.2
5
5
  - 2.0.0
6
- # - 1.9.3
6
+ gemfiles:
7
+ - gemfiles/Gemfile.reform-2.0
8
+ - gemfiles/Gemfile.reform-2.1
9
+ before_install:
10
+ - gem install bundler
data/CHANGES.md CHANGED
@@ -1,10 +1,23 @@
1
+ # 1.1.0
2
+
3
+ * `Representer#represented` defaults to `model` now, not to `contract` anymore.
4
+ * The only way to let Trailblazer pass a document to the operation is via `is_document: true`. There is _no guessing_ anymore based on whether or not `Representer` is mixed into the operation or not.
5
+ * Add `Operation#params!` that works exactly like `#model!`: return another params hash here if you want to change the `params` structure while avoiding modifying the original one.
6
+ * Add `Controller#params!` that works exactly like `Operation#params!` and allows returning an arbitrary params object in the controller. Thanks to @davidpelaez for inspiration.
7
+ * Deprecate `Dispatch` in favor of `Callback`. In operations, please include `Operation::Callback`. Also, introduced `Operation#callback!` which aliases to `#dispatch!`. Goal is having to think less, and now all naming is in line.
8
+
9
+ ## Fixes
10
+
11
+ * `Representer#to_json` now allows passing options.
12
+ * The `:params` key never got propagated to `prepopulate!` when using `Controller#form`. This is now fixed.
13
+
1
14
  # 1.0.4
2
15
 
3
16
  * Fix `Controller#run`, which now returns the operation instance instead of the `Else` object.
4
17
 
5
18
  # 1.0.3
6
19
 
7
- Remove unprofessional `puts`, @smathy.
20
+ * Remove unprofessional `puts`, @smathy.
8
21
 
9
22
  # 1.0.2
10
23
 
data/Gemfile CHANGED
@@ -3,11 +3,16 @@ 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"
6
+ # gem "representable", path: "../representable"
7
+ gem "reform", path: "../reform"
8
+ gem "disposable", path: "../disposable"
8
9
  gem "virtus"
9
10
  # gem "reform", github: "apotonick/reform"
10
- # gem "reform", "~> 2.0.0"
11
- gem "reform", path: "../reform"
11
+ # gem "roar", github: "apotonick/roar"
12
+ # gem "reform", "~> 2.0.5"
13
+ # gem "roar"
14
+ # gem "reform", path: "../reform"
12
15
  gem "roar", path: "../roar"
13
- gem "multi_json"
16
+ gem "multi_json"
17
+
18
+ gem "minitest-line"
data/README.md CHANGED
@@ -14,7 +14,7 @@ _Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation,
14
14
  * Optional [callback](#callbacks) objects allow declaring post-processing logic.
15
15
  3. [Controllers](#controllers) instantly delegate to an operation. No business code in controllers, only HTTP-specific logic.
16
16
  4. [Models](#models) are persistence-only and solely define associations and scopes. No business code is to be found here. No validations, no callbacks.
17
- 5. The presentation layer offers optional [view models](#cells) (Cells) and [representers](#representers) for document APIs.
17
+ 5. The presentation layer offers optional [view models](#views) (Cells) and [representers](#representers) for document APIs.
18
18
 
19
19
  Trailblazer is designed to handle different contexts like user roles by applying [inheritance](#inheritance) between and [composing](#composing) of operations, form objects, policies, representers and callbacks.
20
20
 
@@ -95,7 +95,7 @@ class CommentsController < ApplicationController
95
95
 
96
96
  Again, the controller only dispatchs to the operation and handles successful/invalid processing on the HTTP level. For instance by redirecting, setting flash messages, or signing in a user.
97
97
 
98
- [Learn more.](http://trailblazerb.org/gems/operation/controller.html)
98
+ [Learn more.](http://trailblazer.to/gems/operation/controller.html)
99
99
 
100
100
  ## Operation
101
101
 
@@ -115,7 +115,7 @@ end
115
115
 
116
116
  Operations only need to implement `#process` which receives the params from the caller.
117
117
 
118
- [Learn more.](http://trailblazerb.org/gems/operation)
118
+ [Learn more.](http://trailblazer.to/gems/operation)
119
119
 
120
120
  ## Validations
121
121
 
@@ -144,7 +144,7 @@ The contract (aka _form_) is defined in the `::contract` block. You can implemen
144
144
 
145
145
  In the `#process` method you can define your business logic.
146
146
 
147
- [Learn more.](http://trailblazerb.org/gems/operation/api.html)
147
+ [Learn more.](http://trailblazer.to/gems/operation/api.html)
148
148
 
149
149
  ## Callbacks
150
150
 
@@ -154,6 +154,7 @@ Callbacks can be defined in groups. They use the form object's state tracking to
154
154
 
155
155
  ```ruby
156
156
  class Comment::Create < Trailblazer::Operation
157
+ include Callback
157
158
  callback(:after_save) do
158
159
  on_change :markdownize_body! # this is only run when the form object has changed.
159
160
  end
@@ -163,10 +164,11 @@ Callbacks are never triggered automatically, you have to invoke them! This is ca
163
164
 
164
165
  ```ruby
165
166
  class Comment::Create < Trailblazer::Operation
167
+ include Callback
166
168
  def process(params)
167
169
  validate(params) do
168
170
  contract.save
169
- dispatch!(:after_save) # run markdownize_body!, but only if form changed.
171
+ callback!(:after_save) # run markdownize_body!, but only if form changed.
170
172
  end
171
173
  end
172
174
 
@@ -178,7 +180,7 @@ end
178
180
 
179
181
  No magical triggering of unwanted logic anymore, but explicit invocations where you want it.
180
182
 
181
- [Learn more.](http://trailblazerb.org/gems/operation/callback.html)
183
+ [Learn more.](http://trailblazer.to/gems/operation/callback.html)
182
184
 
183
185
  ## Models
184
186
 
@@ -223,7 +225,7 @@ class Comment::Create < Trailblazer::Operation
223
225
 
224
226
  The policy is evaluated in `#setup!`, raises an exception if `false` and suppresses running `#process`.
225
227
 
226
- [Learn more.](http://trailblazerb.org/gems/operation/policy.html)
228
+ [Learn more.](http://trailblazer.to/gems/operation/policy.html)
227
229
 
228
230
  ## Views
229
231
 
@@ -266,7 +268,7 @@ class Comment::Create < Trailblazer::Operation
266
268
 
267
269
  The operation can then parse incoming JSON documents in `validate` and render a document via `to_json`.
268
270
 
269
- [Learn more.](http://trailblazerb.org/gems/operation/representer.html)
271
+ [Learn more.](http://trailblazer.to/gems/operation/representer.html)
270
272
 
271
273
  ## Tests
272
274
 
@@ -276,7 +278,7 @@ Operations completely replace the need for leaky factories.
276
278
 
277
279
  ```ruby
278
280
  describe Comment::Update do
279
- let(:comment) { Comment::Create.(comment: {body: "[That](http://trailblazerb.org)!"}) }
281
+ let(:comment) { Comment::Create.(comment: {body: "[That](http://trailblazer.to)!"}) }
280
282
  ```
281
283
 
282
284
  ## More
@@ -2,6 +2,5 @@ source "http://rubygems.org"
2
2
 
3
3
  gemspec path: '../'
4
4
 
5
- gem "railties"
6
- gem "activerecord"
7
- gem "sqlite3"
5
+ gem "reform", "~> 2.0.5"
6
+ gem "multi_json"
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec path: '../'
4
+
5
+ gem "reform", github: "apotonick/reform" #"~> 2.1.0.rc1"
6
+ gem "roar", github: "apotonick/roar" # "1.1.0"
7
+ gem "multi_json"
@@ -6,7 +6,8 @@ Trailblazer::Operation.class_eval do
6
6
  autoload :Controller, "trailblazer/operation/controller"
7
7
  autoload :Model, "trailblazer/operation/model"
8
8
  autoload :Collection, "trailblazer/operation/collection"
9
- autoload :Dispatch, "trailblazer/operation/dispatch"
9
+ autoload :Dispatch, "trailblazer/operation/dispatch" # TODO: remove in 1.2.
10
+ autoload :Callback, "trailblazer/operation/callback"
10
11
  autoload :Module, "trailblazer/operation/module"
11
12
  autoload :Representer,"trailblazer/operation/representer"
12
13
  autoload :Policy, "trailblazer/operation/policy"
@@ -8,7 +8,7 @@ module Trailblazer
8
8
  @operation_class = operation_class
9
9
  @params = params
10
10
  @request = request
11
- @is_document = document_request_for?(options)
11
+ @is_document = options[:is_document]
12
12
  end
13
13
 
14
14
  def call
@@ -19,16 +19,6 @@ module Trailblazer
19
19
  private
20
20
  attr_reader :params, :operation_class, :request
21
21
 
22
- # this is a really weak test but will make sure the document_body behavior is only enabled
23
- # for people who know what they're doing. also, this won't work if you use a polymorphic dispatch,
24
- # e.g. `run Comment::Create` where the builder will instantiate Create::JSON which has Representer
25
- # included.
26
- def document_request_for?(options)
27
- return options[:is_document] if options.has_key?(:is_document)
28
-
29
- operation_class < Operation::Representer # TODO: this doesn't work with polymorphic dispatch.
30
- end
31
-
32
22
  def document_body!
33
23
  # this is what happens:
34
24
  # respond_with Comment::Update::JSON.run(params.merge(comment: request.body.string))
@@ -0,0 +1,53 @@
1
+ require "declarative"
2
+ require "disposable/callback"
3
+
4
+ module Trailblazer::Operation::Callback
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+
8
+ base.extend Declarative::Heritage::Inherited
9
+ base.extend Declarative::Heritage::DSL
10
+ end
11
+
12
+ def callback!(name=:default, options={ operation: self, contract: contract, params: @params }) # FIXME: test options.
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)
18
+
19
+ invocations[name] = group
20
+ end
21
+
22
+ def dispatch!(*args, &block)
23
+ callback!(*args, &block)
24
+ end
25
+
26
+ def invocations
27
+ @invocations ||= {}
28
+ end
29
+
30
+ module ClassMethods
31
+ def callbacks
32
+ @callbacks ||= {}
33
+ end
34
+
35
+ def callback(name=:default, constant=nil, &block)
36
+ return callbacks[name][:group] unless constant or block_given?
37
+
38
+ add_callback(name, constant, &block)
39
+ end
40
+
41
+ private
42
+ def add_callback(name, constant, &block)
43
+ heritage.record(:add_callback, name, constant, &block)
44
+
45
+ callbacks[name] ||= {
46
+ group: Class.new(constant || Disposable::Callback::Group),
47
+ context: constant ? nil : :operation # `context: :operation` when the callback is inline. `context: group` otherwise.
48
+ }
49
+
50
+ callbacks[name][:group].class_eval(&block) if block_given?
51
+ end
52
+ end
53
+ end
@@ -3,23 +3,23 @@ require "trailblazer/endpoint"
3
3
  module Trailblazer::Operation::Controller
4
4
  private
5
5
  def form(operation_class, options={})
6
- options[:___dont_deprecate] = 1 # TODO: remove in 1.1.
6
+ res, op, options = operation!(operation_class, options)
7
+ op.contract.prepopulate!(options) # equals to @form.prepopulate!
7
8
 
8
- operation!(operation_class, options).tap do |op|
9
- op.contract.prepopulate!(options) # equals to @form.prepopulate!
10
- end.contract
9
+ op.contract
11
10
  end
12
11
 
13
12
  # Provides the operation instance, model and contract without running #process.
14
13
  # Returns the operation.
15
14
  def present(operation_class, options={})
16
- operation!(operation_class, skip_form: true)
15
+ res, op = operation!(operation_class, options.merge(skip_form: true))
16
+ op
17
17
  end
18
18
 
19
19
  def collection(*args)
20
- operation!(*args).tap do |op|
21
- @collection = op.model
22
- end
20
+ res, op = operation!(*args)
21
+ @collection = op.model
22
+ op
23
23
  end
24
24
 
25
25
  def run(operation_class, options={}, &block)
@@ -32,9 +32,7 @@ private
32
32
 
33
33
  # The block passed to #respond is always run, regardless of the validity result.
34
34
  def respond(operation_class, options={}, &block)
35
- options[:___dont_deprecate] = 1 # TODO: remove in 1.1.
36
-
37
- res, op = operation_for!(operation_class, options) { |params| operation_class.run(params) }
35
+ res, op = operation_for!(operation_class, options) { |params| operation_class.run(params) }
38
36
  namespace = options.delete(:namespace) || []
39
37
 
40
38
  return respond_with *namespace, op, options if not block_given?
@@ -43,37 +41,27 @@ private
43
41
 
44
42
  private
45
43
  def operation!(operation_class, options={}) # or #model or #setup.
46
- res, op = operation_for!(operation_class, options) { |params| [true, operation_class.present(params)] }
47
- op
44
+ operation_for!(operation_class, options) { |params| [true, operation_class.present(params)] }
48
45
  end
49
46
 
50
47
  def process_params!(params)
51
48
  end
52
49
 
50
+ # Override and return arbitrary params object.
51
+ def params!(params)
52
+ params
53
+ end
54
+
53
55
  # Normalizes parameters and invokes the operation (including its builders).
54
56
  def operation_for!(operation_class, options, &block)
55
- options = deprecate_positional_params_argument!(options) # TODO: remove in 1.1.
56
-
57
- params = options.delete(:params) || self.params # TODO: test params: parameter properly in all 4 methods.
57
+ params = options[:params] || self.params # TODO: test params: parameter properly in all 4 methods.
58
+ params = params!(params)
59
+ process_params!(params) # deprecate or rename to #setup_params!
58
60
 
59
- process_params!(params)
60
61
  res, op = Trailblazer::Endpoint.new(operation_class, params, request, options).(&block)
61
62
  setup_operation_instance_variables!(op, options)
62
63
 
63
- [res, op]
64
- end
65
-
66
- def deprecate_positional_params_argument!(options) # TODO: remove in 1.1.
67
- return options if options.has_key?(:skip_form)
68
- return options if options.has_key?(:is_document)
69
- return options if options.has_key?(:params)
70
- return options if options.has_key?(:namespace)
71
- return options if options.has_key?(:___dont_deprecate)
72
- return options if options.size == 0
73
-
74
- warn "[Trailblazer] The positional params argument for #run, #present, #form and #respond is deprecated and will be removed in 1.1.
75
- Please provide a custom params via `run Comment::Create, params: {..}` and have a nice day."
76
- {params: options}
64
+ [res, op, options.merge(params: params)]
77
65
  end
78
66
 
79
67
  def setup_operation_instance_variables!(operation, options)
@@ -1,29 +1,3 @@
1
- require "disposable/callback"
2
-
3
- module Trailblazer::Operation::Dispatch
4
- def self.included(base)
5
- base.extend ClassMethods
6
- base.inheritable_attr :callbacks
7
- base.callbacks = Representable::Cloneable::Hash.new
8
- end
9
-
10
- def dispatch!(name=:default)
11
- group = self.class.callbacks[name].new(contract)
12
- group.(context: self)
13
-
14
- invocations[name] = group
15
- end
16
-
17
- def invocations
18
- @invocations ||= {}
19
- end
20
-
21
- module ClassMethods
22
- def callback(name=:default, constant=nil, &block)
23
- return callbacks[name] unless constant or block_given?
24
-
25
- callbacks[name] ||= Class.new(constant || Disposable::Callback::Group).extend(Representable::Cloneable) # FIXME: why Representable?
26
- callbacks[name].class_eval(&block) if block_given?
27
- end
28
- end
29
- end
1
+ require "trailblazer/operation/callback"
2
+ # TODO: deprecate this module.
3
+ Trailblazer::Operation::Dispatch = Trailblazer::Operation::Callback
@@ -1,11 +1,11 @@
1
+ require "trailblazer/operation/policy/guard"
2
+
1
3
  module Trailblazer
2
4
  class NotAuthorizedError < RuntimeError
3
5
  end
4
6
 
5
7
  # Adds #evaluate_policy to #setup!, and ::policy.
6
8
  module Operation::Policy
7
- require "trailblazer/operation/policy/guard"
8
-
9
9
  def self.included(includer)
10
10
  includer.extend DSL
11
11
  end
@@ -30,14 +30,11 @@ module Trailblazer
30
30
  private
31
31
  module Setup
32
32
  def setup!(params)
33
- super
34
- evaluate_policy(params)
33
+ evaluate_policy(super)
35
34
  end
36
35
  end
37
36
  include Setup
38
37
 
39
-
40
- private
41
38
  def evaluate_policy(params)
42
39
  user = params[:current_user]
43
40
 
@@ -84,4 +81,4 @@ module Trailblazer
84
81
  end
85
82
  end
86
83
  end
87
- end
84
+ end
@@ -27,25 +27,44 @@ module Trailblazer::Operation::Representer
27
27
  self._representer_class = constant
28
28
  end
29
29
 
30
+ require "disposable/version"
30
31
  def infer_representer_class
31
- Disposable::Twin::Schema.from(contract_class,
32
- include: [Representable::JSON],
33
- options_from: :deserializer, # use :instance etc. in deserializer.
34
- superclass: Representable::Decorator,
35
- representer_from: lambda { |inline| inline.representer_class },
36
- exclude_options: [:default, :populator] # TODO: test with populator: in an operation.
37
- )
32
+ if Disposable::VERSION =~ /^0.1/
33
+ warn "[Trailblazer] Reform 2.0 won't be supported in Trailblazer 1.2. Don't be lazy and upgrade to Reform 2.1."
34
+
35
+ Disposable::Twin::Schema.from(contract_class,
36
+ include: [Representable::JSON],
37
+ options_from: :deserializer, # use :instance etc. in deserializer.
38
+ superclass: Representable::Decorator,
39
+ representer_from: lambda { |inline| inline.representer_class },
40
+ )
41
+ else
42
+ Disposable::Rescheme.from(contract_class,
43
+ include: [Representable::JSON],
44
+ options_from: :deserializer, # use :instance etc. in deserializer.
45
+ superclass: Representable::Decorator,
46
+ definitions_from: lambda { |inline| inline.definitions },
47
+ exclude_options: [:default, :populator], # TODO: test with populator: in an operation.
48
+ exclude_properties: [:persisted?]
49
+ )
50
+ end
38
51
  end
39
52
  end
40
53
 
41
54
  private
42
55
  module Rendering
43
- def to_json(*)
44
- self.class.representer_class.new(represented).to_json
56
+ # Override this if you need to pass options to the rendering.
57
+ #
58
+ # def to_json(*)
59
+ # super(include: @params[:include])
60
+ # end
61
+ def to_json(options={})
62
+ self.class.representer_class.new(represented).to_json(options)
45
63
  end
46
64
 
65
+ # Override this if you want to render something else, e.g. the contract.
47
66
  def represented
48
- contract
67
+ model
49
68
  end
50
69
  end
51
70
  include Rendering
@@ -34,7 +34,7 @@ module Trailblazer
34
34
  build_operation(params, raise_on_invalid: true).run.last
35
35
  end
36
36
 
37
- def [](*args) # TODO: remove in 1.1.
37
+ def [](*args) # TODO: remove in 1.2.
38
38
  warn "[Trailblazer] Operation[] is deprecated. Please use Operation.() and have a nice day."
39
39
  call(*args)
40
40
  end
@@ -59,7 +59,6 @@ module Trailblazer
59
59
 
60
60
 
61
61
  def initialize(params, options={})
62
- @params = params
63
62
  @options = options
64
63
  @valid = true
65
64
 
@@ -90,8 +89,21 @@ module Trailblazer
90
89
  private
91
90
  module Setup
92
91
  def setup!(params)
92
+ params = assign_params!(params)
93
93
  setup_params!(params)
94
94
  build_model!(params)
95
+ params # TODO: test me.
96
+ end
97
+
98
+ def assign_params!(params)
99
+ @params = params!(params)
100
+ end
101
+
102
+ # Overwrite #params! if you need to change its structure, by returning a new params object
103
+ # from this method.
104
+ # This is helpful if you don't want to change the original via #setup_params!.
105
+ def params!(params)
106
+ params
95
107
  end
96
108
 
97
109
  def setup_params!(params)
@@ -1,3 +1,3 @@
1
1
  module Trailblazer
2
- VERSION = "1.0.4"
2
+ VERSION = "1.1.0.rc1"
3
3
  end
@@ -1,12 +1,12 @@
1
1
  require 'test_helper'
2
- require "trailblazer/operation/dispatch"
2
+ require "trailblazer/operation/callback"
3
3
 
4
4
  # callbacks are tested in Disposable::Callback::Group.
5
5
  class OperationCallbackTest < MiniTest::Spec
6
6
  Song = Struct.new(:name)
7
7
 
8
8
  class Create < Trailblazer::Operation
9
- include Trailblazer::Operation::Dispatch
9
+ include Trailblazer::Operation::Callback
10
10
 
11
11
  contract do
12
12
  property :name
@@ -23,7 +23,7 @@ class OperationCallbackTest < MiniTest::Spec
23
23
  @model = Song.new
24
24
 
25
25
  validate(params, @model) do
26
- dispatch!
26
+ callback!
27
27
  end
28
28
  end
29
29
 
@@ -61,4 +61,44 @@ class OperationCallbackTest < MiniTest::Spec
61
61
  op = Update.({"name"=>"Keep On Running"})
62
62
  op.dispatched.must_equal [:notify_you!]
63
63
  end
64
+ end
65
+
66
+ # TODO: remove in 1.2.
67
+ require "trailblazer/operation/dispatch"
68
+ class OperationDeprecatedDispatchTest < MiniTest::Spec
69
+ Song = Struct.new(:name)
70
+
71
+ class Create < Trailblazer::Operation
72
+ include Trailblazer::Operation::Dispatch
73
+
74
+ contract do
75
+ property :name
76
+ end
77
+
78
+ callback do
79
+ on_change :notify_me!
80
+ end
81
+
82
+ def process(params)
83
+ @model = Song.new
84
+
85
+ validate(params, @model) do
86
+ dispatch!
87
+ end
88
+ end
89
+
90
+ def dispatched
91
+ @dispatched ||= []
92
+ end
93
+
94
+ private
95
+ def notify_me!(*)
96
+ dispatched << :notify_me!
97
+ end
98
+ end
99
+
100
+ it "invokes all callbacks [deprecated]" do
101
+ op = Create.({"name"=>"Keep On Running"})
102
+ op.dispatched.must_equal [:notify_me!]
103
+ end
64
104
  end
@@ -0,0 +1,111 @@
1
+ require "test_helper"
2
+ require "trailblazer/operation/controller"
3
+
4
+ class ControllerTest < Minitest::Spec
5
+ def self.controller!(&block)
6
+ let (:_controller) {
7
+ Class.new do
8
+ include Trailblazer::Operation::Controller
9
+
10
+ def initialize(params={})
11
+ @params = params
12
+ end
13
+ attr_reader :params, :request
14
+
15
+ class_eval(&block)
16
+ self
17
+ end
18
+ }
19
+ end
20
+
21
+ def controller(params={})
22
+ _controller.new(params)
23
+ end
24
+
25
+
26
+ User = Struct.new(:role)
27
+
28
+ Comment = Struct.new(:body)
29
+
30
+ class Comment::Update < Trailblazer::Operation
31
+ def model!(params)
32
+ Comment.new(params[:body])
33
+ end
34
+
35
+ def inspect
36
+ super.sub(/:0x\w+/, "")
37
+ end
38
+ end
39
+
40
+
41
+ describe "#present with options" do
42
+ controller! do
43
+ def show
44
+ present Comment::Update, params: { current_user: User.new(:admin) }
45
+ end
46
+ end
47
+
48
+ it do
49
+ controller.show.inspect.must_equal "#<ControllerTest::Comment::Update @options={}, @valid=true, @params={:current_user=>#<struct ControllerTest::User role=:admin>}, @model=#<struct ControllerTest::Comment body=nil>>"
50
+ end
51
+ end
52
+
53
+ describe "#params!" do
54
+ controller! do
55
+ def show
56
+ present Comment::Update, params: "Cool!"
57
+ end
58
+
59
+ def params!(params)
60
+ { body: params }
61
+ end
62
+ end
63
+
64
+ it { controller.show.inspect.must_equal "#<ControllerTest::Comment::Update @options={}, @valid=true, @params={:body=>\"Cool!\"}, @model=#<struct ControllerTest::Comment body=\"Cool!\">>" }
65
+ end
66
+
67
+ describe "#form" do
68
+ class Comment::Create < Trailblazer::Operation
69
+ def model!(params)
70
+ Comment.new
71
+ end
72
+
73
+ contract do
74
+ def prepopulate!(options)
75
+ @options = options
76
+ end
77
+ attr_reader :options
78
+ end
79
+ end
80
+
81
+ describe "#prepopulate! options" do
82
+ controller! do
83
+ def show
84
+ form Comment::Create
85
+ end
86
+ end
87
+
88
+ it { controller(__body: "Great!").show.options.inspect.must_equal "{:params=>{:__body=>\"Great!\"}}" }
89
+ end
90
+
91
+ describe "with additional options" do
92
+ controller! do
93
+ def show
94
+ form Comment::Create, admin: true
95
+ end
96
+ end
97
+
98
+ it { controller(__body: "Great!").show.options.inspect.must_equal "{:admin=>true, :params=>{:__body=>\"Great!\"}}" }
99
+ end
100
+
101
+ describe "with options and :params" do
102
+ controller! do
103
+ def show
104
+ form Comment::Create, admin: true, params: params.merge(user: User.new)
105
+ end
106
+ end
107
+
108
+ it { controller(__body: "Great!").show.options.inspect.must_equal "{:admin=>true, :params=>{:__body=>\"Great!\", :user=>#<struct ControllerTest::User role=nil>}}" }
109
+ end
110
+ end
111
+ end
@@ -53,12 +53,16 @@ class DslCallbackTest < MiniTest::Spec
53
53
  end
54
54
 
55
55
  describe "Op.callback" do
56
- it { Operation.callback(:default).must_equal Operation.callbacks[:default] }
56
+ it { Operation.callback(:default).must_equal Operation.callbacks[:default][:group] }
57
57
  end
58
58
 
59
59
  describe "Op.callback :after_save, AfterSaveCallback" do
60
60
  class AfterSaveCallback < Disposable::Callback::Group
61
61
  on_change :after_save!
62
+
63
+ def after_save!(twin, options)
64
+ options[:operation]._invocations << :after_save!
65
+ end
62
66
  end
63
67
 
64
68
  class OpWithExternalCallback < Trailblazer::Operation
@@ -70,8 +74,6 @@ class DslCallbackTest < MiniTest::Spec
70
74
  contract(OpenStruct.new).validate(params)
71
75
  dispatch!(:after_save)
72
76
  end
73
-
74
- def after_save!(*); _invocations << :after_save!; end
75
77
  end
76
78
 
77
79
  it { OpWithExternalCallback.("title"=>"Thunder Rising")._invocations.must_equal([:after_save!]) }
@@ -80,13 +82,16 @@ class DslCallbackTest < MiniTest::Spec
80
82
  describe "Op.callback :after_save, AfterSaveCallback do .. end" do
81
83
  class DefaultCallback < Disposable::Callback::Group
82
84
  on_change :default!
85
+
86
+ def default!(twin, options)
87
+ options[:operation]._invocations << :default!
88
+ end
83
89
  end
84
90
 
85
91
  class OpUsingCallback < Trailblazer::Operation
86
92
  include Dispatch
87
93
  include SongProcess
88
94
  callback :default, DefaultCallback
89
- def default!(*); _invocations << :default!; end
90
95
  end
91
96
 
92
97
  class OpExtendingCallback < Trailblazer::Operation
@@ -94,15 +99,20 @@ class DslCallbackTest < MiniTest::Spec
94
99
  include SongProcess
95
100
  callback :default, DefaultCallback do
96
101
  on_change :after_save!
97
- end
98
102
 
99
- def default!(*); _invocations << :default!; end
100
- def after_save!(*); _invocations << :after_save!; end
103
+ def default!(twin, options)
104
+ options[:operation]._invocations << :extended_default!
105
+ end
106
+
107
+ def after_save!(twin, options)
108
+ options[:operation]._invocations << :after_save!
109
+ end
110
+ end
101
111
  end
102
112
 
103
113
  # this operation copies DefaultCallback and shouldn't run #after_save!.
104
114
  it { OpUsingCallback.(title: "Thunder Rising")._invocations.must_equal([:default!]) }
105
115
  # this operation copies DefaultCallback, extends it and runs #after_save!.
106
- it { OpExtendingCallback.(title: "Thunder Rising")._invocations.must_equal([:default!, :after_save!]) }
116
+ it { OpExtendingCallback.(title: "Thunder Rising")._invocations.must_equal([:extended_default!, :after_save!]) }
107
117
  end
108
118
  end
@@ -60,9 +60,28 @@ class OpPolicyGuardTest < MiniTest::Spec
60
60
 
61
61
  it { Show.({}).wont_equal nil }
62
62
  end
63
- end
64
63
 
65
64
 
65
+ describe "#params!" do
66
+ class Index < Trailblazer::Operation
67
+ include Policy::Guard
68
+
69
+ # make sure the guard receives the correct params.
70
+ policy { |params| params[:valid] == "true" }
71
+
72
+ def params!(params)
73
+ { valid: params }
74
+ end
75
+
76
+ def process(*)
77
+ end
78
+ end
79
+
80
+ it { Index.("true").wont_equal nil }
81
+ it { assert_raises(Trailblazer::NotAuthorizedError) { Index.(false).wont_equal nil } }
82
+ end
83
+ end
84
+
66
85
  class OpBuilderDenyTest < MiniTest::Spec
67
86
  Song = Struct.new(:name)
68
87
 
@@ -94,4 +113,4 @@ class OpBuilderDenyTest < MiniTest::Spec
94
113
  op = Create.(valid: false)
95
114
  end
96
115
  end
97
- end
116
+ end
@@ -24,6 +24,21 @@ class OperationSetupParamsTest < MiniTest::Spec
24
24
  it { OperationSetupParam.run({valid: true}).to_s.must_equal "[true, <OperationSetupParam @model={:valid=>true, :garrett=>\"Rocks!\"}>]" }
25
25
  end
26
26
 
27
+ class OperationParamsTest < MiniTest::Spec
28
+ class Operation < Trailblazer::Operation
29
+ def process(params)
30
+ @model = "#{params} and #{@params==params}"
31
+ end
32
+
33
+ def params!(params)
34
+ { params: params }
35
+ end
36
+ end
37
+
38
+ # allows you returning new params in #params!.
39
+ it { Operation.({valid: true}).model.to_s.must_equal "{:params=>{:valid=>true}} and true" }
40
+ end
41
+
27
42
  # Operation#model.
28
43
  class OperationModelTest < MiniTest::Spec
29
44
  class Operation < Trailblazer::Operation
@@ -123,4 +123,56 @@ class RepresenterTest < MiniTest::Spec
123
123
  res, op = JsonApiCreate.run(album: %{{"title":"Run For Cover"}})
124
124
  op.contract.title.must_equal "Run For Cover"
125
125
  end
126
+ end
127
+
128
+ class InternalRepresenterAPITest < MiniTest::Spec
129
+ Song = Struct.new(:id)
130
+
131
+ describe "#represented" do
132
+ class Show < Trailblazer::Operation
133
+ include Representer, Model
134
+ model Song, :create
135
+
136
+ representer do
137
+ property :class
138
+ end
139
+ end
140
+
141
+ it "uses #model as represented, per default" do
142
+ Show.present({}).to_json.must_equal '{"class":"InternalRepresenterAPITest::Song"}'
143
+ end
144
+
145
+ class ShowContract < Show
146
+ def represented
147
+ contract
148
+ end
149
+ end
150
+
151
+ it "can be overriden to use the contract" do
152
+ ShowContract.present({}).to_json.must_equal %{{"class":"#{ShowContract.contract_class}"}}
153
+ end
154
+ end
155
+
156
+ describe "#to_json" do
157
+ class OptionsShow < Trailblazer::Operation
158
+ include Representer
159
+
160
+ representer do
161
+ property :class
162
+ property :id
163
+ end
164
+
165
+ def to_json(*)
166
+ super(@params)
167
+ end
168
+
169
+ def model!(params)
170
+ Song.new(1)
171
+ end
172
+ end
173
+
174
+ it "allows to pass options to #to_json" do
175
+ OptionsShow.present(include: [:id]).to_json.must_equal '{"id":1}'
176
+ end
177
+ end
126
178
  end
data/trailblazer.gemspec CHANGED
@@ -7,9 +7,9 @@ Gem::Specification.new do |spec|
7
7
  spec.version = Trailblazer::VERSION
8
8
  spec.authors = ["Nick Sutterer"]
9
9
  spec.email = ["apotonick@gmail.com"]
10
- spec.description = %q{A high-level, modular architecture for Rails with domain and form objects, view models, twin decorators and representers.}
11
- spec.summary = %q{A new architecture for Rails.}
12
- spec.homepage = "http://www.trailblazerb.org"
10
+ spec.description = %q{A high-level, modular architecture for Ruby framworks with domain and form objects, view models, twin decorators and representers.}
11
+ spec.summary = %q{A high-level architecture for Ruby and Rails.}
12
+ spec.homepage = "http://trailblazer.to"
13
13
  spec.license = "MIT"
14
14
 
15
15
  spec.files = `git ls-files`.split($/)
@@ -19,7 +19,8 @@ Gem::Specification.new do |spec|
19
19
 
20
20
 
21
21
  spec.add_dependency "uber", ">= 0.0.15"
22
- spec.add_dependency "reform", ">= 1.2.0"
22
+ spec.add_dependency "reform", ">= 2.0.0", "< 3.0.0"
23
+ spec.add_dependency "declarative"
23
24
 
24
25
  spec.add_development_dependency "activemodel" # for Reform::AM::V
25
26
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-22 00:00:00.000000000 Z
11
+ date: 2015-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: uber
@@ -30,14 +30,34 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.2.0
33
+ version: 2.0.0
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: 3.0.0
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
41
  - - ">="
39
42
  - !ruby/object:Gem::Version
40
- version: 1.2.0
43
+ version: 2.0.0
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: 3.0.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: declarative
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
41
61
  - !ruby/object:Gem::Dependency
42
62
  name: activemodel
43
63
  requirement: !ruby/object:Gem::Requirement
@@ -136,8 +156,8 @@ dependencies:
136
156
  - - ">="
137
157
  - !ruby/object:Gem::Version
138
158
  version: '0'
139
- description: A high-level, modular architecture for Rails with domain and form objects,
140
- view models, twin decorators and representers.
159
+ description: A high-level, modular architecture for Ruby framworks with domain and
160
+ form objects, view models, twin decorators and representers.
141
161
  email:
142
162
  - apotonick@gmail.com
143
163
  executables: []
@@ -155,13 +175,15 @@ files:
155
175
  - TODO.md
156
176
  - doc/Trb-The-Stack.png
157
177
  - doc/trb.jpg
158
- - gemfiles/Gemfile.rails
159
178
  - gemfiles/Gemfile.rails.lock
179
+ - gemfiles/Gemfile.reform-2.0
180
+ - gemfiles/Gemfile.reform-2.1
160
181
  - lib/trailblazer.rb
161
182
  - lib/trailblazer/autoloading.rb
162
183
  - lib/trailblazer/endpoint.rb
163
184
  - lib/trailblazer/operation.rb
164
185
  - lib/trailblazer/operation/builder.rb
186
+ - lib/trailblazer/operation/callback.rb
165
187
  - lib/trailblazer/operation/collection.rb
166
188
  - lib/trailblazer/operation/controller.rb
167
189
  - lib/trailblazer/operation/dispatch.rb
@@ -175,12 +197,13 @@ files:
175
197
  - lib/trailblazer/operation/resolver.rb
176
198
  - lib/trailblazer/operation/uploaded_file.rb
177
199
  - lib/trailblazer/version.rb
200
+ - test/callback_test.rb
178
201
  - test/collection_test.rb
179
- - test/dispatch_test.rb
180
202
  - test/model_test.rb
181
203
  - test/module_test.rb
182
204
  - test/operation/builder_test.rb
183
205
  - test/operation/contract_test.rb
206
+ - test/operation/controller_test.rb
184
207
  - test/operation/dsl/callback_test.rb
185
208
  - test/operation/dsl/contract_test.rb
186
209
  - test/operation/dsl/representer_test.rb
@@ -194,7 +217,7 @@ files:
194
217
  - test/rollback_test.rb
195
218
  - test/test_helper.rb
196
219
  - trailblazer.gemspec
197
- homepage: http://www.trailblazerb.org
220
+ homepage: http://trailblazer.to
198
221
  licenses:
199
222
  - MIT
200
223
  metadata: {}
@@ -209,22 +232,23 @@ required_ruby_version: !ruby/object:Gem::Requirement
209
232
  version: '0'
210
233
  required_rubygems_version: !ruby/object:Gem::Requirement
211
234
  requirements:
212
- - - ">="
235
+ - - ">"
213
236
  - !ruby/object:Gem::Version
214
- version: '0'
237
+ version: 1.3.1
215
238
  requirements: []
216
239
  rubyforge_project:
217
240
  rubygems_version: 2.4.8
218
241
  signing_key:
219
242
  specification_version: 4
220
- summary: A new architecture for Rails.
243
+ summary: A high-level architecture for Ruby and Rails.
221
244
  test_files:
245
+ - test/callback_test.rb
222
246
  - test/collection_test.rb
223
- - test/dispatch_test.rb
224
247
  - test/model_test.rb
225
248
  - test/module_test.rb
226
249
  - test/operation/builder_test.rb
227
250
  - test/operation/contract_test.rb
251
+ - test/operation/controller_test.rb
228
252
  - test/operation/dsl/callback_test.rb
229
253
  - test/operation/dsl/contract_test.rb
230
254
  - test/operation/dsl/representer_test.rb