trailblazer 1.0.0.rc1 → 1.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +2 -1
- data/README.md +1 -97
- data/lib/trailblazer/operation.rb +2 -7
- data/lib/trailblazer/operation/controller.rb +13 -10
- data/lib/trailblazer/operation/controller/active_record.rb +1 -1
- data/lib/trailblazer/version.rb +1 -1
- data/test/operation_test.rb +17 -18
- data/test/rails/controller_test.rb +3 -3
- data/test/rails/fake_app/controllers.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07471cd851403f80b0a26ef761be0ceb009dc745
|
4
|
+
data.tar.gz: cad5f9dfb9a3e6ecce4623d4d32bff7688ac62e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7896872cf410a207510668b7589f5e39b45c7d5ae3180682ecd5036745646742ccf1c487a26b7df5407f57ef8c12c94832948d22746472a463ce0a42c7be1594
|
7
|
+
data.tar.gz: 35a858f63b046ff83c7f456e043ccefa1f10e52a0e84c5ac967dfeed36987c03fb8c0a909552060363335779080536a59e7d6c82669a07636fd86012ea532762
|
data/CHANGES.md
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
* `Operation[{..}]` is deprecated in favor of `Operation.({..})`.
|
4
4
|
setup in initialize: when Op.run() with Worker, the policy will be run "delayed" and not with the actual permission set. this will result in many crashing sidekiq workers.
|
5
5
|
* `Operation::CRUD` is now `Operation::Model`.
|
6
|
-
* `Controller#form` now invokes `#prepopulate!` before rendering the view.
|
6
|
+
* `Controller#form` now invokes `#prepopulate!` before rendering the view.
|
7
|
+
* `Controller#present` does not instantiate and assign `@form` anymore.
|
7
8
|
|
8
9
|
# 0.3.4
|
9
10
|
|
data/README.md
CHANGED
@@ -317,107 +317,11 @@ end
|
|
317
317
|
|
318
318
|
This will simply run `Comment::Create[params]`.
|
319
319
|
|
320
|
-
You can pass your own params, too.
|
321
320
|
|
322
|
-
```ruby
|
323
|
-
def create
|
324
|
-
run Comment::Create, params.merge({current_user: current_user})
|
325
|
-
end
|
326
|
-
```
|
327
|
-
|
328
|
-
An additional block will be executed _only if_ the operation result is valid.
|
329
|
-
|
330
|
-
```ruby
|
331
|
-
def create
|
332
|
-
run Comment::Create do |op|
|
333
|
-
return redirect_to(comments_path, notice: op.message)
|
334
|
-
end
|
335
|
-
end
|
336
|
-
```
|
337
|
-
|
338
|
-
Note that the operation instance is yielded to the block.
|
339
|
-
|
340
|
-
The case of an invalid response can be handled after the block.
|
341
|
-
|
342
|
-
```ruby
|
343
|
-
def create
|
344
|
-
run Comment::Create do |op|
|
345
|
-
# valid code..
|
346
|
-
return
|
347
|
-
end
|
348
|
-
|
349
|
-
render action: :new
|
350
|
-
end
|
351
|
-
```
|
352
|
-
|
353
|
-
Don't forget to `return` from the valid block, otherwise both the valid block _and_ the invalid calls after it will be invoked.
|
354
|
-
|
355
|
-
### Responding
|
356
|
-
|
357
|
-
Alternatively, you can use Rails' excellent `#respond_with` to let a responder take care of what to render. Operations can be passed into `respond_with`. This happens automatically in `#respond`, the third way to let Trailblazer invoke an operation.
|
358
|
-
|
359
|
-
```ruby
|
360
|
-
def create
|
361
|
-
respond Comment::Create
|
362
|
-
end
|
363
|
-
```
|
364
|
-
|
365
|
-
This will simply run the operation and chuck the instance into the responder letting the latter sort out what to render or where to redirect. The operation delegates respective calls to its internal `model`.
|
366
|
-
|
367
|
-
`#respond` will accept options to be passed on to `respond_with`, too
|
368
321
|
|
369
|
-
```ruby
|
370
|
-
respond Comment::Create, params, location: brandnew_comments_path
|
371
|
-
```
|
372
|
-
|
373
|
-
You can also handle different formats in that block. It is totally fine to do that in the controller as this is _endpoint_ logic that is HTTP-specific and not business.
|
374
|
-
|
375
|
-
```ruby
|
376
|
-
def create
|
377
|
-
respond Comment::Create do |op, formats|
|
378
|
-
formats.html { redirect_to(op.model, :notice => op.valid? ? "All good!" : "Fail!") }
|
379
|
-
formats.json { render nothing: true }
|
380
|
-
end
|
381
|
-
end
|
382
|
-
```
|
383
|
-
|
384
|
-
The block passed to `#respond` is _always_ executed, regardless of the operation's validity result. Goal is to let the responder handle the validity of the operation.
|
385
|
-
|
386
|
-
The `formats` object is simply passed on to `#respond_with`.
|
387
|
-
|
388
|
-
### Presenting
|
389
|
-
|
390
|
-
For `#show` actions that simply present the model using a HTML page or a JSON or XML document the `#present` method comes in handy.
|
391
|
-
|
392
|
-
```ruby
|
393
|
-
def show
|
394
|
-
present Comment::Update
|
395
|
-
end
|
396
|
-
```
|
397
|
-
|
398
|
-
Again, this will only run the operation's setup and provide the model in `@model`. You can then use a cell or controller view for HTML to present the model.
|
399
|
-
|
400
|
-
For document-based APIs and request types that are not HTTP the operation will be advised to render the JSON or XML document using the operation's representer.
|
401
|
-
|
402
|
-
Note that `#present` will leave rendering up to you - `respond_to` is _not_ called.
|
403
|
-
|
404
|
-
|
405
|
-
In all three cases the following instance variables are assigned: `@operation`, `@form`, `@model`.
|
406
|
-
|
407
|
-
Named instance variables can be included, too. This is documented [here](#named-controller-instance-variables).
|
408
322
|
|
409
323
|
|
410
|
-
|
411
|
-
|
412
|
-
In case you have document-API operations that use representers to deserialize the incoming JSON or XML: You can configure the controller to pass the original request body into the operation via `params["comment"]` - instead of the pre-parsed hash from Rails.
|
413
|
-
|
414
|
-
You need to configure this in the controller.
|
415
|
-
|
416
|
-
```ruby
|
417
|
-
class CommentsController < ApplicationController
|
418
|
-
operation document_formats: :json
|
419
|
-
```
|
420
|
-
|
324
|
+
----
|
421
325
|
|
422
326
|
It's up to the operation's builder to decide which class to instantiate.
|
423
327
|
|
@@ -44,9 +44,9 @@ module Trailblazer
|
|
44
44
|
call(*args)
|
45
45
|
end
|
46
46
|
|
47
|
-
# Runs #setup!
|
47
|
+
# Runs #setup! but doesn't process the operation.
|
48
48
|
def present(params)
|
49
|
-
build_operation(params)
|
49
|
+
build_operation(params)
|
50
50
|
end
|
51
51
|
|
52
52
|
# This is a DSL method. Use ::contract_class and ::contract_class= for the explicit version.
|
@@ -78,11 +78,6 @@ module Trailblazer
|
|
78
78
|
[valid?, self]
|
79
79
|
end
|
80
80
|
|
81
|
-
def present
|
82
|
-
contract!
|
83
|
-
self
|
84
|
-
end
|
85
|
-
|
86
81
|
attr_reader :model
|
87
82
|
|
88
83
|
def errors
|
@@ -3,7 +3,7 @@ require "trailblazer/endpoint"
|
|
3
3
|
module Trailblazer::Operation::Controller
|
4
4
|
private
|
5
5
|
def form(*args)
|
6
|
-
|
6
|
+
operation!(*args).tap do |op|
|
7
7
|
op.contract.prepopulate! # equals to @form.prepopulate!
|
8
8
|
end
|
9
9
|
end
|
@@ -11,18 +11,17 @@ private
|
|
11
11
|
# Provides the operation instance, model and contract without running #process.
|
12
12
|
# Returns the operation.
|
13
13
|
def present(operation_class, params=self.params)
|
14
|
-
|
15
|
-
op
|
14
|
+
operation!(operation_class, params, skip_form: true)
|
16
15
|
end
|
17
16
|
|
18
17
|
def collection(*args)
|
19
|
-
|
18
|
+
operation!(*args).tap do |op|
|
20
19
|
@collection = op.model
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
23
|
def run(operation_class, params=self.params, &block)
|
25
|
-
res, op =
|
24
|
+
res, op = operation_for!(operation_class, params) { operation_class.run(params) }
|
26
25
|
|
27
26
|
yield op if res and block_given?
|
28
27
|
|
@@ -31,7 +30,7 @@ private
|
|
31
30
|
|
32
31
|
# The block passed to #respond is always run, regardless of the validity result.
|
33
32
|
def respond(operation_class, options={}, params=self.params, &block)
|
34
|
-
res, op =
|
33
|
+
res, op = operation_for!(operation_class, params, options) { operation_class.run(params) }
|
35
34
|
namespace = options.delete(:namespace) || []
|
36
35
|
|
37
36
|
return respond_with *namespace, op, options if not block_given?
|
@@ -39,26 +38,30 @@ private
|
|
39
38
|
end
|
40
39
|
|
41
40
|
private
|
41
|
+
def operation!(operation_class, params=self.params, options={}) # or #model or #setup.
|
42
|
+
res, op = operation_for!(operation_class, params, options) { [true, operation_class.present(params)] }
|
43
|
+
op
|
44
|
+
end
|
42
45
|
|
43
46
|
def process_params!(params)
|
44
47
|
end
|
45
48
|
|
46
49
|
# Normalizes parameters and invokes the operation (including its builders).
|
47
|
-
def
|
50
|
+
def operation_for!(operation_class, params, options={}, &block)
|
48
51
|
# Per default, only treat :html as non-document.
|
49
52
|
options[:is_document] ||= request.format == :html ? false : true
|
50
53
|
|
51
54
|
process_params!(params)
|
52
55
|
res, op = Trailblazer::Endpoint.new(operation_class, params, request, options).(&block)
|
53
|
-
setup_operation_instance_variables!(op)
|
56
|
+
setup_operation_instance_variables!(op, options)
|
54
57
|
|
55
58
|
[res, op]
|
56
59
|
end
|
57
60
|
|
58
|
-
def setup_operation_instance_variables!(operation)
|
61
|
+
def setup_operation_instance_variables!(operation, options)
|
59
62
|
@operation = operation
|
60
|
-
@form = operation.contract
|
61
63
|
@model = operation.model
|
64
|
+
@form = operation.contract unless options[:skip_form]
|
62
65
|
end
|
63
66
|
|
64
67
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Assigns an additional instance variable for +@model+ named after the model's table name (e.g. @comment).
|
2
2
|
module Trailblazer::Operation::Controller::ActiveRecord
|
3
3
|
private
|
4
|
-
def setup_operation_instance_variables!(operation)
|
4
|
+
def setup_operation_instance_variables!(operation, options)
|
5
5
|
super
|
6
6
|
instance_variable_set(:"@#{operation_model_name}", @model)
|
7
7
|
end
|
data/lib/trailblazer/version.rb
CHANGED
data/test/operation_test.rb
CHANGED
@@ -118,19 +118,29 @@ class OperationRunTest < MiniTest::Spec
|
|
118
118
|
|
119
119
|
# # Operation#contract returns @contract
|
120
120
|
it { Operation.("yes, true").contract.class.to_s.must_equal "OperationRunTest::Operation::Contract" }
|
121
|
-
end
|
122
121
|
|
123
122
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
123
|
+
|
124
|
+
|
125
|
+
describe "::present" do
|
126
|
+
class NoContractOp < Trailblazer::Operation
|
127
|
+
self.contract_class = nil
|
128
|
+
|
129
|
+
def model!(*)
|
130
|
+
Object
|
131
|
+
end
|
128
132
|
end
|
133
|
+
|
134
|
+
# the operation and model are available, but no contract.
|
135
|
+
it { NoContractOp.present({}).model.must_equal Object }
|
136
|
+
# no contract is built.
|
137
|
+
it { assert_raises(NoMethodError) { NoContractOp.present({}).contract } }
|
138
|
+
it { assert_raises(NoMethodError) { NoContractOp.run({}) } }
|
129
139
|
end
|
140
|
+
end
|
130
141
|
|
131
|
-
# contract is retrieved from ::contract_class.
|
132
|
-
it { assert_raises(NoMethodError) { Operation.run({}) } } # TODO: if you call #validate without defining a contract, the error is quite cryptic.
|
133
142
|
|
143
|
+
class OperationTest < MiniTest::Spec
|
134
144
|
# test #invalid!
|
135
145
|
class OperationWithoutValidateCall < Trailblazer::Operation
|
136
146
|
def process(params)
|
@@ -199,17 +209,6 @@ class OperationTest < MiniTest::Spec
|
|
199
209
|
|
200
210
|
|
201
211
|
|
202
|
-
|
203
|
-
# unlimited arguments for ::run and friends.
|
204
|
-
# class OperationReceivingLottaArguments < Trailblazer::Operation
|
205
|
-
# def process(model, params)
|
206
|
-
# @model = [model, params]
|
207
|
-
# end
|
208
|
-
# include Inspect
|
209
|
-
# end
|
210
|
-
|
211
|
-
# it { OperationReceivingLottaArguments.run(Object, {}).to_s.must_equal %{[true, <OperationReceivingLottaArguments @model=[Object, {}]>]} }
|
212
|
-
|
213
212
|
# ::present only runs #setup! which runs #model!.
|
214
213
|
class ContractOnlyOperation < Trailblazer::Operation
|
215
214
|
self.contract_class = class Contract
|
@@ -78,7 +78,7 @@ class ControllerPresentTest < ActionController::TestCase
|
|
78
78
|
|
79
79
|
get :show, id: band.id
|
80
80
|
|
81
|
-
assert_equal "bands/show: Band,Band,
|
81
|
+
assert_equal "bands/show: Band,Band,Band::Update,Essen,nil", response.body
|
82
82
|
end
|
83
83
|
|
84
84
|
# TODO: this implicitely tests builds. maybe have separate test for that?
|
@@ -130,10 +130,10 @@ class ActiveRecordPresentTest < ActionController::TestCase
|
|
130
130
|
tests ActiveRecordBandsController
|
131
131
|
|
132
132
|
test "#present" do
|
133
|
-
band = Band::Create
|
133
|
+
band = Band::Create.(band: {name: "Nofx"}).model
|
134
134
|
get :show, id: band.id
|
135
135
|
|
136
|
-
assert_equal "active_record_bands/show.html: Band, Band,
|
136
|
+
assert_equal "active_record_bands/show.html: Band, Band, nil, Band::Update", response.body
|
137
137
|
end
|
138
138
|
|
139
139
|
test "#collection" do
|
@@ -63,7 +63,7 @@ class BandsController < ApplicationController
|
|
63
63
|
@locality = params[:band][:locality] unless params[:format] == "json"
|
64
64
|
|
65
65
|
return render json: op.to_json if params[:format] == "json"
|
66
|
-
render text: "bands/show: #{[@klass, @model.class, @
|
66
|
+
render text: "bands/show: #{[@klass, @model.class, @operation.class, @locality, @form.inspect].join(',')}"
|
67
67
|
end
|
68
68
|
|
69
69
|
def new
|
@@ -122,7 +122,7 @@ class ActiveRecordBandsController < ApplicationController
|
|
122
122
|
def show
|
123
123
|
present Band::Update
|
124
124
|
|
125
|
-
render text: "active_record_bands/show.html: #{@model.class}, #{@band.class}, #{@form.
|
125
|
+
render text: "active_record_bands/show.html: #{@model.class}, #{@band.class}, #{@form.inspect}, #{@operation.class}"
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
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.0.
|
4
|
+
version: 1.0.0.rc2
|
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-09-
|
11
|
+
date: 2015-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uber
|