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 +4 -4
- data/.travis.yml +5 -1
- data/CHANGES.md +14 -1
- data/Gemfile +10 -5
- data/README.md +11 -9
- data/gemfiles/{Gemfile.rails → Gemfile.reform-2.0} +2 -3
- data/gemfiles/Gemfile.reform-2.1 +7 -0
- data/lib/trailblazer/autoloading.rb +2 -1
- data/lib/trailblazer/endpoint.rb +1 -11
- data/lib/trailblazer/operation/callback.rb +53 -0
- data/lib/trailblazer/operation/controller.rb +19 -31
- data/lib/trailblazer/operation/dispatch.rb +3 -29
- data/lib/trailblazer/operation/policy.rb +4 -7
- data/lib/trailblazer/operation/representer.rb +29 -10
- data/lib/trailblazer/operation.rb +14 -2
- data/lib/trailblazer/version.rb +1 -1
- data/test/{dispatch_test.rb → callback_test.rb} +43 -3
- data/test/operation/controller_test.rb +111 -0
- data/test/operation/dsl/callback_test.rb +18 -8
- data/test/operation/guard_test.rb +21 -2
- data/test/operation_test.rb +15 -0
- data/test/representer_test.rb +52 -0
- data/trailblazer.gemspec +5 -4
- metadata +37 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c06b905e00be850d576dedb8139524da3f0f5f1d
|
4
|
+
data.tar.gz: f04942ee37e3c73ffc098bd5531fde00e293c566
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f81051754c0537ed2c9c924a386274ed4c04c987968a8039da48318ac2ea565ce1a91f1edc5c433f7c08157a47e65319e3b32e711b5be979a235916548f70880
|
7
|
+
data.tar.gz: 5673d4ab693361e964212ec2d60624f69a3c79031cb062cd40dbf47540b9d528c7c3b215e59bd4b9fc6ab134af5b58496bdf45b4306864227f6419ac30cec33b
|
data/.travis.yml
CHANGED
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
|
-
|
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 "
|
11
|
-
gem "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](#
|
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://
|
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://
|
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://
|
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
|
-
|
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://
|
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://
|
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://
|
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://
|
281
|
+
let(:comment) { Comment::Create.(comment: {body: "[That](http://trailblazer.to)!"}) }
|
280
282
|
```
|
281
283
|
|
282
284
|
## More
|
@@ -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"
|
data/lib/trailblazer/endpoint.rb
CHANGED
@@ -8,7 +8,7 @@ module Trailblazer
|
|
8
8
|
@operation_class = operation_class
|
9
9
|
@params = params
|
10
10
|
@request = request
|
11
|
-
@is_document =
|
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
|
6
|
+
res, op, options = operation!(operation_class, options)
|
7
|
+
op.contract.prepopulate!(options) # equals to @form.prepopulate!
|
7
8
|
|
8
|
-
|
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)
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
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 "
|
2
|
-
|
3
|
-
|
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::
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
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.
|
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)
|
data/lib/trailblazer/version.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'test_helper'
|
2
|
-
require "trailblazer/operation/
|
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::
|
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
|
-
|
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
|
-
|
100
|
-
|
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([:
|
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
|
data/test/operation_test.rb
CHANGED
@@ -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
|
data/test/representer_test.rb
CHANGED
@@ -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
|
11
|
-
spec.summary = %q{A
|
12
|
-
spec.homepage = "http://
|
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", ">=
|
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
|
+
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-
|
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:
|
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:
|
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
|
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://
|
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:
|
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
|
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
|