Class: SolidObjects::ActorDefinition

Inherits:
Object
  • Object
show all
Defined in:
lib/solid_objects/actor_definition.rb,
sig/generated/lib/solid_objects/actor_definition.rbs

Defined Under Namespace

Classes: Handler, StateMigration

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeActorDefinition

Returns a new instance of ActorDefinition.

RBS:

  • () -> void



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/solid_objects/actor_definition.rb', line 31

def initialize
  @state_definition = StateDefinition.new
  @messages = {}
  @queries = {}
  @observables = {}
  @payload_broadcasts = {}
  @state_version = 1
  @state_migrations = []
  @activation_hooks = []
  @deactivation_hooks = []
  @attribute_queries = {}
  @method_messages = {}
end

Instance Attribute Details

#activation_hooksObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def activation_hooks
  @activation_hooks
end

#attribute_queriesObject (readonly)

Returns the value of attribute attribute_queries.

Returns:

  • (Object)


182
183
184
# File 'lib/solid_objects/actor_definition.rb', line 182

def attribute_queries
  @attribute_queries
end

#deactivation_hooksObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def deactivation_hooks
  @deactivation_hooks
end

#messagesObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def messages
  @messages
end

#method_messagesObject (readonly)

Returns the value of attribute method_messages.

Returns:

  • (Object)


182
183
184
# File 'lib/solid_objects/actor_definition.rb', line 182

def method_messages
  @method_messages
end

#observablesObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def observables
  @observables
end

#payload_broadcastsObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def payload_broadcasts
  @payload_broadcasts
end

#queriesObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def queries
  @queries
end

#state_definitionObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def state_definition
  @state_definition
end

#state_migrationsObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def state_migrations
  @state_migrations
end

#state_versionObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: Hash[Symbol, Handler]

  • @payload_broadcasts: Hash[Symbol, Handler]

  • @state_version: Integer

  • @state_migrations: Array[StateMigration]

  • @activation_hooks: Array[Proc]

  • @deactivation_hooks: Array[Proc]

  • @attribute_queries: Hash[Symbol, bool]

  • @method_messages: Hash[Symbol, bool]

Returns:

  • (Object)


20
21
22
# File 'lib/solid_objects/actor_definition.rb', line 20

def state_version
  @state_version
end

Instance Method Details

#actor_message_method_names(actor_class) ⇒ Array[Symbol]

RBS:

  • (Class) -> Array[Symbol]

Parameters:

  • (Class)

Returns:

  • (Array[Symbol])


200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/solid_objects/actor_definition.rb', line 200

def actor_message_method_names(actor_class)
  actor_ancestors = actor_class.ancestors.take_while { |ancestor| ancestor != Actor }
  attribute_method_names = actor_class.send(:generated_attribute_methods).keys
  validate_attribute_methods!(attribute_method_names)

  actor_class.public_instance_methods.filter_map do |name|
    method_name = name.to_sym
    next if attribute_method_names.include?(method_name)
    next unless actor_ancestors.include?(actor_class.instance_method(method_name).owner)

    method_name
  end.uniq
end

#add_attribute(name, default:) ⇒ StateDefinition::Attribute

RBS:

  • (Symbol | String, default: untyped) -> StateDefinition::Attribute

Parameters:

  • (Symbol, String)
  • default: (Object)

Returns:



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/solid_objects/actor_definition.rb', line 46

def add_attribute(name, default:)
  attribute_name = name.to_sym
  if messages.key?(attribute_name) || queries.key?(attribute_name)
    raise InvalidActor, "#{attribute_name.inspect} is already defined"
  end

  state_definition.attribute(attribute_name, default:).tap do
    queries[attribute_name] = Handler.new(
      name: attribute_name,
      block: -> { state.fetch(attribute_name) }
    )
    attribute_queries[attribute_name] = true
  end
end

#add_handler(collection, name, block) ⇒ Handler

RBS:

  • (Hash[Symbol, Handler], Symbol | String, Proc) -> Handler

Parameters:

  • (Hash[Symbol, Handler])
  • (Symbol, String)
  • (Proc)

Returns:



226
227
228
229
230
231
# File 'lib/solid_objects/actor_definition.rb', line 226

def add_handler(collection, name, block)
  handler_name = name.to_sym
  raise InvalidActor, "#{handler_name.inspect} is already defined" if messages.key?(handler_name) || queries.key?(handler_name)

  Handler.new(name: handler_name, block:).tap { |handler| collection[handler_name] = handler }
end

#add_message(name, block) ⇒ Handler

RBS:

  • (Symbol | String, Proc) -> Handler

Parameters:

  • (Symbol, String)
  • (Proc)

Returns:



62
63
64
# File 'lib/solid_objects/actor_definition.rb', line 62

def add_message(name, block)
  add_handler(messages, name, block)
end

#add_method_message(name) ⇒ Handler

RBS:

  • (Symbol) -> Handler

Parameters:

  • (Symbol)

Returns:



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/solid_objects/actor_definition.rb', line 185

def add_method_message(name)
  if messages.key?(name) || queries.key?(name)
    raise InvalidActor, "#{name.inspect} is already defined"
  end

  Handler.new(
    name:,
    block: ->(**arguments) { public_send(name, **arguments) }
  ).tap do |handler|
    messages[name] = handler
    method_messages[name] = true
  end
end

#add_observable(name, block = nil) ⇒ Handler

RBS:

  • (Symbol | String, Proc?) -> Handler

Parameters:

  • (Symbol, String)
  • (Proc, nil)

Returns:



79
80
81
82
83
84
85
86
87
# File 'lib/solid_objects/actor_definition.rb', line 79

