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
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Style/GlobalVars
|
4
|
+
|
5
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
6
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'spec', 'support')
|
7
|
+
require 'petra'
|
8
|
+
require 'faker'
|
9
|
+
|
10
|
+
#
|
11
|
+
# This example shows why continuations and production code / code
|
12
|
+
# that uses external libraries are not a good combination.
|
13
|
+
#
|
14
|
+
|
15
|
+
#----------------------------------------------------------------
|
16
|
+
# Sample Class Definitions
|
17
|
+
#----------------------------------------------------------------
|
18
|
+
|
19
|
+
class SimpleUser
|
20
|
+
attr_accessor :first_name
|
21
|
+
attr_accessor :last_name
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@first_name, @last_name = Faker::Name.name.split(' ')
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
# Do nothing, we just want an explicit save method.
|
29
|
+
# We could also set every attribute write to also be a persistence method
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Petra.configure do
|
34
|
+
configure_class 'SimpleUser' do
|
35
|
+
proxy_instances true
|
36
|
+
|
37
|
+
attribute_reader? do |method_name|
|
38
|
+
%w[first_name last_name].include?(method_name.to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
attribute_writer? do |method_name|
|
42
|
+
%w[first_name= last_name=].include?(method_name.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
persistence_method? do |method_name|
|
46
|
+
%w[save].include?(method_name.to_s)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ConfidentialData < String
|
52
|
+
def read?
|
53
|
+
!!@read
|
54
|
+
end
|
55
|
+
|
56
|
+
def read!
|
57
|
+
@read = true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class SimpleHandler
|
62
|
+
def with_confidential_data(string)
|
63
|
+
@confidential_data = ConfidentialData.new(string)
|
64
|
+
yield
|
65
|
+
rescue Exception
|
66
|
+
# The data might have been compromised! Delete it!
|
67
|
+
@confidential_data = nil
|
68
|
+
raise
|
69
|
+
end
|
70
|
+
|
71
|
+
def do_confidential_stuff(user)
|
72
|
+
puts "User #{user.first_name} #{user.last_name} is very confidential."
|
73
|
+
user.last_name = user.last_name + ' ' + ('I' * @confidential_data.length) + '.'
|
74
|
+
ensure
|
75
|
+
@confidential_data.read!
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
#----------------------------------------------------------------
|
80
|
+
# Helper Methods
|
81
|
+
#----------------------------------------------------------------
|
82
|
+
|
83
|
+
# rubocop:disable Security/Eval
|
84
|
+
def transaction(id_no)
|
85
|
+
Petra.transaction(identifier: eval("$t_id_#{id_no}", nil, __FILE__, __LINE__)) do
|
86
|
+
yield
|
87
|
+
rescue Petra::ValueComparisonError => e
|
88
|
+
e.ignore!
|
89
|
+
e.continue!
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# rubocop:enable Security/Eval
|
94
|
+
|
95
|
+
#----------------------------------------------------------------
|
96
|
+
# Actual Example
|
97
|
+
#----------------------------------------------------------------
|
98
|
+
|
99
|
+
# Create 2 transaction identifiers
|
100
|
+
$t_id_1 = Petra.transaction {}
|
101
|
+
$t_id_2 = Petra.transaction {}
|
102
|
+
|
103
|
+
# Instantiate a handler and a SimpleUser object proxy
|
104
|
+
handler = SimpleHandler.new
|
105
|
+
user = SimpleUser.petra.new
|
106
|
+
|
107
|
+
transaction(1) do
|
108
|
+
user.first_name
|
109
|
+
user.last_name = Faker::Name.last_name
|
110
|
+
user.save
|
111
|
+
end
|
112
|
+
|
113
|
+
transaction(2) do
|
114
|
+
user.first_name, user.last_name = Faker::Name.name.split(' ')
|
115
|
+
user.save
|
116
|
+
Petra.commit!
|
117
|
+
end
|
118
|
+
|
119
|
+
transaction(1) do
|
120
|
+
handler.with_confidential_data('Ulf.') do
|
121
|
+
handler.do_confidential_stuff(user) #=> Undefined method #read! for nil:NilClass...
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# rubocop:enable Style/GlobalVars
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
4
|
+
require 'petra'
|
5
|
+
|
6
|
+
# This file contains a transaction based solution to the dining philosophers problem
|
7
|
+
# with five philosophers. It uses the transactions' retry mechanic to ensure
|
8
|
+
# that both sticks have to be taken at the same time.
|
9
|
+
|
10
|
+
class Philosopher
|
11
|
+
attr_reader :number
|
12
|
+
|
13
|
+
def initialize(number, *sticks)
|
14
|
+
@number = number
|
15
|
+
@left_stick, @right_stick = sticks.map(&:petra)
|
16
|
+
end
|
17
|
+
|
18
|
+
def eating?
|
19
|
+
!!@eating
|
20
|
+
end
|
21
|
+
|
22
|
+
def think
|
23
|
+
sleep(rand(5))
|
24
|
+
end
|
25
|
+
|
26
|
+
def take_stick(stick)
|
27
|
+
fail Petra::Retry if stick.taken
|
28
|
+
stick.taken = true
|
29
|
+
stick.save
|
30
|
+
end
|
31
|
+
|
32
|
+
def put_stick(stick)
|
33
|
+
stick.taken = false
|
34
|
+
stick.save
|
35
|
+
end
|
36
|
+
|
37
|
+
def take_sticks
|
38
|
+
Petra.transaction(identifier: "philosopher_#{@number}") do
|
39
|
+
take_stick(@left_stick)
|
40
|
+
take_stick(@right_stick)
|
41
|
+
Petra.commit!
|
42
|
+
rescue Petra::LockError => e
|
43
|
+
e.retry!
|
44
|
+
rescue Petra::ReadIntegrityError, Petra::WriteClashError => e
|
45
|
+
e.retry!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def put_sticks
|
50
|
+
Petra.transaction(identifier: "philosopher_#{@number}") do
|
51
|
+
put_stick(@left_stick)
|
52
|
+
put_stick(@right_stick)
|
53
|
+
Petra.commit!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def eat
|
58
|
+
take_sticks
|
59
|
+
|
60
|
+
@eating = true
|
61
|
+
sleep(2)
|
62
|
+
@eating = false
|
63
|
+
|
64
|
+
put_sticks
|
65
|
+
end
|
66
|
+
|
67
|
+
def live
|
68
|
+
loop do
|
69
|
+
think
|
70
|
+
eat
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Stick < Mutex
|
76
|
+
attr_reader :number
|
77
|
+
|
78
|
+
def initialize(number)
|
79
|
+
@number = number
|
80
|
+
end
|
81
|
+
|
82
|
+
alias taken locked?
|
83
|
+
|
84
|
+
def taken=(new_value)
|
85
|
+
if new_value
|
86
|
+
try_lock || fail(Exception, 'Already locked!')
|
87
|
+
else
|
88
|
+
unlock
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def save; end
|
93
|
+
end
|
94
|
+
|
95
|
+
Petra.configure do
|
96
|
+
log_level :warn
|
97
|
+
|
98
|
+
configure_class 'Stick' do
|
99
|
+
proxy_instances true
|
100
|
+
|
101
|
+
attribute_reader? do |method_name|
|
102
|
+
%w[taken].include?(method_name.to_s)
|
103
|
+
end
|
104
|
+
|
105
|
+
attribute_writer? do |method_name|
|
106
|
+
%w[taken=].include?(method_name.to_s)
|
107
|
+
end
|
108
|
+
|
109
|
+
persistence_method? do |method_name|
|
110
|
+
%w[save].include?(method_name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# If not set, a thread would silently fail without
|
116
|
+
# interrupting the main thread.
|
117
|
+
Thread.abort_on_exception = true
|
118
|
+
|
119
|
+
sticks = Array.new(5) { |i| Stick.new(i) }
|
120
|
+
philosophers = Array.new(5) { |i| Philosopher.new(i, sticks[i], sticks[(i + 1) % 5]) }
|
121
|
+
|
122
|
+
philosophers.map do |phil|
|
123
|
+
t = Thread.new { phil.live }
|
124
|
+
t.name = "Philosopher #{phil.number}"
|
125
|
+
end
|
126
|
+
|
127
|
+
# The output may contain some invalid states as it might happen
|
128
|
+
# during a commit phase with only one stick taken.
|
129
|
+
loop do
|
130
|
+
philosophers.each_with_index do |phil, idx|
|
131
|
+
stick = sticks[idx]
|
132
|
+
STDOUT.write stick.taken ? ' _ ' : ' | '
|
133
|
+
STDOUT.write phil.eating? ? ' 😁 ' : ' 😑 '
|
134
|
+
end
|
135
|
+
|
136
|
+
STDOUT.write("\r")
|
137
|
+
sleep(0.2)
|
138
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
4
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'spec', 'support', 'classes')
|
5
|
+
require 'petra'
|
6
|
+
require 'simple_user'
|
7
|
+
require 'simple_user_with_auto_save'
|
8
|
+
|
9
|
+
Petra.configure do
|
10
|
+
log_level :warn
|
11
|
+
end
|
12
|
+
|
13
|
+
def log(message, identifier = 'External')
|
14
|
+
puts [identifier, message].join(': ')
|
15
|
+
end
|
16
|
+
|
17
|
+
user = Classes::SimpleUserWithAutoSave.petra.new('John', 'Doe')
|
18
|
+
|
19
|
+
# Start a new transaction and start changing attributes
|
20
|
+
Petra.transaction(identifier: 'tr1') do
|
21
|
+
user.first_name = 'Foo'
|
22
|
+
end
|
23
|
+
|
24
|
+
# No changes outside the transaction yet...
|
25
|
+
log user.name #=> 'John Doe'
|
26
|
+
|
27
|
+
# Continue the same transaction
|
28
|
+
Petra.transaction(identifier: 'tr1') do
|
29
|
+
log(user.name, 'tr1') #=> 'Foo Doe'
|
30
|
+
user.last_name = 'Bar'
|
31
|
+
end
|
32
|
+
|
33
|
+
# Another transaction changes a value already changed in 'tr1'
|
34
|
+
Petra.transaction(identifier: 'tr2') do
|
35
|
+
log(user.name, 'tr2') #=> John Doe
|
36
|
+
user.first_name = 'Moo'
|
37
|
+
Petra.commit!
|
38
|
+
end
|
39
|
+
|
40
|
+
log user.name #=> 'Moo Doe'
|
41
|
+
|
42
|
+
# Try to commit our first transaction
|
43
|
+
Petra.transaction(identifier: 'tr1') do
|
44
|
+
log(user.name, 'tr1')
|
45
|
+
Petra.commit!
|
46
|
+
rescue Petra::WriteClashError => e
|
47
|
+
# => "The attribute `first_name` has been changed externally and in the transaction. (Petra::WriteClashError)"
|
48
|
+
# Let's use our value and go on with committing the transaction
|
49
|
+
e.use_ours!
|
50
|
+
e.continue!
|
51
|
+
end
|
52
|
+
|
53
|
+
# The actual object is updated with the values from tr1
|
54
|
+
log user.name #=> 'Foo Bar'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
module Entries
|
6
|
+
class AttributeChange < Petra::Components::LogEntry
|
7
|
+
field_accessor :old_value
|
8
|
+
field_accessor :new_value
|
9
|
+
field_accessor :method
|
10
|
+
|
11
|
+
def self.kind
|
12
|
+
:attribute_change
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply!
|
16
|
+
# Check if there is an an attribute change veto which is newer than this
|
17
|
+
# attribute change. If there is, we may not apply this entry.
|
18
|
+
# TODO: Check if this behaviour is sufficient.
|
19
|
+
return if transaction.attribute_change_veto?(load_proxy, attribute: attribute)
|
20
|
+
|
21
|
+
# Otherwise, use the logged method to set the new attribute value
|
22
|
+
proxied_object.send(method, new_value)
|
23
|
+
end
|
24
|
+
|
25
|
+
Petra::Components::LogEntry.register_entry_type(kind, self)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
module Entries
|
6
|
+
#
|
7
|
+
# Tells the system to ignore all attribute changes we made to the current
|
8
|
+
# attribute during the transaction.
|
9
|
+
#
|
10
|
+
class AttributeChangeVeto < Petra::Components::LogEntry
|
11
|
+
# Mostly for debugging purposes: The external value that caused
|
12
|
+
# the creation of this log entry
|
13
|
+
field_accessor :external_value
|
14
|
+
|
15
|
+
def self.kind
|
16
|
+
:attribute_change_veto
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# As for ReadIntegrityOverrides, we have to make sure that
|
21
|
+
# AttributeChangeVetoes are always persisted.
|
22
|
+
#
|
23
|
+
def persist?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def persist_on_retry?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def apply!; end
|
32
|
+
|
33
|
+
Petra::Components::LogEntry.register_entry_type(kind, self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
module Entries
|
6
|
+
class AttributeRead < Petra::Components::LogEntry
|
7
|
+
field_accessor :method
|
8
|
+
field_accessor :value
|
9
|
+
|
10
|
+
def self.kind
|
11
|
+
:attribute_read
|
12
|
+
end
|
13
|
+
|
14
|
+
def apply!; end
|
15
|
+
|
16
|
+
Petra::Components::LogEntry.register_entry_type(:attribute_read, self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
module Entries
|
6
|
+
class ObjectDestruction < Petra::Components::LogEntry
|
7
|
+
field_accessor :method
|
8
|
+
|
9
|
+
def self.kind
|
10
|
+
:object_destruction
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply!
|
14
|
+
# TODO: React to `false` responses from destruction methods?
|
15
|
+
proxied_object.send(method)
|
16
|
+
end
|
17
|
+
|
18
|
+
Petra::Components::LogEntry.register_entry_type(:object_destruction, self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
module Entries
|
6
|
+
class ObjectInitialization < Petra::Components::LogEntry
|
7
|
+
field_accessor :method
|
8
|
+
|
9
|
+
def self.kind
|
10
|
+
:object_initialization
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply!; end
|
14
|
+
|
15
|
+
Petra::Components::LogEntry.register_entry_type(kind, self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
module Entries
|
6
|
+
class ObjectPersistence < Petra::Components::LogEntry
|
7
|
+
field_accessor :method
|
8
|
+
|
9
|
+
# Arguments given to the persistence method.
|
10
|
+
# This is especially necessary for persistence methods which are
|
11
|
+
# also attribute writers or similar.
|
12
|
+
field_accessor :args
|
13
|
+
|
14
|
+
def self.kind
|
15
|
+
:object_persistence
|
16
|
+
end
|
17
|
+
|
18
|
+
def apply!
|
19
|
+
proxied_object.send(method, *(args || []))
|
20
|
+
end
|
21
|
+
|
22
|
+
Petra::Components::LogEntry.register_entry_type(kind, self)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Components
|
5
|
+
module Entries
|
6
|
+
#
|
7
|
+
# Tells the system not to raise further ReadIntegrityErrors for the given attribute
|
8
|
+
# as long as the external value stays the same.
|
9
|
+
#
|
10
|
+
class ReadIntegrityOverride < Petra::Components::LogEntry
|
11
|
+
# The external attribute value at the time this log entry
|
12
|
+
# was created. It is used to determine whether a new ReadIntegrityError has
|
13
|
+
# to be raised or not.
|
14
|
+
field_accessor :external_value
|
15
|
+
|
16
|
+
def self.kind
|
17
|
+
:read_integrity_override
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# ReadIntegrityOverrides always have to be persisted:
|
22
|
+
# They are only generated if an exception (ReadIntegrityError, etc) happened
|
23
|
+
# which in most cases (except for a rescue within the transaction proc itself)
|
24
|
+
# means that its execution stopped and the only thing left is persisting the transaction.
|
25
|
+
# Therefore, this log entry will most likely be the last one in the current section
|
26
|
+
# and would be lost if we wouldn't persist it.
|
27
|
+
#
|
28
|
+
def persist?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def persist_on_retry?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply!; end
|
37
|
+
|
38
|
+
Petra::Components::LogEntry.register_entry_type(kind, self)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'petra/components/log_entry'
|
4
|
+
|
5
|
+
module Petra
|
6
|
+
module Components
|
7
|
+
#
|
8
|
+
# An EntrySet is a collection of log entries for a certain section.
|
9
|
+
# It may be used to chain-filter entries, e.g. only object_persisted entries for a certain proxy.
|
10
|
+
#
|
11
|
+
# TODO: Probably be Enumerator::Lazy...
|
12
|
+
#
|
13
|
+
class EntrySet < Array
|
14
|
+
|
15
|
+
#----------------------------------------------------------------
|
16
|
+
# Filters
|
17
|
+
#----------------------------------------------------------------
|
18
|
+
|
19
|
+
def for_proxy(proxy)
|
20
|
+
wrap { select { |e| e.for_object?(proxy.__object_key) } }
|
21
|
+
end
|
22
|
+
|
23
|
+
def of_kind(kind)
|
24
|
+
wrap { select { |e| e.kind?(kind) } }
|
25
|
+
end
|
26
|
+
|
27
|
+
def for_attribute_key(key)
|
28
|
+
wrap { select { |e| e.attribute_key.to_s == key.to_s } }
|
29
|
+
end
|
30
|
+
|
31
|
+
def object_persisted
|
32
|
+
wrap { select(&:object_persisted?) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def not_object_persisted
|
36
|
+
wrap { reject(&:object_persisted?) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def latest
|
40
|
+
last
|
41
|
+
end
|
42
|
+
|
43
|
+
#----------------------------------------------------------------
|
44
|
+
# Persistence / Commit
|
45
|
+
#----------------------------------------------------------------
|
46
|
+
|
47
|
+
#
|
48
|
+
# Applies all log entries which were marked as object persisted
|
49
|
+
# The log entry itself decides whether it is actually executed or not.
|
50
|
+
#
|
51
|
+
def apply!
|
52
|
+
object_persisted.each(&:apply!)
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Tells each log entry to enqueue for persisting.
|
57
|
+
# The individual log entries may decided whether they actually want
|
58
|
+
# to be persisted or not.
|
59
|
+
#
|
60
|
+
def enqueue_for_persisting!
|
61
|
+
each(&:enqueue_for_persisting!)
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare_for_retry!
|
65
|
+
select(&:persist_on_retry?).each(&:enqueue_for_persisting!)
|
66
|
+
end
|
67
|
+
|
68
|
+
#----------------------------------------------------------------
|
69
|
+
# Wrapped Array Methods
|
70
|
+
#----------------------------------------------------------------
|
71
|
+
|
72
|
+
def reverse(*)
|
73
|
+
wrap { super }
|
74
|
+
end
|
75
|
+
|
76
|
+
def sort(*)
|
77
|
+
wrap { super }
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def wrap
|
83
|
+
self.class.new(yield)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|