Class: SolidObjects::ActorDefinition
- Inherits:
-
Object
- Object
- SolidObjects::ActorDefinition
- 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
- #activation_hooks ⇒ Object readonly
-
#attribute_queries ⇒ Object
readonly
Returns the value of attribute attribute_queries.
- #deactivation_hooks ⇒ Object readonly
- #messages ⇒ Object readonly
-
#method_messages ⇒ Object
readonly
Returns the value of attribute method_messages.
-
#observable_broadcasts ⇒ Object
readonly
Returns the value of attribute observable_broadcasts.
- #observables ⇒ Object readonly
- #payload_broadcasts ⇒ Object readonly
- #queries ⇒ Object readonly
- #state_definition ⇒ Object readonly
- #state_migrations ⇒ Object readonly
- #state_version ⇒ Object readonly
Instance Method Summary collapse
- #actor_message_method_names(actor_class) ⇒ Array[Symbol]
- #add_attribute(name, default:) ⇒ StateDefinition::Attribute
- #add_handler(collection:, name:, block:) ⇒ Handler
- #add_message(name, block) ⇒ Handler
- #add_method_message(name) ⇒ Handler
- #add_observable(name, block = nil, broadcast:) ⇒ Handler
- #add_payload_broadcast(name, block) ⇒ Handler
- #add_query(name, block) ⇒ Handler
- #add_state_migration(from:, to:, block:) ⇒ StateMigration
- #broadcasts_observable_value?(name) ⇒ Boolean
- #duplicate ⇒ ActorDefinition
-
#initialize ⇒ ActorDefinition
constructor
A new instance of ActorDefinition.
- #migrate_state(version, data) ⇒ Hash[String, untyped]
- #set_state_version(version) ⇒ Integer
- #synchronize_instance_messages(actor_class) ⇒ ActorDefinition
- #validate_attribute_methods!(attribute_method_names) ⇒ void
Constructor Details
#initialize ⇒ ActorDefinition
Returns a new instance of ActorDefinition.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/solid_objects/actor_definition.rb', line 32 def initialize @state_definition = StateDefinition.new @messages = {} @queries = {} @observables = {} @observable_broadcasts = {} @payload_broadcasts = {} @state_version = 1 @state_migrations = [] @activation_hooks = [] @deactivation_hooks = [] @attribute_queries = {} @method_messages = {} end |
Instance Attribute Details
#activation_hooks ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def activation_hooks @activation_hooks end |
#attribute_queries ⇒ Object (readonly)
Returns the value of attribute attribute_queries.
195 196 197 |
# File 'lib/solid_objects/actor_definition.rb', line 195 def attribute_queries @attribute_queries end |
#deactivation_hooks ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def deactivation_hooks @deactivation_hooks end |
#messages ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def @messages end |
#method_messages ⇒ Object (readonly)
Returns the value of attribute method_messages.
195 196 197 |
# File 'lib/solid_objects/actor_definition.rb', line 195 def @method_messages end |
#observable_broadcasts ⇒ Object (readonly)
Returns the value of attribute observable_broadcasts.
195 196 197 |
# File 'lib/solid_objects/actor_definition.rb', line 195 def observable_broadcasts @observable_broadcasts end |
#observables ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def observables @observables end |
#payload_broadcasts ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def payload_broadcasts @payload_broadcasts end |
#queries ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def queries @queries end |
#state_definition ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def state_definition @state_definition end |
#state_migrations ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def state_migrations @state_migrations end |
#state_version ⇒ Object (readonly)
21 22 23 |
# File 'lib/solid_objects/actor_definition.rb', line 21 def state_version @state_version end |
Instance Method Details
#actor_message_method_names(actor_class) ⇒ Array[Symbol]
214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/solid_objects/actor_definition.rb', line 214 def (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
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/solid_objects/actor_definition.rb', line 48 def add_attribute(name, default:) attribute_name = name.to_sym OperationDispatcher.validate_operation_name!(attribute_name) if .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
240 241 242 243 244 245 246 |
# File 'lib/solid_objects/actor_definition.rb', line 240 def add_handler(collection:, name:, block:) handler_name = name.to_sym OperationDispatcher.validate_operation_name!(handler_name) raise InvalidActor, "#{handler_name.inspect} is already defined" if .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
65 66 67 |
# File 'lib/solid_objects/actor_definition.rb', line 65 def (name, block) add_handler(collection: , name:, block:) end |
#add_method_message(name) ⇒ Handler
198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/solid_objects/actor_definition.rb', line 198 def (name) OperationDispatcher.validate_operation_name!(name) if .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| [name] = handler [name] = true end end |
#add_observable(name, block = nil, broadcast:) ⇒ Handler
82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/solid_objects/actor_definition.rb', line 82 def add_observable(name, block = nil, broadcast:) observable_name = name.to_sym raise InvalidActor, "#{observable_name.inspect} observable is already defined" if observables.key?(observable_name) unless %i[value invalidation].include?(broadcast) raise InvalidActor, "observable broadcast must be :value or :invalidation" end observable_block = block || -> { state.fetch(observable_name) } Handler.new(name: observable_name, block: observable_block).tap do |handler| observables[observable_name] = handler observable_broadcasts[observable_name] = broadcast end end |
#add_payload_broadcast(name, block) ⇒ Handler
102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/solid_objects/actor_definition.rb', line 102 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
70 71 72 73 74 75 76 77 78 79 |
# File 'lib/solid_objects/actor_definition.rb', line 70 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(collection: queries, name:, block:) end |
#add_state_migration(from:, to:, block:) ⇒ StateMigration
137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/solid_objects/actor_definition.rb', line 137 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 |
#broadcasts_observable_value?(name) ⇒ Boolean
97 98 99 |
# File 'lib/solid_objects/actor_definition.rb', line 97 def broadcasts_observable_value?(name) observable_broadcasts.fetch(name.to_sym) == :value end |
#duplicate ⇒ ActorDefinition
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/solid_objects/actor_definition.rb', line 176 def duplicate self.class.new.tap do |copy| copy.instance_variable_set(:@state_definition, state_definition.duplicate) copy.instance_variable_set(:@messages, .dup) copy.instance_variable_set(:@queries, queries.dup) copy.instance_variable_set(:@observables, observables.dup) copy.instance_variable_set(:@observable_broadcasts, observable_broadcasts.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, .dup) end end |
#migrate_state(version, data) ⇒ Hash[String, untyped]
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/solid_objects/actor_definition.rb', line 150 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.}" end |
#set_state_version(version) ⇒ Integer
130 131 132 133 134 |
# File 'lib/solid_objects/actor_definition.rb', line 130 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
117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/solid_objects/actor_definition.rb', line 117 def (actor_class) names = (actor_class) (.keys - names).each do |name| .delete(name) .delete(name) end names.each { |name| (name) unless .key?(name) } self end |
#validate_attribute_methods!(attribute_method_names) ⇒ void
This method returns an undefined value.
229 230 231 232 233 234 235 236 237 |
# File 'lib/solid_objects/actor_definition.rb', line 229 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 |