trailblazer 1.1.2 → 2.0.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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -7
  3. data/CHANGES.md +108 -0
  4. data/COMM-LICENSE +91 -0
  5. data/Gemfile +18 -4
  6. data/LICENSE.txt +7 -20
  7. data/README.md +55 -15
  8. data/Rakefile +21 -2
  9. data/draft-1.2.rb +7 -0
  10. data/lib/trailblazer.rb +17 -4
  11. data/lib/trailblazer/dsl.rb +47 -0
  12. data/lib/trailblazer/operation/auto_inject.rb +47 -0
  13. data/lib/trailblazer/operation/builder.rb +18 -18
  14. data/lib/trailblazer/operation/callback.rb +31 -38
  15. data/lib/trailblazer/operation/contract.rb +46 -0
  16. data/lib/trailblazer/operation/controller.rb +45 -27
  17. data/lib/trailblazer/operation/guard.rb +24 -0
  18. data/lib/trailblazer/operation/model.rb +41 -33
  19. data/lib/trailblazer/operation/nested.rb +43 -0
  20. data/lib/trailblazer/operation/params.rb +13 -0
  21. data/lib/trailblazer/operation/persist.rb +13 -0
  22. data/lib/trailblazer/operation/policy.rb +26 -72
  23. data/lib/trailblazer/operation/present.rb +19 -0
  24. data/lib/trailblazer/operation/procedural/contract.rb +15 -0
  25. data/lib/trailblazer/operation/procedural/validate.rb +22 -0
  26. data/lib/trailblazer/operation/pundit.rb +42 -0
  27. data/lib/trailblazer/operation/representer.rb +25 -92
  28. data/lib/trailblazer/operation/rescue.rb +23 -0
  29. data/lib/trailblazer/operation/resolver.rb +18 -24
  30. data/lib/trailblazer/operation/validate.rb +50 -0
  31. data/lib/trailblazer/operation/wrap.rb +37 -0
  32. data/lib/trailblazer/version.rb +1 -1
  33. data/test/{operation/controller_test.rb → controller_test.rb} +8 -4
  34. data/test/docs/auto_inject_test.rb +30 -0
  35. data/test/docs/contract_test.rb +429 -0
  36. data/test/docs/dry_test.rb +31 -0
  37. data/test/docs/guard_test.rb +143 -0
  38. data/test/docs/nested_test.rb +117 -0
  39. data/test/docs/policy_test.rb +2 -0
  40. data/test/docs/pundit_test.rb +109 -0
  41. data/test/docs/representer_test.rb +268 -0
  42. data/test/docs/rescue_test.rb +153 -0
  43. data/test/docs/wrap_test.rb +174 -0
  44. data/test/gemfiles/Gemfile.ruby-1.9 +3 -0
  45. data/test/gemfiles/Gemfile.ruby-2.0 +12 -0
  46. data/test/gemfiles/Gemfile.ruby-2.3 +12 -0
  47. data/test/module_test.rb +22 -15
  48. data/test/operation/builder_test.rb +66 -18
  49. data/test/operation/callback_test.rb +70 -0
  50. data/test/operation/contract_test.rb +385 -15
  51. data/test/operation/dsl/callback_test.rb +18 -30
  52. data/test/operation/dsl/contract_test.rb +209 -19
  53. data/test/operation/dsl/representer_test.rb +42 -15
  54. data/test/operation/guard_test.rb +1 -147
  55. data/test/operation/model_test.rb +105 -0
  56. data/test/operation/params_test.rb +36 -0
  57. data/test/operation/persist_test.rb +44 -0
  58. data/test/operation/pipedream_test.rb +59 -0
  59. data/test/operation/pipetree_test.rb +104 -0
  60. data/test/operation/present_test.rb +24 -0
  61. data/test/operation/pundit_test.rb +104 -0
  62. data/test/{representer_test.rb → operation/representer_test.rb} +58 -42
  63. data/test/operation/resolver_test.rb +34 -70
  64. data/test/operation_test.rb +57 -189
  65. data/test/test_helper.rb +23 -3
  66. data/trailblazer.gemspec +8 -7
  67. metadata +91 -59
  68. data/gemfiles/Gemfile.rails.lock +0 -130
  69. data/gemfiles/Gemfile.reform-2.0 +0 -6
  70. data/gemfiles/Gemfile.reform-2.1 +0 -7
  71. data/lib/trailblazer/autoloading.rb +0 -15
  72. data/lib/trailblazer/endpoint.rb +0 -31
  73. data/lib/trailblazer/operation.rb +0 -175
  74. data/lib/trailblazer/operation/collection.rb +0 -6
  75. data/lib/trailblazer/operation/dispatch.rb +0 -3
  76. data/lib/trailblazer/operation/model/dsl.rb +0 -29
  77. data/lib/trailblazer/operation/model/external.rb +0 -34
  78. data/lib/trailblazer/operation/policy/guard.rb +0 -35
  79. data/lib/trailblazer/operation/uploaded_file.rb +0 -77
  80. data/test/callback_test.rb +0 -104
  81. data/test/collection_test.rb +0 -57
  82. data/test/model_test.rb +0 -148
  83. data/test/operation/external_model_test.rb +0 -71
  84. data/test/operation/policy_test.rb +0 -97
  85. data/test/operation/reject_test.rb +0 -34
  86. data/test/rollback_test.rb +0 -47
