Module: Eventsimple::Event::ClassMethods

Defined in:
lib/eventsimple/event.rb

Instance Method Summary collapse

Instance Method Details

#create(*args, &block) ⇒ Object

We want to automatically retry writes on concurrency failures. However events with sync reactors may have multiple nested events that are written within the same transaction. We can only catch and retry writes when they the outermost event encapsulating the whole transaction.



150
151
152
153
154
# File 'lib/eventsimple/event.rb', line 150

def create(*args, &block)
  with_locks do
    with_retries(args) { super }
  end
end

#create!(*args, &block) ⇒ Object



156
157
158
159
160
# File 'lib/eventsimple/event.rb', line 156

def create!(*args, &block)
  with_locks do
    with_retries(args) { super }
  end
end

#existing_transaction_in_progress?Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/eventsimple/event.rb', line 185

def existing_transaction_in_progress?
  ActiveRecord::Base.connection.transaction_open?
end

#find_sti_class(type_name) ⇒ Object

We don’t store the full namespaced class name in the events table. Events for an entity are expected to be namespaced under _events_namespace.



123
124
125
126
127
128
129
# File 'lib/eventsimple/event.rb', line 123

def find_sti_class(type_name)
  if _events_namespace.blank?
    super(type_name)
  else
    super("#{_events_namespace}::#{type_name}")
  end
end

#rescue_invalid_transition(&block) ⇒ Object



117
118
119
# File 'lib/eventsimple/event.rb', line 117

def rescue_invalid_transition(&block)
  self._on_invalid_transition = block || ->(error) {}
end

#sti_class_for(type_name) ⇒ Object

Use a no-op deleted class for events that no longer exist in the codebase



132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/eventsimple/event.rb', line 132

def sti_class_for(type_name)
  super
rescue ActiveRecord::SubclassNotFound
  klass_name = "Deleted__#{type_name.demodulize}"
  return const_get(klass_name) if const_defined?(klass_name)

  # NOTE: this should still update the timestamps for the model to prevent
  #       projection drift (since the original projection will
  #       have the timestamps applied for the deleted event).
  klass = Class.new(self)

  const_set(klass_name, klass)
end

#validate_with(form_klass) ⇒ Object



113
114
115
# File 'lib/eventsimple/event.rb', line 113

def validate_with(form_klass)
  @validate_with = form_klass
end

#with_locks(&block) ⇒ Object



162
163
164
165
166
167
168
# File 'lib/eventsimple/event.rb', line 162

def with_locks(&block)
  if _outbox_enabled
    base_class.with_advisory_lock(base_class.name, { transaction: true }, &block)
  else
    yield
  end
end

#with_retries(args, &block) ⇒ Object

rubocop:disable Metrics/AbcSize



170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/eventsimple/event.rb', line 170

def with_retries(args, &block) # rubocop:disable Metrics/AbcSize
  entity = args[0][_aggregate_klass.model_name.element.to_sym]

  # Only implement retries when the event is not already inside a transaction.
  if entity&.persisted? && !existing_transaction_in_progress?
    Retriable.with_context(:optimistic_locking, on_retry: proc { entity.reload }, &block)
  else
    yield
  end
rescue ActiveRecord::StaleObjectError => e
  raise e unless existing_transaction_in_progress?

  raise e, "#{e.message} No retries are attempted when already inside a transaction."
end