Class: Petra::Components::Section
- Inherits:
-
Object
- Object
- Petra::Components::Section
- Defined in:
- lib/petra/components/section.rb
Instance Attribute Summary collapse
-
#savepoint ⇒ Object
readonly
Returns the value of attribute savepoint.
-
#transaction ⇒ Object
readonly
Returns the value of attribute transaction.
Instance Method Summary collapse
- #apply_log_entries! ⇒ Object
-
#attribute_change_vetoes ⇒ Object
Holds all attribute change vetoes for the current section.
-
#created_objects ⇒ Array<Petra::Proxies::ObjectProxy>
It does not matter whether the section was persisted or not in this case, the only condition is that the object was “object_persisted” after its initialization.
-
#destroyed_objects ⇒ Array<Petra::Proxies::ObjectProxies>
Objects which were destroyed during the current section.
- #enqueue_for_persisting! ⇒ Object
-
#initialize(transaction, savepoint: nil) ⇒ Section
constructor
A new instance of Section.
-
#initialized_objects ⇒ Array<Petra::Proxies::ObjectProxy>
Objects which were initialized, but not yet persisted during this section.
-
#initialized_or_created_objects ⇒ Object
This method will also return objects which were not yet ‘object_persisted`, e.g.
-
#log_attribute_change(proxy, attribute:, old_value:, new_value:, method: nil) ⇒ Object
Generates a log entry for an attribute change in a certain object.
-
#log_attribute_change_veto(proxy, attribute:, external_value:) ⇒ Object
Logs the fact that the user decided to “undo” all previous changes made to the given attribute.
-
#log_attribute_read(proxy, attribute:, value:, method: nil, **options) ⇒ Object
Generates a log entry for an attribute read in a certain object.
- #log_entries ⇒ Petra::Components::EntrySet
-
#log_object_destruction(proxy, method: nil) ⇒ Object
Logs the destruction of an object.
-
#log_object_initialization(proxy, method: nil) ⇒ Object
Logs the initialization of an object.
-
#log_object_persistence(proxy, method: nil, args: []) ⇒ Object
Logs the persistence of an object.
-
#log_read_integrity_override(proxy, attribute:, external_value:, update_value: false) ⇒ Object
Logs the fact that the user decided to ignore further ReadIntegrityErrors on the given attribute as long as its external value stays the same.
-
#objects ⇒ Array<Petra::Proxies::ObjectProxy>
All Objects that were part of this section.
- #persisted? ⇒ Boolean
- #prepare_for_retry! ⇒ Object
-
#read_attributes ⇒ Hash<Petra::Proxies::ObjectProxy, Array<String,Symbol>>
Only entries which were previously marked as object persisted are taken into account.
-
#read_integrity_override(proxy, attribute:) ⇒ Object
The external value at the time the requested read integrity override was placed.
-
#read_integrity_override?(proxy, attribute:) ⇒ Boolean
trueif there is a read integrity override for the given attribute name. -
#read_integrity_overrides ⇒ Object
Holds all read integrity overrides which were generated during this section.
-
#read_objects ⇒ Array<Petra::Proxies::ObjectProxy>
Objects that were read during this section Only read log entries which were marked as object persisted are taken into account.
-
#read_set ⇒ Object
Holds the values which were last read from attribute readers.
-
#read_value_for(proxy, attribute:) ⇒ Object, NilClass
The attribute value which was read from the original object during this section or
nil. -
#read_value_for?(proxy, attribute:) ⇒ Boolean
trueif a new object attribute with the given name was read during this section. - #recently_initialized_object!(proxy) ⇒ Object
- #recently_initialized_object?(proxy) ⇒ Boolean
-
#recently_initialized_objects ⇒ Object
As objects which were initialized inside a transaction receive a temporary ID whose generation again requires knowledge about their membership regarding the below object sets leading to an infinite loop, we have to keep a temporary list of object ids (ruby) until they received their transaction object id.
-
#reset! ⇒ Object
Removes all log entries and empties the read and write set.
-
#savepoint_version ⇒ Fixnum
The savepoint’s version number.
-
#value_for(proxy, attribute:) ⇒ Object, NilClass
The value which was set for the given attribute during this session.
-
#value_for?(proxy, attribute:) ⇒ Boolean
trueif this section’s write set contains a value for the given attribute (if a new value was set during this section). -
#write_set ⇒ Object
The write set in a section only holds the latest value for each attribute/object combination.
Constructor Details
#initialize(transaction, savepoint: nil) ⇒ Section
Returns a new instance of Section.
10 11 12 13 14 |
# File 'lib/petra/components/section.rb', line 10 def initialize(transaction, savepoint: nil) @transaction = transaction @savepoint = savepoint || next_savepoint_name load_persisted_log_entries end |
Instance Attribute Details
#savepoint ⇒ Object (readonly)
Returns the value of attribute savepoint.
8 9 10 |
# File 'lib/petra/components/section.rb', line 8 def savepoint @savepoint end |
#transaction ⇒ Object (readonly)
Returns the value of attribute transaction.
7 8 9 |
# File 'lib/petra/components/section.rb', line 7 def transaction @transaction end |
Instance Method Details
#apply_log_entries! ⇒ Object
429 430 431 |
# File 'lib/petra/components/section.rb', line 429 def apply_log_entries! log_entries.apply! end |
#attribute_change_vetoes ⇒ Object
Holds all attribute change vetoes for the current section. If an attribute key is in this hash, it means that all previous changes made to it should be voided.
If an attribute is changed again after a veto was added, it is removed from this hash.
67 68 69 |
# File 'lib/petra/components/section.rb', line 67 def attribute_change_vetoes @attribute_change_vetoes ||= {} end |
#created_objects ⇒ Array<Petra::Proxies::ObjectProxy>
It does not matter whether the section was persisted or not in this case, the only condition is that the object was “object_persisted” after its initialization
368 369 370 371 372 |
# File 'lib/petra/components/section.rb', line 368 def created_objects cache_if_persisted(:created_objects) do log_entries.of_kind(:object_initialization).object_persisted.map(&:load_proxy).uniq end end |
#destroyed_objects ⇒ Array<Petra::Proxies::ObjectProxies>
Returns Objects which were destroyed during the current section.
400 401 402 403 404 |
# File 'lib/petra/components/section.rb', line 400 def destroyed_objects cache_if_persisted(:destroyed_objects) do log_entries.of_kind(:object_destruction).map(&:load_proxy).uniq end end |
#enqueue_for_persisting! ⇒ Object
436 437 438 439 |
# File 'lib/petra/components/section.rb', line 436 def enqueue_for_persisting! log_entries.enqueue_for_persisting! @persisted = true end |
#initialized_objects ⇒ Array<Petra::Proxies::ObjectProxy>
Returns Objects which were initialized, but not yet persisted during this section. This may only be the case for the current section.
378 379 380 381 382 |
# File 'lib/petra/components/section.rb', line 378 def initialized_objects cache_if_persisted(:initialized_objects) do log_entries.of_kind(:object_initialization).not_object_persisted.map(&:load_proxy).uniq end end |
#initialized_or_created_objects ⇒ Object
This method will also return objects which were not yet ‘object_persisted`, e.g. to be used during the current transaction section
390 391 392 393 394 |
# File 'lib/petra/components/section.rb', line 390 def initialized_or_created_objects cache_if_persisted(:initialized_or_created_objects) do (initialized_objects + created_objects).uniq end end |
#log_attribute_change(proxy, attribute:, old_value:, new_value:, method: nil) ⇒ Object
Generates a log entry for an attribute change in a certain object. If old and new value are the same, no log entry is created.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/petra/components/section.rb', line 153 def log_attribute_change(proxy, attribute:, old_value:, new_value:, method: nil) # Generate a read set entry if we didn't read this attribute before. # This is necessary as real attribute reads are not necessarily performed in the same section # as attribute changes and persistence (e.g. #edit and #update in Rails) # This has to be done even if the attribute wasn't really changed as the user most likely # saw the current value and therefore decided not to change it. unless transaction.read_attribute_value?(proxy, attribute: attribute) log_attribute_read(proxy, attribute: attribute, value: old_value, method: method) end return if old_value == new_value # Replace any existing value for the current attribute in the # memory write set with the new value add_to_write_set(proxy, attribute, new_value) add_log_entry(proxy, attribute: attribute, method: method, kind: 'attribute_change', old_value: old_value, new_value: new_value) Petra.logger.info "Logged attribute change (#{old_value} => #{new_value})", :yellow end |
#log_attribute_change_veto(proxy, attribute:, external_value:) ⇒ Object
Logs the fact that the user decided to “undo” all previous changes made to the given attribute
293 294 295 296 297 298 299 300 301 |
# File 'lib/petra/components/section.rb', line 293 def log_attribute_change_veto(proxy, attribute:, external_value:) add_log_entry(proxy, kind: 'attribute_change_veto', attribute: attribute, external_value: external_value) # Also log the current external attribute value, so the transaction uses the newest available one log_attribute_read(proxy, attribute: attribute, value: external_value, persist_on_retry: true) end |
#log_attribute_read(proxy, attribute:, value:, method: nil, **options) ⇒ Object
Generates a log entry for an attribute read in a certain object.
183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/petra/components/section.rb', line 183 def log_attribute_read(proxy, attribute:, value:, method: nil, **) add_to_read_set(proxy, attribute, value) add_log_entry(proxy, attribute: attribute, method: method, kind: 'attribute_read', value: value, **) Petra.logger.info "Logged attribute read (#{attribute} => #{value})", :yellow true end |
#log_entries ⇒ Petra::Components::EntrySet
130 131 132 |
# File 'lib/petra/components/section.rb', line 130 def log_entries @log_entries ||= EntrySet.new end |
#log_object_destruction(proxy, method: nil) ⇒ Object
Logs the destruction of an object. Currently, this is only used with ActiveRecord::Base instances, but there might be a way to handle GC with normal ruby objects (attach a handler to at least get notified).
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/petra/components/section.rb', line 247 def log_object_destruction(proxy, method: nil) # Destruction is a form of persistence, resp. its opposite. # Therefore, we have to make sure that any other log entries for this # object will be transaction persisted as the may have lead to the object's destruction. # # Currently, this happens even if the object hasn't been persisted prior to # its destruction which is accepted behaviour e.g. by ActiveRecord instances. # We'll have to see if this should stay the common behaviour. log_entries.for_proxy(proxy).each(&:mark_as_object_persisted!) # As for attribute persistence, every attribute which was read in the current section # might have had impact on the destruction of this object. Therefore, we have # to make sure that all these log entries will be persisted. log_entries.of_kind(:attribute_read).each(&:mark_as_object_persisted!) add_log_entry(proxy, kind: 'object_destruction', method: method, object_persisted: true) true end |
#log_object_initialization(proxy, method: nil) ⇒ Object
Logs the initialization of an object
199 200 201 202 203 204 205 206 207 |
# File 'lib/petra/components/section.rb', line 199 def log_object_initialization(proxy, method: nil) # Mark this object as recently initialized recently_initialized_object!(proxy) add_log_entry(proxy, kind: 'object_initialization', method: method) true end |
#log_object_persistence(proxy, method: nil, args: []) ⇒ Object
Logs the persistence of an object. This basically means that the attribute updates were written to a shared memory. This might simply be the process memory for normal ruby objects, but might also be a call to save() or update() for ActiveRecord::Base instances.
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/petra/components/section.rb', line 220 def log_object_persistence(proxy, method: nil, args: []) # All log entries for the current object prior to this persisting method # have to be persisted as the object itself is. # This includes the object initialization log entry log_entries.for_proxy(proxy).each(&:mark_as_object_persisted!) # All attribute reads prior to this have to be persisted # as they might have had impact on the current object state. # This does not only include the current object, but everything that was # read until now! # TODO: Could this be more intelligent? log_entries.of_kind(:attribute_read).each(&:mark_as_object_persisted!) add_log_entry(proxy, method: method, kind: 'object_persistence', object_persisted: true, args: args) true end |
#log_read_integrity_override(proxy, attribute:, external_value:, update_value: false) ⇒ Object
Logs the fact that the user decided to ignore further ReadIntegrityErrors on the given attribute as long as its external value stays the same.
279 280 281 282 283 284 285 286 287 |
# File 'lib/petra/components/section.rb', line 279 def log_read_integrity_override(proxy, attribute:, external_value:, update_value: false) add_log_entry(proxy, kind: 'read_integrity_override', attribute: attribute, external_value: external_value) # If requested, add a new read log entry for the new external value log_attribute_read(proxy, attribute: attribute, value: external_value, persist_on_retry: true) if update_value end |
#objects ⇒ Array<Petra::Proxies::ObjectProxy>
Returns All Objects that were part of this section. Only log entries marked as object persisted are taken into account.
346 347 348 349 350 |
# File 'lib/petra/components/section.rb', line 346 def objects cache_if_persisted(:all_objects) do log_entries.object_persisted.map(&:load_proxy).uniq end end |
#persisted? ⇒ Boolean
23 24 25 |
# File 'lib/petra/components/section.rb', line 23 def persisted? !!@persisted end |
#prepare_for_retry! ⇒ Object
422 423 424 |
# File 'lib/petra/components/section.rb', line 422 def prepare_for_retry! log_entries.prepare_for_retry! end |
#read_attributes ⇒ Hash<Petra::Proxies::ObjectProxy, Array<String,Symbol>>
Only entries which were previously marked as object persisted are taken into account.
333 334 335 336 337 338 339 340 |
# File 'lib/petra/components/section.rb', line 333 def read_attributes cache_if_persisted(:read_attributes) do log_entries.of_kind(:attribute_read).object_persisted.each_with_object({}) do |entry, h| h[entry.load_proxy] ||= [] h[entry.load_proxy] << entry.attribute unless h[entry.load_proxy].include?(entry.attribute) end end end |
#read_integrity_override(proxy, attribute:) ⇒ Object
Returns The external value at the time the requested read integrity override was placed.
119 120 121 |
# File 'lib/petra/components/section.rb', line 119 def read_integrity_override(proxy, attribute:) read_integrity_overrides[proxy.__attribute_key(attribute)] end |
#read_integrity_override?(proxy, attribute:) ⇒ Boolean
Returns true if there is a read integrity override for the given attribute name.
111 112 113 |
# File 'lib/petra/components/section.rb', line 111 def read_integrity_override?(proxy, attribute:) read_integrity_overrides.key?(proxy.__attribute_key(attribute)) end |
#read_integrity_overrides ⇒ Object
Holds all read integrity overrides which were generated during this section. There should normally only be one per section.
The hash maps attribute keys to the external value at the time the corresponding log entry was generated. Please take a look at Petra::Components::Entries::ReadIntegrityOverride for more information about this kind of log entry.
55 56 57 |
# File 'lib/petra/components/section.rb', line 55 def read_integrity_overrides @read_integrity_overrides ||= {} end |
#read_objects ⇒ Array<Petra::Proxies::ObjectProxy>
Returns Objects that were read during this section Only read log entries which were marked as object persisted are taken into account.
356 357 358 359 360 |
# File 'lib/petra/components/section.rb', line 356 def read_objects cache_if_persisted(:read_objects) do read_attributes.keys end end |
#read_set ⇒ Object
Holds the values which were last read from attribute readers
34 35 36 |
# File 'lib/petra/components/section.rb', line 34 def read_set @read_set ||= {} end |
#read_value_for(proxy, attribute:) ⇒ Object, NilClass
Returns the attribute value which was read from the original object during this section or nil. Please check whether the attribute was read at all during this section using #read_value_for?.
103 104 105 |
# File 'lib/petra/components/section.rb', line 103 def read_value_for(proxy, attribute:) read_set[proxy.__attribute_key(attribute)] end |
#read_value_for?(proxy, attribute:) ⇒ Boolean
Returns true if a new object attribute with the given name was read during this section. Each attribute is only put into the read set once - except for when the read value wasn’t used afterwards (no persistence).
94 95 96 |
# File 'lib/petra/components/section.rb', line 94 def read_value_for?(proxy, attribute:) read_set.key?(proxy.__attribute_key(attribute)) end |
#recently_initialized_object!(proxy) ⇒ Object
318 319 320 |
# File 'lib/petra/components/section.rb', line 318 def recently_initialized_object!(proxy) recently_initialized_objects << proxy.send(:proxied_object).object_id end |
#recently_initialized_object?(proxy) ⇒ Boolean
322 323 324 |
# File 'lib/petra/components/section.rb', line 322 def recently_initialized_object?(proxy) recently_initialized_objects.include?(proxy.send(:proxied_object).object_id) end |
#recently_initialized_objects ⇒ Object
As objects which were initialized inside a transaction receive a temporary ID whose generation again requires knowledge about their membership regarding the below object sets leading to an infinite loop, we have to keep a temporary list of object ids (ruby) until they received their transaction object id
314 315 316 |
# File 'lib/petra/components/section.rb', line 314 def recently_initialized_objects @recently_initialized_objects ||= [] end |
#reset! ⇒ Object
Removes all log entries and empties the read and write set. This should only be done on the current section and as long as the log entries haven’t been persisted.
415 416 417 418 419 420 |
# File 'lib/petra/components/section.rb', line 415 def reset! fail Petra::PetraError, 'An already persisted section may not be reset' if persisted? @log_entries = [] @read_set = [] @write_set = [] end |
#savepoint_version ⇒ Fixnum
Returns the savepoint’s version number.
19 20 21 |
# File 'lib/petra/components/section.rb', line 19 def savepoint_version savepoint.split('/')[1].to_i end |
#value_for(proxy, attribute:) ⇒ Object, NilClass
Returns the value which was set for the given attribute during this session. Please note that setting attributes to nil is normal behaviour, so please make sure you always check whether there actually is value in the write set using #value_for?.
77 78 79 |
# File 'lib/petra/components/section.rb', line 77 def value_for(proxy, attribute:) write_set[proxy.__attribute_key(attribute)] end |
#value_for?(proxy, attribute:) ⇒ Boolean
Returns true if this section’s write set contains a value for the given attribute (if a new value was set during this section).
85 86 87 |
# File 'lib/petra/components/section.rb', line 85 def value_for?(proxy, attribute:) write_set.key?(proxy.__attribute_key(attribute)) end |
#write_set ⇒ Object
The write set in a section only holds the latest value for each attribute/object combination. The change history is done using log entries. Therefore, the write set is a simple hash mapping object-attribute-keys to their latest value.
43 44 45 |
# File 'lib/petra/components/section.rb', line 43 def write_set @write_set ||= {} end |