@@ -1,152 +1,6 @@
1
1
  require "test_helper"
2
- require "trailblazer/operation/policy"
3
2
 
4
- class OpPolicyGuardTest < MiniTest::Spec
5
- Song = Struct.new(:name)
6
3
 
7
- class Create < Trailblazer::Operation
8
- include Policy::Guard
9
4
 
10
- def model!(*)
11
- Song.new
12
- end
13
5
 
14
- policy do |params|
15
- model.is_a?(Song) and params[:valid]
16
- end
17
-
18
- def process(*)
19
- end
20
- end
21
-
22
- # valid.
23
- it do
24
- op = Create.(valid: true)
25
- end
26
-
27
- # invalid.
28
- it do
29
- assert_raises Trailblazer::NotAuthorizedError do
30
- op = Create.(valid: false)
31
- end
32
- end
33
-
34
-
35
- describe "inheritance" do
36
- class Update < Create
37
- policy do |params|
38
- params[:valid] == "correct"
39
- end
40
- end
41
-
42
- class Delete < Create
43
- end
44
-
45
- it do
46
- Create.(valid: true).wont_equal nil
47
- Delete.(valid: true).wont_equal nil
48
- Update.(valid: "correct").wont_equal nil
49
- end
50
- end
51
-
52
-
53
- describe "no policy defined, but included" do
54
- class Show < Trailblazer::Operation
55
- include Policy::Guard
56
-
57
- def process(*)
58
- end
59
- end
60
-
61
- it { Show.({}).wont_equal nil }
62
- end
63
-
64
-
65
- describe "#params!" do
66
- class Index < Trailblazer::Operation
67
- include Policy::Guard
68
-
69
- # make sure the guard receives the correct params.
70
- policy { |params| params[:valid] == "true" }
71
-
72
- def params!(params)
73
- { valid: params }
74
- end
75
-
76
- def process(*)
77
- end
78
- end
79
-
80
- it { Index.("true").wont_equal nil }
81
- it { assert_raises(Trailblazer::NotAuthorizedError) { Index.(false).wont_equal nil } }
82
- end
83
-
84
- describe "with Callable" do
85
- class Find < Trailblazer::Operation
86
- include Policy::Guard
87
-
88
- class Guardian
89
- include Uber::Callable
90
-
91
- def call(context, params)
92
- params == "true"
93
- end
94
- end
95
-
96
- policy Guardian.new
97
-
98
- def process(*)
99
- end
100
- end
101
-
102
- it { Find.("true").wont_equal nil }
103
- it { assert_raises(Trailblazer::NotAuthorizedError) { Find.(false).wont_equal nil } }
104
- end
105
-
106
- describe "with Proc" do
107
- class Follow < Trailblazer::Operation
108
- include Policy::Guard
109
-
110
- policy ->(params) { params == "true" } # TODO: is this really executed in op context?
111
-
112
- def process(*)
113
- end
114
- end
115
-
116
- it { Follow.("true").wont_equal nil }
117
- it { assert_raises(Trailblazer::NotAuthorizedError) { Follow.(false).wont_equal nil } }
118
- end
119
- end
120
-
121
- class OpBuilderDenyTest < MiniTest::Spec
122
- Song = Struct.new(:name)
123
-
124
- class Create < Trailblazer::Operation
125
- include Deny
126
-
127
- builds do |params|
128
- deny! unless params[:valid]
129
- end
130
-
131
- def process(params)
132
- end
133
- end
134
-
135
- class Update < Create
136
- builds -> (params) do
137
- deny! unless params[:valid]
138
- end
139
- end
140
-
141
- # valid.
142
- it do
143
- op = Create.(valid: true)
144
- end
145
-
146
- # invalid.
147
- it do
148
- assert_raises Trailblazer::NotAuthorizedError do
149
- op = Create.(valid: false)
150
- end
151
- end
152
- end
6
+ # FIXME: what about block passed to ::policy?
@@ -0,0 +1,105 @@
1
+ require "test_helper"
2
+
3
+ class ModelTest < Minitest::Spec
4
+ Song = Struct.new(:id) do
5
+ def self.find(id); new(id) end
6
+ def self.find_by(id:nil); id.nil? ? nil : new(id) end
7
+ end
8
+
9
+ #---
10
+ # use Model semantics, no customizations.
11
+ class Create < Trailblazer::Operation
12
+ self.| Model Song, :new
13
+ end
14
+
15
+ # :new new.
16
+ it { Create.({})["model"].inspect.must_equal %{#<struct ModelTest::Song id=nil>} }
17
+
18
+ class Update < Create
19
+ self.~ Model :update # DISCUSS: do we need the ~ operator?
20
+ end
21
+
22
+ # :find it
23
+ it { Update.({ id: 1 })["model"].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
24
+
25
+ #- inheritance
26
+ it { Update["pipetree"].inspect.must_equal %{[>>operation.new,&model.build]} }
27
+
28
+ # override #model with Model included.
29
+ class Upsert < Create
30
+ def model!(params); params.to_s end
31
+ end
32
+
33
+ it { Upsert.(id: 9)["model"].must_equal %{{:id=>9}} }
34
+
35
+ #---
36
+ # :find_by, exceptionless.
37
+ class Find < Trailblazer::Operation
38
+ self.| Model Song, :find_by
39
+ self.| :process
40
+
41
+ def process(*); self["x"] = true end
42
+ end
43
+
44
+ # can't find model.
45
+ #- result object, model
46
+ it do
47
+ Find.(id: nil)["result.model"].failure?.must_equal true
48
+ Find.(id: nil)["x"].must_equal nil
49
+ Find.(id: nil).failure?.must_equal true
50
+ end
51
+
52
+ #- result object, model
53
+ it do
54
+ Find.(id: 9)["result.model"].success?.must_equal true
55
+ Find.(id: 9)["x"].must_equal true
56
+ Find.(id: 9)["model"].inspect.must_equal %{#<struct ModelTest::Song id=9>}
57
+ end
58
+
59
+ #---
60
+ # override #model!, without any Model inclusions.
61
+ class Delete < Trailblazer::Operation
62
+ self.| Model :model!
63
+ def model!(params); params.to_s end
64
+ end
65
+
66
+ it { Delete.(id: 1)["model"].must_equal %{{:id=>1}} }
67
+
68
+ #---
69
+ # creating the model before operation instantiation (ex Model::External)
70
+ class Show < Create
71
+ extend Model::BuildMethods # FIXME: how do we communicate that and prevent the include from Model[] ?
72
+ self.| Model( Song, :update ), before: "operation.new"
73
+ end
74
+
75
+ it { Show.({id: 1})["model"].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
76
+
77
+
78
+ # TODO: with builder!
79
+
80
+ #---
81
+ # provide your own object ModelBuilder that includes BuilderMethods.
82
+ # this tests that BuildMethods is interchangable and acts as an example how to decouple
83
+ # the model building from the operation.
84
+ class Index < Trailblazer::Operation
85
+ # DISCUSS: help user to do this kind of behavior?
86
+ # model Song, :find # ModelBuilder can read this via skills that we pass to it.
87
+ self["model.class"] = Song
88
+ self["model.action"] = :find
89
+
90
+ # this is to be able to use BuildModel.
91
+ class ModelBuilder
92
+ include Trailblazer::Operation::Model::BuildMethods # #instantiate_model and so on.
93
+ alias_method :call, :model!
94
+
95
+ def initialize(skills); @delegator = skills end
96
+
97
+ extend Uber::Delegates
98
+ delegates :@delegator, :[]
99
+ end
100
+
101
+ self.> ->(options) { options["model"] = ModelBuilder.new(options).(options["params"]) }, after: "operation.new"
102
+ end
103
+
104
+ it { Index.(id: 1)["model"].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
105
+ end
@@ -0,0 +1,36 @@
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
@@ -0,0 +1,44 @@
1
+ require "test_helper"
2
+
3
+ class PersistTest < Minitest::Spec
4
+ Song = Struct.new(:title, :saved) do
5
+ def save; title=="Fail!" ? false : self.saved = true; end
6
+ end
7
+
8
+ class Create < Trailblazer::Operation
9
+ extend Contract::DSL
10
+ contract do
11
+ property :title
12
+ end
13
+
14
+ self.| Model( Song, :new )
15
+ self.| Contract::Build()
16
+ self.| Contract::Validate()
17
+ self.< ->(options) { options["1. fail"] = "Validate" }
18
+ self.| Persist()
19
+ self.< ->(options) { options["2. fail"] = "Persist" }
20
+ end
21
+
22
+ it { Create.(title: "In Recital")["model"].title.must_equal "In Recital" }
23
+ it { Create.(title: "In Recital")["model"].saved.must_equal true }
24
+ # failure
25
+ it do
26
+ result = Create.(title: "Fail!")
27
+ result["model"].saved.must_equal nil
28
+ result["model"].title.must_equal "Fail!"
29
+ result["2. fail"].must_equal "Persist"
30
+ result.success?.must_equal false
31
+ end
32
+
33
+ #---
34
+ #- inheritance
35
+ class Update < Create
36
+ end
37
+
38
+ it { Update["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&validate.params.extract,&contract.validate,<PersistTest::Update:17,&persist.save,<PersistTest::Update:19]} }
39
+
40
+ #---
41
+ it do
42
+ skip "show how save! could be applied and how we could rescue and deviate to left track"
43
+ end
44
+ end
@@ -0,0 +1,59 @@
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
+ self.| Model[ Song, :new ] # model!)
24
+ self.| Policy::Guard[ ->(options){ options["current_user"] == ::Module } ]
25
+ self.| Contract[ MyContract]
26
+ self.| Policy[ Auth, :user_and_model?]
27
+ self.< Contract[ MyContract]
28
+
29
+ # self.| :model
30
+ # self.| :guard
31
+ # self.| :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
+ # self.|> "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
@@ -0,0 +1,104 @@
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
+ self.& MyValidate, before: Call #replace: Contract::ValidLegacySwitch
46
+ #
47
+ MyAfterSave = ->(input, options) { input["after_save"] = true }
48
+ self.> MyAfterSave, after: MyValidate
49
+
50
+ ValidateFailureLogger = ->(input, options) { input["validate fail"] = true }
51
+ self.< ValidateFailureLogger, after: MyValidate
52
+
53
+ self.> ->(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
+ self.< 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