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,342 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'petra/util/field_accessors'
|
4
|
+
require 'petra/util/registrable'
|
5
|
+
|
6
|
+
module Petra
|
7
|
+
module Components
|
8
|
+
#
|
9
|
+
# A log entry is basically a struct with certain helper functions.
|
10
|
+
# This class contains some base functionality and a map of more specific log entry types.
|
11
|
+
#
|
12
|
+
# Registered entry types may define own +field_accessors+ which will be
|
13
|
+
# serialized when persisting the log entries.
|
14
|
+
#
|
15
|
+
class LogEntry
|
16
|
+
include Comparable
|
17
|
+
include Petra::Util::Registrable
|
18
|
+
include Petra::Util::FieldAccessors
|
19
|
+
|
20
|
+
acts_as_register :entry_type
|
21
|
+
|
22
|
+
field_accessor :savepoint
|
23
|
+
field_accessor :transaction_identifier
|
24
|
+
|
25
|
+
# Identifier usually used to persist the current entry.
|
26
|
+
# It is set by the used persistence adapter.
|
27
|
+
field_accessor :entry_identifier
|
28
|
+
|
29
|
+
# An object is a 'new object' if it was created during this transaction and does
|
30
|
+
# not exist outside of it yet. This information is necessary when restoring
|
31
|
+
# proxies from previous sections
|
32
|
+
field_accessor :new_object
|
33
|
+
|
34
|
+
# Identifies the object the changes were performed on,
|
35
|
+
# e.g. "User", 1 for @user.save
|
36
|
+
# The object class is also needed to load the corresponding class configuration
|
37
|
+
attr_reader :object_class
|
38
|
+
attr_reader :attribute
|
39
|
+
|
40
|
+
field_accessor :object_key
|
41
|
+
field_accessor :attribute_key
|
42
|
+
|
43
|
+
# Means that the client persisted the object referenced by this log entry, e.g. through #save in case of AR
|
44
|
+
attr_accessor :object_persisted
|
45
|
+
|
46
|
+
# Marks this log entry to be persisted on section retries even if the object was not persisted
|
47
|
+
attr_writer :persist_on_retry
|
48
|
+
|
49
|
+
# Means that the log entry was actually persisted within the transaction
|
50
|
+
attr_accessor :transaction_persisted
|
51
|
+
|
52
|
+
attr_reader :section
|
53
|
+
|
54
|
+
alias object_persisted? object_persisted
|
55
|
+
alias transaction_persisted? transaction_persisted
|
56
|
+
alias new_object? new_object
|
57
|
+
|
58
|
+
def self.log!(kind, section:, **fields)
|
59
|
+
fail ArgumentError, "#{kind} is not a valid entry type" unless registered_entry_type?(kind)
|
60
|
+
registered_entry_type(kind).new(section, **fields)
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Initializes a new log entry based on the given section and options
|
65
|
+
#
|
66
|
+
# @param [Hash] fields
|
67
|
+
# @option options [String] :savepoint (section.savepoint)
|
68
|
+
# The savepoint name this log entry is part of.
|
69
|
+
#
|
70
|
+
# @option options [String] :transaction_identifier (section.transaction.identifier)
|
71
|
+
# The entry's transaction's identifier
|
72
|
+
#
|
73
|
+
# @option options [String, Symbol] :method
|
74
|
+
# The method which caused this log entry.
|
75
|
+
# In some cases, it makes sense to specify a different method here as
|
76
|
+
# this value is used when actually applying the log entry (see Petra::Proxies::ActiveRecordProxy)
|
77
|
+
#
|
78
|
+
# @option options [String] attribute_key
|
79
|
+
# The unique key for the attribute which was altered/read in this log entry.
|
80
|
+
# Only available for attribute_read/attribute_change entries
|
81
|
+
#
|
82
|
+
# @option options [String] object_key
|
83
|
+
# The unique key for the object/proxy which caused this log entry.
|
84
|
+
# The internal values for @object_class and @object_id are automatically set based on it.
|
85
|
+
#
|
86
|
+
# @option options [String] kind
|
87
|
+
# The entry's kind, e.g. 'object_initialization'
|
88
|
+
#
|
89
|
+
# @option options [String] old_value
|
90
|
+
# @option options [String] new_value
|
91
|
+
#
|
92
|
+
def initialize(section, **fields)
|
93
|
+
@section = section
|
94
|
+
|
95
|
+
@object_persisted = fields.delete(:object_persisted)
|
96
|
+
@transaction_persisted = fields.delete(:transaction_persisted)
|
97
|
+
|
98
|
+
# Restore the given field accessors
|
99
|
+
fields.each do |k, v|
|
100
|
+
send("#{k}=", v)
|
101
|
+
end
|
102
|
+
|
103
|
+
self.savepoint ||= section.savepoint
|
104
|
+
self.transaction_identifier ||= section.transaction.identifier
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# If both entries were made in the same section, the smaller entry was
|
109
|
+
# generated earlier than the other.
|
110
|
+
# If both entries are in different sections, the one with a smaller
|
111
|
+
# savepoint version is considered smaller.
|
112
|
+
#
|
113
|
+
def <=>(other)
|
114
|
+
if section == other.section
|
115
|
+
section.log_entries.index(self) <=> section.log_entries.index(other)
|
116
|
+
else
|
117
|
+
section.savepoint_version <=> other.section.savepoint_version
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
#----------------------------------------------------------------
|
122
|
+
# Internal Field Handling
|
123
|
+
#----------------------------------------------------------------
|
124
|
+
|
125
|
+
def attribute_change?
|
126
|
+
kind?(:attribute_change)
|
127
|
+
end
|
128
|
+
|
129
|
+
def attribute_read?
|
130
|
+
kind?(:attribute_read)
|
131
|
+
end
|
132
|
+
|
133
|
+
def object_persistence?
|
134
|
+
kind?(:object_persistence)
|
135
|
+
end
|
136
|
+
|
137
|
+
def object_initialization?
|
138
|
+
kind?(:object_initialization)
|
139
|
+
end
|
140
|
+
|
141
|
+
def mark_as_object_persisted!
|
142
|
+
@object_persisted = true
|
143
|
+
end
|
144
|
+
|
145
|
+
def mark_as_persisted!(identifier)
|
146
|
+
@transaction_persisted = true
|
147
|
+
@entry_identifier = identifier
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# @return [Boolean] +true+ if this log entry should be destroyed
|
152
|
+
# if it is enqueued for the next persisting phase
|
153
|
+
#
|
154
|
+
def marked_for_destruction?
|
155
|
+
!!@marked_for_destruction
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# @return [Hash] the necessary information about this entry to reproduce it later
|
160
|
+
# The result is mainly used when serializing the step later.
|
161
|
+
#
|
162
|
+
# @param [Hash] options
|
163
|
+
#
|
164
|
+
# @option options [String] :entry_identifier
|
165
|
+
# A section-unique identifier for the current log entry.
|
166
|
+
# It is usually given by the used persistence adapter.
|
167
|
+
#
|
168
|
+
# Information about the object / transaction persistence is not kept as this method
|
169
|
+
# will only be used during persistence or on already persisted entries
|
170
|
+
#
|
171
|
+
def to_h(**options)
|
172
|
+
fields.each_with_object(options.merge('kind' => self.class.kind)) do |(k, v), h|
|
173
|
+
h[k] = v unless v.nil?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
#
|
178
|
+
# Builds a log entry from the given section and hash, but automatically sets the persistence flags
|
179
|
+
#
|
180
|
+
# @return [Petra::Components::LogEntry]
|
181
|
+
#
|
182
|
+
def self.from_hash(section, fields)
|
183
|
+
log!(fields.delete('kind'),
|
184
|
+
section: section,
|
185
|
+
object_persisted: true,
|
186
|
+
transaction_persisted: true,
|
187
|
+
**fields.symbolize_keys)
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# @return [Boolean] +true+ if this log entry was made in the context of the given object (key)
|
192
|
+
#
|
193
|
+
def for_object?(object_key)
|
194
|
+
self.object_key == object_key
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
# @return [Boolean] +true+ if this log entry is of the given kind
|
199
|
+
#
|
200
|
+
def kind?(kind)
|
201
|
+
self.class.kind.to_s == kind.to_s
|
202
|
+
end
|
203
|
+
|
204
|
+
#----------------------------------------------------------------
|
205
|
+
# Persistence
|
206
|
+
#----------------------------------------------------------------
|
207
|
+
|
208
|
+
#
|
209
|
+
# Adds the log entry to the persistence queue if the following conditions are met:
|
210
|
+
#
|
211
|
+
# 1. The log entry has to be marked as 'object_persisted', meaning that the object was saved
|
212
|
+
# during/after the action which created the the entry
|
213
|
+
# 2. The log entry hasn't been persisted previously
|
214
|
+
#
|
215
|
+
# This does not automatically mark this log entry as persisted,
|
216
|
+
# this is done once the persistence adapter finished its work
|
217
|
+
#
|
218
|
+
def enqueue_for_persisting!
|
219
|
+
return if transaction_persisted?
|
220
|
+
return unless persist? || persist_on_retry? && transaction.retry_in_progress?
|
221
|
+
Petra.transaction_manager.persistence_adapter.enqueue(self)
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# @return [boolean] If this returns +true+, the log entry will be
|
226
|
+
# persisted when a section is retried even if #persist? would return +false+
|
227
|
+
#
|
228
|
+
def persist_on_retry?
|
229
|
+
!!@persist_on_retry
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# May be overridden by more specialized log entries,
|
234
|
+
# the basic version will persist an entry as long as it is marked
|
235
|
+
# as object persisted
|
236
|
+
#
|
237
|
+
def persist?
|
238
|
+
object_persisted?
|
239
|
+
end
|
240
|
+
|
241
|
+
#----------------------------------------------------------------
|
242
|
+
# Commit
|
243
|
+
#----------------------------------------------------------------
|
244
|
+
|
245
|
+
#
|
246
|
+
# Applies the action performed in the current log entry
|
247
|
+
# to the corresponding object
|
248
|
+
#
|
249
|
+
def apply!
|
250
|
+
not_implemented # nop?
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
# Tries to undo a previously done #apply!
|
255
|
+
# This is currently only possible for attribute changes as we do not know
|
256
|
+
# how to undo destruction / persistence for general objects
|
257
|
+
#
|
258
|
+
def undo!
|
259
|
+
load_proxy.send(:__undo_application__, self)
|
260
|
+
end
|
261
|
+
|
262
|
+
#----------------------------------------------------------------
|
263
|
+
# Object Helpers
|
264
|
+
#----------------------------------------------------------------
|
265
|
+
|
266
|
+
#
|
267
|
+
# @return [Petra::Proxies::ObjectProxy] the proxy this log entry was made for
|
268
|
+
#
|
269
|
+
def load_proxy
|
270
|
+
@load_proxy ||= transaction.objects.fetch(object_key) do
|
271
|
+
new_object? ? initialize_proxy : restore_proxy
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def to_s
|
276
|
+
"#{section.savepoint}/#{@object_id} => #{self.class.kind}"
|
277
|
+
end
|
278
|
+
|
279
|
+
protected
|
280
|
+
|
281
|
+
def proxied_object
|
282
|
+
load_proxy.send(:proxied_object)
|
283
|
+
end
|
284
|
+
|
285
|
+
#
|
286
|
+
# Initializes a proxy for the object which was initialized in this log entry.
|
287
|
+
# This is done by initializing a new object and set the old generated object_id for its proxy
|
288
|
+
#
|
289
|
+
# @return [Petra::Proxies::ObjectProxy] a proxy for a clean object (no attributes set).
|
290
|
+
# Every attribute value is taken from its write set.
|
291
|
+
#
|
292
|
+
def initialize_proxy
|
293
|
+
klass = object_class.constantize
|
294
|
+
instance = configurator.__inherited_value(:init_method, proc_expected: true, base: klass)
|
295
|
+
Petra::Proxies::ObjectProxy.for(instance, object_id: @object_id)
|
296
|
+
end
|
297
|
+
|
298
|
+
#
|
299
|
+
# Loads an object which most likely existed outside of the transaction
|
300
|
+
# and wraps it in a proxy.
|
301
|
+
#
|
302
|
+
# @return [Petra::Proxies::ObjectProxy] a proxy for the object this log entry is about.
|
303
|
+
#
|
304
|
+
# Please note that no custom attributes are set, they will be served from the write set.
|
305
|
+
#
|
306
|
+
# TODO: Raise an exception here if a proxy could not be restored.
|
307
|
+
# This most likely means that the object was destroyed outside of the transaction!
|
308
|
+
#
|
309
|
+
def restore_proxy
|
310
|
+
klass = object_class.constantize
|
311
|
+
instance = configurator.__inherited_value(:lookup_method, @object_id, proc_expected: true, base: klass)
|
312
|
+
Petra::Proxies::ObjectProxy.for(instance)
|
313
|
+
end
|
314
|
+
|
315
|
+
def object_key=(key)
|
316
|
+
self[:object_key] = key
|
317
|
+
@object_class, @object_id = key.split('/') if key
|
318
|
+
end
|
319
|
+
|
320
|
+
def attribute_key=(key)
|
321
|
+
self[:attribute_key] = key
|
322
|
+
@object_class, @object_id, @attribute = key.split('/') if key
|
323
|
+
end
|
324
|
+
|
325
|
+
def configurator
|
326
|
+
@configurator ||= Petra.configuration[object_class]
|
327
|
+
end
|
328
|
+
|
329
|
+
def transaction
|
330
|
+
Petra.current_transaction
|
331
|
+
end
|
332
|
+
|
333
|
+
#
|
334
|
+
# Ensures that the currently set log entry kind is actually one of the valid ones
|
335
|
+
#
|
336
|
+
def validate_kind!
|
337
|
+
return if Petra::Components::LogEntry.registered_entry_type?(kind)
|
338
|
+
fail ArgumentError, "#{kind} is not a valid log entry kind."
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
#
|
6
|
+
# This class encapsulates the methods a transaction may use to
|
7
|
+
# gather information about the objects which were used during its execution.
|
8
|
+
#
|
9
|
+
# It also functions as an object cache, mapping proxy keys to (temporary) object proxies
|
10
|
+
#
|
11
|
+
class ProxyCache
|
12
|
+
def initialize(transaction)
|
13
|
+
@transaction = transaction
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Returns the proxy for the given object key from cache.
|
18
|
+
# If there is no valid object cached yet, the given block is executed
|
19
|
+
# and its result saved under the given key.
|
20
|
+
#
|
21
|
+
# @return [Petra::Proxies::ObjectProxy] the object proxy by the given object key
|
22
|
+
#
|
23
|
+
def fetch(key)
|
24
|
+
@cache ||= {}
|
25
|
+
return @cache[key.to_s] if @cache.key?(key.to_s)
|
26
|
+
fail ArgumentError, "Object `#{key}` is not cached and no block was given." unless block_given?
|
27
|
+
@cache[key.to_s] = yield
|
28
|
+
end
|
29
|
+
|
30
|
+
# Shortcut to retrieve already cached objects.
|
31
|
+
# As #[] may not receive a block, it will automatically fail if the
|
32
|
+
# cache entry couldn't be found.
|
33
|
+
alias [] fetch
|
34
|
+
|
35
|
+
delegate :sections, :current_section, :verify_attribute_integrity!, to: :@transaction
|
36
|
+
|
37
|
+
#
|
38
|
+
# @return [Hash<Petra::Proxies::ObjectProxy, Array<String,Symbol>>]
|
39
|
+
# All attributes which were read during this transaction grouped by the objects (proxies)
|
40
|
+
# they belong to.
|
41
|
+
#
|
42
|
+
def read_attributes
|
43
|
+
sections.each_with_object({}) do |section, h|
|
44
|
+
section.read_attributes.each do |proxy, attributes|
|
45
|
+
h[proxy] ||= []
|
46
|
+
h[proxy] = (h[proxy] + attributes).uniq
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Objects that will have impact during the commit phase in order of their appearance
|
53
|
+
# during the transaction's execution.
|
54
|
+
#
|
55
|
+
def fateful(klass = nil)
|
56
|
+
filtered_objects(:objects, klass)
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Objects that were read during the transaction
|
61
|
+
#
|
62
|
+
# @param [Class] klass
|
63
|
+
#
|
64
|
+
# @return [Array<Petra::Proxies::ObjectProxy>]
|
65
|
+
#
|
66
|
+
def read(klass = nil)
|
67
|
+
filtered_objects(:read_objects, klass)
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Objects that were initialized and persisted during the transaction
|
72
|
+
#
|
73
|
+
# @param [Class] klass
|
74
|
+
#
|
75
|
+
# @return [Array<Petra::Proxies::ObjectProxy>]
|
76
|
+
#
|
77
|
+
def created(klass = nil)
|
78
|
+
filtered_objects(:created_objects, klass)
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Like #created, but it will also include not-yet persisted objects
|
83
|
+
# of non-persisted sections
|
84
|
+
#
|
85
|
+
# @param [Class] klass
|
86
|
+
#
|
87
|
+
# @return [Array<Petra::Proxies::ObjectProxy>]
|
88
|
+
#
|
89
|
+
def initialized_or_created(klass = nil)
|
90
|
+
filtered_objects(:initialized_or_created_objects, klass)
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# @see #filtered_objects
|
95
|
+
#
|
96
|
+
# @param [Class] klass
|
97
|
+
#
|
98
|
+
# @return [Array<Petra::Proxies::ObjectProxy>] objects which were initialized within the transaction,
|
99
|
+
# but not yet object persisted
|
100
|
+
#
|
101
|
+
def initialized(klass = nil)
|
102
|
+
filtered_objects(:initialized_objects, klass)
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# @see #filtered_objects
|
107
|
+
#
|
108
|
+
# @param [Class] klass
|
109
|
+
#
|
110
|
+
# @return [Array<Petra::Proxies::ObjectProxy>] objects which were destroyed within the transaction
|
111
|
+
#
|
112
|
+
def destroyed(klass = nil)
|
113
|
+
filtered_objects(:destroyed_objects, klass)
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# @param [Petra::Proxies::ObjectProxy] proxy
|
118
|
+
#
|
119
|
+
# @return [Boolean] +true+ if the given object (proxy) was initialized AND persisted during the transaction.
|
120
|
+
#
|
121
|
+
def created?(proxy)
|
122
|
+
created.include?(proxy)
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# @param [Petra::Proxies::ObjectProxy] proxy
|
127
|
+
#
|
128
|
+
# @return [Boolean] +true+ if the given object (proxy) was initialized, but not yet persisted
|
129
|
+
# during this transaction. This means in particular that the object did not exist before
|
130
|
+
# the transaction started.
|
131
|
+
#
|
132
|
+
def initialized?(proxy)
|
133
|
+
initialized.include?(proxy)
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# @param [Petra::Proxies::ObjectProxy]
|
138
|
+
#
|
139
|
+
# @return [Boolean] +true+ if the given object did not exist outside of the transaction,
|
140
|
+
# meaning that it was initialized and optionally persisted during its execution
|
141
|
+
#
|
142
|
+
def new?(proxy)
|
143
|
+
current_section.recently_initialized_object?(proxy) || initialized_or_created.include?(proxy)
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# @param [Petra::Proxies::ObjectProxy] proxy
|
148
|
+
#
|
149
|
+
# @return [Boolean] +true+ if the given object existed before the transaction started
|
150
|
+
#
|
151
|
+
def existing?(proxy)
|
152
|
+
!new?(proxy)
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# @param [Petra::Proxies::ObjectProxy] proxy
|
157
|
+
#
|
158
|
+
# @return [Boolean] +true+ if the given object was destroyed during this transaction
|
159
|
+
#
|
160
|
+
def destroyed?(proxy)
|
161
|
+
destroyed.include?(proxy)
|
162
|
+
end
|
163
|
+
|
164
|
+
#----------------------------------------------------------------
|
165
|
+
# Helpers
|
166
|
+
#----------------------------------------------------------------
|
167
|
+
|
168
|
+
def current_numerical_id
|
169
|
+
# FIXME: The string comparison will not work for numbers > 10! It has to be replaced with a numeric comparison!
|
170
|
+
# FIXME: This also causes problems when the last new element is deleted and a new one created
|
171
|
+
@current_numerical_id ||= (initialized_or_created.max_by(&:__object_id)&.__object_id || 'new_0')
|
172
|
+
.match(/new_(\d+)/)[1].to_i
|
173
|
+
end
|
174
|
+
|
175
|
+
def inc_current_numerical_id
|
176
|
+
@current_numerical_id = current_numerical_id + 1
|
177
|
+
end
|
178
|
+
|
179
|
+
def next_id
|
180
|
+
format('new_%05d', inc_current_numerical_id)
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
# Performs an integrity check on all attributes which were read in this transaction
|
185
|
+
#
|
186
|
+
# @raise [Petra::ReadIntegrityError] Raised if one of the read attribute has be changed externally
|
187
|
+
#
|
188
|
+
def verify_read_attributes!(force: false)
|
189
|
+
read_attributes.each do |proxy, attributes|
|
190
|
+
attributes.each { |a| verify_attribute_integrity!(proxy, attribute: a, force: force) }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
#
|
197
|
+
# Collects objects of a certain kind from all sections and filters by a given class name (optionally)
|
198
|
+
# There is no caching here as both, sections and log entries will cache the actual objects/sets
|
199
|
+
#
|
200
|
+
def filtered_objects(kind, klass = nil)
|
201
|
+
result = sections.flat_map(&kind)
|
202
|
+
|
203
|
+
# If a class (name) was given, only return objects which are of the given type
|
204
|
+
klass ? result.select { |p| p.send(:for_class?, klass) } : result
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|