boba 0.0.10 → 0.0.11

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: 647563ad7c660344d53748974e37859839f76947244d61f8c604dc3bbcd6714f
4
- data.tar.gz: b0cbad626310c0f1d00a8e495f42092db5a6ae68717bf1c72f9d43dfd406e8b3
3
+ metadata.gz: 0a299592dce0ee68ddd4ad96f0c6d35a665739102bfa02c8e5d3f3cf26ed0531
4
+ data.tar.gz: 71d4ebe6c98f11586fef81e8d7b8892adce83bb2fbeafa516369947cbcd0dd9e
5
5
  SHA512:
6
- metadata.gz: add06fdf5f157f4586dd34b49ecad83ce9ddddda62473fa3cf52cce6caae742436e7fb889bf9c0fb85bb7258310d8413f258228b972f632b77e70a2dafbc9dab
7
- data.tar.gz: 9537d9e2541a96b6c1c5f9e76d129cb22c0f9c17ce7b5d0fee94ecf2289a93d809a66a9fe429b95742a811c2a1029afda63063e25a5c4baff9519e5429bd4299
6
+ metadata.gz: b34ab72d0801c768da549880ad247e978538e29197c51ff09b8d0e3c6d18b23e4433a808948613b98f3621ed0f486cd5fc7f44dad2f0e4587aec1070bf14cb0a
7
+ data.tar.gz: 1fca80c8c8a75f2763e4682b4b3e5543af3700abe8b2bd3c0fcb097c749d9292b67375c4121fc0ec43c49502678e0c8ee5fd3da59f02ce930a39ec5a1971f302
data/lib/boba/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Boba
5
- VERSION = "0.0.10"
5
+ VERSION = "0.0.11"
6
6
  end
@@ -49,7 +49,9 @@ module Tapioca
49
49
 
50
50
  sig { override.void }
51
51
  def decorate
52
- attachments = ::Paperclip::AttachmentRegistry.names_for(constant)
52
+ # this is a bit awkward, but load order determines the return order here, so sort to ensure consistency across
53
+ # all environments.
54
+ attachments = ::Paperclip::AttachmentRegistry.names_for(constant).sort
53
55
  return if attachments.empty?
54
56
 
55
57
  root.create_path(constant) do |klass|
