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 +4 -4
- data/.simplecov +1 -1
- data/lib/moribus.rb +19 -1
- data/lib/moribus/extensions/has_current_extension.rb +12 -1
- data/lib/moribus/tracked_behavior.rb +37 -6
- data/lib/moribus/version.rb +1 -1
- data/merge.patch +22 -0
- data/spec/moribus_spec.rb +158 -72
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd6054e76e011057a8862362538096a93e561ab1
|
4
|
+
data.tar.gz: 4aba542cca9591c5766a24a6513dc8b793545e80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
threshold, actual = 97.354, SimpleCov.result.covered_percent
|
16
16
|
if actual < threshold
|
17
17
|
msg = "\nLow coverage: "
|
18
18
|
msg << red("#{actual}%")
|
data/lib/moribus.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 =
|
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}
|
93
|
-
|
94
|
-
sql << "WHERE #{klass.quoted_primary_key} =
|
95
|
-
|
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
|
data/lib/moribus/version.rb
CHANGED
data/merge.patch
ADDED
@@ -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
|
data/spec/moribus_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
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 =>
|
10
|
-
create!(:name =>
|
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 =>
|
18
|
-
create!(:name =>
|
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 =>
|
26
|
-
create!(:name =>
|
25
|
+
create!(:name => "none", :description => "")
|
26
|
+
create!(:name => "jr" , :description => "Junior")
|
27
27
|
end
|
28
28
|
|
29
|
-
class SpecPersonName < MoribusSpecModel(:first_name
|
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(
|
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
|
-
|
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,
|
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
|
115
|
+
:spec_customer_id => 1,
|
88
116
|
:spec_person_name_id => 1,
|
89
|
-
:is_current
|
90
|
-
:created_at
|
91
|
-
:updated_at
|
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
|
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
|
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 =>
|
160
|
+
@existing = SpecPersonName.create! :first_name => "John", :last_name => "Smith"
|
132
161
|
end
|
133
162
|
|
134
|
-
it "
|
163
|
+
it "doesn't duplicate records" do
|
135
164
|
expect {
|
136
|
-
SpecPersonName.create :first_name =>
|
165
|
+
SpecPersonName.create :first_name => " John ", :last_name => "Smith"
|
137
166
|
}.not_to change(SpecPersonName, :count)
|
138
167
|
end
|
139
168
|
|
140
|
-
it "
|
141
|
-
name = SpecPersonName.new :first_name =>
|
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 "
|
175
|
+
it "creates a new record if lookup fails" do
|
147
176
|
expect {
|
148
|
-
SpecPersonName.create :first_name =>
|
177
|
+
SpecPersonName.create :first_name => "Alice", :last_name => "Smith"
|
149
178
|
}.to change(SpecPersonName, :count).by(1)
|
150
179
|
end
|
151
180
|
|
152
|
-
it "
|
153
|
-
name = SpecPersonName.create :first_name =>
|
154
|
-
name.update_attributes :first_name =>
|
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 =>
|
189
|
+
@existing = SpecCustomerFeature.create(:feature_name => "Pays")
|
161
190
|
SpecCustomerFeature.clear_cache
|
162
191
|
end
|
163
192
|
|
164
|
-
it "
|
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
|
-
|
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 "
|
171
|
-
expect{ SpecCustomerFeature.create(:feature_name =>
|
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 "
|
175
|
-
feature = SpecCustomerFeature.create(:feature_name =>
|
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 "
|
180
|
-
feature = SpecCustomerFeature.create(:feature_name =>
|
181
|
-
expect(SpecCustomerFeature.aggregated_records_cache[feature.feature_name].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
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
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 "
|
224
|
-
email = SpecCustomerEmail.create(:spec_customer => @customer,
|
225
|
-
|
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
|
229
|
-
let(:first_time) { Time.zone.parse(
|
230
|
-
let(:second_time) { Time.zone.parse(
|
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 "
|
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 "
|
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) }.
|
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 "
|
261
|
-
|
262
|
-
|
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
|
351
|
+
describe "with Aggregated" do
|
267
352
|
before do
|
268
|
-
@info.spec_person_name =
|
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 "
|
359
|
+
it "supersedes when nested record changes" do
|
274
360
|
old_id = @info.id
|
275
|
-
@customer.spec_customer_info.spec_person_name.first_name =
|
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
|
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 =>
|
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 "
|
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 "
|
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 "
|
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
|
395
|
+
expect(@info.last_name).to eq "Smith"
|
310
396
|
end
|
311
397
|
|
312
|
-
it "
|
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
|
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
|
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 "
|
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.
|
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-
|
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
|