trailblazer 0.3.1 → 0.3.2
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/CHANGES.md +5 -0
- data/Gemfile +1 -1
- data/README.md +70 -0
- data/Rakefile +1 -1
- data/lib/trailblazer/autoloading.rb +1 -0
- data/lib/trailblazer/operation.rb +18 -19
- data/lib/trailblazer/operation/controller.rb +1 -3
- data/lib/trailblazer/operation/module.rb +29 -0
- data/lib/trailblazer/version.rb +1 -1
- data/test/module_test.rb +93 -0
- data/test/operation/contract_test.rb +30 -0
- data/test/rails/fake_app/song/operations.rb +4 -15
- data/test/rails/test_helper.rb +6 -1
- data/test/test_helper.rb +5 -0
- data/trailblazer.gemspec +0 -2
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6fe908e23b8ef76962d2eba061d666567e204120
|
4
|
+
data.tar.gz: 3df42356be844b922d258de9f3e64ed72f5b5a9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9190472c21be262f4a216f52180c9b91bbf9c073650148fe53aec8b83ba2493f0365d4d10ebd603daa4a9f6c295411a634f66b302e7d5895c6fe8fdca66783b4
|
7
|
+
data.tar.gz: 119fbafbce0d79e95c9f4131400ba6860e12fe40f8e9e0901c4457da3450c0940915d9bb51e3ac0f502fd00cd6022224e4d082041dec843bf878537cdb3b6e71
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# 0.3.2
|
2
|
+
|
3
|
+
* Allow to use `#contract` before `#validate`. The contract will be instantiated once per `#run` using `#contract` and then memoized. This allows to add/modify the contract _before_ you validate it using `#validate`.
|
4
|
+
* New signature for `Operation#contract_for(model, contract_class)`. It used to be contract, then model.
|
5
|
+
|
1
6
|
# 0.3.1
|
2
7
|
|
3
8
|
* Autoload `Dispatch`.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -420,6 +420,76 @@ Comment::Create.run(params) do |op|
|
|
420
420
|
end
|
421
421
|
```
|
422
422
|
|
423
|
+
### Inheritance
|
424
|
+
|
425
|
+
Operations fully support inheritance and will copy the contract, the callback groups and any methods from the original operation class.
|
426
|
+
|
427
|
+
```ruby
|
428
|
+
class Create < Trailblazer::Operation
|
429
|
+
contract do
|
430
|
+
property :title
|
431
|
+
end
|
432
|
+
|
433
|
+
callback(:before_save) do
|
434
|
+
on_change :notify!
|
435
|
+
end
|
436
|
+
|
437
|
+
def notify!(*)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
```
|
441
|
+
|
442
|
+
This happens with normal Ruby inheritance.
|
443
|
+
|
444
|
+
```ruby
|
445
|
+
class Update < Create
|
446
|
+
# inherited:
|
447
|
+
# contract
|
448
|
+
# callback(:before_save)
|
449
|
+
# def notify!(*)
|
450
|
+
end
|
451
|
+
```
|
452
|
+
|
453
|
+
You can customize callback groups and contracts using the `:inherit` option, add and remove properties or add methods.
|
454
|
+
|
455
|
+
[Learn more](http://trailblazerb.org/gems/operation/inheritance.html)
|
456
|
+
|
457
|
+
### Modules
|
458
|
+
|
459
|
+
In case inheritance is not enough for you, use modules to share common functionality.
|
460
|
+
|
461
|
+
```ruby
|
462
|
+
module ExtendedCreate
|
463
|
+
include Trailblazer::Operation::Module
|
464
|
+
|
465
|
+
contract do
|
466
|
+
property :id
|
467
|
+
end
|
468
|
+
|
469
|
+
callback do
|
470
|
+
on_update :update!
|
471
|
+
end
|
472
|
+
|
473
|
+
def update!(song)
|
474
|
+
# do something
|
475
|
+
end
|
476
|
+
end
|
477
|
+
```
|
478
|
+
|
479
|
+
Modules can be included and will simply run the declarations in the including class.
|
480
|
+
|
481
|
+
```ruby
|
482
|
+
class Create::Admin < Create
|
483
|
+
include ExtendedCreate
|
484
|
+
|
485
|
+
# contract has :title and :id now.
|
486
|
+
```
|
487
|
+
|
488
|
+
Modules are often used to modify an existing operation for admin or signed-in roles.
|
489
|
+
|
490
|
+
[Learn more](http://trailblazerb.org/gems/operation/inheritance.html)
|
491
|
+
|
492
|
+
|
423
493
|
## Form API
|
424
494
|
|
425
495
|
Usually, an operation has a form object.
|
data/Rakefile
CHANGED
@@ -1,10 +1,6 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
require 'reform'
|
1
|
+
require "uber/builder"
|
2
|
+
require "reform"
|
4
3
|
|
5
|
-
# TODO: OP[] without wrapper, OP.run with (eg for params)
|
6
|
-
|
7
|
-
# to be documented: only implement #setup! when not using CRUD and when ::present needed (make example for simple Op without #setup!)
|
8
4
|
|
9
5
|
module Trailblazer
|
10
6
|
class Operation
|
@@ -70,12 +66,10 @@ module Trailblazer
|
|
70
66
|
|
71
67
|
def present(*params)
|
72
68
|
setup!(*params)
|
73
|
-
|
74
|
-
@contract = contract_for(nil, @model)
|
69
|
+
contract!
|
75
70
|
self
|
76
71
|
end
|
77
72
|
|
78
|
-
attr_reader :contract
|
79
73
|
attr_reader :model
|
80
74
|
|
81
75
|
def errors
|
@@ -86,6 +80,10 @@ module Trailblazer
|
|
86
80
|
@valid
|
87
81
|
end
|
88
82
|
|
83
|
+
def contract(*args)
|
84
|
+
contract!(*args)
|
85
|
+
end
|
86
|
+
|
89
87
|
private
|
90
88
|
def setup!(*params)
|
91
89
|
setup_params!(*params)
|
@@ -105,8 +103,8 @@ module Trailblazer
|
|
105
103
|
def setup_params!(*params)
|
106
104
|
end
|
107
105
|
|
108
|
-
def validate(params, model, contract_class=nil)
|
109
|
-
|
106
|
+
def validate(params, model=nil, contract_class=nil)
|
107
|
+
contract!(model, contract_class)
|
110
108
|
|
111
109
|
if @valid = validate_contract(params)
|
112
110
|
yield contract if block_given?
|
@@ -133,8 +131,15 @@ module Trailblazer
|
|
133
131
|
|
134
132
|
# Instantiate the contract, either by using the user's contract passed into #validate
|
135
133
|
# or infer the Operation contract.
|
136
|
-
def contract_for(
|
137
|
-
|
134
|
+
def contract_for(model=nil, contract_class=nil)
|
135
|
+
model ||= self.model
|
136
|
+
contract_class ||= self.class.contract_class
|
137
|
+
|
138
|
+
contract_class.new(model)
|
139
|
+
end
|
140
|
+
|
141
|
+
def contract!(*args)
|
142
|
+
@contract ||= contract_for(*args)
|
138
143
|
end
|
139
144
|
|
140
145
|
class InvalidContract < RuntimeError
|
@@ -143,9 +148,3 @@ module Trailblazer
|
|
143
148
|
end
|
144
149
|
|
145
150
|
require 'trailblazer/operation/crud'
|
146
|
-
|
147
|
-
# run
|
148
|
-
# setup
|
149
|
-
# process
|
150
|
-
# contract
|
151
|
-
# validate
|
@@ -12,9 +12,7 @@ private
|
|
12
12
|
yield @operation if block_given?
|
13
13
|
end
|
14
14
|
|
15
|
-
# Doesn't run #
|
16
|
-
# TODO: allow only_setup.
|
17
|
-
# TODO: dependency to CRUD (::model_name)
|
15
|
+
# Doesn't run #process.
|
18
16
|
def present(operation_class, params=self.params)
|
19
17
|
res, op = operation!(operation_class, params) { [true, operation_class.present(params)] }
|
20
18
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Trailblazer::Operation::Module
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
base.extend Included
|
5
|
+
end
|
6
|
+
|
7
|
+
module Included # TODO: use representable's inheritance mechanism.
|
8
|
+
def included(base)
|
9
|
+
super
|
10
|
+
instructions.each { |cfg|
|
11
|
+
method = cfg[0]
|
12
|
+
args = cfg[1].dup
|
13
|
+
block = cfg[2]
|
14
|
+
# options = args.extract_options!.dup # we need to duplicate options has as AM::Validations messes it up later.
|
15
|
+
|
16
|
+
base.send(method, *args, &block) } # property :name, {} do .. end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def method_missing(method, *args, &block)
|
22
|
+
instructions << [method, args, block]
|
23
|
+
end
|
24
|
+
|
25
|
+
def instructions
|
26
|
+
@instructions ||= []
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/trailblazer/version.rb
CHANGED
data/test/module_test.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require "trailblazer/operation/module"
|
3
|
+
require "trailblazer/operation/dispatch"
|
4
|
+
|
5
|
+
class OperationModuleTest < MiniTest::Spec
|
6
|
+
Song = Struct.new(:name, :artist)
|
7
|
+
Artist = Struct.new(:id, :full_name)
|
8
|
+
|
9
|
+
class Create < Trailblazer::Operation
|
10
|
+
include Trailblazer::Operation::Dispatch
|
11
|
+
|
12
|
+
contract do
|
13
|
+
property :name
|
14
|
+
property :artist, populate_if_empty: Artist do
|
15
|
+
property :id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
callback do
|
20
|
+
on_change :notify_me!
|
21
|
+
end
|
22
|
+
|
23
|
+
def process(params)
|
24
|
+
@model = Song.new
|
25
|
+
|
26
|
+
validate(params, @model) do
|
27
|
+
contract.sync
|
28
|
+
|
29
|
+
dispatch!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def dispatched
|
34
|
+
@dispatched ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def notify_me!(*)
|
39
|
+
dispatched << :notify_me!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
module SignedIn
|
45
|
+
include Trailblazer::Operation::Module
|
46
|
+
|
47
|
+
contract do
|
48
|
+
property :artist, inherit: true do
|
49
|
+
property :full_name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
callback do
|
54
|
+
on_change :notify_you!
|
55
|
+
end
|
56
|
+
|
57
|
+
def notify_you!(*)
|
58
|
+
dispatched << :notify_you!
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class Update < Create
|
64
|
+
callback do
|
65
|
+
on_change :notify_them!
|
66
|
+
end
|
67
|
+
|
68
|
+
include SignedIn
|
69
|
+
|
70
|
+
def notify_them!(*)
|
71
|
+
dispatched << :notify_them!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
it do
|
77
|
+
op = Create.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
|
78
|
+
|
79
|
+
op.dispatched.must_equal [:notify_me!]
|
80
|
+
op.model.name.must_equal "Feelings"
|
81
|
+
op.model.artist.id.must_equal 1
|
82
|
+
op.model.artist.full_name.must_equal nil # property not declared.
|
83
|
+
end
|
84
|
+
|
85
|
+
it do
|
86
|
+
op = Update.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
|
87
|
+
|
88
|
+
op.dispatched.must_equal [:notify_me!, :notify_them!, :notify_you!]
|
89
|
+
op.model.name.must_equal "Feelings"
|
90
|
+
op.model.artist.id.must_equal 1
|
91
|
+
op.model.artist.full_name.must_equal "The Offspring" # property declared via Module.
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
|
4
|
+
class OperationContractTest < MiniTest::Spec
|
5
|
+
|
6
|
+
class Operation < Trailblazer::Operation
|
7
|
+
contract do
|
8
|
+
property :id
|
9
|
+
property :title
|
10
|
+
property :length
|
11
|
+
end
|
12
|
+
|
13
|
+
def process(params)
|
14
|
+
@model = Struct.new(:id, :title, :length).new
|
15
|
+
|
16
|
+
contract.id = 1
|
17
|
+
validate(params) do
|
18
|
+
contract.length = 3
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# allow using #contract before #validate.
|
24
|
+
it do
|
25
|
+
op = Operation.(title: "Beethoven")
|
26
|
+
op.contract.id.must_equal 1
|
27
|
+
op.contract.title.must_equal "Beethoven"
|
28
|
+
op.contract.length.must_equal 3
|
29
|
+
end
|
30
|
+
end
|
@@ -57,12 +57,9 @@ class Band < ActiveRecord::Base
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
require "representable/json"
|
60
61
|
class JSON < self
|
61
62
|
include Representer
|
62
|
-
require "reform/form/json"
|
63
|
-
contract do
|
64
|
-
include Reform::Form::JSON # this allows deserialising JSON.
|
65
|
-
end
|
66
63
|
|
67
64
|
representer do
|
68
65
|
collection :songs, inherit: true, render_empty: false # tested in ControllerPresentTest.
|
@@ -77,17 +74,9 @@ class Band < ActiveRecord::Base
|
|
77
74
|
end
|
78
75
|
end
|
79
76
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
# return Admin if params[:admin]
|
84
|
-
# end
|
85
|
-
builds do |params|
|
86
|
-
if params[:format] == "json"
|
87
|
-
JSON
|
88
|
-
elsif params[:admin]
|
89
|
-
Admin
|
90
|
-
end
|
77
|
+
builds -> (params) do
|
78
|
+
return JSON if params[:format] == "json"
|
79
|
+
return Admin if params[:admin]
|
91
80
|
end
|
92
81
|
end
|
93
82
|
|
data/test/rails/test_helper.rb
CHANGED
data/test/test_helper.rb
CHANGED
data/trailblazer.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'trailblazer/version'
|
@@ -26,7 +25,6 @@ Gem::Specification.new do |spec|
|
|
26
25
|
spec.add_development_dependency "bundler"
|
27
26
|
spec.add_development_dependency "rake"
|
28
27
|
spec.add_development_dependency "minitest"
|
29
|
-
# spec.add_development_dependency "minitest-spec-rails" # TODO: can anyone make this work (test/rails).
|
30
28
|
spec.add_development_dependency "sidekiq", "~> 3.1.0"
|
31
29
|
spec.add_development_dependency "actionpack", '>= 3.0.0' # Rails is optional.
|
32
30
|
spec.add_development_dependency "rails"
|
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: 0.3.
|
4
|
+
version: 0.3.2
|
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-07-
|
11
|
+
date: 2015-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uber
|
@@ -208,6 +208,7 @@ files:
|
|
208
208
|
- lib/trailblazer/operation/controller/active_record.rb
|
209
209
|
- lib/trailblazer/operation/crud.rb
|
210
210
|
- lib/trailblazer/operation/dispatch.rb
|
211
|
+
- lib/trailblazer/operation/module.rb
|
211
212
|
- lib/trailblazer/operation/representer.rb
|
212
213
|
- lib/trailblazer/operation/responder.rb
|
213
214
|
- lib/trailblazer/operation/uploaded_file.rb
|
@@ -219,6 +220,8 @@ files:
|
|
219
220
|
- test/dispatch_test.rb
|
220
221
|
- test/fixtures/apotomo.png
|
221
222
|
- test/fixtures/cells.png
|
223
|
+
- test/module_test.rb
|
224
|
+
- test/operation/contract_test.rb
|
222
225
|
- test/operation_test.rb
|
223
226
|
- test/rails/controller_test.rb
|
224
227
|
- test/rails/endpoint_test.rb
|
@@ -271,6 +274,8 @@ test_files:
|
|
271
274
|
- test/dispatch_test.rb
|
272
275
|
- test/fixtures/apotomo.png
|
273
276
|
- test/fixtures/cells.png
|
277
|
+
- test/module_test.rb
|
278
|
+
- test/operation/contract_test.rb
|
274
279
|
- test/operation_test.rb
|
275
280
|
- test/rails/controller_test.rb
|
276
281
|
- test/rails/endpoint_test.rb
|