@@ -0,0 +1,385 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ return unless defined?(StateMachines)
5
+
6
+ require "tapioca/dsl/helpers/active_record_constants_helper"
7
+
8
+ module Tapioca
9
+ module Dsl
10
+ module Compilers
11
+ # `Tapioca::Dsl::Compilers::StateMachines` generates RBI files for classes that setup a
12
+ # [`state_machine`](https://github.com/state-machines/state_machines). The compiler also
13
+ # processes the extra methods generated by
14
+ # [StateMachines Active Record](https://github.com/state-machines/state_machines-activerecord)
15
+ # and [StateMachines Active Model](https://github.com/state-machines/state_machines-activemodel)
16
+ # integrations.
17
+ #
18
+ # For example, with the following `Vehicle` class:
19
+ #
20
+ # ~~~rb
21
+ # class Vehicle
22
+ # state_machine :alarm_state, initial: :active, namespace: :'alarm' do
23
+ # event :enable do
24
+ # transition all => :active
25
+ # end
26
+ #
27
+ # event :disable do
28
+ # transition all => :off
29
+ # end
30
+ #
31
+ # state :active, :value => 1
32
+ # state :off, :value => 0
33
+ # end
34
+ # end
35
+ # ~~~
36
+ #
37
+ # this compiler will produce the RBI file `vehicle.rbi` with the following content:
38
+ #
39
+ # ~~~rbi
40
+ # # vehicle.rbi
41
+ # # typed: true
42
+ # class Vehicle
43
+ # include StateMachineInstanceHelperModule
44
+ # extend StateMachineClassHelperModule
45
+ #
46
+ # module StateMachineClassHelperModule
47
+ # sig { params(event: T.any(String, Symbol)).returns(String) }
48
+ # def human_alarm_state_event_name(event); end
49
+ #
50
+ # sig { params(state: T.any(String, Symbol)).returns(String) }
51
+ # def human_alarm_state_name(state); end
52
+ # end
53
+ #
54
+ # module StateMachineInstanceHelperModule
55
+ # sig { returns(T::Boolean) }
56
+ # def alarm_active?; end
57
+ #
58
+ # sig { returns(T::Boolean) }
59
+ # def alarm_off?; end
60
+ #
61
+ # sig { returns(Integer) }
62
+ # def alarm_state; end
63
+ #
64
+ # sig { params(value: Integer).returns(Integer) }
65
+ # def alarm_state=(value); end
66
+ #
67
+ # sig { params(state: T.any(String, Symbol)).returns(T::Boolean) }
68
+ # def alarm_state?(state); end
69
+ #
70
+ # sig { params(args: T.untyped).returns(T::Array[T.any(String, Symbol)]) }
71
+ # def alarm_state_events(*args); end
72
+ #
73
+ # sig { returns(T.any(String, Symbol)) }
74
+ # def alarm_state_name; end
75
+ #
76
+ # sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
77
+ # def alarm_state_paths(*args); end
78
+ #
79
+ # sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
80
+ # def alarm_state_transitions(*args); end
81
+ #
82
+ # sig { returns(T::Boolean) }
83
+ # def can_disable_alarm?; end
84
+ #
85
+ # sig { returns(T::Boolean) }
86
+ # def can_enable_alarm?; end
87
+ #
88
+ # sig { params(args: T.untyped).returns(T::Boolean) }
89
+ # def disable_alarm(*args); end
90
+ #
91
+ # sig { params(args: T.untyped).returns(T::Boolean) }
92
+ # def disable_alarm!(*args); end
93
+ #
94
+ # sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
95
+ # def disable_alarm_transition(*args); end
96
+ #
97
+ # sig { params(args: T.untyped).returns(T::Boolean) }
98
+ # def enable_alarm(*args); end
99
+ #
100
+ # sig { params(args: T.untyped).returns(T::Boolean) }
101
+ # def enable_alarm!(*args); end
102
+ #
103
+ # sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
104
+ # def enable_alarm_transition(*args); end
105
+ #
106
+ # sig { params(event: T.any(String, Symbol), args: T.untyped).returns(T::Boolean) }
107
+ # def fire_alarm_state_event(event, *args); end
108
+ #
109
+ # sig { returns(String) }
110
+ # def human_alarm_state_name; end
111
+ # end
112
+ # end
113
+ # ~~~
114
+ class StateMachines < Compiler
115
+ extend T::Sig
116
+
117
+ ACTIVE_RECORD_RELATION_MODULE_NAMES = [
118
+ "GeneratedRelationMethods",
119
+ "GeneratedAssociationRelationMethods",
120
+ ].freeze
121
+
122
+ ConstantType = type_member { { fixed: T.all(Module, ::StateMachines::ClassMethods) } }
123
+
124
+ sig { override.void }
125
+ def decorate
126
+ return if constant.state_machines.empty?
127
+
128
+ root.create_path(T.unsafe(constant)) do |klass|
129
+ instance_module_name = "StateMachineInstanceHelperModule"
130
+ class_module_name = "StateMachineClassHelperModule"
131
+
132
+ instance_module = RBI::Module.new(instance_module_name)
133
+ klass << instance_module
134
+
135
+ class_module = RBI::Module.new(class_module_name)
136
+ klass << class_module
137
+
138
+ constant.state_machines.each_value do |machine|
139
+ state_type = state_type_for(machine)
140
+
141
+ define_state_accessor(instance_module, machine, state_type)
142
+ define_state_predicate(instance_module, machine)
143
+ define_event_helpers(instance_module, machine)
144
+ define_path_helpers(instance_module, machine)
145
+ define_name_helpers(instance_module, class_module, machine)
146
+ define_scopes(class_module, machine)
147
+
148
+ define_state_methods(instance_module, machine)
149
+ define_event_methods(instance_module, machine)
150
+ end
151
+
152
+ if uses_active_record_integration?(constant)
153
+ define_activerecord_methods(instance_module)
154
+
155
+ [
156
+ Tapioca::Dsl::Helpers::ActiveRecordConstantsHelper::RelationMethodsModuleName,
157
+ Tapioca::Dsl::Helpers::ActiveRecordConstantsHelper::AssociationRelationMethodsModuleName,
158
+ ].each do |module_name|
159
+ klass.create_module(module_name).create_include(class_module_name)
160
+ end
161
+ end
162
+
163
+ klass.create_include(instance_module_name)
164
+ klass.create_extend(class_module_name)
165
+ end
166
+ end
167
+
168
+ class << self
169
+ extend T::Sig
170
+
171
+ sig { override.returns(T::Enumerable[Module]) }
172
+ def gather_constants
173
+ all_classes.select { |mod| ::StateMachines::InstanceMethods > mod }
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ sig { params(constant: Module).returns(T::Boolean) }
180
+ def uses_active_record_integration?(constant)
181
+ ::StateMachines::Integrations.match(constant)&.integration_name == :active_record
182
+ end
183
+
184
+ sig { params(machine: ::StateMachines::Machine).returns(String) }
185
+ def state_type_for(machine)
186
+ value_types = machine.states.map { |state| state.value.class.name }.uniq
187
+
188
+ if value_types.size == 1
189
+ value_types.first
190
+ else
191
+ "T.any(#{value_types.join(", ")})"
192
+ end
193
+ end
194
+
195
+ sig { params(instance_module: RBI::Module).void }
196
+ def define_activerecord_methods(instance_module)
197
+ instance_module.create_method(
198
+ "changed_for_autosave?",
199
+ return_type: "T::Boolean",
200
+ )
201
+ end
202
+
203
+ sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
204
+ def define_state_methods(instance_module, machine)
205
+ machine.states.each do |state|
206
+ instance_module.create_method(
207
+ "#{state.qualified_name}?",
208
+ return_type: "T::Boolean",
209
+ )
210
+ end
211
+ end
212
+
213
+ sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
214
+ def define_event_methods(instance_module, machine)
215
+ machine.events.each do |event|
216
+ instance_module.create_method(
217
+ "can_#{event.qualified_name}?",
218
+ return_type: "T::Boolean",
219
+ )
220
+ instance_module.create_method(
221
+ "#{event.qualified_name}_transition",
222
+ parameters: [create_rest_param("args", type: "T.untyped")],
223
+ return_type: "T.nilable(::StateMachines::Transition)",
224
+ )
225
+ instance_module.create_method(
226
+ event.qualified_name.to_s,
227
+ parameters: [create_rest_param("args", type: "T.untyped")],
228
+ return_type: "T::Boolean",
229
+ )
230
+ instance_module.create_method(
231
+ "#{event.qualified_name}!",
232
+ parameters: [create_rest_param("args", type: "T.untyped")],
233
+ return_type: "T::Boolean",
234
+ )
235
+ end
236
+ end
237
+
238
+ sig do
239
+ params(
240
+ instance_module: RBI::Module,
241
+ machine: ::StateMachines::Machine,
242
+ state_type: String,
243
+ ).void
244
+ end
245
+ def define_state_accessor(instance_module, machine, state_type)
246
+ owner_class = machine.owner_class
247
+ attribute = machine.attribute.to_sym
248
+ attribute_name = machine.attribute.to_s
249
+
250
+ # AR classes don't define their attributes until the DB is hit and they're fully loaded, so force it here if
251
+ # needed
252
+ if !owner_class.instance_methods.include?(attribute) && owner_class.respond_to?(:define_attribute_methods)
253
+ owner_class.define_attribute_methods
254
+ end
255
+
256
+ if owner_class.instance_methods.include?(attribute)
257
+ instance_module.create_method(
258
+ attribute_name,
259
+ return_type: state_type,
260
+ ) if ::StateMachines::HelperModule === owner_class.instance_method(attribute).owner
261
+ instance_module.create_method(
262
+ "#{attribute_name}=",
263
+ parameters: [create_param("value", type: state_type)],
264
+ return_type: state_type,
265
+ ) if ::StateMachines::HelperModule === owner_class.instance_method("#{attribute}=").owner
266
+ end
267
+ end
268
+
269
+ sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
270
+ def define_state_predicate(instance_module, machine)
271
+ instance_module.create_method(
272
+ "#{machine.name}?",
273
+ parameters: [create_param("state", type: "T.any(String, Symbol)")],
274
+ return_type: "T::Boolean",
275
+ )
276
+ end
277
+
278
+ sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
279
+ def define_event_helpers(instance_module, machine)
280
+ events_attribute = machine.attribute(:events).to_s
281
+ transitions_attribute = machine.attribute(:transitions).to_s
282
+ event_attribute = machine.attribute(:event).to_s
283
+ event_transition_attribute = machine.attribute(:event_transition).to_s
284
+
285
+ instance_module.create_method(
286
+ events_attribute,
287
+ parameters: [create_rest_param("args", type: "T.untyped")],
288
+ return_type: "T::Array[T.any(String, Symbol)]",
289
+ )
290
+ instance_module.create_method(
291
+ transitions_attribute,
292
+ parameters: [create_rest_param("args", type: "T.untyped")],
293
+ return_type: "T::Array[::StateMachines::Transition]",
294
+ )
295
+ instance_module.create_method(
296
+ "fire_#{event_attribute}",
297
+ parameters: [
298
+ create_param("event", type: "T.any(String, Symbol)"),
299
+ create_rest_param("args", type: "T.untyped"),
300
+ ],
301
+ return_type: "T::Boolean",
302
+ )
303
+ if machine.action
304
+ instance_module.create_method(
305
+ event_attribute,
306
+ return_type: "T.nilable(Symbol)",
307
+ )
308
+ instance_module.create_method(
309
+ "#{event_attribute}=",
310
+ parameters: [create_param("value", type: "T.any(String, Symbol)")],
311
+ return_type: "T.any(String, Symbol)",
312
+ )
313
+ instance_module.create_method(
314
+ event_transition_attribute,
315
+ return_type: "T.nilable(::StateMachines::Transition)",
316
+ )
317
+ instance_module.create_method(
318
+ "#{event_transition_attribute}=",
319
+ parameters: [create_param("value", type: "::StateMachines::Transition")],
320
+ return_type: "::StateMachines::Transition",
321
+ )
322
+ end
323
+ end
324
+
325
+ sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
326
+ def define_path_helpers(instance_module, machine)
327
+ paths_attribute = machine.attribute(:paths).to_s
328
+
329
+ instance_module.create_method(
330
+ paths_attribute,
331
+ parameters: [create_rest_param("args", type: "T.untyped")],
332
+ return_type: "T::Array[::StateMachines::Transition]",
333
+ )
334
+ end
335
+
336
+ sig do
337
+ params(
338
+ instance_module: RBI::Module,
339
+ class_module: RBI::Module,
340
+ machine: ::StateMachines::Machine,
341
+ ).void
342
+ end
343
+ def define_name_helpers(instance_module, class_module, machine)
344
+ name_attribute = machine.attribute(:name).to_s
345
+ event_name_attribute = machine.attribute(:event_name).to_s
346
+
347
+ class_module.create_method(
348
+ "human_#{name_attribute}",
349
+ parameters: [create_param("state", type: "T.any(String, Symbol)")],
350
+ return_type: "String",
351
+ )
352
+ class_module.create_method(
353
+ "human_#{event_name_attribute}",
354
+ parameters: [create_param("event", type: "T.any(String, Symbol)")],
355
+ return_type: "String",
356
+ )
357
+ instance_module.create_method(
358
+ name_attribute,
359
+ return_type: "T.any(String, Symbol)",
360
+ )
361
+ instance_module.create_method(
362
+ "human_#{name_attribute}",
363
+ return_type: "String",
364
+ )
365
+ end
366
+
367
+ sig { params(class_module: RBI::Module, machine: ::StateMachines::Machine).void }
368
+ def define_scopes(class_module, machine)
369
+ helper_modules = machine.instance_variable_get(:@helper_modules)
370
+ class_methods = helper_modules[:class].instance_methods(false)
371
+
372
+ class_methods
373
+ .select { |method| method.to_s.start_with?("with_", "without_") }
374
+ .each do |method|
375
+ class_module.create_method(
376
+ method.to_s,
377
+ parameters: [create_rest_param("states", type: "T.any(String, Symbol)")],
378
+ return_type: "T.untyped",
379
+ )
380
+ end
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Angellist
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-27 00:00:00.000000000 Z
11
+ date: 2024-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sorbet-static-and-runtime
@@ -57,15 +57,15 @@ files:
57
57
  - lib/tapioca/dsl/compilers/attr_json.rb
