trailblazer-macro 2.1.15 → 2.1.16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 173e4338e28a9317e1d4982e9cfa9c2662b1639f852c63e17d389d8aac1dbf6f
4
- data.tar.gz: cfe156150849b2114783ae54799dbc6a9cc1aaf98ee94a076cf2236855c1aebd
3
+ metadata.gz: 2de3e28697d847ca2ffb3f49b595955c58a6a4495f0896b4b280016094115507
4
+ data.tar.gz: 91ccc278f803d068dc21b25dd02e8e3dc8d2842931309e58dcee21163667b7c2
5
5
  SHA512:
6
- metadata.gz: b5d91f20bf7ea34b3328a7cb2419dc3d3df264e998605f887afee09379bc10a41222016f0f63c0512f5caab2236b8528997096baf3b4ec0f17ae2b4b8bb25303
7
- data.tar.gz: 3a80eb7b70c7c8d9667bad7571c7d8471dc7bfc28ad00fdb40f94ceb5dc4fcae1de7ffae9a4d76848c61526f06cb2d0d85c612e2a5d01f2acb849ed28c1821f0
6
+ metadata.gz: 58028a68a5cdc8c0da14209657b4e7b7aed56543f8022c58622db04dddb2a21fe5b42ded72c8ab38deedb76e34e6e027250b76bf5031a17e1af7d22a0f540706
7
+ data.tar.gz: 8803eb8c3640359c889c7eed9eb9e78bbd362f06edd9f28bdf0dc47690956d7f70f7cb128ae0fe7bcaa7a4301eeda8833b119218f8ce0ee5d1ffd526d96df1f1
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 2.1.16
2
+
3
+ * Fix a bug in `patch` where `Subprocess()` was missing in `Macro::Strategy`.
4
+ * Introduce `Model::Build` and `Model::Find` macros, which are a replacement for the less
5
+ customizable `Model()`. See https://trailblazer.to/2.1/docs/macro/#macro-model-model-find
6
+
1
7
  # 2.1.15
2
8
 
3
9
  * Use `Macro.id_for` for `Rescue`, meaning IDs will now be consistent à la `Rescue/1` instead of `Rescue(1)`.
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gemspec
9
9
  # gem "trailblazer-activity-dsl-linear", github: "trailblazer/trailblazer-activity-dsl-linear"
10
10
  # gem "trailblazer-macro-contract", git: "https://github.com/trailblazer/trailblazer-macro-contract"
11
11
 
12
- # gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
12
+ gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
13
13
  # gem "trailblazer-developer", path: "../trailblazer-developer"
14
14
  # gem "trailblazer-activity", path: "../trailblazer-activity"
15
15
  gem "trailblazer-operation", path: "../trailblazer-operation"
