Class: Funes::Event

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Attributes, ActiveModel::Model, Inspection
Defined in:
app/models/funes/event.rb

Overview

Base class for all events in the Funes event sourcing framework.

Events are immutable facts that represent something that happened in the system. They use ActiveModel for attributes and validations, making them familiar to Rails developers.

## Event Validation

Events support three types of validation:

  • **Own validation:** Standard ActiveModel validations defined on the event class itself.

  • **Adjacent state validation:** Validation errors from consistency projections that check if the event would lead to an invalid state.

  • **Interpretation errors:** Errors added via event.errors.add(…) inside interpretation blocks. When used in a consistency projection, these are automatically transferred to interpretation_errors and cause the event to be rejected.

The ‘valid?` method returns `true` only if both validations pass. The `errors` method merges both types of errors for display.

## Defining Events

Events inherit from ‘Funes::Event` and define attributes using ActiveModel::Attributes:

Examples:

Define a simple event

class Order::Placed < Funes::Event
  attribute :total, :decimal
  attribute :customer_id, :string
  attribute :at, :datetime, default: -> { Time.current }

  validates :total, presence: true, numericality: { greater_than: 0 }
  validates :customer_id, presence: true
end

Using the event

event = Order::Placed.new(total: 99.99, customer_id: "cust-123")
stream.append(event)

Handling validation errors

event = stream.append(Order::Placed.new(total: -10))
unless event.valid?
  puts event.own_errors.full_messages      # => Event's own validation errors
  puts event.state_errors.full_messages    # => Consistency projection errors
  puts event.errors.full_messages          # => All errors merged
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Inspection

#attribute_for_inspect, #full_inspect, #inspect, #pretty_print

Instance Attribute Details

#_adjacent_state_errorsObject

Returns the value of attribute _adjacent_state_errors.



53
54
55
# File 'app/models/funes/event.rb', line 53

def _adjacent_state_errors
  @_adjacent_state_errors
end

#_event_entryFunes::EventEntry?

Returns The persisted EventEntry record (internal use).

Returns:



61
62
63
# File 'app/models/funes/event.rb', line 61

def _event_entry
  @_event_entry
end

#_interpretation_errorsObject

Returns the value of attribute _interpretation_errors.



57
58
59
# File 'app/models/funes/event.rb', line 57

def _interpretation_errors
  @_interpretation_errors
end

#adjacent_state_errorsActiveModel::Errors

Returns Validation errors from consistency projections.

Returns:

  • (ActiveModel::Errors)

    Validation errors from consistency projections.



53
# File 'app/models/funes/event.rb', line 53

attr_accessor :_adjacent_state_errors

#interpretation_errorsActiveModel::Errors

Returns Explicit rejection errors from consistency projection interpretation blocks.

Returns:

  • (ActiveModel::Errors)

    Explicit rejection errors from consistency projection interpretation blocks.



57
# File 'app/models/funes/event.rb', line 57

attr_accessor :_interpretation_errors

Instance Method Details

#created_atTime?

Returns the timestamp when the event was persisted.

Examples:

event = Order::Placed.new(total: 99.99)
event.created_at  # => nil

stream.append(event)
event.created_at  # => 2026-02-18 12:00:00 UTC

Returns:

  • (Time, nil)

    The time the event was created, or ‘nil` if not yet persisted.



109
# File 'app/models/funes/event.rb', line 109

def created_at = _event_entry&.created_at

#errorsActiveModel::Errors

Get all validation errors (both event and state errors merged).

This method merges the event’s own validation errors with any errors from consistency projections, prefixing state errors with a localized message.

Examples:

event.errors.full_messages
# => ["Total must be greater than 0", "Led to invalid state: Quantity on hand must be >= 0"]

Returns:

  • (ActiveModel::Errors)

    All validation errors combined.



200
201
202
203
204
205
206
207
208
209
# File 'app/models/funes/event.rb', line 200

def errors
  return super unless !_adjacent_state_errors.empty? || !_interpretation_errors.empty?

  tmp_errors = ActiveModel::Errors.new(self)
  tmp_errors.merge!(super)
  merge_errors_into(tmp_errors, _adjacent_state_errors, state_errors: true)
  merge_errors_into(tmp_errors, _interpretation_errors)

  tmp_errors
end

#invalid?Boolean

Check if the event is invalid.

An event is invalid if any of its own validations fail, it leads to an invalid state (adjacent_state_errors), or it has been explicitly rejected via interpretation_errors.

Examples:

event = Order::Placed.new(total: -10)
event.invalid?  # => true (own validation failed)

Returns:

  • (Boolean)

    ‘true` if the event is invalid, `false` otherwise.