def add_observable(name, block = nil)
  observable_name = name.to_sym
  raise InvalidActor, "#{observable_name.inspect} observable is already defined" if observables.key?(observable_name)

  observable_block = block || -> { state.fetch(observable_name) }
  Handler.new(name: observable_name, block: observable_block).tap do |handler|
    observables[observable_name] = handler
  end
end

#add_payload_broadcast(name, block) ⇒ Handler

RBS:

  • (Symbol | String, Proc) -> Handler

Parameters:

  • (Symbol, String)
  • (Proc)

Returns:



90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/solid_objects/actor_definition.rb', line 90

def add_payload_broadcast(name, block)
  payload_name = name.to_sym
  if payload_broadcasts.key?(payload_name)
    raise InvalidActor, "#{payload_name.inspect} payload broadcast is already defined"
  end
  unless payload_name.to_s.match?(/\A[a-zA-Z0-9_]+\z/)
    raise InvalidActor, "payload broadcast names may contain only letters, digits, and underscores"
  end

  Handler.new(name: payload_name, block:).tap do |handler|
    payload_broadcasts[payload_name] = handler
  end
end

#add_query(name, block) ⇒ Handler

RBS:

  • (Symbol | String, Proc) -> Handler

Parameters:

  • (Symbol, String)
  • (Proc)

Returns:



67
68
69
70
71
72
73
74
75
76
# File 'lib/solid_objects/actor_definition.rb', line 67

def add_query(name, block)
  query_name = name.to_sym
  if attribute_queries.delete(query_name)
    return Handler.new(name: query_name, block:).tap do |handler|
      queries[query_name] = handler
    end
  end

  add_handler(queries, name, block)
end

#add_state_migration(from, to, block) ⇒ StateMigration

RBS:

  • (Integer, Integer, Proc) -> StateMigration

Parameters:

  • (Integer)
  • (Integer)
  • (Proc)

Returns:



125
126
127
128
129
130
131
132
133
134
135
# File 'lib/solid_objects/actor_definition.rb', line 125

def add_state_migration(from, to, block)
  unless to == from + 1
    raise InvalidActor, "state migrations must advance exactly one version"
  end

  if state_migrations.any? { |migration| migration.from == from }
    raise InvalidActor, "state migration from version #{from} is already defined"
  end

  StateMigration.new(from:, to:, block:).tap { |migration| state_migrations << migration }
end

#duplicateActorDefinition

RBS:

  • () -> ActorDefinition

Returns:



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/solid_objects/actor_definition.rb', line 164

def duplicate
  self.class.new.tap do |copy|
    copy.instance_variable_set(:@state_definition, state_definition.duplicate)
    copy.instance_variable_set(:@messages, messages.dup)
    copy.instance_variable_set(:@queries, queries.dup)
    copy.instance_variable_set(:@observables, observables.dup)
    copy.instance_variable_set(:@payload_broadcasts, payload_broadcasts.dup)
    copy.instance_variable_set(:@state_version, state_version)
    copy.instance_variable_set(:@state_migrations, state_migrations.dup)
    copy.instance_variable_set(:@activation_hooks, activation_hooks.dup)
    copy.instance_variable_set(:@deactivation_hooks, deactivation_hooks.dup)
    copy.instance_variable_set(:@attribute_queries, attribute_queries.dup)
    copy.instance_variable_set(:@method_messages, method_messages.dup)
  end
end

#migrate_state(version, data) ⇒ Hash[String, untyped]

RBS:

  • (Integer, Hash[String, untyped]) -> Hash[String, untyped]

Parameters:

  • (Integer)
  • (Hash[String, untyped])

Returns:

  • (Hash[String, untyped])


138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/solid_objects/actor_definition.rb', line 138

def migrate_state(version, data)
  migrated = Serialization.deep_copy(data)
  current_version = version

  while current_version < state_version
    migration = state_migrations.find { |candidate| candidate.from == current_version }
    raise StateMigrationError, "missing state migration from version #{current_version}" unless migration

    migrated = Serialization.dump(migration.block.call(migrated))
    current_version = migration.to
  end

  if current_version > state_version
    raise StateMigrationError, "stored state version #{current_version} is newer than code version #{state_version}"
  end

  migrated
rescue StateMigrationError
  raise
rescue ActiveRecord::ReadOnlyError
  raise
rescue => error
  raise StateMigrationError, "state migration failed: #{error.message}"
end

#set_state_version(version) ⇒ Integer

RBS:

  • (Integer) -> Integer

Parameters:

  • (Integer)

Returns:

  • (Integer)


118
119
120
121
122
# File 'lib/solid_objects/actor_definition.rb', line 118

def set_state_version(version)
  raise InvalidActor, "state version must be positive" unless version.positive?

  @state_version = version
end

#synchronize_instance_messages(actor_class) ⇒ ActorDefinition

RBS:

  • (Class) -> ActorDefinition

Parameters:

  • (Class)

Returns:



105
106
107
108
109
110
111
112
113
114
115
# File 'lib/solid_objects/actor_definition.rb', line 105

def synchronize_instance_messages(actor_class)
  names = actor_message_method_names(actor_class)

  (method_messages.keys - names).each do |name|
    messages.delete(name)
    method_messages.delete(name)
  end

  names.each { |name| add_method_message(name) unless method_messages.key?(name) }
  self
end

#validate_attribute_methods!(attribute_method_names) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[Symbol]) -> void

Parameters:

  • (Array[Symbol])


215
216
217
218
219
220
221
222
223
# File 'lib/solid_objects/actor_definition.rb', line 215

def validate_attribute_methods!(attribute_method_names)
  state_definition.to_h.each_key do |name|
    [ name, :"#{name}=" ].each do |method_name|
      next if attribute_method_names.include?(method_name)

      raise InvalidActor, "actor method #{method_name} overrides attribute #{name.inspect}"
    end
  end
end