Class: Funes::Event

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Attributes, ActiveModel::Model
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 two 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.

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

Instance Attribute Details

#adjacent_state_errorsActiveModel::Errors

Returns Validation errors from consistency projections.

Returns:

  • (ActiveModel::Errors)

    Validation errors from consistency projections.



49
50
51
# File 'app/models/funes/event.rb', line 49

def adjacent_state_errors
  @adjacent_state_errors
end

#event_errorsActiveModel::Errors?

Returns The event's own validation errors (internal use).

Returns:

  • (ActiveModel::Errors, nil)

    The event's own validation errors (internal use).



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

def event_errors
  @event_errors
end

Instance Method Details

#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.



126
127
128
129
130
131
132
133
134
135
# File 'app/models/funes/event.rb', line 126

def errors
  return super if @event_errors.nil?

  tmp_errors = ActiveModel::Errors.new(nil)
  tmp_errors.merge!(event_errors)
  adjacent_state_errors.each do |error|
    tmp_errors.add(:base, "#{I18n.t("funes.events.led_to_invalid_state_prefix")}: #{error.full_message}")
  end
  tmp_errors
end

#inspectString

Custom string representation of the event.

Examples:

event = Order::Placed.new(total: 99.99)
event.inspect  # => "<Order::Placed: {:total=>99.99}>"

Returns:

  • (String)

    A string showing the event class name and attributes.



73
74
75
# File 'app/models/funes/event.rb', line 73

def inspect
  "<#{self.class.name}: #{attributes}>"
end

#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.



112
113
114
# File 'app/models/funes/event.rb', line 112

def own_errors
  event_errors || errors
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.



101
102
103
# File 'app/models/funes/event.rb', line 101

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.



87
88
89
# File 'app/models/funes/event.rb', line 87

def valid?
  super && (adjacent_state_errors.nil? || adjacent_state_errors.empty?)
end