petra_core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.rubocop.yml +83 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +74 -0
- data/MIT-LICENSE +20 -0
- data/README.md +726 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/examples/continuation_error.rb +125 -0
- data/examples/dining_philosophers.rb +138 -0
- data/examples/showcase.rb +54 -0
- data/lib/petra/components/entries/attribute_change.rb +29 -0
- data/lib/petra/components/entries/attribute_change_veto.rb +37 -0
- data/lib/petra/components/entries/attribute_read.rb +20 -0
- data/lib/petra/components/entries/object_destruction.rb +22 -0
- data/lib/petra/components/entries/object_initialization.rb +19 -0
- data/lib/petra/components/entries/object_persistence.rb +26 -0
- data/lib/petra/components/entries/read_integrity_override.rb +42 -0
- data/lib/petra/components/entry_set.rb +87 -0
- data/lib/petra/components/log_entry.rb +342 -0
- data/lib/petra/components/proxy_cache.rb +209 -0
- data/lib/petra/components/section.rb +543 -0
- data/lib/petra/components/transaction.rb +405 -0
- data/lib/petra/components/transaction_manager.rb +214 -0
- data/lib/petra/configuration/base.rb +132 -0
- data/lib/petra/configuration/class_configurator.rb +309 -0
- data/lib/petra/configuration/configurator.rb +67 -0
- data/lib/petra/core_ext.rb +27 -0
- data/lib/petra/exceptions.rb +181 -0
- data/lib/petra/persistence_adapters/adapter.rb +154 -0
- data/lib/petra/persistence_adapters/file_adapter.rb +239 -0
- data/lib/petra/proxies/abstract_proxy.rb +149 -0
- data/lib/petra/proxies/enumerable_proxy.rb +44 -0
- data/lib/petra/proxies/handlers/attribute_read_handler.rb +45 -0
- data/lib/petra/proxies/handlers/missing_method_handler.rb +47 -0
- data/lib/petra/proxies/method_handlers.rb +213 -0
- data/lib/petra/proxies/module_proxy.rb +12 -0
- data/lib/petra/proxies/object_proxy.rb +310 -0
- data/lib/petra/util/debug.rb +45 -0
- data/lib/petra/util/extended_attribute_accessors.rb +51 -0
- data/lib/petra/util/field_accessors.rb +35 -0
- data/lib/petra/util/registrable.rb +48 -0
- data/lib/petra/util/test_helpers.rb +9 -0
- data/lib/petra/version.rb +5 -0
- data/lib/petra.rb +100 -0
- data/lib/tasks/petra_tasks.rake +5 -0
- data/petra.gemspec +36 -0
- metadata +208 -0
@@ -0,0 +1,543 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
class Section
|
6
|
+
|
7
|
+
attr_reader :transaction
|
8
|
+
attr_reader :savepoint
|
9
|
+
|
10
|
+
def initialize(transaction, savepoint: nil)
|
11
|
+
@transaction = transaction
|
12
|
+
@savepoint = savepoint || next_savepoint_name
|
13
|
+
load_persisted_log_entries
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# @return [Fixnum] the savepoint's version number
|
18
|
+
#
|
19
|
+
def savepoint_version
|
20
|
+
savepoint.split('/')[1].to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def persisted?
|
24
|
+
!!@persisted
|
25
|
+
end
|
26
|
+
|
27
|
+
#----------------------------------------------------------------
|
28
|
+
# Read / Write set
|
29
|
+
#----------------------------------------------------------------
|
30
|
+
|
31
|
+
#
|
32
|
+
# Holds the values which were last read from attribute readers
|
33
|
+
#
|
34
|
+
def read_set
|
35
|
+
@read_set ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# The write set in a section only holds the latest value for each
|
40
|
+
# attribute/object combination. The change history is done using log entries.
|
41
|
+
# Therefore, the write set is a simple hash mapping object-attribute-keys to their latest value.
|
42
|
+
#
|
43
|
+
def write_set
|
44
|
+
@write_set ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Holds all read integrity overrides which were generated during this section.
|
49
|
+
# There should normally only be one per section.
|
50
|
+
#
|
51
|
+
# The hash maps attribute keys to the external value at the time the corresponding
|
52
|
+
# log entry was generated. Please take a look at Petra::Components::Entries::ReadIntegrityOverride
|
53
|
+
# for more information about this kind of log entry.
|
54
|
+
#
|
55
|
+
def read_integrity_overrides
|
56
|
+
@read_integrity_overrides ||= {}
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Holds all attribute change vetoes for the current section.
|
61
|
+
# If an attribute key is in this hash, it means that all previous changes
|
62
|
+
# made to it should be voided.
|
63
|
+
#
|
64
|
+
# If an attribute is changed again after a veto was added, it is removed from
|
65
|
+
# this hash.
|
66
|
+
#
|
67
|
+
def attribute_change_vetoes
|
68
|
+
@attribute_change_vetoes ||= {}
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# @return [Object, NilClass] the value which was set for the given attribute
|
73
|
+
# during this session. Please note that setting attributes to +nil+ is normal behaviour,
|
74
|
+
# so please make sure you always check whether there actually is value in the write set
|
75
|
+
# using #value_for?
|
76
|
+
#
|
77
|
+
def value_for(proxy, attribute:)
|
78
|
+
write_set[proxy.__attribute_key(attribute)]
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# @return [Boolean] +true+ if this section's write set contains a value
|
83
|
+
# for the given attribute (if a new value was set during this section)
|
84
|
+
#
|
85
|
+
def value_for?(proxy, attribute:)
|
86
|
+
write_set.key?(proxy.__attribute_key(attribute))
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# @return [Boolean] +true+ if a new object attribute with the given name
|
91
|
+
# was read during this section. Each attribute is only put into the read set once - except
|
92
|
+
# for when the read value wasn't used afterwards (no persistence)
|
93
|
+
#
|
94
|
+
def read_value_for?(proxy, attribute:)
|
95
|
+
read_set.key?(proxy.__attribute_key(attribute))
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# @return [Object, NilClass] the attribute value which was read from the original object
|
100
|
+
# during this section or +nil+. Please check whether the attribute was read at all during
|
101
|
+
# this section using #read_value_for?
|
102
|
+
#
|
103
|
+
def read_value_for(proxy, attribute:)
|
104
|
+
read_set[proxy.__attribute_key(attribute)]
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# @return [Boolean] +true+ if there is a read integrity override for
|
109
|
+
# the given attribute name
|
110
|
+
#
|
111
|
+
def read_integrity_override?(proxy, attribute:)
|
112
|
+
read_integrity_overrides.key?(proxy.__attribute_key(attribute))
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# @return [Object] The external value at the time the requested
|
117
|
+
# read integrity override was placed.
|
118
|
+
#
|
119
|
+
def read_integrity_override(proxy, attribute:)
|
120
|
+
read_integrity_overrides[proxy.__attribute_key(attribute)]
|
121
|
+
end
|
122
|
+
|
123
|
+
#----------------------------------------------------------------
|
124
|
+
# Log Entries
|
125
|
+
#----------------------------------------------------------------
|
126
|
+
|
127
|
+
#
|
128
|
+
# @return [Petra::Components::EntrySet]
|
129
|
+
#
|
130
|
+
def log_entries
|
131
|
+
@log_entries ||= EntrySet.new
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Generates a log entry for an attribute change in a certain object.
|
136
|
+
# If old and new value are the same, no log entry is created.
|
137
|
+
#
|
138
|
+
# @param [Petra::Components::ObjectProxy] proxy
|
139
|
+
# The proxy which received the method call to change the attribute
|
140
|
+
#
|
141
|
+
# @param [String, Symbol] attribute
|
142
|
+
# The name of the attribute which was changed
|
143
|
+
#
|
144
|
+
# @param [Object] old_value
|
145
|
+
# The attribute's value before the change
|
146
|
+
#
|
147
|
+
# @param [Object] new_value
|
148
|
+
# The attribute's new value
|
149
|
+
#
|
150
|
+
# @param [String, Symbol] method
|
151
|
+
# The method which was used to change the attribute
|
152
|
+
#
|
153
|
+
def log_attribute_change(proxy, attribute:, old_value:, new_value:, method: nil)
|
154
|
+
# Generate a read set entry if we didn't read this attribute before.
|
155
|
+
# This is necessary as real attribute reads are not necessarily performed in the same section
|
156
|
+
# as attribute changes and persistence (e.g. #edit and #update in Rails)
|
157
|
+
# This has to be done even if the attribute wasn't really changed as the user most likely
|
158
|
+
# saw the current value and therefore decided not to change it.
|
159
|
+
unless transaction.read_attribute_value?(proxy, attribute: attribute)
|
160
|
+
log_attribute_read(proxy, attribute: attribute, value: old_value, method: method)
|
161
|
+
end
|
162
|
+
|
163
|
+
return if old_value == new_value
|
164
|
+
|
165
|
+
# Replace any existing value for the current attribute in the
|
166
|
+
# memory write set with the new value
|
167
|
+
add_to_write_set(proxy, attribute, new_value)
|
168
|
+
add_log_entry(proxy,
|
169
|
+
attribute: attribute,
|
170
|
+
method: method,
|
171
|
+
kind: 'attribute_change',
|
172
|
+
old_value: old_value,
|
173
|
+
new_value: new_value)
|
174
|
+
|
175
|
+
Petra.logger.info "Logged attribute change (#{old_value} => #{new_value})", :yellow
|
176
|
+
end
|
177
|
+
|
178
|
+
#
|
179
|
+
# Generates a log entry for an attribute read in a certain object.
|
180
|
+
#
|
181
|
+
# @see #log_attribute_change for parameter details
|
182
|
+
#
|
183
|
+
def log_attribute_read(proxy, attribute:, value:, method: nil, **options)
|
184
|
+
add_to_read_set(proxy, attribute, value)
|
185
|
+
add_log_entry(proxy,
|
186
|
+
attribute: attribute,
|
187
|
+
method: method,
|
188
|
+
kind: 'attribute_read',
|
189
|
+
value: value,
|
190
|
+
**options)
|
191
|
+
|
192
|
+
Petra.logger.info "Logged attribute read (#{attribute} => #{value})", :yellow
|
193
|
+
true
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# Logs the initialization of an object
|
198
|
+
#
|
199
|
+
def log_object_initialization(proxy, method: nil)
|
200
|
+
# Mark this object as recently initialized
|
201
|
+
recently_initialized_object!(proxy)
|
202
|
+
|
203
|
+
add_log_entry(proxy,
|
204
|
+
kind: 'object_initialization',
|
205
|
+
method: method)
|
206
|
+
true
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Logs the persistence of an object. This basically means that the attribute updates were
|
211
|
+
# written to a shared memory. This might simply be the process memory for normal ruby objects,
|
212
|
+
# but might also be a call to save() or update() for ActiveRecord::Base instances.
|
213
|
+
#
|
214
|
+
# @param [Petra::Components::ObjectProxy] proxy
|
215
|
+
# The proxy which received the method call
|
216
|
+
#
|
217
|
+
# @param [String, Symbol] method
|
218
|
+
# The method which caused the persistence change
|
219
|
+
#
|
220
|
+
def log_object_persistence(proxy, method: nil, args: [])
|
221
|
+
# All log entries for the current object prior to this persisting method
|
222
|
+
# have to be persisted as the object itself is.
|
223
|
+
# This includes the object initialization log entry
|
224
|
+
log_entries.for_proxy(proxy).each(&:mark_as_object_persisted!)
|
225
|
+
|
226
|
+
# All attribute reads prior to this have to be persisted
|
227
|
+
# as they might have had impact on the current object state.
|
228
|
+
# This does not only include the current object, but everything that was
|
229
|
+
# read until now!
|
230
|
+
# TODO: Could this be more intelligent?
|
231
|
+
log_entries.of_kind(:attribute_read).each(&:mark_as_object_persisted!)
|
232
|
+
|
233
|
+
add_log_entry(proxy,
|
234
|
+
method: method,
|
235
|
+
kind: 'object_persistence',
|
236
|
+
object_persisted: true,
|
237
|
+
args: args)
|
238
|
+
|
239
|
+
true
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
# Logs the destruction of an object.
|
244
|
+
# Currently, this is only used with ActiveRecord::Base instances, but there might
|
245
|
+
# be a way to handle GC with normal ruby objects (attach a handler to at least get notified).
|
246
|
+
#
|
247
|
+
def log_object_destruction(proxy, method: nil)
|
248
|
+
# Destruction is a form of persistence, resp. its opposite.
|
249
|
+
# Therefore, we have to make sure that any other log entries for this
|
250
|
+
# object will be transaction persisted as the may have lead to the object's destruction.
|
251
|
+
#
|
252
|
+
# Currently, this happens even if the object hasn't been persisted prior to
|
253
|
+
# its destruction which is accepted behaviour e.g. by ActiveRecord instances.
|
254
|
+
# We'll have to see if this should stay the common behaviour.
|
255
|
+
log_entries.for_proxy(proxy).each(&:mark_as_object_persisted!)
|
256
|
+
|
257
|
+
# As for attribute persistence, every attribute which was read in the current section
|
258
|
+
# might have had impact on the destruction of this object. Therefore, we have
|
259
|
+
# to make sure that all these log entries will be persisted.
|
260
|
+
log_entries.of_kind(:attribute_read).each(&:mark_as_object_persisted!)
|
261
|
+
|
262
|
+
add_log_entry(proxy,
|
263
|
+
kind: 'object_destruction',
|
264
|
+
method: method,
|
265
|
+
object_persisted: true)
|
266
|
+
true
|
267
|
+
end
|
268
|
+
|
269
|
+
#
|
270
|
+
# Logs the fact that the user decided to ignore further ReadIntegrityErrors
|
271
|
+
# on the given attribute as long as its external value stays the same.
|
272
|
+
#
|
273
|
+
# @param [Boolean] update_value
|
274
|
+
# If +true+, a new read set entry is generated along with the RIO one.
|
275
|
+
# This will cause the transaction to display the new external value instead of the
|
276
|
+
# one we last read and will also automatically invalidate the RIO entry which
|
277
|
+
# is only kept to have the whole transaction time line.
|
278
|
+
#
|
279
|
+
def log_read_integrity_override(proxy, attribute:, external_value:, update_value: false)
|
280
|
+
add_log_entry(proxy,
|
281
|
+
kind: 'read_integrity_override',
|
282
|
+
attribute: attribute,
|
283
|
+
external_value: external_value)
|
284
|
+
|
285
|
+
# If requested, add a new read log entry for the new external value
|
286
|
+
log_attribute_read(proxy, attribute: attribute, value: external_value, persist_on_retry: true) if update_value
|
287
|
+
end
|
288
|
+
|
289
|
+
#
|
290
|
+
# Logs the fact that the user decided to "undo" all previous changes
|
291
|
+
# made to the given attribute
|
292
|
+
#
|
293
|
+
def log_attribute_change_veto(proxy, attribute:, external_value:)
|
294
|
+
add_log_entry(proxy,
|
295
|
+
kind: 'attribute_change_veto',
|
296
|
+
attribute: attribute,
|
297
|
+
external_value: external_value)
|
298
|
+
|
299
|
+
# Also log the current external attribute value, so the transaction uses the newest available one
|
300
|
+
log_attribute_read(proxy, attribute: attribute, value: external_value, persist_on_retry: true)
|
301
|
+
end
|
302
|
+
|
303
|
+
#----------------------------------------------------------------
|
304
|
+
# Object Handling
|
305
|
+
#----------------------------------------------------------------
|
306
|
+
|
307
|
+
#
|
308
|
+
# As objects which were initialized inside a transaction receive
|
309
|
+
# a temporary ID whose generation again requires knowledge about
|
310
|
+
# their membership regarding the below object sets leading to an
|
311
|
+
# infinite loop, we have to keep a temporary list of object ids (ruby)
|
312
|
+
# until they received their transaction object id
|
313
|
+
#
|
314
|
+
def recently_initialized_objects
|
315
|
+
@recently_initialized_objects ||= []
|
316
|
+
end
|
317
|
+
|
318
|
+
def recently_initialized_object!(proxy)
|
319
|
+
recently_initialized_objects << proxy.send(:proxied_object).object_id
|
320
|
+
end
|
321
|
+
|
322
|
+
def recently_initialized_object?(proxy)
|
323
|
+
recently_initialized_objects.include?(proxy.send(:proxied_object).object_id)
|
324
|
+
end
|
325
|
+
|
326
|
+
#
|
327
|
+
# @return [Hash<Petra::Proxies::ObjectProxy, Array<String,Symbol>>]
|
328
|
+
# All attributes which were read during this section grouped by the objects (proxies)
|
329
|
+
# they belong to.
|
330
|
+
#
|
331
|
+
# Only entries which were previously marked as object persisted are taken into account.
|
332
|
+
#
|
333
|
+
def read_attributes
|
334
|
+
cache_if_persisted(:read_attributes) do
|
335
|
+
log_entries.of_kind(:attribute_read).object_persisted.each_with_object({}) do |entry, h|
|
336
|
+
h[entry.load_proxy] ||= []
|
337
|
+
h[entry.load_proxy] << entry.attribute unless h[entry.load_proxy].include?(entry.attribute)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
#
|
343
|
+
# @return [Array<Petra::Proxies::ObjectProxy>] All Objects that were part of this section.
|
344
|
+
# Only log entries marked as object persisted are taken into account
|
345
|
+
#
|
346
|
+
def objects
|
347
|
+
cache_if_persisted(:all_objects) do
|
348
|
+
log_entries.object_persisted.map(&:load_proxy).uniq
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
#
|
353
|
+
# @return [Array<Petra::Proxies::ObjectProxy>] Objects that were read during this section
|
354
|
+
# Only read log entries which were marked as object persisted are taken into account
|
355
|
+
#
|
356
|
+
def read_objects
|
357
|
+
cache_if_persisted(:read_objects) do
|
358
|
+
read_attributes.keys
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
#
|
363
|
+
# @return [Array<Petra::Proxies::ObjectProxy>] Objects that were created during this section.
|
364
|
+
#
|
365
|
+
# It does not matter whether the section was persisted or not in this case,
|
366
|
+
# the only condition is that the object was "object_persisted" after its initialization
|
367
|
+
#
|
368
|
+
def created_objects
|
369
|
+
cache_if_persisted(:created_objects) do
|
370
|
+
log_entries.of_kind(:object_initialization).object_persisted.map(&:load_proxy).uniq
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
#
|
375
|
+
# @return [Array<Petra::Proxies::ObjectProxy>] Objects which were initialized, but not
|
376
|
+
# yet persisted during this section. This may only be the case for the current section
|
377
|
+
#
|
378
|
+
def initialized_objects
|
379
|
+
cache_if_persisted(:initialized_objects) do
|
380
|
+
log_entries.of_kind(:object_initialization).not_object_persisted.map(&:load_proxy).uniq
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
#
|
385
|
+
# @see #created_objects
|
386
|
+
#
|
387
|
+
# This method will also return objects which were not yet `object_persisted`, e.g.
|
388
|
+
# to be used during the current transaction section
|
389
|
+
#
|
390
|
+
def initialized_or_created_objects
|
391
|
+
cache_if_persisted(:initialized_or_created_objects) do
|
392
|
+
(initialized_objects + created_objects).uniq
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
#
|
397
|
+
# @return [Array<Petra::Proxies::ObjectProxies>] Objects which were destroyed
|
398
|
+
# during the current section
|
399
|
+
#
|
400
|
+
def destroyed_objects
|
401
|
+
cache_if_persisted(:destroyed_objects) do
|
402
|
+
log_entries.of_kind(:object_destruction).map(&:load_proxy).uniq
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
#----------------------------------------------------------------
|
407
|
+
# Persistence
|
408
|
+
#----------------------------------------------------------------
|
409
|
+
|
410
|
+
#
|
411
|
+
# Removes all log entries and empties the read and write set.
|
412
|
+
# This should only be done on the current section and as long as the log
|
413
|
+
# entries haven't been persisted.
|
414
|
+
#
|
415
|
+
def reset!
|
416
|
+
fail Petra::PetraError, 'An already persisted section may not be reset' if persisted?
|
417
|
+
@log_entries = []
|
418
|
+
@read_set = []
|
419
|
+
@write_set = []
|
420
|
+
end
|
421
|
+
|
422
|
+
def prepare_for_retry!
|
423
|
+
log_entries.prepare_for_retry!
|
424
|
+
end
|
425
|
+
|
426
|
+
#
|
427
|
+
# @see Petra::Components::EntrySet#apply
|
428
|
+
#
|
429
|
+
def apply_log_entries!
|
430
|
+
log_entries.apply!
|
431
|
+
end
|
432
|
+
|
433
|
+
#
|
434
|
+
# @see Petra::Components::EntrySet#enqueue_for_persisting!
|
435
|
+
#
|
436
|
+
def enqueue_for_persisting!
|
437
|
+
log_entries.enqueue_for_persisting!
|
438
|
+
@persisted = true
|
439
|
+
end
|
440
|
+
|
441
|
+
private
|
442
|
+
|
443
|
+
#
|
444
|
+
# Executes the block and caches its result if the current section has already
|
445
|
+
# been persisted (= won't change any more)
|
446
|
+
#
|
447
|
+
def cache_if_persisted(name)
|
448
|
+
@cache ||= {}
|
449
|
+
return (@cache[name.to_s] ||= yield) if persisted?
|
450
|
+
yield
|
451
|
+
end
|
452
|
+
|
453
|
+
#
|
454
|
+
# Adds a new log entry to the current section.
|
455
|
+
# New log entries are not automatically persisted, this is done through #enqueue_for_persisting!
|
456
|
+
#
|
457
|
+
# @param [Petra::Components::ObjectProxy] proxy
|
458
|
+
#
|
459
|
+
# @param [Boolean] object_persisted
|
460
|
+
#
|
461
|
+
# @param [Hash] options
|
462
|
+
#
|
463
|
+
def add_log_entry(proxy, kind:, object_persisted: false, **options)
|
464
|
+
attribute = options.delete(:attribute)
|
465
|
+
attribute_key = attribute && proxy.__attribute_key(attribute)
|
466
|
+
|
467
|
+
Petra::Components::LogEntry.log!(kind,
|
468
|
+
section: self,
|
469
|
+
transaction_identifier: transaction.identifier,
|
470
|
+
savepoint: savepoint,
|
471
|
+
attribute_key: attribute_key,
|
472
|
+
object_key: proxy.__object_key,
|
473
|
+
object_persisted: object_persisted,
|
474
|
+
transaction_persisted: persisted?,
|
475
|
+
new_object: proxy.__new?,
|
476
|
+
**options).tap do |entry|
|
477
|
+
Petra.logger.debug "Added Log Entry: #{entry}", :yellow
|
478
|
+
log_entries << entry
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
#
|
483
|
+
# In case the this section is not the latest one in the current transaction,
|
484
|
+
# we have to load the steps previously done from the persistence value
|
485
|
+
#
|
486
|
+
# Also sets the `persisted?` flag depending on whether this section has
|
487
|
+
# be previously persisted or not.
|
488
|
+
#
|
489
|
+
def load_persisted_log_entries
|
490
|
+
@log_entries = EntrySet.new(Petra.transaction_manager.persistence_adapter.log_entries(self))
|
491
|
+
@log_entries.each do |entry|
|
492
|
+
if entry.kind?(:attribute_change)
|
493
|
+
write_set[entry.attribute_key] = entry.new_value
|
494
|
+
elsif entry.kind?(:attribute_read)
|
495
|
+
read_set[entry.attribute_key] = entry.value
|
496
|
+
elsif entry.kind?(:read_integrity_override)
|
497
|
+
read_integrity_overrides[entry.attribute_key] = entry.external_value
|
498
|
+
elsif entry.kind?(:attribute_change_veto)
|
499
|
+
attribute_change_vetoes[entry.attribute_key] = entry.external_value
|
500
|
+
# Remove any value changes done to the attribute previously in this section
|
501
|
+
# This will speed up finding active attribute change vetoes as
|
502
|
+
# the search is already canceled if no write set entry exists.
|
503
|
+
write_set.delete(entry.attribute_key)
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
@persisted = @log_entries.any?
|
508
|
+
end
|
509
|
+
|
510
|
+
def proxied_object(proxy)
|
511
|
+
proxy.send(:proxied_object)
|
512
|
+
end
|
513
|
+
|
514
|
+
#
|
515
|
+
# Sets a new value for the given attribute in this section's write set
|
516
|
+
#
|
517
|
+
def add_to_write_set(proxy, attribute, value)
|
518
|
+
write_set[proxy.__attribute_key(attribute)] = value
|
519
|
+
end
|
520
|
+
|
521
|
+
#
|
522
|
+
# @see #add_to_write_set
|
523
|
+
#
|
524
|
+
def add_to_read_set(proxy, attribute, value)
|
525
|
+
read_set[proxy.__attribute_key(attribute)] = value
|
526
|
+
end
|
527
|
+
|
528
|
+
#
|
529
|
+
# Builds the next savepoint name based on the transaction identifier and a version number
|
530
|
+
#
|
531
|
+
def next_savepoint_name
|
532
|
+
version = if transaction.sections.empty?
|
533
|
+
1
|
534
|
+
else
|
535
|
+
transaction.sections.last.savepoint_version + 1
|
536
|
+
end
|
537
|
+
|
538
|
+
[transaction.identifier, version.to_s].join('/')
|
539
|
+
end
|
540
|
+
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|