HornsAndHooves-moribus 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e5e4d15715c31a7fc3c7fbc7e5374f27271d735a
4
- data.tar.gz: def9ae949ad1d956852b70e23f8652bd2d977f42
3
+ metadata.gz: cd6054e76e011057a8862362538096a93e561ab1
4
+ data.tar.gz: 4aba542cca9591c5766a24a6513dc8b793545e80
5
5
  SHA512:
6
- metadata.gz: dc9fe7bfc87c9a2be5e04781d5345c87bb3b10ba6ed9925641cb60bc80e1e93eb07a1a737c9ffbf2e9e60872451753a27cd42e601245741f85b7b47f26a3c4bc
7
- data.tar.gz: c26e4d4ab22786a432b6d6aec34d932b2466b921a0c860b5bf71cd67c9811872bc9c492950e65c0dd3f0aa0406be5d48aa3c681e80a8153928eb1fd2d9872f83
6
+ metadata.gz: 8f53fb45401fc981d4add1c06d91ffdf6aa15276a203a5ddfd318b4a6154520942436d2ffdb22243f47000a09afdae364ee1c42619eaa40d9240f1257b226092
7
+ data.tar.gz: 24f83c44a0e8a9d4abaae44f2167a77110b86ab8766bfb44be54e45ffdc93919f00e4daeded5d5a0d33bfe00b7874a31006bb19d4144bacbb939c8e10750e58a
data/.simplecov CHANGED
@@ -12,7 +12,7 @@ SimpleCov.start do
12
12
  # Fail the build when coverage is weak:
13
13
  at_exit do
14
14
  SimpleCov.result.format!
15
- threshold, actual = 97.15, SimpleCov.result.covered_percent
15
+ threshold, actual = 97.354, SimpleCov.result.covered_percent
16
16
  if actual < threshold
17
17
  msg = "\nLow coverage: "
18
18
  msg << red("#{actual}%")
@@ -22,6 +22,8 @@ module Moribus
22
22
 
23
23
  # :nodoc:
24
24
  module ClassMethods
25
+ attr_reader :parent_relation_keys
26
+
25
27
  # Adds aggregated behavior to a model.
26
28
  def acts_as_aggregated(options = {})
27
29
  options.symbolize_keys!
@@ -44,10 +46,11 @@ module Moribus
44
46
  def acts_as_tracked(options = {})
45
47
  options.symbolize_keys!
46
48
 
47
- options.assert_valid_keys(:preceding_key)
49
+ options.assert_valid_keys(:preceding_key, :by)
48
50
  include TrackedBehavior
49
51
 
50
52
  @preceding_key_column = options[:preceding_key]
53
+ @parent_relation_keys = get_parent_relation_keys(options[:by])
51
54
  end
52
55
  private :acts_as_tracked
53
56
 
@@ -60,6 +63,21 @@ module Moribus
60
63
  def acts_as_tracked?
61
64
  self < TrackedBehavior
62
65
  end
66
+
67
+ # Return array of parent relation foreign keys, which is used for lock_version calculation.
68
+ #
69
+ # @param parent [Symbol,Array<Symbol>]
70
+ # @return [Array<String>]
71
+ def get_parent_relation_keys(parent)
72
+ parents = Array(parent)
73
+
74
+ reflect_on_all_associations.inject([]) do |result, relation|
75
+ result << relation.foreign_key if parents.include?(relation.name)
76
+
77
+ result
78
+ end
79
+ end
80
+ private :get_parent_relation_keys
63
81
  end
64
82
 
65
83
  # Marks +self+ as a new record. Sets +id+ attribute to nil, but memorizes
@@ -9,7 +9,18 @@ module Moribus
9
9
  if target.new_record?
10
10
  target.is_current = false
11
11
  else
