trailblazer 1.0.4 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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