159
# File 'app/models/funes/event.rb', line 159

def invalid? = !valid?

#occurred_atTime?

Returns the timestamp when the event actually occurred.

When an event is recorded retroactively (with an explicit ‘at:` on append), this returns the actual time of the event. Otherwise, it equals `created_at`.

Examples:

event = stream.append(Salary::Raised.new(amount: 6500), at: Time.new(2025, 2, 15))
event.occurred_at  # => 2025-02-15 00:00:00 UTC

Returns:

  • (Time, nil)

    The actual time of the event, or ‘nil` if not yet persisted.



121
# File 'app/models/funes/event.rb', line 121

def occurred_at = _event_entry&.occurred_at

#own_errorsActiveModel::Errors

Get the event’s own validation errors (excluding state errors).

Examples:

event = Order::Placed.new(total: -10)
event.own_errors.full_messages  # => ["Total must be greater than 0"]

Returns:

  • (ActiveModel::Errors)

    Only the event’s own validation errors.



182
183
184
185
186
187
188
# File 'app/models/funes/event.rb', line 182

def own_errors
  tmp_errors = ActiveModel::Errors.new(self)
  tmp_errors.merge!(base_errors)
  merge_errors_into(tmp_errors, _interpretation_errors)

  tmp_errors
end

#persisted?Boolean

Check if the event has been persisted to the database.

An event is considered persisted if it was either saved via ‘EventStream#append` or reconstructed from an Funes::EventEntry via `to_klass_instance`.

Examples:

event = Order::Placed.new(total: 99.99)
event.persisted?  # => false

stream.append(event)
event.persisted?  # => true (if no validation errors)

Returns:

  • (Boolean)

    ‘true` if the event has been persisted, `false` otherwise.



95
96
97
# File 'app/models/funes/event.rb', line 95

def persisted?
  _event_entry.present?
end

#state_errorsActiveModel::Errors

Get validation errors from consistency projections.

These are errors that indicate the event would lead to an invalid state, even if the event itself is valid.

Examples:

event = stream.append(Inventory::ItemShipped.new(quantity: 9999))
event.state_errors.full_messages  # => ["Quantity on hand must be >= 0"]

Returns:

  • (ActiveModel::Errors)

    Errors from consistency projection validation.



171
172
173
# File 'app/models/funes/event.rb', line 171

def state_errors
  _adjacent_state_errors
end

#valid?Boolean

Check if the event is valid.

An event is valid only if both its own validations pass AND it doesn’t lead to an invalid state (no adjacent_state_errors from consistency projections).

Examples:

event = Order::Placed.new(total: 99.99, customer_id: "cust-123")
event.valid?  # => true or false

Returns:

  • (Boolean)

    ‘true` if the event is valid, `false` otherwise.



145
146
147
# File 'app/models/funes/event.rb', line 145

def valid?
  super && _adjacent_state_errors.empty? && _interpretation_errors.empty?
end

#versionInteger?

Returns the version number of the event within its stream.

Each event in a stream gets an incrementing version number used for optimistic concurrency control. The version is assigned when the event is persisted.

Examples:

event = stream.append(Order::Placed.new(total: 99.99))
event.version  # => 1

Returns:

  • (Integer, nil)

    The version number, or ‘nil` if not yet persisted.



133
# File 'app/models/funes/event.rb', line 133

def version = _event_entry&.version