12
- target.update_attribute(:is_current, false)
12
+ # Use custom update to avoid running ActiveRecord optimistic locking
13
+ # and to avoid updating lock_version column:
14
+ klass = target.class
15
+ is_current_col = klass.columns.detect { |c| c.name == "is_current" }
16
+ id_column = klass.columns.detect { |c| c.name == klass.primary_key }
17
+
18
+ sql = "UPDATE #{klass.quoted_table_name} " \
19
+ "SET \"is_current\" = #{klass.quote_value(false, is_current_col)} "
20
+ sql << "WHERE #{klass.quoted_primary_key} = " \
21
+ "#{klass.quote_value(target.send(klass.primary_key), id_column)} "
22
+
23
+ klass.connection.update sql
13
24
  end
14
25
  end
15
26
  end
@@ -29,11 +29,16 @@ module Moribus
29
29
  begin
30
30
  # SQL UPDATE statement is executed in first place to prevent
31
31
  # crashing on uniqueness constraints with 'is_current' condition.
32
- yield if update_current
32
+ if update_current
33
+ set_lock
34
+ yield
35
+ end
33
36
  ensure
34
37
  to_persistent! if new_record?
35
38
  end
36
39
  else
40
+ set_lock if new_record?
41
+
37
42
  yield
38
43
  end
39
44
  end
@@ -50,7 +55,9 @@ module Moribus
50
55
  # optimistic locking behavior.
51
56
  def update_current
52
57
  statement = current_to_false_sql_statement
58
+
53
59
  affected_rows = self.class.connection.update statement
60
+
54
61
  unless affected_rows == 1
55
62
  raise ActiveRecord::StaleObjectError.new(self, "update_current")
56
63
  end
@@ -58,6 +65,24 @@ module Moribus
58
65
  end
59
66
  private :update_current
60
67
 
68
+ # Set incremental lock_version column.
69
+ def set_lock
70
+ klass = self.class
71
+ lock_column_name = klass.locking_column
72
+
73
+ if has_attribute?(lock_column_name) && klass.parent_relation_keys.count > 0
74
+ criteria = klass.parent_relation_keys.inject({}) do |result, parent_key|
75
+ result[parent_key] = read_attribute(parent_key)
76
+ result
77
+ end
78
+
79
+ lock_value = klass.unscoped.where(criteria).count
80
+
81
+ write_attribute(lock_column_name, lock_value)
82
+ end
83
+ end
84
+ private :set_lock
85
+
61
86
  # Generate an arel statement to update the 'is_current' state of the
62
87
  # record to false. And perform the very same actions AR does for record
63
88
  # update, but using only a single 'is_current' column.
@@ -76,11 +101,12 @@ module Moribus
76
101
  # private :current_to_false_arel_statement
77
102
 
78
103
  # Generate SQL statement to be used to update 'is_current' state of record to false.
104
+ # TODO: need to find way to track stale objects
79
105
  def current_to_false_sql_statement
80
106
  klass = self.class
81
107
  is_current_col = klass.columns.detect { |c| c.name == "is_current" }
82
108
  lock_column_name = klass.locking_column
83
- lock_value = respond_to?(lock_column_name) && send(lock_column_name).to_i
109
+ lock_value = has_attribute?(lock_column_name) && read_attribute(lock_column_name).to_i
84
110
  lock_column = if lock_value
85
111
  klass.columns.detect { |c| c.name == lock_column_name }
86
112
  else
@@ -89,10 +115,15 @@ module Moribus
89
115
  id_column = klass.columns.detect { |c| c.name == klass.primary_key }
90
116
  quoted_lock_column = klass.connection.quote_column_name(lock_column_name)
91
117
 
