trailblazer 2.0.7 → 2.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +35 -1
- data/Gemfile +6 -12
- data/README.md +3 -1
- data/Rakefile +6 -17
- data/lib/trailblazer.rb +7 -4
- data/lib/trailblazer/deprecation/call.rb +46 -0
- data/lib/trailblazer/deprecation/context.rb +43 -0
- data/lib/trailblazer/operation/contract.rb +40 -9
- data/lib/trailblazer/operation/deprecations.rb +21 -0
- data/lib/trailblazer/operation/guard.rb +5 -5
- data/lib/trailblazer/operation/model.rb +15 -10
- data/lib/trailblazer/operation/nested.rb +56 -85
- data/lib/trailblazer/operation/persist.rb +4 -2
- data/lib/trailblazer/operation/policy.rb +16 -7
- data/lib/trailblazer/operation/pundit.rb +3 -3
- data/lib/trailblazer/operation/representer.rb +5 -0
- data/lib/trailblazer/operation/rescue.rb +12 -9
- data/lib/trailblazer/operation/validate.rb +36 -29
- data/lib/trailblazer/operation/wrap.rb +49 -11
- data/lib/trailblazer/task.rb +20 -0
- data/lib/trailblazer/version.rb +1 -1
- data/test/benchmark.rb +63 -0
- data/test/deprecation/call_test.rb +42 -0
- data/test/deprecation/context_test.rb +19 -0
- data/test/docs/contract_test.rb +73 -53
- data/test/docs/dry_test.rb +2 -2
- data/test/docs/fast_test.rb +133 -13
- data/test/docs/guard_test.rb +28 -35
- data/test/docs/macro_test.rb +1 -1
- data/test/docs/model_test.rb +13 -13
- data/test/docs/nested_test.rb +54 -122
- data/test/docs/operation_test.rb +42 -43
- data/test/docs/pundit_test.rb +16 -16
- data/test/docs/representer_test.rb +18 -18
- data/test/docs/rescue_test.rb +29 -29
- data/test/docs/trace_test.rb +82 -0
- data/test/docs/wrap_test.rb +59 -26
- data/test/module_test.rb +75 -75
- data/test/nested_test.rb +293 -0
- data/test/operation/contract_test.rb +23 -153
- data/test/operation/dsl/contract_test.rb +9 -9
- data/test/operation/dsl/representer_test.rb +169 -169
- data/test/operation/model_test.rb +15 -21
- data/test/operation/persist_test.rb +18 -11
- data/test/operation/pundit_test.rb +25 -23
- data/test/operation/representer_test.rb +254 -254
- data/test/test_helper.rb +5 -2
- data/test/variables_test.rb +158 -0
- data/trailblazer.gemspec +1 -1
- data/untitled +33 -0
- metadata +25 -27
- data/lib/trailblazer/operation/callback.rb +0 -35
- data/lib/trailblazer/operation/procedural/contract.rb +0 -15
- data/lib/trailblazer/operation/procedural/validate.rb +0 -22
- data/test/operation/callback_test.rb +0 -70
- data/test/operation/dsl/callback_test.rb +0 -106
- data/test/operation/params_test.rb +0 -36
- data/test/operation/pipedream_test.rb +0 -59
- data/test/operation/pipetree_test.rb +0 -104
- data/test/operation/present_test.rb +0 -24
- data/test/operation/resolver_test.rb +0 -47
- data/test/operation_test.rb +0 -143
@@ -1,70 +0,0 @@
|
|
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
|
-
#---
|
8
|
-
# with contract and disposable semantics
|
9
|
-
class Create < Trailblazer::Operation
|
10
|
-
extend Contract::DSL
|
11
|
-
|
12
|
-
contract do
|
13
|
-
property :name
|
14
|
-
end
|
15
|
-
|
16
|
-
step Model( Song, :new )
|
17
|
-
step Contract::Build()
|
18
|
-
step Contract::Validate()
|
19
|
-
step Callback( :default )
|
20
|
-
|
21
|
-
|
22
|
-
extend Callback::DSL
|
23
|
-
|
24
|
-
callback do
|
25
|
-
on_change :notify_me!
|
26
|
-
on_change :notify_you!
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
# TODO: always dispatch, pass params.
|
31
|
-
|
32
|
-
def dispatched
|
33
|
-
self["dispatched"] ||= []
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
def notify_me!(*)
|
38
|
-
dispatched << :notify_me!
|
39
|
-
end
|
40
|
-
|
41
|
-
def notify_you!(*)
|
42
|
-
dispatched << :notify_you!
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
|
-
class Update < Create
|
48
|
-
# TODO: allow skipping groups.
|
49
|
-
# skip_dispatch :notify_me!
|
50
|
-
|
51
|
-
callback do
|
52
|
-
remove! :on_change, :notify_me!
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
#---
|
57
|
-
#- inheritance
|
58
|
-
it { Update["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,>callback.default]} }
|
59
|
-
|
60
|
-
|
61
|
-
it "invokes all callbacks" do
|
62
|
-
res = Create.({"name"=>"Keep On Running"})
|
63
|
-
res["dispatched"].must_equal [:notify_me!, :notify_you!]
|
64
|
-
end
|
65
|
-
|
66
|
-
it "does not invoke removed callbacks" do
|
67
|
-
res = Update.({"name"=>"Keep On Running"})
|
68
|
-
res["dispatched"].must_equal [:notify_you!]
|
69
|
-
end
|
70
|
-
end
|
@@ -1,106 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class DslCallbackTest < MiniTest::Spec
|
4
|
-
module SongProcess
|
5
|
-
def _invocations
|
6
|
-
self["x"] ||= []
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.included(includer)
|
10
|
-
includer.extend Trailblazer::Operation::Contract::DSL
|
11
|
-
includer.contract do
|
12
|
-
property :title
|
13
|
-
end
|
14
|
-
includer.| Trailblazer::Operation::Model[OpenStruct, :new]
|
15
|
-
includer.| Trailblazer::Operation::Contract::Build[includer["contract.default.class"]]
|
16
|
-
includer.| Trailblazer::Operation::Contract::Validate[]
|
17
|
-
includer.| Trailblazer::Operation::Callback[:default]
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
describe "inheritance across operations" do
|
22
|
-
class Operation < Trailblazer::Operation
|
23
|
-
extend Callback::DSL
|
24
|
-
include SongProcess
|
25
|
-
|
26
|
-
callback do
|
27
|
-
on_change :default!
|
28
|
-
end
|
29
|
-
|
30
|
-
class Admin < self
|
31
|
-
callback do
|
32
|
-
on_change :admin_default!
|
33
|
-
end
|
34
|
-
|
35
|
-
callback(:after_save) { on_change :after_save! }
|
36
|
-
|
37
|
-
def admin_default!(*); _invocations << :admin_default!; end
|
38
|
-
def after_save!(*); _invocations << :after_save!; end
|
39
|
-
|
40
|
-
step Trailblazer::Operation::Callback[:after_save]
|
41
|
-
end
|
42
|
-
|
43
|
-
def default!(*); _invocations << :default!; end
|
44
|
-
end
|
45
|
-
|
46
|
-
it { Operation.({"title"=> "Love-less"})["x"].must_equal([:default!]) }
|
47
|
-
it { Operation::Admin.({"title"=> "Love-less"})["x"].must_equal([:default!, :admin_default!, :after_save!]) }
|
48
|
-
end
|
49
|
-
|
50
|
-
describe "Op.callback :after_save, AfterSaveCallback" do
|
51
|
-
class AfterSaveCallback < Disposable::Callback::Group
|
52
|
-
on_change :after_save!
|
53
|
-
|
54
|
-
def after_save!(twin, options)
|
55
|
-
options[:operation]._invocations << :after_save!
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
class OpWithExternalCallback < Trailblazer::Operation
|
60
|
-
include SongProcess
|
61
|
-
extend Callback::DSL
|
62
|
-
callback :after_save, AfterSaveCallback
|
63
|
-
|
64
|
-
step Callback[:after_save]
|
65
|
-
end
|
66
|
-
|
67
|
-
it { OpWithExternalCallback.("title"=>"Thunder Rising").must_equal([:after_save!]) }
|
68
|
-
end
|
69
|
-
|
70
|
-
describe "Op.callback :after_save, AfterSaveCallback do .. end" do
|
71
|
-
class DefaultCallback < Disposable::Callback::Group
|
72
|
-
on_change :default!
|
73
|
-
|
74
|
-
def default!(twin, options)
|
75
|
-
options[:operation]._invocations << :default!
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
class OpUsingCallback < Trailblazer::Operation
|
80
|
-
extend Callback::DSL
|
81
|
-
include SongProcess
|
82
|
-
callback :default, DefaultCallback
|
83
|
-
end
|
84
|
-
|
85
|
-
class OpExtendingCallback < Trailblazer::Operation
|
86
|
-
extend Callback::DSL
|
87
|
-
include SongProcess
|
88
|
-
callback :default, DefaultCallback do
|
89
|
-
on_change :after_save!
|
90
|
-
|
91
|
-
def default!(twin, options)
|
92
|
-
options[:operation]._invocations << :extended_default!
|
93
|
-
end
|
94
|
-
|
95
|
-
def after_save!(twin, options)
|
96
|
-
options[:operation]._invocations << :after_save!
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# this operation copies DefaultCallback and shouldn't run #after_save!.
|
102
|
-
it { OpUsingCallback.(title: "Thunder Rising")["x"].must_equal([:default!]) }
|
103
|
-
# this operation copies DefaultCallback, extends it and runs #after_save!.
|
104
|
-
it { OpExtendingCallback.(title: "Thunder Rising")["x"].must_equal([:extended_default!, :after_save!]) }
|
105
|
-
end
|
106
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
require "trailblazer/operation/params"
|
3
|
-
|
4
|
-
class OperationParamsTest < MiniTest::Spec
|
5
|
-
#---
|
6
|
-
# in call(params) and process(params), argument is always self["params"]
|
7
|
-
class Find < Trailblazer::Operation
|
8
|
-
def process(params); self["eql?"] = self["params"].object_id==params.object_id end
|
9
|
-
end
|
10
|
-
class Seek < Trailblazer::Operation
|
11
|
-
include Test::ReturnCall
|
12
|
-
def call(params); self["eql?"] = self["params"].object_id==params.object_id end
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
it { Find.({ id: 1 })["eql?"].must_equal true }
|
17
|
-
# self["params"] is what gets passed in.
|
18
|
-
it { Find.({ id: 1 })["params"].must_equal({ id: 1 }) }
|
19
|
-
it { Seek.({ id: 1 }).must_equal true }
|
20
|
-
|
21
|
-
|
22
|
-
#---
|
23
|
-
# change self["params"] via Operation#
|
24
|
-
class Create < Trailblazer::Operation
|
25
|
-
include Params
|
26
|
-
def process(params) self[:x] = params end
|
27
|
-
def params!(params); params.merge(garrett: "Rocks!") end
|
28
|
-
end
|
29
|
-
|
30
|
-
# allows you changing params in #setup_params!.
|
31
|
-
it {
|
32
|
-
result = Create.( id: 1 )
|
33
|
-
result["params"].inspect.must_equal %{{:id=>1, :garrett=>\"Rocks!\"}}
|
34
|
-
result["params.original"].inspect.must_equal %{{:id=>1}}
|
35
|
-
}
|
36
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class PipedreamTest < Minitest::Spec
|
4
|
-
Song = Struct.new(:title)
|
5
|
-
|
6
|
-
class Create < Trailblazer::Operation
|
7
|
-
class MyContract < Reform::Form
|
8
|
-
property :title
|
9
|
-
end
|
10
|
-
|
11
|
-
class Auth
|
12
|
-
def initialize(user, model); @user, @model = user, model end
|
13
|
-
def user_and_model?; @user == Module && @model.class == Song end
|
14
|
-
end
|
15
|
-
|
16
|
-
# design principles:
|
17
|
-
# * include as less code as possible into the op class.
|
18
|
-
# * make the flow super explicit without making it cryptic (only 3 new operators)
|
19
|
-
# * avoid including DSL modules in favor of passing those configurations directly to the "step".
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
step Model[ Song, :new ] # model!)
|
24
|
-
step Policy::Guard[ ->(options){ options["current_user"] == ::Module } ]
|
25
|
-
step Contract[ MyContract]
|
26
|
-
step Policy[ Auth, :user_and_model?]
|
27
|
-
failure Contract[ MyContract]
|
28
|
-
|
29
|
-
# step :model
|
30
|
-
# step :guard
|
31
|
-
# step :contract
|
32
|
-
|
33
|
-
|
34
|
-
# ok Model[Song, :new] # model!)
|
35
|
-
# ok Policy::Guard[ ->(options){ options["current_user"] == ::Module } ]
|
36
|
-
# ok Contract[MyContract]
|
37
|
-
# fail Contract[MyContract]
|
38
|
-
# step> "contract"
|
39
|
-
|
40
|
-
# | :bla
|
41
|
-
# | ->
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
# TODO: test with contract constant (done).
|
46
|
-
# test with inline contract.
|
47
|
-
# test with override contract!.
|
48
|
-
|
49
|
-
it do
|
50
|
-
puts Create["pipetree"].inspect(style: :rows)
|
51
|
-
result = Create.({}, { "current_user" => Module })
|
52
|
-
|
53
|
-
result["model"].inspect.must_equal %{#<struct PipedreamTest::Song title=nil>}
|
54
|
-
result["result.policy"].success?.must_equal true
|
55
|
-
result["contract"].class.superclass.must_equal Reform::Form
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
-
end
|
@@ -1,104 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
# self["pipetree"] = ::Pipetree[
|
4
|
-
# Trailblazer::Operation::New,
|
5
|
-
# # SetupParams,
|
6
|
-
# Trailblazer::Operation::Model::Build,
|
7
|
-
# Trailblazer::Operation::Model::Assign,
|
8
|
-
# Trailblazer::Operation::Call,
|
9
|
-
# ]
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# def deserialize(*)
|
14
|
-
# super
|
15
|
-
# self.datetime = DateTime.parse("#{date} #{time}")
|
16
|
-
# end
|
17
|
-
|
18
|
-
class PipetreeTest < Minitest::Spec
|
19
|
-
Song = Struct.new(:title)
|
20
|
-
|
21
|
-
class Create < Trailblazer::Operation
|
22
|
-
include Builder
|
23
|
-
include Pipetree # this will add the functions, again, unfortunately. definitely an error source.
|
24
|
-
end
|
25
|
-
|
26
|
-
it { Create["pipetree"].inspect.must_equal %{[>>Build,>>New,>>Call,Result::Build,>>New,>>Call,Result::Build]} }
|
27
|
-
|
28
|
-
#---
|
29
|
-
# playground
|
30
|
-
require "trailblazer/operation/policy"
|
31
|
-
require "trailblazer/operation/guard"
|
32
|
-
|
33
|
-
class Edit < Trailblazer::Operation
|
34
|
-
include Builder
|
35
|
-
include Policy::Guard
|
36
|
-
include Contract::Step
|
37
|
-
contract do
|
38
|
-
property :title
|
39
|
-
validates :title, presence: true
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
MyValidate = ->(input, options) { res= input.validate(options["params"]) { |f| f.sync } }
|
44
|
-
# we can have a separate persist step and wrap in transaction. where do we pass contract, though?
|
45
|
-
step MyValidate, before: Call #replace: Contract::ValidLegacySwitch
|
46
|
-
#
|
47
|
-
MyAfterSave = ->(input, options) { input["after_save"] = true }
|
48
|
-
success MyAfterSave, after: MyValidate
|
49
|
-
|
50
|
-
ValidateFailureLogger = ->(input, options) { input["validate fail"] = true }
|
51
|
-
failure ValidateFailureLogger, after: MyValidate
|
52
|
-
|
53
|
-
success ->(input, options) { input.process(options["params"]) }, replace: Call, name: "my.params"
|
54
|
-
|
55
|
-
include Model
|
56
|
-
|
57
|
-
LogBreach = ->(input, options) { input.log_breach! }
|
58
|
-
|
59
|
-
failure LogBreach, after: Policy::Evaluate
|
60
|
-
|
61
|
-
model Song
|
62
|
-
policy ->(*) { self["current_user"] }
|
63
|
-
|
64
|
-
def log_breach!
|
65
|
-
self["breach"] = true
|
66
|
-
end
|
67
|
-
|
68
|
-
def process(params)
|
69
|
-
self["my.valid"] = true
|
70
|
-
end
|
71
|
-
|
72
|
-
self["pipetree"]._insert(Contract::ValidLegacySwitch, {delete: true}, nil, nil)
|
73
|
-
end
|
74
|
-
|
75
|
-
puts Edit["pipetree"].inspect(style: :rows)
|
76
|
-
|
77
|
-
it { Edit["pipetree"].inspect.must_equal %{[>>operation.build,>>operation.new,&model.build,&policy.guard.evaluate,<LogBreach,>contract.build,&MyValidate,<ValidateFailureLogger,>MyAfterSave,>my.params["params"]]} }
|
78
|
-
|
79
|
-
# valid case.
|
80
|
-
it {
|
81
|
-
# puts "valid"
|
82
|
-
# puts Edit["pipetree"].inspect(style: :rows)
|
83
|
-
result = Edit.({ title: "Stupid 7" }, "current_user" => true)
|
84
|
-
# puts "success! #{result.inspect}"
|
85
|
-
result["my.valid"].must_equal true
|
86
|
-
result["breach"].must_equal nil
|
87
|
-
result["after_save"].must_equal true
|
88
|
-
result["validate fail"].must_equal nil
|
89
|
-
}
|
90
|
-
# beach! i mean breach!
|
91
|
-
it {
|
92
|
-
# puts "beach"
|
93
|
-
# puts Edit["pipetree"].inspect(style: :rows)
|
94
|
-
result = Edit.({})
|
95
|
-
# puts "@@@@@ #{result.inspect}"
|
96
|
-
result["my.valid"].must_equal nil
|
97
|
-
result["breach"].must_equal true
|
98
|
-
result["validate fail"].must_equal true
|
99
|
-
result["after_save"].must_equal nil
|
100
|
-
}
|
101
|
-
end
|
102
|
-
|
103
|
-
# TODO: show the execution path in pipetree
|
104
|
-
# unified result.contract, result.policy interface
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
require "trailblazer/operation/present"
|
4
|
-
|
5
|
-
class PresentTest < Minitest::Spec
|
6
|
-
class Create < Trailblazer::Operation
|
7
|
-
include Test::ReturnCall
|
8
|
-
include Present
|
9
|
-
|
10
|
-
include Model::Builder
|
11
|
-
def model!(*); Object end
|
12
|
-
|
13
|
-
def call(params)
|
14
|
-
"#call run!"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
it do
|
19
|
-
result = Create.present
|
20
|
-
result["model"].must_equal Object
|
21
|
-
end
|
22
|
-
|
23
|
-
it { Create.().must_equal "#call run!" }
|
24
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# require "test_helper"
|
2
|
-
|
3
|
-
# class ResolverTest < Minitest::Spec
|
4
|
-
# Song = Struct.new(:id) do
|
5
|
-
# def self.find(id); new(id) end
|
6
|
-
# end
|
7
|
-
|
8
|
-
# class Auth
|
9
|
-
# def initialize(*args); @user, @model = *args end
|
10
|
-
# def only_user?; @user == Module && @model.nil? end
|
11
|
-
# def user_object?; @user == Object end
|
12
|
-
# def user_and_model?; @user == Module && @model.class == Song end
|
13
|
-
# def inspect; "<Auth: user:#{@user.inspect}, model:#{@model.inspect}>" end
|
14
|
-
# end
|
15
|
-
|
16
|
-
# class A < Trailblazer::Operation
|
17
|
-
# extend Builder::DSL
|
18
|
-
# builds ->(options) {
|
19
|
-
# return P if options["params"] == { some: "params", id:1 }
|
20
|
-
# return B if options["policy.default"].inspect == %{<Auth: user:Module, model:#<struct ResolverTest::Song id=3>>} # both user and model:id are set!
|
21
|
-
# return M if options["model"].inspect == %{#<struct ResolverTest::Song id=9>}
|
22
|
-
# }
|
23
|
-
|
24
|
-
# step Model( Song, :update ), before: "operation.new"
|
25
|
-
# step Policy::Pundit( Auth, :user_and_model? ), before: "operation.new"
|
26
|
-
# require "trailblazer/operation/resolver"
|
27
|
-
# step Resolver(), before: "operation.new"
|
28
|
-
|
29
|
-
# step :process
|
30
|
-
|
31
|
-
# class P < self; end
|
32
|
-
# class B < self; end
|
33
|
-
# class M < self; end
|
34
|
-
|
35
|
-
# def process(*); self["x"] = self.class end
|
36
|
-
# end
|
37
|
-
|
38
|
-
# it { A["pipetree"].inspect.must_equal %{[&model.build,&policy.default.eval,>>builder.call,>>operation.new,&process]} }
|
39
|
-
|
40
|
-
# it { r=A.({ some: "params", id: 1 }, { "current_user" => Module })
|
41
|
-
# puts r.inspect
|
42
|
-
|
43
|
-
# }
|
44
|
-
# it { A.({ some: "params", id: 1 }, { "current_user" => Module })["x"].must_equal A::P }
|
45
|
-
# it { A.({ id: 3 }, { "current_user" => Module })["x"].must_equal A::B }
|
46
|
-
# it { A.({ id: 9 }, { "current_user" => Module })["x"].must_equal A::M }
|
47
|
-
# end
|