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