92
- "UPDATE #{klass.quoted_table_name} SET \"is_current\" = #{klass.quote_value(false, is_current_col)} ".tap do |sql|
93
- sql << ", #{quoted_lock_column} = #{klass.quote_value(lock_value + 1, lock_column)} " if lock_value
94
- sql << "WHERE #{klass.quoted_primary_key} = #{klass.quote_value(@_before_to_new_record_values[:id], id_column)} "
95
- sql << "AND #{quoted_lock_column} = #{klass.quote_value(lock_value, lock_column)}" if lock_value
118
+ "UPDATE #{klass.quoted_table_name} " \
119
+ "SET \"is_current\" = #{klass.quote_value(false, is_current_col)} ".tap do |sql|
120
+ sql << "WHERE #{klass.quoted_primary_key} = " \
121
+ "#{klass.quote_value(@_before_to_new_record_values[:id], id_column)}"
122
+
123
+ if lock_value
124
+ sql << " AND \"is_current\" = #{klass.quote_value(true, is_current_col)}"
125
+ sql << " AND #{quoted_lock_column} = #{klass.quote_value(lock_value, lock_column)}"
126
+ end
96
127
  end
97
128
  end
98
129
  private :current_to_false_sql_statement
@@ -1,3 +1,3 @@
1
1
  module Moribus # :nodoc:
2
- VERSION = "0.2.1" # :nodoc:
2
+ VERSION = "0.3.0" # :nodoc:
3
3
  end
@@ -0,0 +1,22 @@
1
+ diff --git a/.simplecov b/.simplecov
2
+ index d428836..a242fb5 100644
3
+ --- a/.simplecov
4
+ +++ b/.simplecov
5
+ @@ -12,7 +12,7 @@ SimpleCov.start do
6
+ # Fail the build when coverage is weak:
7
+ at_exit do
8
+ SimpleCov.result.format!
9
+ - threshold, actual = 97.15, SimpleCov.result.covered_percent
10
+ + threshold, actual = 97.354, SimpleCov.result.covered_percent
11
+ if actual < threshold
12
+ msg = "\nLow coverage: "
13
+ msg << red("#{actual}%")
14
+ diff --git a/lib/moribus/version.rb b/lib/moribus/version.rb
15
+ index 20963da..629a05d 100644
16
+ --- a/lib/moribus/version.rb
17
+ +++ b/lib/moribus/version.rb
18
+ @@ -1,3 +1,3 @@
19
+ module Moribus # :nodoc:
20
+ - VERSION = "0.2.1" # :nodoc:
21
+ + VERSION = "0.3.0" # :nodoc:
22
+ end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
3
  describe Moribus do
4
4
  before do
@@ -6,29 +6,32 @@ describe Moribus do
6
6
  acts_as_enumerated
7
7
 
8
8
  self.enumeration_model_updates_permitted = true
9
- create!(:name => 'inactive', :description => 'Inactive')
10
- create!(:name => 'active', :description => 'Active')
9
+ create!(:name => "inactive", :description => "Inactive")
10
+ create!(:name => "active" , :description => "Active")
11
11
  end
12
12
 
13
13
  class SpecType < MoribusSpecModel(:name => :string, :description => :string)
14
14
  acts_as_enumerated
15
15
 
16
16
  self.enumeration_model_updates_permitted = true
17
- create!(:name => 'important', :description => 'Important')
18
- create!(:name => 'unimportant', :description => 'Unimportant')
17
+ create!(:name => "important" , :description => "Important")
18
+ create!(:name => "unimportant", :description => "Unimportant")
19
19
  end
20
20
 
21
21
  class SpecSuffix < MoribusSpecModel(:name => :string, :description => :string)
22
22
  acts_as_enumerated
23
23
 
24
24
  self.enumeration_model_updates_permitted = true
25
- create!(:name => 'none', :description => '')
26
- create!(:name => 'jr', :description => 'Junior')
25
+ create!(:name => "none", :description => "")
26
+ create!(:name => "jr" , :description => "Junior")
27
27
  end
28
28
 
29
- class SpecPersonName < MoribusSpecModel(:first_name => :string, :last_name => :string, :spec_suffix_id => :integer)
29
+ class SpecPersonName < MoribusSpecModel(:first_name => :string,
30
+ :last_name => :string,
31
+ :spec_suffix_id => :integer
32
+ )
30
33
  acts_as_aggregated
