sandthorn 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.
@@ -0,0 +1,30 @@
1
+ require "dirty_hashy"
2
+ require "sandthorn/aggregate_root_base"
3
+
4
+ module Sandthorn
5
+ module AggregateRoot
6
+ module DirtyHashy
7
+ include Sandthorn::AggregateRoot::Base
8
+
9
+ def self.included(base)
10
+ base.extend(Sandthorn::AggregateRoot::Base::ClassMethods)
11
+ end
12
+
13
+ def aggregate_initialize
14
+ @hashy = ::DirtyHashy.new
15
+ end
16
+
17
+ def get_delta
18
+ extract_relevant_aggregate_instance_variables.each do |var|
19
+ @hashy[var.to_s.delete("@")] = self.instance_variable_get("#{var}")
20
+ end
21
+ aggregate_attribute_deltas = []
22
+ @hashy.changes.each do |attribute|
23
+ aggregate_attribute_deltas << { :attribute_name => attribute[0], :old_value => attribute[1][0], :new_value => attribute[1][1]}
24
+ end
25
+ aggregate_attribute_deltas
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ module Sandthorn
2
+ module AggregateRootSnapshot
3
+ attr_reader :aggregate_snapshot
4
+
5
+ def aggregate_snapshot!
6
+
7
+ if @aggregate_events.count > 0
8
+ raise "Can't take snapshot on object with unsaved events"
9
+ end
10
+
11
+ @aggregate_snapshot = {
12
+ :event_name => "aggregate_set_from_snapshot",
13
+ :event_args => [self],
14
+ :aggregate_version => @aggregate_current_event_version
15
+ }
16
+ end
17
+
18
+ def save_snapshot
19
+ raise "No snapshot has been created!" unless @aggregate_snapshot
20
+ @aggregate_snapshot[:event_data] = Sandthorn.serialize @aggregate_snapshot[:event_args]
21
+ @aggregate_snapshot[:event_args] = nil
22
+ Sandthorn.save_snapshot @aggregate_snapshot, @aggregate_id, self.class.name
23
+ @aggregate_snapshot = nil
24
+ end
25
+ private
26
+ def aggregate_create_event_when_extended
27
+ self.aggregate_snapshot!
28
+ vars = extract_relevant_aggregate_instance_variables
29
+ vars.each do |var_name|
30
+ value = instance_variable_get var_name
31
+ dump = Marshal.dump(value)
32
+ store_aggregate_instance_variable var_name, dump
33
+ end
34
+ #@aggregate_snapshot[:event_data] = Sandthorn.serialize @aggregate_snapshot[:event_args]
35
+ store_aggregate_event "instance_extended_as_aggregate", @aggregate_snapshot[:event_args]
36
+ @aggregate_snapshot = nil
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ module Sandthorn
2
+ module Errors
3
+ class Error < StandardError; end
4
+ class AggregateNotFound < Error; end
5
+ class ConcurrencyError < Error; end
6
+ class ConfigurationError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,63 @@
1
+ module Sandthorn
2
+ module EventInspector
3
+ def has_unsaved_event? event_name, options = {}
4
+ unsaved = events_with_trace_info
5
+ if self.aggregate_events.empty?
6
+ unsaved = []
7
+ else
8
+ unsaved.reject! { |e| e[:aggregate_version] < self.aggregate_events.first[:aggregate_version] }
9
+ end
10
+ matching_events = unsaved.select { |e| e[:event_name] == event_name }
11
+ event_exists = matching_events.length > 0
12
+ trace = has_trace? matching_events, options.fetch(:trace, {})
13
+
14
+ return event_exists && trace
15
+ end
16
+ def has_saved_event? event_name, options = {}
17
+ saved = events_with_trace_info
18
+ saved.reject! { |e| e[:aggregate_version] >= self.aggregate_events.first[:aggregate_version] } unless self.aggregate_events.empty?
19
+ matching_events = saved.select { |e| e[:event_name] == event_name }
20
+ event_exists = matching_events.length > 0
21
+ trace = has_trace? matching_events, options.fetch(:trace, {})
22
+
23
+ return event_exists && trace
24
+ end
25
+ def has_event? event_name, options = {}
26
+ matching_events = events_with_trace_info.select { |e| e[:event_name] == event_name }
27
+ event_exists = matching_events.length > 0
28
+ trace = has_trace? matching_events, options.fetch(:trace, {})
29
+ return event_exists && trace
30
+ end
31
+ def events_with_trace_info
32
+ saved = Sandthorn.get_aggregate_events self.aggregate_id, self.class
33
+ unsaved = self.aggregate_events
34
+ all = saved.concat(unsaved).sort { |a, b| a[:aggregate_version] <=> b[:aggregate_version] }
35
+ extracted = all.collect do |e|
36
+ if e[:event_args].nil? && !e[:event_data].nil?
37
+ data = Sandthorn.deserialize e[:event_data]
38
+ else
39
+ data = e[:event_args]
40
+ end
41
+ trace = data[:trace] unless data.nil? || !data.is_a?(Hash)
42
+ {aggregate_version: e[:aggregate_version], event_name: e[:event_name].to_sym, trace: trace }
43
+ end
44
+ return extracted
45
+ end
46
+ private
47
+ def get_unsaved_events event_name
48
+ self.aggregate_events.select { |e| e[:event_name] == event_name.to_s }
49
+ end
50
+ def get_saved_events event_name
51
+ saved_events = Sandthorn.get_aggregate_events self.aggregate_id, self.class
52
+ saved_events.select { |e| e[:event_name] == event_name.to_s }
53
+ end
54
+
55
+ def has_trace? events_to_check, trace_info
56
+ return true if trace_info.empty?
57
+ events_to_check.each do |event|
58
+ return false if event[:trace] != trace_info
59
+ end
60
+ true
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module Sandthorn
2
+ VERSION = "0.0.1"
3
+ end
data/sandthorn.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sandthorn/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sandthorn"
8
+ spec.version = Sandthorn::VERSION
9
+ spec.authors = ["Lars Krantz", "Morgan Hallgren"]
10
+ spec.email = ["lars.krantz@alaz.se", "morgan.hallgren@gmail.com"]
11
+ spec.description = %q{Event sourcing gem}
12
+ spec.summary = %q{Event sourcing gem}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "gem-release"
25
+ spec.add_development_dependency "pry"
26
+ spec.add_development_dependency "pry-doc"
27
+ spec.add_development_dependency "awesome_print"
28
+ spec.add_development_dependency "autotest-standalone"
29
+ spec.add_development_dependency "sqlite3"
30
+ spec.add_development_dependency "sandthorn_driver_sequel"
31
+
32
+ spec.add_runtime_dependency "dirty_hashy"
33
+ spec.add_runtime_dependency "uuidtools"
34
+
35
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'uuidtools'
3
+ require 'sandthorn/aggregate_root_dirty_hashy'
4
+
5
+
6
+
7
+ class PersonTest
8
+ include Sandthorn::AggregateRoot::DirtyHashy
9
+ attr_reader :name
10
+ attr_reader :age
11
+ attr_reader :relationship_status
12
+ attr_reader :my_array
13
+ attr_reader :my_hash
14
+
15
+ def initialize name, age, relationship_status
16
+ @name = name
17
+ @age = age
18
+ @relationship_status = relationship_status
19
+ @my_array = []
20
+ @my_hash = {}
21
+ end
22
+
23
+ def change_name new_name
24
+ @name = new_name
25
+ record_event new_name
26
+ end
27
+
28
+ def change_relationship new_relationship
29
+ @relationship_status = new_relationship
30
+ record_event new_relationship
31
+ end
32
+
33
+ def add_to_array element
34
+ @my_array << element
35
+ record_event element
36
+ end
37
+
38
+ def add_to_hash name,value
39
+ @my_hash[name] = value
40
+ record_event name,value
41
+ end
42
+ end
43
+
44
+ describe 'Property Delta Event Sourcing' do
45
+ let(:person) { PersonTest.new "Lasse",40,:married}
46
+
47
+ it 'should be able to set name' do
48
+ person.change_name "Klabbarparen"
49
+ person.name.should eql("Klabbarparen")
50
+ #puts person.aggregate_events
51
+ end
52
+
53
+ it 'should be able to build from events' do
54
+ person.change_name "Klabbarparen"
55
+ builded = PersonTest.aggregate_build person.aggregate_events
56
+ builded.name.should eql(person.name)
57
+ builded.aggregate_id.should eql(person.aggregate_id)
58
+ end
59
+
60
+ it 'should not have any events when built up' do
61
+ person.change_name "Mattias"
62
+ builded = PersonTest.aggregate_build person.aggregate_events
63
+ builded.aggregate_events.should be_empty
64
+ end
65
+
66
+ it 'should detect change on array' do
67
+ person.add_to_array "Foo"
68
+ person.add_to_array "bar"
69
+
70
+ builded = PersonTest.aggregate_build person.aggregate_events
71
+ builded.my_array.should include "Foo"
72
+ builded.my_array.should include "bar"
73
+ end
74
+
75
+ it 'should detect change on hash' do
76
+ person.add_to_hash :foo, "bar"
77
+ person.add_to_hash :bar, "foo"
78
+
79
+ builded = PersonTest.aggregate_build person.aggregate_events
80
+ builded.my_hash[:foo].should eql("bar")
81
+ builded.my_hash[:bar].should eql("foo")
82
+
83
+ person.add_to_hash :foo, "BAR"
84
+
85
+ #events = person.aggregate_events
86
+ #events << builded.aggregate_events
87
+ #puts events
88
+
89
+ builded2 = PersonTest.aggregate_build person.aggregate_events
90
+ builded2.my_hash[:foo].should eql("BAR")
91
+ end
92
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+ require 'sandthorn/aggregate_root_dirty_hashy'
3
+
4
+ module Sandthorn
5
+ module AggregateRoot
6
+ class DirtyClass
7
+ include Sandthorn::AggregateRoot::DirtyHashy
8
+ attr_reader :name, :age
9
+ attr :sex
10
+ attr_writer :writer
11
+
12
+ def initialize args = {}
13
+ @name = args.fetch(:name, nil)
14
+ @sex = args.fetch(:sex, nil)
15
+ @writer = args.fetch(:writer, nil)
16
+ end
17
+
18
+ def change_name value
19
+ unless name == value
20
+ @name = value
21
+ commit
22
+ end
23
+ end
24
+
25
+ def change_sex value
26
+ unless sex == value
27
+ @sex = value
28
+ end
29
+ end
30
+
31
+ def change_writer value
32
+ unless writer == value
33
+ @writer = value
34
+ end
35
+ end
36
+
37
+
38
+ end
39
+
40
+
41
+ describe "when making a change on a aggregate" do
42
+ let(:dirty_obejct) {
43
+ o = DirtyClass.new
44
+ o
45
+ }
46
+
47
+ context "new with args" do
48
+
49
+ let(:subject) { DirtyClass.new(name: "Mogge", sex: "hen", writer: true) }
50
+ it "should set the values" do
51
+ expect(subject.name).to eql "Mogge"
52
+ expect(subject.sex).to eql "hen"
53
+ expect{subject.writer}.to raise_error
54
+ end
55
+ end
56
+
57
+ context "when changing name (attr_reader)" do
58
+
59
+ it "should get new_name" do
60
+ dirty_obejct.change_name "new_name"
61
+ dirty_obejct.name.should eql "new_name"
62
+ end
63
+
64
+ it "should generate one event on new" do
65
+ expect(dirty_obejct.aggregate_events.length).to eql 1
66
+ end
67
+
68
+ it "should generate 2 events new and change_name" do
69
+ dirty_obejct.change_name "new_name"
70
+ expect(dirty_obejct.aggregate_events.length).to eql 2
71
+ end
72
+ end
73
+
74
+ context "when changing sex (attr)" do
75
+ it "should get new_sex" do
76
+ dirty_obejct.change_sex "new_sex"
77
+ dirty_obejct.sex.should eql "new_sex"
78
+ end
79
+ end
80
+
81
+ context "when changing writer (attr_writer)" do
82
+ it "should raise error" do
83
+ expect{dirty_obejct.change_writer "new_writer"}.to raise_error
84
+ end
85
+ end
86
+
87
+ context "save" do
88
+ it "should not have events on aggregete after save" do
89
+ expect(dirty_obejct.save.aggregate_events.length).to eql 0
90
+ end
91
+
92
+ it "should have aggregate_originating_version == 0 pre save" do
93
+ expect(dirty_obejct.aggregate_originating_version).to eql 0
94
+ end
95
+
96
+ it "should have aggregate_originating_version == 1 post save" do
97
+ expect(dirty_obejct.save.aggregate_originating_version).to eql 1
98
+ end
99
+ end
100
+
101
+ context "find" do
102
+ before(:each) { dirty_obejct.save }
103
+ it "should find by id" do
104
+ expect(DirtyClass.find(dirty_obejct.id).id).to eql dirty_obejct.id
105
+ end
106
+
107
+ it "should hold changed name" do
108
+ dirty_obejct.change_name("morgan").save
109
+ expect(DirtyClass.find(dirty_obejct.id).name).to eql "morgan"
110
+ end
111
+
112
+ it "should raise error if trying to find id that not exist" do
113
+ expect{DirtyClass.find("666")}.to raise_error
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,260 @@
1
+ require 'spec_helper'
2
+ require 'uuidtools'
3
+ require 'sandthorn/aggregate_root_dirty_hashy'
4
+ require 'sandthorn/aggregate_root_snapshot'
5
+ require 'date'
6
+
7
+
8
+ module BankAccountInterestCommands
9
+ def calculate_interest! until_date = DateTime.now
10
+ # skipping all safety-checks..
11
+ # and this is of course horribly wrong financially speaking.. whatever
12
+ pay_out_unpaid_interest!
13
+ interest_calculation_time = until_date - @last_interest_calculation
14
+ days_with_interest = interest_calculation_time.to_i
15
+ unpaid_interest = @balance * @current_interest_info[:interest_rate] * days_with_interest / 365.2425
16
+ added_unpaid_interest_event unpaid_interest,until_date
17
+ end
18
+
19
+ def pay_out_unpaid_interest!
20
+ paid_out_unpaid_interest_balance_event @unpaid_interest_balance
21
+ end
22
+
23
+ def change_interest! new_interest_rate, interest_valid_from
24
+ calculate_interest!
25
+ changed_interest_rate_event new_interest_rate,interest_valid_from
26
+ end
27
+ end
28
+ module BankAccountWithdrawalCommands
29
+ def withdraw_from_atm! amount, atm_id
30
+ withdrew_amount_from_atm_event amount, atm_id
31
+ end
32
+
33
+ def withdraw_from_cashier! amount, cashier_id
34
+ withdrew_amount_from_cashier_event amount, cashier_id
35
+ charged_cashier_withdrawal_fee_event 50
36
+ end
37
+ end
38
+
39
+ module BankAccountVisaCardPurchasesCommands
40
+ def charge_card! amount, merchant_id
41
+ visa = VisaCardTransactionGateway.new
42
+ transaction_id = visa.charge_card "3030-3333-4252-2535", merchant_id, amount
43
+ paid_with_visa_card_event amount, transaction_id
44
+ end
45
+
46
+ end
47
+
48
+ class VisaCardTransactionGateway
49
+ def initialize
50
+ @visa_connector = "foo_bar"
51
+ end
52
+ def charge_card visa_card_number, merchant_id, amount
53
+ transaction_id = UUIDTools::UUID.random_create.to_s
54
+ end
55
+ end
56
+
57
+ module BankAccountDepositCommmands
58
+ def deposit_at_bank_office! amount, cashier_id
59
+ deposited_to_cashier_event amount, cashier_id
60
+ end
61
+
62
+ def transfer_money_from_another_account! amount, from_account_number
63
+ incoming_transfer_event amount,from_account_number
64
+ end
65
+ end
66
+
67
+ class BankAccount
68
+ include Sandthorn::AggregateRoot::DirtyHashy
69
+
70
+ attr_reader :balance
71
+ attr_reader :account_number
72
+ attr_reader :current_interest_info
73
+ attr_reader :account_creation_date
74
+ attr_reader :unpaid_interest_balance
75
+ attr_reader :last_interest_calculation
76
+
77
+ def initialize *args
78
+ account_number = args[0]
79
+ interest_rate = args[1]
80
+ creation_date = args[2]
81
+
82
+ @current_interest_info = {}
83
+ @current_interest_info[:interest_rate] = interest_rate
84
+ @current_interest_info[:interest_valid_from] = creation_date
85
+ @balance = 0
86
+ @unpaid_interest_balance = 0
87
+ @account_creation_date = creation_date
88
+ @last_interest_calculation = creation_date
89
+ end
90
+
91
+ def changed_interest_rate_event new_interest_rate, interest_valid_from
92
+ @current_interest_info[:interest_rate] = new_interest_rate
93
+ @current_interest_info[:interest_valid_from] = interest_valid_from
94
+ record_event new_interest_rate,interest_valid_from
95
+ end
96
+
97
+ def added_unpaid_interest_event interest_amount, calculated_until
98
+ @unpaid_interest_balance += interest_amount
99
+ @last_interest_calculation = calculated_until
100
+ record_event interest_amount, calculated_until
101
+ end
102
+
103
+ def paid_out_unpaid_interest_balance_event interest_amount
104
+ @unpaid_interest_balance -= interest_amount
105
+ @balance += interest_amount
106
+ record_event interest_amount
107
+ end
108
+
109
+ def withdrew_amount_from_atm_event amount, atm_id
110
+ @balance -= amount
111
+ record_event amount,atm_id
112
+ end
113
+
114
+ def withdrew_amount_from_cashier_event amount, cashier_id
115
+ @balance -= amount
116
+ record_event amount, cashier_id
117
+ end
118
+
119
+ def paid_with_visa_card_event amount, visa_card_transaction_id
120
+ @balance -= amount
121
+ record_event amount,visa_card_transaction_id
122
+ end
123
+
124
+ def charged_cashier_withdrawal_fee_event amount
125
+ @balance -= amount
126
+ record_event amount
127
+ end
128
+
129
+ def deposited_to_cashier_event amount, cashier_id
130
+ @balance = self.balance + amount
131
+ record_event amount,cashier_id
132
+ end
133
+
134
+ def incoming_transfer_event amount, from_account_number
135
+ current_balance = self.balance
136
+ @balance = amount + current_balance
137
+ record_event amount, from_account_number
138
+ end
139
+
140
+ end
141
+
142
+ def a_test_account
143
+ a = BankAccount.new "91503010111",0.031415, Date.new(2011,10,12)
144
+ a.extend BankAccountDepositCommmands
145
+ a.transfer_money_from_another_account! 90000, "FOOBAR"
146
+ a.deposit_at_bank_office! 10000, "Lars Idorn"
147
+
148
+ a.extend BankAccountVisaCardPurchasesCommands
149
+ a.charge_card! 1000, "Starbucks Coffee"
150
+
151
+ a.extend BankAccountInterestCommands
152
+ a.calculate_interest!
153
+ return a
154
+ end
155
+
156
+ #Tests part
157
+ describe "when doing aggregate_find on an aggregate with a snapshot" do
158
+ let(:aggregate) do
159
+ a = a_test_account
160
+ a.save
161
+ a.extend Sandthorn::AggregateRootSnapshot
162
+ a.aggregate_snapshot!
163
+ a.save_snapshot
164
+ a.charge_card! 9000, "Apple"
165
+ a.save
166
+ a
167
+ end
168
+ it "should be loaded with correct version" do
169
+ org = aggregate
170
+ loaded = BankAccount.find org.aggregate_id
171
+ expect(loaded.balance).to eql org.balance
172
+ end
173
+ end
174
+
175
+ describe 'when generating state on an aggregate root' do
176
+
177
+ before(:each) do
178
+ @original_account = a_test_account
179
+ events = @original_account.aggregate_events
180
+ @account = BankAccount.aggregate_build events
181
+ @account.extend Sandthorn::AggregateRootSnapshot
182
+ @account.aggregate_snapshot!
183
+ end
184
+
185
+ it 'account should have properties set' do
186
+ @account.balance.should eql 99000
187
+ @account.unpaid_interest_balance.should be > 1000
188
+ end
189
+
190
+ it 'should store snapshot data in aggregate_snapshot' do
191
+ @account.aggregate_snapshot.should be_a(Hash)
192
+ end
193
+
194
+ it 'should store aggregate_version in aggregate_snapshot' do
195
+ @account.aggregate_snapshot[:aggregate_version].should eql(@original_account.aggregate_current_event_version)
196
+ end
197
+
198
+ it 'should be able to load up from snapshot' do
199
+
200
+ events = [@account.aggregate_snapshot]
201
+ loaded = BankAccount.aggregate_build events
202
+
203
+ loaded.balance.should eql(@original_account.balance)
204
+ loaded.account_number.should eql(@original_account.account_number)
205
+ loaded.current_interest_info.should eql(@original_account.current_interest_info)
206
+ loaded.account_creation_date.should eql(@original_account.account_creation_date)
207
+ loaded.unpaid_interest_balance.should eql(@original_account.unpaid_interest_balance)
208
+ loaded.last_interest_calculation.should eql(@original_account.last_interest_calculation)
209
+ loaded.aggregate_id.should eql(@original_account.aggregate_id)
210
+ loaded.aggregate_originating_version.should eql(@account.aggregate_originating_version)
211
+
212
+ end
213
+
214
+ end
215
+
216
+ describe 'when saving to repository' do
217
+ let(:account) {a_test_account.extend Sandthorn::AggregateRootSnapshot}
218
+ it 'should raise an error if trying to save before creating a snapshot' do
219
+ lambda {account.save_snapshot}.should raise_error (RuntimeError)
220
+ end
221
+ it 'should not raise an error if snapshot was created' do
222
+ account.save
223
+ account.aggregate_snapshot!
224
+ lambda {account.save_snapshot}.should_not raise_error
225
+ end
226
+ it 'should set aggregate_snapshot to nil' do
227
+ account.save
228
+ account.aggregate_snapshot!
229
+ account.save_snapshot
230
+ account.aggregate_snapshot.should eql(nil)
231
+ end
232
+
233
+ it 'should raise error if trying to create snapshot before events are saved on object' do
234
+ lambda {account.aggregate_snapshot!}.should raise_error
235
+ end
236
+
237
+ it 'should not raise an error if trying to create snapshot on object when events are saved' do
238
+ account.save
239
+ lambda {account.aggregate_snapshot!}.should_not raise_error
240
+ end
241
+
242
+ it 'should get snapshot on account find when a snapshot is saved' do
243
+
244
+ account.save
245
+ account.aggregate_snapshot!
246
+ account.save_snapshot
247
+
248
+ loaded = BankAccount.find account.aggregate_id
249
+
250
+ loaded.balance.should eql(account.balance)
251
+ loaded.account_number.should eql(account.account_number)
252
+ loaded.current_interest_info.should eql(account.current_interest_info)
253
+ loaded.account_creation_date.should eql(account.account_creation_date)
254
+ loaded.unpaid_interest_balance.should eql(account.unpaid_interest_balance)
255
+ loaded.last_interest_calculation.should eql(account.last_interest_calculation)
256
+ loaded.aggregate_id.should eql(account.aggregate_id)
257
+ loaded.aggregate_originating_version.should eql(account.aggregate_originating_version)
258
+
259
+ end
260
+ end