HornsAndHooves-moribus 0.2.1 → 0.3.0

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 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