trailblazer 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -3
- data/CHANGES.md +33 -0
- data/Gemfile +4 -1
- data/README.md +171 -166
- data/Rakefile +10 -2
- data/gemfiles/Gemfile.rails.lock +42 -11
- data/lib/trailblazer/autoloading.rb +4 -3
- data/lib/trailblazer/endpoint.rb +40 -0
- data/lib/trailblazer/operation.rb +15 -8
- data/lib/trailblazer/operation/collection.rb +7 -0
- data/lib/trailblazer/operation/controller.rb +30 -22
- data/lib/trailblazer/operation/controller/active_record.rb +6 -2
- data/lib/trailblazer/operation/crud.rb +3 -5
- data/lib/trailblazer/operation/dispatch.rb +29 -0
- data/lib/trailblazer/operation/representer.rb +41 -5
- data/lib/trailblazer/operation/responder.rb +2 -2
- data/lib/trailblazer/operation/uploaded_file.rb +4 -4
- data/lib/trailblazer/operation/worker.rb +8 -10
- data/lib/trailblazer/rails/railtie.rb +10 -7
- data/lib/trailblazer/version.rb +1 -1
- data/test/collection_test.rb +56 -0
- data/test/crud_test.rb +23 -1
- data/test/dispatch_test.rb +63 -0
- data/test/operation_test.rb +84 -125
- data/test/rails/controller_test.rb +51 -0
- data/test/rails/endpoint_test.rb +86 -0
- data/test/rails/fake_app/cells.rb +2 -2
- data/test/rails/fake_app/config.rb +1 -1
- data/test/rails/fake_app/controllers.rb +27 -0
- data/test/rails/fake_app/models.rb +10 -1
- data/test/rails/fake_app/rails_app.rb +15 -1
- data/test/rails/fake_app/song/operations.rb +38 -2
- data/test/rails/fake_app/views/bands/index.html.erb +1 -0
- data/test/rails/fake_app/views/songs/another_view.html.erb +2 -0
- data/test/representer_test.rb +126 -0
- data/test/responder_test.rb +2 -4
- data/test/rollback_test.rb +47 -0
- data/test/test_helper.rb +43 -1
- data/test/uploaded_file_test.rb +4 -4
- data/test/worker_test.rb +13 -9
- data/trailblazer.gemspec +7 -3
- metadata +68 -29
@@ -18,9 +18,9 @@ module Trailblazer
|
|
18
18
|
path = persist!
|
19
19
|
|
20
20
|
hash = {
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
21
|
+
filename: @uploaded.original_filename,
|
22
|
+
type: @uploaded.content_type,
|
23
|
+
tempfile_path: path
|
24
24
|
}
|
25
25
|
|
26
26
|
cleanup!
|
@@ -40,7 +40,7 @@ module Trailblazer
|
|
40
40
|
file.close # TODO: can we test that?
|
41
41
|
File.unlink(file)
|
42
42
|
|
43
|
-
ActionDispatch::Http::UploadedFile.new(hash.merge(:
|
43
|
+
ActionDispatch::Http::UploadedFile.new(hash.merge(tempfile: tmp))
|
44
44
|
end
|
45
45
|
|
46
46
|
private
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'sidekiq/worker'
|
2
|
-
# require 'active_support/hash_with_indifferent_access'
|
3
2
|
require 'active_support/core_ext/hash/indifferent_access'
|
4
3
|
|
5
4
|
|
6
5
|
class Trailblazer::Operation
|
7
6
|
# only kicks in when Operation::run, #run will still do it real-time
|
7
|
+
# Works with Reform 2, only.
|
8
8
|
module Worker
|
9
9
|
def self.included(base)
|
10
10
|
base.send(:include, Sidekiq::Worker) # TODO: this will work with any bg gem.
|
@@ -61,22 +61,20 @@ class Trailblazer::Operation
|
|
61
61
|
private
|
62
62
|
module ClassMethods
|
63
63
|
def file_marshaller_representer
|
64
|
-
@file_marshaller_representer ||= contract_class.schema.apply do |dfn|
|
65
|
-
dfn.delete!(:prepare)
|
66
|
-
|
64
|
+
@file_marshaller_representer ||= contract_class.schema(include: [Representable::Hash]).apply do |dfn|
|
67
65
|
dfn.merge!(
|
68
|
-
:
|
69
|
-
:
|
66
|
+
getter: lambda { |*| self[dfn.name.to_sym] },
|
67
|
+
setter: lambda { |fragment, *| self[dfn.name.to_s] = fragment }
|
70
68
|
) # FIXME: allow both sym and str.
|
71
69
|
|
72
|
-
dfn.merge!(:
|
70
|
+
dfn.merge!(class: Hash) and next if dfn[:form] or dfn[:twin] # nested properties need a class for deserialization.
|
73
71
|
next unless dfn[:file]
|
74
72
|
|
75
73
|
# TODO: where do we set /tmp/uploads?
|
76
74
|
dfn.merge!(
|
77
|
-
:
|
78
|
-
:
|
79
|
-
:
|
75
|
+
serialize: lambda { |file, *| Trailblazer::Operation::UploadedFile.new(file, tmp_dir: "/tmp/uploads").to_hash },
|
76
|
+
deserialize: lambda { |object, hash, *| Trailblazer::Operation::UploadedFile.from_hash(hash) },
|
77
|
+
class: Hash
|
80
78
|
)
|
81
79
|
end
|
82
80
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Trailblazer
|
2
2
|
class Railtie < Rails::Railtie
|
3
|
-
def self.
|
3
|
+
def self.autoload_operations(app)
|
4
4
|
Dir.glob("app/concepts/**/crud.rb") do |f|
|
5
5
|
path = f.sub("app/concepts/", "")
|
6
6
|
model = path.sub("/crud.rb", "")
|
@@ -10,16 +10,19 @@ module Trailblazer
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
def self.autoload_cells(app)
|
14
|
+
Dir.glob("app/concepts/**/*cell.rb") do |f|
|
15
|
+
require_dependency "#{app.root}/#{f}" # load app/concepts/{concept}/cell.rb.
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
13
19
|
# thank you, http://stackoverflow.com/a/17573888/465070
|
14
20
|
initializer 'trailblazer.install', after: :load_config_initializers do |app|
|
15
21
|
# the trb autoloading has to be run after initializers have been loaded, so we can tweak inclusion of features in
|
16
22
|
# initializers.
|
17
|
-
|
18
|
-
Trailblazer::Railtie.
|
19
|
-
|
20
|
-
ActionDispatch::Reloader.to_prepare do
|
21
|
-
Trailblazer::Railtie.autoload_crud_operations(app)
|
22
|
-
end
|
23
|
+
ActionDispatch::Reloader.to_prepare do
|
24
|
+
Trailblazer::Railtie.autoload_operations(app)
|
25
|
+
Trailblazer::Railtie.autoload_cells(app)
|
23
26
|
end
|
24
27
|
end
|
25
28
|
end
|
data/lib/trailblazer/version.rb
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "trailblazer/operation/collection"
|
3
|
+
|
4
|
+
class CollectionTest < MiniTest::Spec
|
5
|
+
Song = Struct.new(:title, :id) do
|
6
|
+
class << self
|
7
|
+
attr_accessor :all_records
|
8
|
+
|
9
|
+
def all
|
10
|
+
all_records
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
class CreateOperation < Trailblazer::Operation
|
17
|
+
include CRUD
|
18
|
+
model Song
|
19
|
+
action :create
|
20
|
+
|
21
|
+
contract do
|
22
|
+
property :title
|
23
|
+
validates :title, presence: true
|
24
|
+
end
|
25
|
+
|
26
|
+
def process(params)
|
27
|
+
validate(params[:song]) do |f|
|
28
|
+
f.sync
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class FetchCollectionOperation < CreateOperation
|
34
|
+
include Trailblazer::Operation::Collection
|
35
|
+
|
36
|
+
model Song
|
37
|
+
|
38
|
+
contract do
|
39
|
+
property :title
|
40
|
+
end
|
41
|
+
|
42
|
+
def model!(params)
|
43
|
+
Song.all
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# ::present.
|
48
|
+
it do
|
49
|
+
Song.all_records = [
|
50
|
+
CreateOperation.(song: {title: "Blue Rondo a la Turk"}).model,
|
51
|
+
CreateOperation.(song: {title: "Mercy Day For Mr. Vengeance"}).model
|
52
|
+
]
|
53
|
+
op = FetchCollectionOperation.present(user_id: 0)
|
54
|
+
op.model.must_equal Song.all_records
|
55
|
+
end
|
56
|
+
end
|
data/test/crud_test.rb
CHANGED
@@ -5,6 +5,7 @@ class CrudTest < MiniTest::Spec
|
|
5
5
|
Song = Struct.new(:title, :id) do
|
6
6
|
class << self
|
7
7
|
attr_accessor :find_result # TODO: eventually, replace with AR test.
|
8
|
+
attr_accessor :all_records
|
8
9
|
|
9
10
|
def find(id)
|
10
11
|
find_result
|
@@ -102,6 +103,7 @@ class CrudTest < MiniTest::Spec
|
|
102
103
|
end
|
103
104
|
|
104
105
|
|
106
|
+
|
105
107
|
# Op#setup_model!
|
106
108
|
class SetupModelOperation < CreateOperation
|
107
109
|
def setup_model!(params)
|
@@ -141,4 +143,24 @@ class CrudTest < MiniTest::Spec
|
|
141
143
|
end
|
142
144
|
|
143
145
|
it { ContractKnowsModelNameOperation.present(song: {title: "Direct Hit"}).contract.class.model_name.to_s.must_equal "CrudTest::Song" }
|
144
|
-
|
146
|
+
|
147
|
+
|
148
|
+
# allow passing validate(params, model, contract_class)
|
149
|
+
class OperationWithPrivateContract < Trailblazer::Operation
|
150
|
+
include CRUD
|
151
|
+
model Song
|
152
|
+
|
153
|
+
class Contract < Reform::Form
|
154
|
+
property :title
|
155
|
+
end
|
156
|
+
|
157
|
+
def process(params)
|
158
|
+
validate(params[:song], model, Contract) do |f|
|
159
|
+
f.sync
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# uses private Contract class.
|
165
|
+
it { OperationWithPrivateContract.(song: {title: "Blue Rondo a la Turk"}).model.title.must_equal "Blue Rondo a la Turk" }
|
166
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
# callbacks are tested in Disposable::Callback::Group.
|
4
|
+
class OperationCallbackTest < MiniTest::Spec
|
5
|
+
Song = Struct.new(:name)
|
6
|
+
|
7
|
+
class Create < Trailblazer::Operation
|
8
|
+
include Dispatch
|
9
|
+
|
10
|
+
contract do
|
11
|
+
property :name
|
12
|
+
end
|
13
|
+
|
14
|
+
callback do
|
15
|
+
on_change :notify_me!
|
16
|
+
on_change :notify_you!
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO: always dispatch, pass params.
|
20
|
+
|
21
|
+
def process(params)
|
22
|
+
@model = Song.new
|
23
|
+
|
24
|
+
validate(params, @model) do
|
25
|
+
dispatch!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def dispatched
|
30
|
+
@dispatched ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def notify_me!(*)
|
35
|
+
dispatched << :notify_me!
|
36
|
+
end
|
37
|
+
|
38
|
+
def notify_you!(*)
|
39
|
+
dispatched << :notify_you!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
class Update < Create
|
45
|
+
# TODO: allow skipping groups.
|
46
|
+
# skip_dispatch :notify_me!
|
47
|
+
|
48
|
+
callback do
|
49
|
+
remove! :on_change, :notify_me!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
it "invokes all callbacks" do
|
55
|
+
op = Create.({"name"=>"Keep On Running"})
|
56
|
+
op.dispatched.must_equal [:notify_me!, :notify_you!]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "does not invoke removed callbacks" do
|
60
|
+
op = Update.({"name"=>"Keep On Running"})
|
61
|
+
op.dispatched.must_equal [:notify_you!]
|
62
|
+
end
|
63
|
+
end
|
data/test/operation_test.rb
CHANGED
@@ -1,27 +1,45 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
self.class == b.class
|
3
|
+
module Inspect
|
4
|
+
def inspect
|
5
|
+
"<#{self.class.to_s.split("::").last} @model=#{@model}>"
|
7
6
|
end
|
7
|
+
alias_method :to_s, :inspect
|
8
8
|
end
|
9
9
|
|
10
10
|
class OperationSetupParamsTest < MiniTest::Spec
|
11
11
|
class OperationSetupParam < Trailblazer::Operation
|
12
12
|
def process(params)
|
13
|
-
params
|
13
|
+
@model = params
|
14
14
|
end
|
15
15
|
|
16
16
|
def setup_params!(params)
|
17
17
|
params.merge!(garrett: "Rocks!")
|
18
18
|
end
|
19
|
+
|
20
|
+
include Inspect
|
19
21
|
end
|
20
22
|
|
21
|
-
|
22
|
-
it { OperationSetupParam.run({valid: true}).must_equal [true, {valid
|
23
|
+
# allows you changing params in #setup_params!.
|
24
|
+
it { OperationSetupParam.run({valid: true}).to_s.must_equal "[true, <OperationSetupParam @model={:valid=>true, :garrett=>\"Rocks!\"}>]" }
|
23
25
|
end
|
24
26
|
|
27
|
+
# Operation#model.
|
28
|
+
class OperationModelTest < MiniTest::Spec
|
29
|
+
class Operation < Trailblazer::Operation
|
30
|
+
def process(params)
|
31
|
+
end
|
32
|
+
|
33
|
+
def model!(params)
|
34
|
+
params
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# #model.
|
39
|
+
it { Operation.(Object).model.must_equal Object }
|
40
|
+
end
|
41
|
+
|
42
|
+
|
25
43
|
class OperationRunTest < MiniTest::Spec
|
26
44
|
class Operation < Trailblazer::Operation
|
27
45
|
# allow providing your own contract.
|
@@ -29,15 +47,13 @@ class OperationRunTest < MiniTest::Spec
|
|
29
47
|
def initialize(*)
|
30
48
|
end
|
31
49
|
def validate(params)
|
32
|
-
return
|
33
|
-
|
50
|
+
return true if params == "yes, true"
|
51
|
+
false
|
34
52
|
end
|
35
53
|
|
36
54
|
def errors
|
37
55
|
Struct.new(:to_s).new("Op just calls #to_s on Errors!")
|
38
56
|
end
|
39
|
-
|
40
|
-
include Comparable
|
41
57
|
self
|
42
58
|
end
|
43
59
|
|
@@ -45,68 +61,66 @@ class OperationRunTest < MiniTest::Spec
|
|
45
61
|
model = Object
|
46
62
|
validate(params, model)
|
47
63
|
end
|
48
|
-
end
|
49
64
|
|
50
|
-
|
65
|
+
include Inspect
|
66
|
+
end
|
51
67
|
|
52
68
|
# contract is inferred from self::contract_class.
|
53
|
-
|
69
|
+
# ::run returns result set when run without block.
|
70
|
+
it { Operation.run("not true").to_s.must_equal %{[false, <Operation @model=>]} }
|
71
|
+
it { Operation.run("yes, true").to_s.must_equal %{[true, <Operation @model=>]} }
|
54
72
|
|
55
|
-
#
|
56
|
-
it { Operation.call(true).must_equal operation }
|
57
|
-
it { Operation.(true).must_equal operation }
|
58
|
-
# #[] is alias for .()
|
59
|
-
it { Operation[true].must_equal operation }
|
60
|
-
|
61
|
-
# ::[] raises exception when invalid.
|
73
|
+
# ::call raises exception when invalid.
|
62
74
|
it do
|
63
|
-
exception = assert_raises(Trailblazer::Operation::InvalidContract) { Operation
|
75
|
+
exception = assert_raises(Trailblazer::Operation::InvalidContract) { Operation.("not true") }
|
64
76
|
exception.message.must_equal "Op just calls #to_s on Errors!"
|
65
77
|
end
|
66
78
|
|
67
|
-
#
|
68
|
-
it { Operation.
|
69
|
-
|
79
|
+
# return operation when ::call
|
80
|
+
it { Operation.("yes, true").to_s.must_equal %{<Operation @model=>} }
|
81
|
+
# #[] is alias for .()
|
82
|
+
it { Operation["yes, true"].to_s.must_equal %{<Operation @model=>} }
|
83
|
+
|
70
84
|
|
71
85
|
# ::run with block returns operation.
|
72
86
|
# valid executes block.
|
73
87
|
it "block" do
|
74
88
|
outcome = nil
|
75
|
-
res = Operation.run(true) do
|
89
|
+
res = Operation.run("yes, true") do
|
76
90
|
outcome = "true"
|
77
91
|
end
|
78
92
|
|
79
93
|
outcome.must_equal "true" # block was executed.
|
80
|
-
res.must_equal
|
94
|
+
res.to_s.must_equal %{<Operation @model=>}
|
81
95
|
end
|
82
96
|
|
83
97
|
# invalid doesn't execute block.
|
84
98
|
it "block, invalid" do
|
85
99
|
outcome = nil
|
86
|
-
res = Operation.run(false) do
|
100
|
+
res = Operation.run("no, not true, false") do
|
87
101
|
outcome = "true"
|
88
102
|
end
|
89
103
|
|
90
104
|
outcome.must_equal nil # block was _not_ executed.
|
91
|
-
res.must_equal
|
105
|
+
res.to_s.must_equal %{<Operation @model=>}
|
92
106
|
end
|
93
107
|
|
94
108
|
# block yields operation
|
95
109
|
it do
|
96
110
|
outcome = nil
|
97
|
-
res = Operation.run(true) do |op|
|
111
|
+
res = Operation.run("yes, true") do |op|
|
98
112
|
outcome = op
|
99
113
|
end
|
100
114
|
|
101
|
-
outcome.must_equal
|
102
|
-
res.must_equal
|
115
|
+
outcome.to_s.must_equal %{<Operation @model=>} # block was executed.
|
116
|
+
res.to_s.must_equal %{<Operation @model=>}
|
103
117
|
end
|
104
118
|
|
105
|
-
# Operation#contract returns @contract
|
106
|
-
|
107
|
-
it { Operation.(true).contract.must_equal contract }
|
119
|
+
# # Operation#contract returns @contract
|
120
|
+
it { Operation.("yes, true").contract.class.to_s.must_equal "OperationRunTest::Operation::Contract" }
|
108
121
|
end
|
109
122
|
|
123
|
+
|
110
124
|
class OperationTest < MiniTest::Spec
|
111
125
|
class Operation < Trailblazer::Operation
|
112
126
|
def process(params)
|
@@ -117,36 +131,20 @@ class OperationTest < MiniTest::Spec
|
|
117
131
|
# contract is retrieved from ::contract_class.
|
118
132
|
it { assert_raises(NoMethodError) { Operation.run({}) } } # TODO: if you call #validate without defining a contract, the error is quite cryptic.
|
119
133
|
|
120
|
-
#
|
121
|
-
# DISCUSS: not sure if we need that.
|
122
|
-
# class OperationWithoutProcessMethod < Trailblazer::Operation
|
123
|
-
# end
|
124
|
-
|
125
|
-
# it { OperationWithoutProcessMethod[{}].must_be_kind_of OperationWithoutProcessMethod }
|
126
|
-
|
127
|
-
# #process and no validate.
|
134
|
+
# test #invalid!
|
128
135
|
class OperationWithoutValidateCall < Trailblazer::Operation
|
129
136
|
def process(params)
|
130
137
|
params || invalid!(params)
|
131
138
|
end
|
139
|
+
|
140
|
+
include Inspect
|
132
141
|
end
|
133
142
|
|
134
143
|
# ::run
|
135
|
-
it { OperationWithoutValidateCall.run(
|
136
|
-
#
|
137
|
-
it { OperationWithoutValidateCall.(
|
138
|
-
|
139
|
-
it { OperationWithoutValidateCall.run(nil).must_equal [false, nil] }
|
140
|
-
# ::run with block, invalid
|
141
|
-
it do
|
142
|
-
OperationWithoutValidateCall.run(false) { @outcome = "true" }.must_equal false
|
143
|
-
@outcome.must_equal nil
|
144
|
-
end
|
145
|
-
# ::run with block, valid
|
146
|
-
it do
|
147
|
-
OperationWithoutValidateCall.run(true) { @outcome = "true" }.must_equal true
|
148
|
-
@outcome.must_equal "true"
|
149
|
-
end
|
144
|
+
it { OperationWithoutValidateCall.run(true).to_s.must_equal %{[true, <OperationWithoutValidateCall @model=>]} }
|
145
|
+
# invalid.
|
146
|
+
it { OperationWithoutValidateCall.run(false).to_s.must_equal %{[false, <OperationWithoutValidateCall @model=>]} }
|
147
|
+
|
150
148
|
|
151
149
|
# #validate yields contract when valid
|
152
150
|
class OperationWithValidateBlock < Trailblazer::Operation
|
@@ -162,7 +160,7 @@ class OperationTest < MiniTest::Spec
|
|
162
160
|
|
163
161
|
def process(params)
|
164
162
|
validate(params, Object.new) do |c|
|
165
|
-
@secret_contract = c
|
163
|
+
@secret_contract = c.class
|
166
164
|
end
|
167
165
|
end
|
168
166
|
|
@@ -170,58 +168,15 @@ class OperationTest < MiniTest::Spec
|
|
170
168
|
end
|
171
169
|
|
172
170
|
it { OperationWithValidateBlock.run(false).last.secret_contract.must_equal nil }
|
173
|
-
it
|
171
|
+
it { OperationWithValidateBlock.(true).secret_contract.must_equal OperationWithValidateBlock::Contract }
|
174
172
|
|
175
|
-
# manually setting @valid
|
176
|
-
class OperationWithManualValid < Trailblazer::Operation
|
177
|
-
def process(params)
|
178
|
-
@valid = false
|
179
|
-
params
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# ::run
|
184
|
-
it { OperationWithManualValid.run(Object).must_equal [false, Object] }
|
185
|
-
# ::[]
|
186
|
-
it { OperationWithManualValid.(Object).must_equal(Object) }
|
187
|
-
|
188
|
-
# re-assign params
|
189
|
-
class OperationReassigningParams < Trailblazer::Operation
|
190
|
-
def process(params)
|
191
|
-
params = params[:title]
|
192
|
-
params
|
193
|
-
end
|
194
|
-
end
|
195
173
|
|
196
|
-
#
|
197
|
-
|
198
|
-
|
199
|
-
# #invalid!(result)
|
200
|
-
class OperationCallingInvalid < Trailblazer::Operation
|
201
|
-
def process(params)
|
202
|
-
return 1 if params
|
203
|
-
invalid!(2)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
it { OperationCallingInvalid.run(true).must_equal [true, 1] }
|
208
|
-
it { OperationCallingInvalid.run(nil).must_equal [false, 2] }
|
209
|
-
|
210
|
-
# #invalid! without result defaults to operation instance.
|
211
|
-
class OperationCallingInvalidWithoutResult < Trailblazer::Operation
|
212
|
-
include Comparable
|
213
|
-
def process(params)
|
214
|
-
invalid!
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
it { OperationCallingInvalidWithoutResult.run(true).must_equal [false, OperationCallingInvalidWithoutResult.new] }
|
219
|
-
|
220
|
-
# calling return from #validate block leaves result true.
|
221
|
-
class OperationUsingReturnInValidate < Trailblazer::Operation
|
174
|
+
# test validate wit if/else
|
175
|
+
class OperationWithValidateAndIf < Trailblazer::Operation
|
222
176
|
self.contract_class = class Contract
|
223
177
|
def initialize(*)
|
224
178
|
end
|
179
|
+
|
225
180
|
def validate(params)
|
226
181
|
params
|
227
182
|
end
|
@@ -229,24 +184,31 @@ class OperationTest < MiniTest::Spec
|
|
229
184
|
end
|
230
185
|
|
231
186
|
def process(params)
|
232
|
-
validate(params, Object)
|
233
|
-
|
187
|
+
if validate(params, Object.new)
|
188
|
+
@secret_contract = contract.class
|
189
|
+
else
|
190
|
+
@secret_contract = "so wrong!"
|
234
191
|
end
|
235
|
-
2
|
236
192
|
end
|
193
|
+
|
194
|
+
attr_reader :secret_contract
|
237
195
|
end
|
238
196
|
|
239
|
-
it {
|
240
|
-
it {
|
197
|
+
it { OperationWithValidateAndIf.run(false).last.secret_contract.must_equal "so wrong!" }
|
198
|
+
it { OperationWithValidateAndIf.(true).secret_contract.must_equal OperationWithValidateAndIf::Contract }
|
199
|
+
|
200
|
+
|
201
|
+
|
241
202
|
|
242
203
|
# unlimited arguments for ::run and friends.
|
243
204
|
class OperationReceivingLottaArguments < Trailblazer::Operation
|
244
205
|
def process(model, params)
|
245
|
-
[model, params]
|
206
|
+
@model = [model, params]
|
246
207
|
end
|
208
|
+
include Inspect
|
247
209
|
end
|
248
210
|
|
249
|
-
it { OperationReceivingLottaArguments.run(Object, {}).must_equal
|
211
|
+
it { OperationReceivingLottaArguments.run(Object, {}).to_s.must_equal %{[true, <OperationReceivingLottaArguments @model=[Object, {}]>]} }
|
250
212
|
|
251
213
|
# ::present only runs #setup! which runs #model!.
|
252
214
|
class ContractOnlyOperation < Trailblazer::Operation
|
@@ -270,16 +232,13 @@ class OperationTest < MiniTest::Spec
|
|
270
232
|
it { ContractOnlyOperation.present({}).contract._model.must_equal Object }
|
271
233
|
end
|
272
234
|
|
235
|
+
|
273
236
|
class OperationBuilderTest < MiniTest::Spec
|
274
|
-
class
|
237
|
+
class ParentOperation < Trailblazer::Operation
|
275
238
|
def process(params)
|
276
|
-
"operation"
|
277
239
|
end
|
278
240
|
|
279
241
|
class Sub < self
|
280
|
-
def process(params)
|
281
|
-
"sub:operation"
|
282
|
-
end
|
283
242
|
end
|
284
243
|
|
285
244
|
builds do |params|
|
@@ -287,13 +246,13 @@ class OperationBuilderTest < MiniTest::Spec
|
|
287
246
|
end
|
288
247
|
end
|
289
248
|
|
290
|
-
it {
|
291
|
-
it {
|
292
|
-
|
293
|
-
it {
|
294
|
-
it { Operation.({sub: true}).must_equal "sub:operation" }
|
249
|
+
it { ParentOperation.run({}).last.class.must_equal ParentOperation }
|
250
|
+
it { ParentOperation.run({sub: true}).last.class.must_equal ParentOperation::Sub }
|
251
|
+
it { ParentOperation.({}).class.must_equal ParentOperation }
|
252
|
+
it { ParentOperation.({sub: true}).class.must_equal ParentOperation::Sub }
|
295
253
|
end
|
296
254
|
|
255
|
+
|
297
256
|
# ::contract builds Reform::Form class
|
298
257
|
class OperationInheritanceTest < MiniTest::Spec
|
299
258
|
class Operation < Trailblazer::Operation
|
@@ -357,4 +316,4 @@ class OperationErrorsTest < MiniTest::Spec
|
|
357
316
|
res, op = Operation.run({})
|
358
317
|
op.errors.to_s.must_equal "{:title=>[\"can't be blank\"]}"
|
359
318
|
end
|
360
|
-
end
|
319
|
+
end
|