petra_core 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|