Class: Tapioca::Dsl::Compilers::AASM

Inherits:
Tapioca::Dsl::Compiler show all
Extended by:
T::Sig
Defined in:
lib/tapioca/dsl/compilers/aasm.rb

Overview

‘Tapioca::Dsl::Compilers::AASM` generate types for AASM state machines. This gem dynamically defines constants and methods at runtime. For example, given a class:

class MyClass
  include AASM

  aasm do
    state :sleeping, initial: true
    state :running, :cleaning

    event :run do
      transitions from: :sleeping, to: :running
    end
  end
end

This will result in the following constants being defined:

STATE_SLEEPING, STATE_RUNNING, STATE_CLEANING

and the following methods being defined:

sleeping?, running?, cleaning?
run, run!, run_without_validation!, may_run?

Constant Summary collapse

EVENT_CALLBACKS =

Taken directly from the AASM::Core::Event class, here: github.com/aasm/aasm/blob/0e03746/lib/aasm/core/event.rb#L21-L29

T.let(
  [
    "after",
    "after_commit",
    "after_transaction",
    "before",
    "before_transaction",
    "ensure",
    "error",
    "before_success",
    "success",
  ].freeze,
  T::Array[String],
)
GLOBAL_CALLBACKS =
T.let(
  [
    "after_all_transitions",
    "after_all_transactions",
    "before_all_transactions",
    "before_all_events",
    "after_all_events",
    "error_on_all_events",
    "ensure_on_all_events",
  ].freeze,
  T::Array[String],
)
TRANSITION_CALLBACKS =
T.let(
  [
    "on_transition",
    "guard",
    "after",
    "success",
  ].freeze,
  T::Array[String],
)
ConstantType =
type_member { { fixed: T.all(T::Class[::AASM], ::AASM::ClassMethods) } }

Constants included from Runtime::Reflection

Runtime::Reflection::ANCESTORS_METHOD, Runtime::Reflection::CLASS_METHOD, Runtime::Reflection::CONSTANTS_METHOD, Runtime::Reflection::EQUAL_METHOD, Runtime::Reflection::METHOD_METHOD, Runtime::Reflection::NAME_METHOD, Runtime::Reflection::OBJECT_ID_METHOD, Runtime::Reflection::PRIVATE_INSTANCE_METHODS_METHOD, Runtime::Reflection::PROTECTED_INSTANCE_METHODS_METHOD, Runtime::Reflection::PUBLIC_INSTANCE_METHODS_METHOD, Runtime::Reflection::REQUIRED_FROM_LABELS, Runtime::Reflection::SINGLETON_CLASS_METHOD, Runtime::Reflection::SUPERCLASS_METHOD, Runtime::Reflection::UNDEFINED_CONSTANT

Constants included from SorbetHelper

SorbetHelper::FEATURE_REQUIREMENTS, SorbetHelper::SORBET_BIN, SorbetHelper::SORBET_EXE_PATH_ENV_VAR, SorbetHelper::SORBET_GEM_SPEC, SorbetHelper::SORBET_PAYLOAD_URL, SorbetHelper::SPOOM_CONTEXT

Instance Attribute Summary

Attributes inherited from Tapioca::Dsl::Compiler

#constant, #options, #root

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Tapioca::Dsl::Compiler

#add_error, #compiler_enabled?, handles?, #initialize, processable_constants, requested_constants=, reset_state

Methods included from T::Generic::TypeStoragePatch

#[], #has_attached_class!, #type_member, #type_template

Methods included from Runtime::Reflection

#abstract_type_of, #ancestors_of, #are_equal?, #class_of, #constant_defined?, #constantize, #constants_of, #descendants_of, #file_candidates_for, #final_module?, #inherited_ancestors_of, #method_of, #name_of, #name_of_type, #object_id_of, #private_instance_methods_of, #protected_instance_methods_of, #public_instance_methods_of, #qualified_name_of, #resolve_loc, #sealed_module?, #signature_of, #signature_of!, #singleton_class_of, #superclass_of

Methods included from Runtime::AttachedClassOf

#attached_class_of

Methods included from RBIHelper

#as_nilable_type, #as_non_nilable_type, #create_block_param, #create_kw_opt_param, #create_kw_param, #create_kw_rest_param, #create_opt_param, #create_param, #create_rest_param, #create_typed_param, #sanitize_signature_types, serialize_type_variable, #valid_method_name?, #valid_parameter_name?

Methods included from SorbetHelper

#sorbet, #sorbet_path, #sorbet_supports?

Constructor Details

This class inherits a constructor from Tapioca::Dsl::Compiler

Class Method Details

.gather_constantsObject



218
219
220
# File 'lib/tapioca/dsl/compilers/aasm.rb', line 218

def gather_constants
  T.cast(ObjectSpace.each_object(::AASM::ClassMethods), T::Enumerable[Module])
end

Instance Method Details

#decorateObject



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/tapioca/dsl/compilers/aasm.rb', line 86

def decorate
  state_machine_store = ::AASM::StateMachineStore.fetch(constant)
  return unless state_machine_store

  state_machines = state_machine_store.machine_names.map { |n| constant.aasm(n) }
  return if state_machines.all? { |m| m.states.empty? }

  root.create_path(constant) do |model|
    state_machines.each do |state_machine|
      namespace = state_machine.__send__(:namespace)

      # Create all of the constants and methods for each state
      state_machine.states.each do |state|
        name = state.name
        name = "#{namespace}_#{name}" if namespace

        model.create_constant("STATE_#{name.upcase}", value: "T.let(T.unsafe(nil), Symbol)")
        model.create_method("#{name}?", return_type: "T::Boolean")
      end

      # Create all of the methods for each event
      parameters = [create_rest_param("opts", type: "T.untyped")]
      state_machine.events.each do |event|
        model.create_method(event.name.to_s, parameters: parameters)
        model.create_method("#{event.name}!", parameters: parameters)
        model.create_method("#{event.name}_without_validation!", parameters: parameters)
        model.create_method("may_#{event.name}?", return_type: "T::Boolean")

        # For events, if there's a namespace the default methods are created in addition to
        # namespaced ones.
        next unless namespace

        name = "#{event.name}_#{namespace}"

        model.create_method(name.to_s, parameters: parameters)
        model.create_method("#{name}!", parameters: parameters)
        model.create_method("may_#{name}?", return_type: "T::Boolean")

        # There's no namespaced method created for `_without_validation`, so skip
        # defining a method for:
        #   "#{name}_without_validation!"
      end
    end

    # Create the overall state machine method, which will return an
    # instance of the PrivateAASMMachine class.
    model.create_method(
      "aasm",
      parameters: [
        create_rest_param("args", type: "T.untyped"),
        create_block_param("block", type: "T.nilable(T.proc.bind(PrivateAASMMachine).void)"),
      ],
      return_type: "PrivateAASMMachine",
      class_method: true,
    )

    # Create a private machine class that we can pass around for the
    # purpose of binding various procs passed to methods without having
    # to explicitly bind self in each one.
    model.create_class("PrivateAASMMachine", superclass_name: "AASM::Base") do |machine|
      machine.create_method(
        "event",
        parameters: [
          create_param("name", type: "T.untyped"),
          create_opt_param("options", default: "nil", type: "T.untyped"),
          create_block_param("block", type: "T.proc.bind(PrivateAASMEvent).void"),
        ],
      )

      constant_name = name_of(constant)

      GLOBAL_CALLBACKS.each do |method|
        machine.create_method(
          method,
          parameters: [
            create_rest_param("callbacks", type: "T.any(String, Symbol, T::Class[T.anything], Proc)"),
            create_block_param("block", type: "T.nilable(T.proc.bind(#{constant_name}).void)"),
          ],
        )
      end

      # Create a private event class that we can pass around for the
      # purpose of binding all of the callbacks without having to
      # explicitly bind self in each one.
      machine.create_class("PrivateAASMEvent", superclass_name: "AASM::Core::Event") do |event|
        EVENT_CALLBACKS.each do |method|
          event.create_method(
            method,
            parameters: [
              create_opt_param("symbol", type: "T.nilable(Symbol)", default: "nil"),
              create_block_param(
                "block",
                type: "T.nilable(T.proc.bind(#{constant_name}).params(opts: T.untyped).void)",
              ),
            ],
          )
        end

        event.create_method(
          "transitions",
          parameters: [
            create_opt_param("definitions", default: "nil", type: "T.untyped"),
            create_block_param("block", type: "T.nilable(T.proc.bind(PrivateAASMTransition).void)"),
          ],
        )
      end

      machine.create_class("PrivateAASMTransition", superclass_name: "AASM::Core::Transition") do |transition|
        TRANSITION_CALLBACKS.each do |method|
          return_type = "T.untyped"
          return_type = "T::Boolean" if method == "guard"

          transition.create_method(
            method,
            parameters: [
              create_block_param(
                "block",
                type: "T.nilable(T.proc.bind(#{constant_name}).params(opts: T.untyped).void)",
              ),
            ],
            return_type: return_type,
          )
        end
      end
    end
  end
end