@@ -0,0 +1,175 @@
1
+ module Trailblazer
2
+ module Macro
3
+ class Model
4
+ # New API for retrieving models by ID.
5
+ # Only handles keyword argument style.
6
+ #
7
+ #
8
+ # DESIGN NOTES
9
+ # * params[:id] extraction and the actual query are two separate components in the final finder activity.
10
+ def self.Find(model_class, positional_method = nil, find_method: nil, id: "model.find", not_found_terminus: false, query: nil, **keyword_options, &block)
11
+ # 1. optional: translate kws/positionals into local kws
12
+ # 2. build :query
13
+ # 3. build find activity
14
+
15
+ # raise "unknown options #{keyword_options}" if keyword_options.size > 1
16
+
17
+ params_key, block, finder_step_options =
18
+ if positional_method
19
+ for_explicit_positional(model_class, positional_method, **keyword_options, &block)
20
+ elsif find_method.nil? && query.nil? # translate_from_shorthand
21
+ for_shorthand(model_class, **keyword_options, &block)
22
+ else # options passed explicitly, kws. this still means we need to translate find_method to query, or use user's query.
23
+ # TODO: sort out query: default it or take user's
24
+
25
+ if query.nil?
26
+ for_keywords(model_class, find_method: find_method, **keyword_options, &block)
27
+ else
28
+ # raise "IMPLEMENT ME"
29
+ for_query(model_class, query, **keyword_options, &block)
30
+ end
31
+ end
32
+
33
+ task = finder_activity_for(
34
+ params_key: params_key,
35
+ finder: finder_step_options,
36
+ &block
37
+ )
38
+
39
+ options = options_for(task, id: id)
40
+
41
+ options = options.merge(Activity::Railway.Output(:failure) => Activity::Railway.End(:not_found)) if not_found_terminus
42
+
43
+ options
44
+ end
45
+
46
+ # Defaulting happening.
47
+ def self.normalize_keys(column_key: :id, params_key: column_key, **)
48
+ return params_key, column_key
49
+ end
50
+
51
+ def self.for_shorthand(model_class, **options, &block)
52
+ # translate shorthand form.
53
+ find_method_name, column_key = options.to_a[0]
54
+
55
+ params_key = options.key?(:params_key) ? options[:params_key] : column_key # TODO: use method for this.
56
+
57
+ [
58
+ params_key,
59
+ block,
60
+ Find::KeywordArguments.new(model_class: model_class, find_method: find_method_name, column_key: column_key),
61
+ ]
62
+ end
63
+
64
+ def self.for_explicit_positional(model_class, positional_method, **options, &block)
65
+ params_key, _ = normalize_keys(**options)
66
+
67
+ [
68
+ params_key,
69
+ block,
70
+ Find::Positional.new(model_class: model_class, find_method: positional_method), # query
71
+ ]
72
+ end
73
+
74
+ def self.for_keywords(model_class, find_method:, **options, &block) # FIXME: defaulting is redundant with bla_explicit_positional.
75
+ params_key, column_key = normalize_keys(**options)
76
+
77
+ finder = Find::KeywordArguments.new(model_class: model_class, find_method: find_method, column_key: column_key)
78
+
79
+ [params_key, block, finder]
80
+ end
81
+
82
+ def self.for_query(model_class, query, column_key: :id, params_key: column_key, **, &block) # FIXME: defaulting is redundant with bla_explicit_positional.
83
+ query_on_model_class = ->(ctx, **kws) { model_class.instance_exec(ctx, **kws, &query) } # FIXME: we can only use procs here. what about methods, classes etc?
84
+
85
+ finder = Macro.task_adapter_for_decider(query_on_model_class, variable_name: :model) # FIXME: {:model} is hard-coded.
86
+
87
+ [
88
+ params_key,
89
+ block,
90
+ {task: finder} # circuit interface for the Task::Adapter.
91
+ ]
92
+ end
93
+
94
+ def self.options_for(task, id:)
95
+ options = Activity::Railway.Subprocess(task).merge(id: id)
96
+
97
+ inject = {
98
+ Activity::Railway.Inject() => [:params], # pass-through {:params} if it's in ctx.
99
+ }
100
+
101
+ out = { # TODO: use Outject once it is implemented.
102
+ Activity::Railway.Out() => ->(ctx, **) { ctx.key?(:model) ? {model: ctx[:model]} : {} }
103
+ }
104
+
105
+ options = options.merge(inject)
106
+ options = options.merge(out)
107
+ end
108
+
109
+ # Finder activity consists of two steps:
110
+ # {extract_id}, and the finder code.
111
+ #
112
+ # |-- model.build
113
+ # | |-- Start.default
114
+ # | |-- extract_id
115
+ # | |-- finder.Trailblazer::Macro::Model::Find::Positional
116
+ # | `-- End.success
117
+ # |-- validate
118
+ def self.finder_activity_for(params_key:, finder:, **, &block)
119
+ id_from =
120
+ if block
121
+ block
122
+ else
123
+ ->(ctx, params: {}, **) { params[params_key] } # default id_from
124
+ end
125
+
126
+ extract_id = Macro.task_adapter_for_decider(id_from, variable_name: :id)
127
+
128
+ Class.new(Activity::Railway) do
129
+ step task: extract_id, id: :extract_id
130
+ step finder, id: "finder.#{finder.class}" # FIXME: discuss ID.
131
+ end
132
+ end
133
+
134
+ # Runtime code.
135
+ module Find
136
+ class Positional
137
+ def initialize(model_class:, find_method:)
138
+ @model_class = model_class
139
+ @find_method = find_method
140
+ end
141
+
142
+ def call(ctx, id:, **)
143
+ ctx[:model] = @model_class.send(@find_method, id)
144
+ end
145
+ end
146
+
147
+ class KeywordArguments
148
+ def initialize(model_class:, find_method:, column_key:)
149
+ @model_class = model_class
150
+ @find_method = find_method
151
+ @column_key = column_key.to_sym
152
+ end
153
+
154
+ def call(ctx, id:, **)
155
+ ctx[:model] = @model_class.send(@find_method, @column_key => id)
156
+ end
157
+ end
158
+
159
+ class NoArgument < Positional
160
+ def call(ctx, **)
161
+ ctx[:model] = @model_class.send(@find_method)
162
+ end
163
+ end
164
+ end # Find
165
+
166
+ def self.Build(model_class, method = :new, id: "model.build")
167
+ activity = Class.new(Activity::Railway) do
168
+ step Find::NoArgument.new(model_class: model_class, find_method: method)
169
+ end
170
+
171
+ options_for(activity, id: id)
172
+ end
173
+ end
174
+ end
175
+ end
@@ -38,7 +38,7 @@ module Trailblazer
38
38
  # TODO: for legacy reasons, we pass `:id` to {#id_for}. In 2.2, remove the id hint and use
