trailblazer-macro 2.1.15 → 2.1.16

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 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