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



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

def initialize
  @state_definition = StateDefinition.new
  @messages = {}
  @queries = {}
  @observables = {}
  @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]

  • @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)


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

def activation_hooks
  @activation_hooks
end

#attribute_queriesObject (readonly)

Returns the value of attribute attribute_queries.

Returns:

  • (Object)


163
164
165
# File 'lib/solid_objects/actor_definition.rb', line 163

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]

  • @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)


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

def deactivation_hooks
  @deactivation_hooks
end

#messagesObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: 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)


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

def messages
  @messages
end

#method_messagesObject (readonly)

Returns the value of attribute method_messages.

Returns:

  • (Object)


163
164
165
# File 'lib/solid_objects/actor_definition.rb', line 163

def method_messages
  @method_messages
end

#observablesObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: 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)


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

def observables
  @observables
end

#queriesObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: 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)


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

def queries
  @queries
end

#state_definitionObject (readonly)

RBS:

  • @state_definition: StateDefinition

  • @messages: Hash[Symbol, Handler]

  • @queries: Hash[Symbol, Handler]

  • @observables: 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)


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

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]

  • @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)


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

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]

  • @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)


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

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


181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/solid_objects/actor_definition.rb', line 181

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:



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/solid_objects/actor_definition.rb', line 43

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:



207
208
209
210
211
212
# File 'lib/solid_objects/actor_definition.rb', line 207

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:



59
60
61
# File 'lib/solid_objects/actor_definition.rb', line 59

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

#add_method_message(name) ⇒ Handler

RBS:

  • (Symbol) -> Handler

Parameters:

  • (Symbol)

Returns:



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

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:



76
77
78
79
80
81
82
83
84
# File 'lib/solid_objects/actor_definition.rb', line 76

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_query(name, block) ⇒ Handler

RBS:

  • (Symbol | String, Proc) -> Handler

Parameters:

  • (Symbol, String)
  • (Proc)

Returns:



64
65
66
67
68
69
70
71
72
73
# File 'lib/solid_objects/actor_definition.rb', line 64

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:



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

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:



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/solid_objects/actor_definition.rb', line 146

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(:@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])


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/solid_objects/actor_definition.rb', line 120

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)


100
101
102
103
104
# File 'lib/solid_objects/actor_definition.rb', line 100

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:



87
88
89
90
91
92
93
94
95
96
97
# File 'lib/solid_objects/actor_definition.rb', line 87

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


196
197
198
199
200
201
202
203
204
# File 'lib/solid_objects/actor_definition.rb', line 196

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