39
39
  # generic {Macro.id_for} behavior.
40
40
  def self.random_id
41
- Macro.id_for(nil, macro: :Rescue, id: rand(100))
41
+ Macro.id_for(nil, macro: :Rescue, id: rand(1000))
42
42
  end
43
43
  end
44
44
  end
@@ -4,7 +4,7 @@ module Trailblazer
4
4
  class Strategy # We want to look like a real {Linear::Strategy}.
5
5
  class << self
6
6
  extend Forwardable
7
- def_delegators :block_activity, :step, :pass, :fail # TODO: add all DSL::Helper
7
+ def_delegators :block_activity, :step, :pass, :fail, :Subprocess # TODO: add all DSL::Helper
8
8
  end
9
9
 
10
10
  # This makes {Wrap} look like {block_activity}.
@@ -1,7 +1,7 @@
1
1
  module Trailblazer
2
2
  module Version
3
3
  module Macro
4
- VERSION = "2.1.15"
4
+ VERSION = "2.1.16"
5
5
  end
6
6
  end
7
7
  end
@@ -4,6 +4,7 @@ require "trailblazer/operation" # TODO: remove this dependency
4
4
 
5
5
  require "trailblazer/macro/strategy"
6
6
  require "trailblazer/macro/model"
7
+ require "trailblazer/macro/model/find"
7
8
  require "trailblazer/macro/policy"
8
9
  require "trailblazer/macro/guard"
9
10
  require "trailblazer/macro/pundit"
@@ -79,6 +80,7 @@ module Trailblazer
79
80
 
80
81
  module Activity::DSL::Linear::Helper
81
82
  Constants::Policy = Trailblazer::Macro::Policy
83
+ Constants::Model = Trailblazer::Macro::Model
82
84
 
83
85
  # Extending the {Linear::Helper} namespace is the canonical way to import
84
86
  # macros into Railway, FastTrack, Operation, etc.
@@ -190,6 +190,7 @@ class EachTest < Minitest::Spec
190
190
 
191
191
  it "{item_key: :composer}" do
192
192
  E::Mailer.send_options = []
193
+
193
194
  assert_invoke E::Song::Operation::Cover, params: {id: 1},