58
58
  - lib/tapioca/dsl/compilers/money_rails.rb
59
59
  - lib/tapioca/dsl/compilers/paperclip.rb
60
- - lib/tapioca/dsl/compilers/state_machines_extended.rb
60
+ - lib/tapioca/dsl/compilers/state_machines.rb
61
61
  homepage: https://github.com/angellist/boba
62
62
  licenses:
63
63
  - MIT
64
64
  metadata:
65
65
  bug_tracker_uri: https://github.com/angellist/boba/issues
66
- changelog_uri: https://github.com/angellist/boba/blob/0.0.10/History.md
66
+ changelog_uri: https://github.com/angellist/boba/blob/0.0.11/History.md
67
67
  homepage_uri: https://github.com/angellist/boba
68
- source_code_uri: https://github.com/angellist/boba/tree/0.0.10
68
+ source_code_uri: https://github.com/angellist/boba/tree/0.0.11
69
69
  rubygems_mfa_required: 'true'
70
70
  post_install_message:
71
71
  rdoc_options: []
@@ -1,44 +0,0 @@
1
- # typed: ignore
2
- # frozen_string_literal: true
3
-
4
- require "tapioca/dsl/compilers/state_machines"
5
-
6
- return unless defined?(Tapioca::Dsl::Compilers::StateMachines)
7
-
8
- module Tapioca
9
- module Dsl
10
- module Compilers
11
- # `Tapioca::Dsl::Compilers::StateMachinesExtended` extends the default state machines compiler provided by Tapioca
12
- # to allow for calling `with_state` and `without_state` on all Active Record relations. This is a temporary fix
13
- # until a more durable solution can be found for this type of issue.
14
- # See https://github.com/Shopify/tapioca/pull/1994#issuecomment-2302624697.
15
- class StateMachinesExtended < ::Tapioca::Dsl::Compilers::StateMachines
16
- ACTIVE_RECORD_RELATION_MODULE_NAMES = [
17
- "GeneratedRelationMethods",
18
- "GeneratedAssociationRelationMethods",
19
- ].freeze
20
-
21
- def decorate
22
- # This should really get checked at the gather_constants level but here we are...
23
- return if T::AbstractUtils.abstract_module?(constant)
24
- return if constant.state_machines.empty?
25
-
26
- # This is a hack to make sure the instance methods are defined on the constant. Somehow the constant is being
27
- # loaded but the actual `state_machine` call is not being executed, so the instance methods don't exist yet.
28
- # Instantiating an empty class fixes it.
29
- constant.try(:new)
30
-
31
- super()
32
-
33
- root.create_path(T.unsafe(constant)) do |klass|
34
- class_module_name = "StateMachineClassHelperModule"
35
-
36
- ACTIVE_RECORD_RELATION_MODULE_NAMES.each do |module_name|
37
- klass.create_module(module_name).create_include(class_module_name)
38
- end
39
- end
40
- end
41
- end
42
- end
43
- end
44
- end