Class: Yes::Core::Utils::CommandUtils Private

Inherits:
Object
  • Object
show all
Defined in:
lib/yes/core/utils/command_utils.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Handles command and handler class operations for aggregates

Since:

  • 0.1.0

Defined Under Namespace

Classes: CommandNotFoundError

Constant Summary collapse

ASSIGN_COMMAND_PREFIX =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 0.1.0

'assign_'

Instance Method Summary collapse

Constructor Details

#initialize(context:, aggregate:, aggregate_id:) ⇒ CommandUtils

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of CommandUtils.

Parameters:

  • context (String)

    The context namespace

  • aggregate (String)

    The aggregate name

  • aggregate_id (String)

    The ID of the aggregate

Since:

  • 0.1.0



18
19
20
21
22
# File 'lib/yes/core/utils/command_utils.rb', line 18

def initialize(context:, aggregate:, aggregate_id:)
  @context = context
  @aggregate = aggregate
  @aggregate_id = aggregate_id
end

Instance Method Details

#build_attribute_command(attribute, payload) ⇒ Yes::Core::Command

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds a change command instance for the given attribute and payload

Parameters:

  • attribute (Symbol)

    The attribute name

  • payload (Hash)

    The command payload

Returns:

Raises:

  • (RuntimeError)

    If the command class cannot be found

Since:

  • 0.1.0



30
31
32
# File 'lib/yes/core/utils/command_utils.rb', line 30

def build_attribute_command(attribute, payload)
  build_command(:"change_#{attribute}", payload)
end

#build_command(command_name, payload) ⇒ Yes::Core::Command

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds a command instance for the given command name and payload

Parameters:

  • command_name (Symbol)

    The command name

  • payload (Hash)

    The command payload

Returns:

Raises:

  • (RuntimeError)

    If the command class cannot be found

Since:

  • 0.1.0



40
41
42
43
# File 'lib/yes/core/utils/command_utils.rb', line 40

def build_command(command_name, payload)
  command_class = fetch_class(command_name, :command)
  command_class.new("#{aggregate.underscore}_id": aggregate_id, **payload)
end

#build_event(command_name:, payload:, metadata: {}) ⇒ PgEventstore::Event

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds a PgEventstore::Event instance

Parameters:

  • command_name (Symbol)

    The command name

  • payload (Hash)

    The event payload

  • metadata (Hash) (defaults to: {})

    Event metadata

Returns:

  • (PgEventstore::Event)

    The event instance

Since:

  • 0.1.0



89
90
91
92
93
94
95
96
# File 'lib/yes/core/utils/command_utils.rb', line 89

def build_event(command_name:, payload:, metadata: {})
  event_class = Yes::Core.configuration.event_classes_for_command(context, aggregate, command_name).first
  event_class.new(
    type: "#{context}::#{aggregate_name_with_draft_suffix(aggregate, , context:)}#{event_class.name.demodulize}",
    data: payload,
    metadata:
  )
end

#build_group_command(group_name, payload) ⇒ Yes::Core::Commands::CommandGroup

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds a command_group instance for a given group name and flat payload. The aggregate_id is injected automatically.

Parameters:

  • group_name (Symbol)

    The command group name

  • payload (Hash)

    The flat / partially-nested input payload

Returns:

Raises:

  • (RuntimeError)

    If the command_group class cannot be found

Since:

  • 0.1.0



61
62
63
64
# File 'lib/yes/core/utils/command_utils.rb', line 61

def build_group_command(group_name, payload)
  group_class = fetch_class(group_name, :command_group)
  group_class.new("#{aggregate.underscore}_id": aggregate_id, **payload)
end

#build_stream(context: @context, name: @aggregate, id: @aggregate_id, metadata: {}) ⇒ PgEventstore::Stream

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds a PgEventstore::Stream instance

Parameters:

  • context (String) (defaults to: @context)

    The context name

  • name (String) (defaults to: @aggregate)

    The stream name

  • id (String) (defaults to: @aggregate_id)

    The stream ID

Returns:

  • (PgEventstore::Stream)

    The stream instance

Since:

  • 0.1.0



104
105
106
107
108
109
110
# File 'lib/yes/core/utils/command_utils.rb', line 104

def build_stream(context: @context, name: @aggregate, id: @aggregate_id, metadata: {})
  PgEventstore::Stream.new(
    context:,
    stream_name: aggregate_name_with_draft_suffix(name, , context:),
    stream_id: id
  )
end

#command_name_from_event(event, aggregate_class) ⇒ Symbol

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns The command name.

Parameters:

  • event (PgEventstore::Event)

    The event

  • aggregate_class (Class)

    The aggregate class

Returns:

  • (Symbol)

    The command name

Raises:

Since:

  • 0.1.0



143
144
145
146
147
148
149
# File 'lib/yes/core/utils/command_utils.rb', line 143