194
195
  expected_ctx_variables: {
195
196
  model: B::Song.find_by(id: 1),
@@ -287,6 +288,7 @@ class EachTest < Minitest::Spec
287
288
 
288
289
  it "Each(Activity::Railway)" do
289
290
  D::Mailer.send_options = []
291
+
290
292
  assert_invoke D::Song::Operation::Cover, params: {id: 1},
291
293
  seq: "[:rearrange]",
292
294
  expected_ctx_variables: {
@@ -113,6 +113,7 @@ class DocsModelFindByTitleTest < Minitest::Spec
113
113
  it do
114
114
  #:key-title-fail
115
115
  result = Song::Operation::Update.(params: {title: nil}, seq: [])
116
+
116
117
  assert_equal result[:model].inspect, %{nil}
117
118
  #:key-title-fail end
118
119
  end
@@ -204,6 +205,7 @@ class DocsModelEmptyDITest < Minitest::Spec
204
205
 
205
206
  it do
206
207
  result = Song::Operation::Create.(params: {}, :"model.class" => Hit, seq: [])
208
+
207
209
  assert_equal result[:model].inspect, %{#<struct #{Hit} id=nil, title=nil>}
208
210
  end
209
211
  end
@@ -224,15 +226,16 @@ class DocsModelIOTest < Minitest::Spec
224
226
  #:in end
225
227
 
226
228
  result = Song::Operation::Create.(params: {}, my_id: 1, :"model.class" => Hit)
229
+
227
230
  assert_equal result[:model].inspect, %{#<struct #{Hit} id=1, title=nil>}
228
231
  =begin
229
232
  #:in-call
230
233
  result = Create.(my_id: 1)
231
234
  #:in-call end
232
235
  =end
233
- end
234
236
  end
235
237
  end
238
+ end
236
239
 
237
240
  class Model404TerminusTest < Minitest::Spec
238
241
  Song = Class.new(DocsModelTest::Song)
@@ -191,6 +191,7 @@ class EachTest < Minitest::Spec
191
191
 
192
192
  it "{item_key: :composer}" do
193
193
  E::Mailer.send_options = []
194
+
194
195
  assert_invoke E::Song::Activity::Cover, params: {id: 1},
195
196
  expected_ctx_variables: {
196
197
  model: B::Song.find_by(id: 1),
@@ -288,6 +289,7 @@ class EachTest < Minitest::Spec
288
289
 
289
290
  it "Each(Activity::Railway)" do
290
291
  D::Mailer.send_options = []
292
+
291
293
  assert_invoke D::Song::Activity::Cover, params: {id: 1},
292
294
  seq: "[:rearrange]",
293
295
  expected_ctx_variables: {
@@ -600,7 +602,7 @@ class EachStrategyComplianceTest < Minitest::Spec
600
602
  cover_patched.include(T.def_steps(:log_email, :notify_composers))
601
603
 
602
604
  #@ Original class isn't changed.
603
- assert_invoke Song::Activity::Cover, params: {id: 1}, seq: [],
605
+ assert_invoke Song::Activity::Cover, params: {id: 1},
604
606
  expected_ctx_variables: {
605
607
  model: Song.find_by(id: 1),
606
608
  },
@@ -608,7 +610,7 @@ class EachStrategyComplianceTest < Minitest::Spec
608
610
 
609
611
  #@ Patched class runs
610
612
  # Trailblazer::Developer.wtf?(cover_patched, [params: {id: 1}, seq: []])
611
- assert_invoke cover_patched, params: {id: 1}, seq: [],
613
+ assert_invoke cover_patched, params: {id: 1},
612
614
  expected_ctx_variables: {
613
615
  model: Song.find_by(id: 1),
614
616
  },
@@ -789,6 +791,7 @@ class DocsEachUnitTest < Minitest::Spec
789
791
 
790
792
  # signal, (_ctx, _) = Trailblazer::Activity::TaskWrap.invoke(activity, [ctx])
791
793
  signal, (_ctx, _) = Trailblazer::Developer.wtf?(activity, [ctx], exec_context: my_exec_context)
794
+
792
795
  assert_equal _ctx[:collected_from_each], ["1-0", "2-1", "3-2"]
793
796
  end
794
797
 
@@ -826,6 +829,7 @@ class DocsEachUnitTest < Minitest::Spec
826
829
  },
827
830
  {}]
828
831
  )
832
+
829
833
  assert_invoke activity, dataset: ["one", "two", "three"], current_user: Object, expected_ctx_variables: {collected_from_each: ["one-0-Object", "two-1-Object", "three-2-Object"]}
830
834
  end
831
835
 
@@ -901,6 +905,7 @@ class DocsEachUnitTest < Minitest::Spec
901
905
 
902
906
  #@ fail at 3 but still collect 3rd iteration!
903
907
  Trailblazer::Developer.wtf?(activity, [{dataset: [1,2,3]}, {}])
908
+
904
909
  assert_invoke activity, dataset: [1,2,3],
905
910
  expected_ctx_variables: {collected_from_each: ["1", "2", "3"]},
906
911
  seq: "[]",
@@ -16,12 +16,13 @@ class DocsGuardProcTest < Minitest::Spec
16
16
  end
17
17
  #:proc end
18
18
 
19
- it { Create.(pass: false)[:x].must_be_nil }
20
- it { Create.(pass: true)[:x].must_equal true }
19
+ it { assert_nil Create.(pass: false)[:x] }
20
+
21
+ it { assert_equal Create.(pass: true)[:x], true }
21
22
 
22
23
  #- result object, guard
23
- it { Create.(pass: true)[:"result.policy.default"].success?.must_equal true }
24
- it { Create.(pass: false)[:"result.policy.default"].success?.must_equal false }
24
+ it { assert_equal Create.(pass: true)[:"result.policy.default"].success?, true }
25
+ it { assert_equal Create.(pass: false)[:"result.policy.default"].success?, false }
25
26
 
26
27
  #---
27
28
  #- Guard inheritance
@@ -29,7 +30,7 @@ class DocsGuardProcTest < Minitest::Spec
29
30
  step Policy::Guard( ->(options, current_user:, **) { current_user } ), override: true
30
31
  end
31
32
 
32
- it { Trailblazer::Developer.railway(New).must_equal %{[>policy.default.eval,>process]} }
33
+ it { assert_equal Trailblazer::Developer.railway(New), %{[>policy.default.eval,>process]} }
33
34
  end
34
35
 
35
36
  #---
@@ -58,8 +59,9 @@ class DocsGuardTest < Minitest::Spec
58
59
  end
59
60
  #:callable-op end
60
61
 
61
- it { Create.(pass: false)[:x].must_be_nil }
62
- it { Create.(pass: true)[:x].must_equal true }
62
+ it { assert_nil Create.(pass: false)[:x] }
63
+
64
+ it { assert_equal Create.(pass: true)[:x], true }
63
65
  end
64
66
 
65
67
  #---
@@ -83,8 +85,8 @@ class DocsGuardMethodTest < Minitest::Spec
83
85
  end
84
86
  #:method end
85
87
 
86
- it { Create.(pass: false).inspect(:x).must_equal %{<Result:false [nil] >} }
87
- it { Create.(pass: true).inspect(:x).must_equal %{<Result:true [true] >} }
88
+ it { assert_equal Create.(pass: false).inspect(:x), %{<Result:false [nil] >} }
89
+ it { assert_equal Create.(pass: true).inspect(:x), %{<Result:true [true] >} }
88
90
  end
89
91
 
90
92
  #---
@@ -97,8 +99,8 @@ class DocsGuardNamedTest < Minitest::Spec
97
99
  end
98
100
  #:name end
99
101
 
100
- it { Create.(:current_user => nil )[:"result.policy.user"].success?.must_equal false }
101
- it { Create.(:current_user => Module)[:"result.policy.user"].success?.must_equal true }
102
+ it { assert_equal Create.(:current_user => nil )[:"result.policy.user"].success?, false }
103
+ it { assert_equal Create.(:current_user => Module)[:"result.policy.user"].success?, true }
102
104
 
103
105
  it {
104
106
  #:name-result
@@ -117,7 +119,7 @@ class DocsGuardInjectionTest < Minitest::Spec
117
119
  end
118
120
  #:di-op end
119
121
 
120
- it { Create.(:current_user => Module).inspect("").must_equal %{<Result:true [nil] >} }
122
+ it { assert_equal Create.(:current_user => Module).inspect(""), %{<Result:true [nil] >} }
121
123
  it {
122
124
  result =
123
125
  #:di-call
@@ -126,7 +128,7 @@ class DocsGuardInjectionTest < Minitest::Spec
126
128
  :"policy.default.eval" => Trailblazer::Operation::Policy::Guard.build(->(options, **) { false })
127
129
  )
128
130
  #:di-call end
129
- result.inspect("").must_equal %{<Result:false [nil] >} }
131
+ assert_equal result.inspect(""), %{<Result:false [nil] >} }
130
132
  end
131
133
 
132
134
  #---
@@ -137,7 +139,7 @@ class DocsGuardMissingKeywordTest < Minitest::Spec
137
139
  end
138
140
 
139
141
  it { assert_raises(ArgumentError) { Create.() } }
140
- it { Create.(:current_user => Module).success?.must_equal true }
142
+ it { assert_equal Create.(:current_user => Module).success?, true }
141
143
  end
142
144
 
143
145
  #---
@@ -151,7 +153,7 @@ class DocsGuardPositionTest < Minitest::Spec
151
153
  end
152
154
  #:before end
153
155
 
154
- it { Trailblazer::Developer.railway(Create).must_equal %{[>policy.default.eval,>model!]} }
156
+ it { assert_equal Trailblazer::Developer.railway(Create), %{[>policy.default.eval,>model!]} }
155
157
  it do
156
158
  #:before-pipe
157
159
  Trailblazer::Developer.railway(Create, style: :rows) #=>
@@ -28,7 +28,7 @@ class DocsMacroTest < Minitest::Spec
28
28
  end
29
29
  =end
30
30
 
31
- it { Trailblazer::Developer.railway(Create).must_equal %{[>my_policy.manager]} }
31
+ it { assert_equal Trailblazer::Developer.railway(Create), %{[>my_policy.manager]} }
32
32
  end
33
33
 
34
34