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