trailblazer 2.0.7 → 2.1.0.beta1
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 +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
|