31
- has_enumerated :spec_suffix, :default => ''
34
+ has_enumerated :spec_suffix, :default => ""
32
35
 
33
36
  validates_presence_of :first_name, :last_name
34
37
 
@@ -42,7 +45,8 @@ describe Moribus do
42
45
  acts_as_aggregated :cache_by => :feature_name
43
46
  end
44
47
 
45
- class SpecCustomerInfo < MoribusSpecModel( :spec_customer_id => :integer!,
48
+ class SpecCustomerInfo < MoribusSpecModel(
49
+ :spec_customer_id => :integer!,
46
50
  :spec_person_name_id => :integer,
47
51
  :spec_status_id => :integer,
48
52
  :spec_type_id => :integer,
@@ -50,7 +54,8 @@ describe Moribus do
50
54
  :lock_version => :integer,
51
55
  :created_at => :datetime,
52
56
  :updated_at => :datetime,
53
- :previous_id => :integer )
57
+ :previous_id => :integer
58
+ )
54
59
  attr :custom_field
55
60
 
56
61
  belongs_to :spec_customer, :inverse_of => :spec_customer_info, :touch => true
@@ -58,17 +63,40 @@ describe Moribus do
58
63
  has_enumerated :spec_status
59
64
  has_enumerated :spec_type
60
65
 
61
- acts_as_tracked :preceding_key => :previous_id
66
+ acts_as_tracked :by => :spec_customer, :preceding_key => :previous_id
67
+ end
68
+
69
+ class SpecCustomerInfoWithType < MoribusSpecModel(
70
+ :spec_customer_id => :integer!,
71
+ :spec_status_id => :integer,
72
+ :spec_type_id => :integer,
73
+ :is_current => :boolean,
74
+ :lock_version => :integer,
75
+ :created_at => :datetime,
76
+ :updated_at => :datetime
77
+ )
78
+ attr :custom_field
79
+
80
+ belongs_to :spec_customer, :inverse_of => :spec_customer_info_with_type, :touch => true
81
+ has_enumerated :spec_status
82
+ has_enumerated :spec_type
83
+
84
+ acts_as_tracked :by => [:spec_customer, :spec_type]
62
85
  end
63
86
 
64
87
  class SpecCustomer < MoribusSpecModel(:spec_status_id => :integer)
65
88
  has_one_current :spec_customer_info, :inverse_of => :spec_customer
66
- has_enumerated :spec_status, :default => 'inactive'
89
+ has_one_current :spec_customer_info_with_type, :inverse_of => :spec_customer
90
+ has_enumerated :spec_status, :default => "inactive"
67
91
 
68
92
  delegate_associated :spec_person_name, :custom_field, :spec_type, :to => :spec_customer_info
69
93
  end
70
94
 
71
- class SpecCustomerEmail < MoribusSpecModel(:spec_customer_id => :integer, :email => :string, :is_current => :boolean, :status => :string)
95
+ class SpecCustomerEmail < MoribusSpecModel(:spec_customer_id => :integer,
96
+ :email => :string,
97
+ :is_current => :boolean,
98
+ :status => :string
99
+ )
72
100
  connection.add_index table_name, [:email, :is_current], :unique => true
73
101
 
74
102
  belongs_to :spec_customer
@@ -84,18 +112,19 @@ describe Moribus do
84
112
  describe "common behavior" do
85
113
  before do
86
114
  @info = SpecCustomerInfo.create(
87
- :spec_customer_id => 1,
115
+ :spec_customer_id => 1,
88
116
  :spec_person_name_id => 1,
89
- :is_current => true,
90
- :created_at => 5.days.ago,
91
- :updated_at => 5.days.ago
117
+ :is_current => true,
118
+ :created_at => 5.days.ago,
119
+ :updated_at => 5.days.ago
92
120
  )
93
121
  end
94
122
 
95
123
  it "should revert changes if exception is raised" do
96
- old_id = @info.id
124
+ old_id = @info.id
97
125
  old_updated_at = @info.updated_at
