Module: EcsRails::Presence::Entity
Overview
Instance Method Summary collapse
-
#add(component_class) ⇒ EcsRails::Component
Ensures a row exists for
component_classon this entity and returns the now-persisted instance (RFC-0009). -
#has?(component_class) ⇒ Boolean
True iff a row exists for
component_classon this entity (RFC-0009). -
#remove(component_class) ⇒ self
Destroys the row for
component_classif present, and resets the reader to a virtual default instance — exactly RFC-0006’scomponent.destroysemantics.
Instance Method Details
#add(component_class) ⇒ EcsRails::Component
Ensures a row exists for component_class on this entity and returns the
now-persisted instance (RFC-0009).
Immediate, not deferred to the next save: the whole reason add exists
is that the save cascade never persists a marker. Idempotent: an existing
row is returned untouched, never a second INSERT — the unique entity_id
index (ADR-0005) would forbid one, and correctness should not depend on
catching that. Validated: it uses save!, so add(Email) raises
RecordInvalid (address is required) while add(Moderator) always
succeeds. You cannot add an empty required component (ADR-0009).
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/ecs_rails/presence.rb', line 70 def add(component_class) name = ecs_presence_reader(component_class) # The reader goes through RFC-0006's memo. When no row exists on this # instance it loads one (a SELECT) or builds a virtual; when a row does # exist — including one this instance already added — it is already the # persisted instance, so we return it and no second INSERT is attempted. component = public_send(name) return component if component.persisted? # A virtual instance: persist it. entity_id is restamped for the same # reason the cascade does (RFC-0006) — on a freshly built virtual for a # new entity it may not have been set yet. The memo now holds this same, # persisted instance, so a later `entity.moderator` answers from it with # no further query. component.entity_id = id component.save! component end |
#has?(component_class) ⇒ Boolean
True iff a row exists for component_class on this entity (RFC-0009).
Side-effect-free, like valid? (RFC-0007): it never materialises,
persists, or dirties anything. It consults RFC-0006’s memo first — a
component dirtied-and-saved on this instance is present without a fresh
query — and otherwise issues a bare existence check (SELECT … EXISTS),
a load of nothing.
The DSL also generates a per-component predicate, so user.moderator?
asks exactly this question.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/ecs_rails/presence.rb', line 111 def has?(component_class) name = ecs_presence_reader(component_class) # Memo first: if this instance already loaded (and persisted) the # component, the answer is known without touching the database. A merely # virtual memo entry is *not* proof of absence — a row could have been # written elsewhere — so it falls through to the existence check rather # than answering false from memory. cached = @ecs_components&.[](name) return true if cached&.persisted? # Bare existence check: no row is instantiated, so nothing is loaded and # nothing is dirtied. One query per component, as everywhere else in the # gem (architecture.md §7 non-goal: query optimisation). component_class.where(entity_id: id).exists? end |
#remove(component_class) ⇒ self
Destroys the row for component_class if present, and resets the reader
to a virtual default instance — exactly RFC-0006’s component.destroy
semantics. Idempotent when absent. Returns the entity (RFC-0009).
142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/ecs_rails/presence.rb', line 142 def remove(component_class) name = ecs_presence_reader(component_class) # Reach the component through the reader so a present row is loaded with # its callbacks intact. `component.destroy` fires Lazy::Component's # after_destroy, which calls #ecs_forget_component here — dropping the # memo and nilling the association, so the next read rebuilds a virtual. # When nothing is there to destroy the read leaves a virtual in the memo, # which is already the reset state, so remove is idempotent. component = public_send(name) component.destroy if component.persisted? self end |