def command_name_from_event(event, aggregate_class)
  event_name = event.type.split('::').last.sub(event.stream.stream_name.sub(/(Draft|EditTemplate)/, ''), '').underscore
  command = aggregate_class.commands.values.find { _1.event_name.to_s == event_name }
  raise CommandNotFoundError, "Command not found for event #{event_name}" unless command

  command.name
end

#fetch_guard_evaluator_class(name) ⇒ Class

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Fetches the guard evaluator class for a given command name

Parameters:

  • name (Symbol)

    The command name

Returns:

  • (Class)

    The guard evaluator class

Raises:

  • (RuntimeError)

    If the guard evaluator class cannot be found

Since:

  • 0.1.0



50
51
52
# File 'lib/yes/core/utils/command_utils.rb', line 50

def fetch_guard_evaluator_class(name)
  fetch_class(name, :guard_evaluator)
end

#fetch_guard_evaluator_class_for_group(group_name) ⇒ Class

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Fetches the guard evaluator class for a given command group name.

Parameters:

  • group_name (Symbol)

    The command group name

Returns:

  • (Class)

    The guard evaluator class

Raises:

  • (RuntimeError)

    If the guard evaluator class cannot be found

Since:

  • 0.1.0



71
72
73
# File 'lib/yes/core/utils/command_utils.rb', line 71

def fetch_guard_evaluator_class_for_group(group_name)
  fetch_class(group_name, :command_group_guard_evaluator)
end

#fetch_state_updater_class(name) ⇒ Class

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Fetches the state updater class for a given command name

Parameters:

  • name (Symbol)

    The command name

Returns:

  • (Class)

    The state updater class

Since:

  • 0.1.0



79
80
81
# File 'lib/yes/core/utils/command_utils.rb', line 79

def fetch_state_updater_class(name)
  fetch_class(name, :state_updater)
end

#prepare_assign_command_payload(command_name, payload) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 0.1.0



126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/yes/core/utils/command_utils.rb', line 126

def prepare_assign_command_payload(command_name, payload)
  return payload unless command_name.to_s.starts_with?(ASSIGN_COMMAND_PREFIX)

  attribute_name = command_name.to_s.split(ASSIGN_COMMAND_PREFIX).last.to_sym
  name_with_id = :"#{attribute_name}_id"
  key = payload.key?(attribute_name) ? attribute_name : name_with_id

  return payload unless payload[key].is_a?(Yes::Core::Aggregate)

  payload[name_with_id] = payload.delete(key).id
  payload
end

#prepare_command_payload(command_name, payload, aggregate_class) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Prepares the payload for a command

Parameters:

  • command_name (Symbol)

    The command name

  • payload (Hash)

    The command payload

  • aggregate_class (Class)

    The aggregate class

Returns:

  • (Hash)

    The prepared payload

Since:

  • 0.1.0



157
158
159
160
161
162
163
164
# File 'lib/yes/core/utils/command_utils.rb', line 157

def prepare_command_payload(command_name, payload, aggregate_class)
  return append_locale_param(command_name, payload, aggregate_class) if payload.is_a?(Hash)

  payload_attributes = aggregate_class.commands[command_name].payload_attributes.except(:locale)
  raise 'Payload attributes must be a Hash with a single key (not including locale key)' if payload_attributes.length > 1

  append_locale_param(command_name, { payload_attributes.keys.first => payload }, aggregate_class)
end

#prepare_default_payload(command_name, payload, aggregate_class) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Prefills default values in the payload if they are not provided

Parameters:

  • command_name (Symbol)

    The command name

  • payload (Hash)

    The command payload

  • aggregate_class (Class)

    The aggregate class

Returns:

  • (Hash)

    The prepared payload

Since:

  • 0.1.0



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/yes/core/utils/command_utils.rb', line 172

def prepare_default_payload(command_name, payload, aggregate_class)
  return payload unless payload.is_a?(Hash)

  attributes_with_defaults = aggregate_class.commands[command_name].payload_attributes.select do |_key, value|
    value.is_a?(Hash) && value.key?(:default)
  end

  return payload unless attributes_with_defaults.any?

  additions = {}
  attributes_with_defaults.each do |key, value|
    next if payload.key?(key)

    default = value[:default]
    additions[key] = default.respond_to?(:call) ? default.call : default
  end
  payload.merge(additions)
end

#stream_revision(stream) ⇒ Integer

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Gets the current revision of a stream

Parameters:

  • stream (PgEventstore::Stream)

    The stream to check

Returns:

  • (Integer)

    The current revision

Since:

  • 0.1.0



116
117
118
119
120
121
122
123
124
# File 'lib/yes/core/utils/command_utils.rb', line 116

def stream_revision(stream)
  PgEventstore.client.read(
    stream,
    options: { direction: 'Backwards', max_count: 1 },
    middlewares: []
  ).first&.stream_revision || 0
rescue PgEventstore::StreamNotFoundError
  :no_stream
end