98
126
  old_created_at = @info.created_at
127
+
99
128
  suppress(Exception) do
100
129
  expect {
101
130
  @info.update_attributes :spec_customer_id => nil, :spec_person_name_id => 2
@@ -108,7 +137,7 @@ describe Moribus do
108
137
  end
109
138
  end
110
139
 
111
- describe 'Aggregated' do
140
+ describe "Aggregated" do
112
141
  context "definition" do
113
142
  it "should raise an error on an unknown option" do
114
143
  expect{
@@ -128,111 +157,121 @@ describe Moribus do
128
157
  end
129
158
 
130
159
  before do
131
- @existing = SpecPersonName.create! :first_name => 'John', :last_name => 'Smith'
160
+ @existing = SpecPersonName.create! :first_name => "John", :last_name => "Smith"
132
161
  end
133
162
 
134
- it "should not duplicate records" do
163
+ it "doesn't duplicate records" do
135
164
  expect {
136
- SpecPersonName.create :first_name => ' John ', :last_name => 'Smith'
165
+ SpecPersonName.create :first_name => " John ", :last_name => "Smith"
137
166
  }.not_to change(SpecPersonName, :count)
138
167
  end
139
168
 
140
- it "should lookup self and replace id with existing on create" do
141
- name = SpecPersonName.new :first_name => 'John', :last_name => 'Smith'
169
+ it "looks up self and replaces id with existing on create" do
170
+ name = SpecPersonName.new :first_name => "John", :last_name => "Smith"
142
171
  name.save
143
172
  expect(name.id).to eq @existing.id
144
173
  end
145
174
 
146
- it "should create a new record if lookup fails" do
175
+ it "creates a new record if lookup fails" do
147
176
  expect {
148
- SpecPersonName.create :first_name => 'Alice', :last_name => 'Smith'
177
+ SpecPersonName.create :first_name => "Alice", :last_name => "Smith"
149
178
  }.to change(SpecPersonName, :count).by(1)
150
179
  end
151
180
 
152
- it "should lookup self and replace id with existing on update" do
153
- name = SpecPersonName.create :first_name => 'Alice', :last_name => 'Smith'
154
- name.update_attributes :first_name => 'John'
181
+ it "looks up self and replaces id with existing on update" do
182
+ name = SpecPersonName.create :first_name => "Alice", :last_name => "Smith"
183
+ name.update_attributes :first_name => "John"
155
184
  expect(name.id).to eq @existing.id
156
185
  end
157
186
 
158
187
  context "with caching" do
159
188
  before do
160
- @existing = SpecCustomerFeature.create(:feature_name => 'Pays')
189
+ @existing = SpecCustomerFeature.create(:feature_name => "Pays")
161
190
  SpecCustomerFeature.clear_cache
162
191
  end
163
192
 
164
- it "should lookup the existing value and add it to the cache" do
193
+ it "looks up the existing value and adds it to the cache" do
165
194
  feature = SpecCustomerFeature.new :feature_name => @existing.feature_name
166
- expect{ feature.save }.to change(SpecCustomerFeature.aggregated_records_cache, :length).by(1)
195
+
196
+ expect{ feature.save }.
197
+ to change(SpecCustomerFeature.aggregated_records_cache, :length).by(1)
198
+
167
199
  expect(feature.id).to eq @existing.id
168
200
  end
169
201
 
170
- it "should add the freshly-created record to the cache" do
171
- expect{ SpecCustomerFeature.create(:feature_name => 'Fraud') }.to change(SpecCustomerFeature.aggregated_records_cache, :length).by(1)
202
+ it "adds the freshly-created record to the cache" do
203
+ expect{ SpecCustomerFeature.create(:feature_name => "Fraud") }.
204
+ to change(SpecCustomerFeature.aggregated_records_cache, :length).by(1)
172
205
  end
173
206
 
174
- it "should freeze the cached object" do
175
- feature = SpecCustomerFeature.create(:feature_name => 'Cancelled')
207
+ it "freezes the cached object" do
208
+ feature = SpecCustomerFeature.create(:feature_name => "Cancelled")
176
209
  expect(SpecCustomerFeature.aggregated_records_cache[feature.feature_name]).to be_frozen
177
210
  end
178
211
 
179
- it "should cache the clone of the record, not the record itself" do
180
- feature = SpecCustomerFeature.create(:feature_name => 'Returned')
181
- expect(SpecCustomerFeature.aggregated_records_cache[feature.feature_name].object_id).not_to eq feature.object_id
212
+ it "caches the clone of the record, not the record itself" do
213
+ feature = SpecCustomerFeature.create(:feature_name => "Returned")
214
+ expect(SpecCustomerFeature.aggregated_records_cache[feature.feature_name].object_id).
215
+ not_to eq feature.object_id
182
216
  end
183
217
  end
184
218
  end
185
219
 
186
- describe 'Tracked' do
220
+ describe "Tracked" do
187
221
  before do
188
222
  @customer = SpecCustomer.create
189
223
  @info = @customer.create_spec_customer_info :spec_person_name_id => 1
190
224
  end
191
225
 
192
- it "should create a new current record if updated" do
226
+ it "creates a new current record if updated" do
193
227
  expect {
194
228
  @info.update_attributes(:spec_person_name_id => 2)
195
229
  }.to change(SpecCustomerInfo, :count).by(1)
196
230
  end
197
231
 
198
- it "should replace itself with new id" do
232
+ it "replaces itself with new id" do
199
233
  old_id = @info.id
200
234
  @info.update_attributes(:spec_person_name_id => 2)
201
235
  expect(@info.id).not_to eq old_id
202
236
  end
203
237
 
204
- it "should set is_current record to false for superseded record" do
238
+ it "sets is_current record to false for superseded record" do
205
239
  old_id = @info.id
206
240
  @info.update_attributes(:spec_person_name_id => 2)
207
241
  expect(SpecCustomerInfo.find(old_id).is_current).to eq false
208
242
  end
209
243
 
210
- it "should set previous_id to the id of the previous record" do
244
+ it "sets previous_id to the id of the previous record" do
211
245
  old_id = @info.id
212
246
  @info.update_attributes(:spec_person_name_id => 2)
213
247
  expect(@info.previous_id).to eq old_id
214
248
  end
215
249
 
216
- it "assigning a new current record should change is_current to false for previous one" do
250
+ it "changes is_current to false for previous one when assigning a new current record" do
217
251
  new_info = SpecCustomerInfo.new :spec_person_name_id => 2, :is_current => true
218
252
  @customer.spec_customer_info = new_info
219
253
  expect(new_info.spec_customer_id).to eq @customer.id
254
+ @info.reload
220
255
  expect(@info.is_current ).to eq false
221
256
  end
222
257
 
223
- it "should not crash on superseding with 'is_current' conditional constraint" do
224
- email = SpecCustomerEmail.create(:spec_customer => @customer, :email => 'foo@bar.com', :status => 'unverified', :is_current => true)
225
- expect{ email.update_attributes(:status => 'verified') }.not_to raise_error
258
+ it "does not crash on superseding with 'is_current' conditional constraint" do
259
+ email = SpecCustomerEmail.create( :spec_customer => @customer,
260
+ :email => "foo@bar.com",
261
+ :status => "unverified",
262
+ :is_current => true
263
+ )
264
+ expect{ email.update_attributes(:status => "verified") }.not_to raise_error
226
265
  end
227
266
 
228
- describe 'updated_at and created_at' do
229
- let(:first_time) { Time.zone.parse('2012-07-16 00:00:00') }
230
- let(:second_time) { Time.zone.parse('2012-07-17 08:10:15') }
267
+ describe "updated_at and created_at" do
268
+ let(:first_time) { Time.zone.parse("2012-07-16 00:00:00") }
269
+ let(:second_time) { Time.zone.parse("2012-07-17 08:10:15") }
231
270
 
232
271
  before { Timecop.freeze(first_time) }
233
272
  after { Timecop.return }
234
273
 
235
- it "should be updated on change" do
274
+ it "is updated on change" do
236
275
  info = @customer.create_spec_customer_info :spec_person_name_id => 1
237
276
  expect(info.updated_at).to eq first_time
238
277
  expect(info.created_at).to eq first_time
@@ -251,28 +290,75 @@ describe Moribus do
251
290
  @info2 = @customer.reload.spec_customer_info
252
291
  end
253
292
 
254
- it "should raise stale object error" do
293
+ it "raises a stale object error" do
255
294
  @info1.update_attributes(:spec_person_name_id => 3)
256
295
 
257
- expect{ @info2.update_attributes(:spec_person_name_id => 4) }.to raise_error(ActiveRecord::StaleObjectError)
296
+ expect{ @info2.update_attributes(:spec_person_name_id => 4) }.
297
+ to raise_error(ActiveRecord::StaleObjectError)
298
+ end
299
+
300
+ it "updates lock_version incrementally for each new record" do
301
+ spec_customer_info = @customer.spec_customer_info
302
+
303
+ expect {
304
+ spec_customer_info.update_attributes(:spec_person_name_id => 3)
305
+ }.to change { spec_customer_info.lock_version }.from(0).to(1)
306
+
307
+ expect {
308
+ spec_customer_info.update_attributes(:spec_person_name_id => 4)
309
+ }.to change { spec_customer_info.lock_version }.from(1).to(2)
310
+ end
311
+
312
+ it "does not fail if no locking_column is present" do
313
+ email = SpecCustomerEmail.create(:spec_customer_id => 1, :email => "foo@bar.com")
314
+ expect{ email.update_attributes(:email => "foo2@bar.com") }.not_to raise_error
315
+ end
316
+
317
+ it "updates lock_version column based on parent relation" do
318
+ @other_customer = SpecCustomer.create
319
+ spec_customer_info = @customer.spec_customer_info
320
+
321
+ expect {
322
+ spec_customer_info.update_attributes(:spec_person_name_id => 3)
323
+ }.to change { spec_customer_info.lock_version }.from(0).to(1)
324
+
325
+ expect {
326
+ spec_customer_info.update_attributes(:spec_customer => @other_customer)
327
+ }.to change { spec_customer_info.lock_version }.from(1).to(0)
258
328
  end
259
329
 
260
- it "should not fail if no locking_column present" do
261
- email = SpecCustomerEmail.create(:spec_customer_id => 1, :email => 'foo@bar.com')
262
- expect{ email.update_attributes(:email => 'foo2@bar.com') }.not_to raise_error
330
+ it "updates lock_version column base on relation list from 'by' option" do
331
+ info_with_type =
332
+ @customer.reload.create_spec_customer_info_with_type(:spec_type => :important)
333
+
334
+ expect( info_with_type.lock_version ).to eq 0
335
+
336
+ other_info_with_type =
337
+ @customer.reload.create_spec_customer_info_with_type(:spec_type => :unimportant)
338
+
339
+ expect( other_info_with_type.lock_version ).to eq 0
340
+
341
+ info_with_type.update_attributes(:spec_status => :active)
342
+ expect( info_with_type.lock_version ).to eq 1
343
+
344
+ info_with_type.update_attributes(:spec_status => :inactive)
345
+ expect( info_with_type.lock_version ).to eq 2
346
+
347
+ expect( other_info_with_type.lock_version ).to eq 0
263
348
  end
264
349
  end
265
350
 
266
- describe 'with Aggregated' do
351
+ describe "with Aggregated" do
267
352
  before do
268
- @info.spec_person_name = SpecPersonName.create(:first_name => 'John', :last_name => 'Smith')
353
+ @info.spec_person_name =
354
+ SpecPersonName.create(:first_name => "John", :last_name => "Smith")
269
355
  @info.save
270
356
  @info.reload
271
357
  end
272
358
 
273
- it "should supersede when nested record changes" do
359
+ it "supersedes when nested record changes" do
274
360
  old_id = @info.id
275
- @customer.spec_customer_info.spec_person_name.first_name = 'Alice'
361
+ @customer.spec_customer_info.spec_person_name.first_name = "Alice"
276
362
  expect{ @customer.save }.to change(@info, :spec_person_name_id)
277
363
  expect(@info.id).not_to eq old_id
278
364
  expect(@info.is_current).to eq true
@@ -281,19 +367,19 @@ describe Moribus do
281
367
  end
282
368
  end
283
369
 
284
- describe 'Delegations' do
370
+ describe "Delegations" do
285
371
  before do
286
372
  @customer = SpecCustomer.create(
287
373
  :spec_customer_info_attributes => {
288
- :spec_person_name_attributes => {:first_name => ' John ', :last_name => 'Smith'} } )
374
+ :spec_person_name_attributes => {:first_name => " John ", :last_name => "Smith"} } )
289
375
  @info = @customer.spec_customer_info
