Skip to content

class Athena::ORM::UnitOfWork
inherits Reference #

Tracks the entities an AORM::EntityManager manages, and writes their changes to the database when it is flushed.

The unit of work keeps an identity map of every managed entity, keyed by class and identifier, so loading the same row more than once returns the same instance. It also keeps a copy of each managed entity's data as of when it was loaded or last flushed. On AORM::EntityManager#flush, it compares each managed entity against that copy to compute its change set. It then issues the INSERT, UPDATE and DELETE statements for new, changed and removed entities within a single transaction, ordered so that rows are inserted before the rows referencing them.

Each entity manager owns one unit of work, available via AORM::EntityManager#unit_of_work. It is usually only used indirectly through the entity manager, whose #persist, #remove and #flush methods delegate to it.

Inspecting a Flush#

The read side of the unit of work is mostly useful within flush event listeners, to see what a flush is about to write. For example, a listener on AORM::Events::OnFlushEventArgs runs after every change set has been computed, but before any SQL is executed:

dispatcher = AED::EventDispatcher.new

dispatcher.listener AORM::Events::OnFlushEventArgs do |event|
  uow = event.entity_manager.unit_of_work

  uow.scheduled_entity_insertions.each do |entity|
    Log.info { "Inserting #{entity.class}" }
  end

  uow.scheduled_entity_updates.each do |entity|
    uow.entity_changeset(entity).each do |field, change|
      Log.info { "#{entity.class}##{field}: #{change.old.try &.value} -> #{change.new.value}" }
    end
  end

  uow.scheduled_entity_deletions.each do |entity|
    Log.info { "Deleting #{entity.class}" }
  end
end

em = AORM::EntityManager.new connection, dispatcher

Inserts and updates are written from the change sets computed before AORM::Events::OnFlushEventArgs is dispatched. A listener of that event that persists an entity computes its change set with #compute_change_set, and one that changes a managed entity recomputes it with #recompute_single_entity_change_set.

Warning

Directly calling methods on the unit of work that change its state is not supported. Use the AORM::EntityManager API instead.

Methods#

#compute_change_set(class_metadata : AORM::Mapping::ClassInterface, entity : AORM::Entity) : Nil#

Computes the change set of the managed entity, comparing its current state with the data it was loaded or last flushed with. The change set of an entity being inserted holds every field its INSERT writes.

Change sets are computed at the start of a flush, before AORM::Events::OnFlushEventArgs is dispatched. So a listener of that event that persists an entity also has to compute its change set:

em.persist phone
em.unit_of_work.compute_change_set em.class_metadata(Phone), phone
View source

#entity_changeset(entity : AORM::Entity) : Hash#

Returns the changes the current flush computed for entity, as a Change per field name.

Includes mapped fields and the owning side of ToOne associations, whose values are the related entities. Entities being inserted have a change, with a nil Change#old, for each field their INSERT writes. Returns an empty hash if entity has no changes, or outside of a flush, since change sets are cleared once it completes.

View source

#entity_identifier(entity : AORM::Entity) : Hash(String, AORM::Mapping::Value)#

Returns the identifier of entity, keyed by field name.

Raises if the unit of work doesn't know the identifier of entity, such as when it isn't managed, or is new and its identifier is generated on insert.

View source

#entity_state(entity : AORM::Entity, assume : EntityState | Nil = nil) : EntityState#

Returns the EntityState of entity in relation to this unit of work.

Entities that are managed or removed are tracked, so their state is known. For any other entity, assume is returned if given. Otherwise entity is EntityState::New if it has no identifier, and its identifier is looked up in the identity map, and possibly the database, to tell whether it is EntityState::New or EntityState::Detached.

View source

#is_in_identity_map?(entity : AORM::Entity) : Bool#

Returns true if entity is registered in the identity map.

Entities are registered once their identifier is known: when they are loaded, when a new entity with an assigned identifier is persisted, or when a generated identifier is read back on insert.

ameba:disable Naming/PredicateName

View source

#is_scheduled_for_delete?(entity : AORM::Entity) : Bool#

Returns true if entity will be deleted on the next flush.

ameba:disable Naming/PredicateName

View source

#is_scheduled_for_insert?(entity : AORM::Entity) : Bool#

Returns true if entity will be inserted on the next flush.

ameba:disable Naming/PredicateName

View source

#is_scheduled_for_update?(entity : AORM::Entity) : Bool#

Returns true if entity changed, and will be updated by the current flush.

Like #scheduled_entity_updates, this is only known during a flush.

ameba:disable Naming/PredicateName

View source

#recompute_single_entity_change_set(class_metadata : AORM::Mapping::ClassInterface, entity : AORM::Entity) : Nil#

Recomputes the change set of the managed entity, independently of the change sets computed at the start of a flush. The changes it finds are added to the entity's change set, and schedule it to be updated if it isn't already. Raises if entity isn't managed.

Change sets are computed before AORM::Events::OnFlushEventArgs is dispatched, so a listener of that event that changes the fields of a managed entity recomputes its change set:

user.name = "George"
em.unit_of_work.recompute_single_entity_change_set em.class_metadata(User), user

Changes to the entity's collections aren't recomputed.

ameba:disable Metrics/CyclomaticComplexity

View source

#scheduled_entity_deletions : Set(AORM::Entity)#

Returns the entities that will be deleted on the next flush.

Entities are scheduled for deletion when they are removed.

View source

#scheduled_entity_insertions : Set(AORM::Entity)#

Returns the entities that will be inserted on the next flush.

Entities are scheduled for insertion when they are persisted.

View source

#scheduled_entity_updates : Set(AORM::Entity)#

Returns the managed entities that changed, and will be updated by the current flush.

Updates are only known once a flush has computed the change sets, so this is empty outside of a flush.

View source