290
376
  end
291
377
 
292
- it "should have delegated column information" do
378
+ it "has delegated column information" do
293
379
  expect(@customer.column_for_attribute(:first_name)).not_to be_nil
294
380
  end
295
381
 
296
- it "should not delegate special methods" do
382
+ it "does not delegate special methods" do
297
383
  expect(@customer).not_to respond_to(:reset_first_name)
298
384
  expect(@customer).not_to respond_to(:first_name_was)
299
385
  expect(@customer).not_to respond_to(:first_name_before_type_cast)
@@ -302,30 +388,30 @@ describe Moribus do
302
388
  expect(@customer).not_to respond_to(:lock_version)
303
389
  end
304
390
 
305
- it "should delegate methods to aggregated parts" do
391
+ it "delegates methods to aggregated parts" do
306
392
  expect(@info).to respond_to(:first_name)
307
393
  expect(@info).to respond_to(:first_name=)
308
394
  expect(@info).to respond_to(:spec_suffix)
309
- expect(@info.last_name).to eq 'Smith'
395
+ expect(@info.last_name).to eq "Smith"
310
396
  end
311
397
 
312
- it "should delegate methods to representation" do
398
+ it "delegates methods to representation" do
313
399
  expect(@customer).to respond_to(:first_name)
314
400
  expect(@customer).to respond_to(:first_name=)
315
401
  expect(@customer).to respond_to(:spec_suffix)
316
- expect(@customer.last_name).to eq 'Smith'
402
+ expect(@customer.last_name).to eq "Smith"
317
403
  expect(@customer).to respond_to(:custom_field)
318
404
  expect(@customer).to respond_to(:custom_field=)
319
405
  end
320
406
 
321
- it 'should properly delegate enumerated attributes' do
407
+ it "properly delegates enumerated attributes" do
322
408
  expect(@customer).to respond_to(:spec_type)
323
409
  expect(@customer).to respond_to(:spec_type=)
324
410
  @customer.spec_type = :important
325
411
  expect(@customer.spec_type === :important).to eq true
326
412
  end
327
413
 
328
- it "should raise NoMethodError if unknown method received" do
414
+ it "raises NoMethodError if unknown method is received" do
329
415
  expect{ @customer.impossibru }.to raise_error(NoMethodError)
330
416
  end
331
417
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: HornsAndHooves-moribus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - HornsAndHooves
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-01-22 00:00:00.000000000 Z
13
+ date: 2015-09-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -149,6 +149,7 @@ files:
149
149
  - lib/moribus/macros.rb
150
150
  - lib/moribus/tracked_behavior.rb
151
151
  - lib/moribus/version.rb
152
+ - merge.patch
152
153
  - spec/dummy/README.rdoc
153
154
  - spec/dummy/Rakefile
154
155
  - spec/dummy/app/assets/javascripts/application.js