couchbase-model-relationship 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +19 -0
- data/couchbase-model-relationship.gemspec +33 -0
- data/lib/couchbase/model/attributes.rb +36 -0
- data/lib/couchbase/model/complex_attributes.rb +38 -0
- data/lib/couchbase/model/deep_copier.rb +45 -0
- data/lib/couchbase/model/dirty.rb +81 -0
- data/lib/couchbase/model/id_prefix.rb +53 -0
- data/lib/couchbase/model/relationship.rb +34 -0
- data/lib/couchbase/model/relationship/association.rb +40 -0
- data/lib/couchbase/model/relationship/child.rb +25 -0
- data/lib/couchbase/model/relationship/parent.rb +193 -0
- data/lib/couchbase/model/relationship/version.rb +7 -0
- data/spec/association_spec.rb +49 -0
- data/spec/attributes_spec.rb +33 -0
- data/spec/child_spec.rb +33 -0
- data/spec/complex_attributes_spec.rb +54 -0
- data/spec/deep_copier_spec.rb +56 -0
- data/spec/dirty_spec.rb +139 -0
- data/spec/id_prefix_spec.rb +45 -0
- data/spec/parent_spec.rb +282 -0
- data/spec/spec_helper.rb +197 -0
- metadata +246 -0
data/spec/dirty_spec.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class DirtyTest < Couchbase::Model
|
4
|
+
attribute :name
|
5
|
+
attribute :complex
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "Dirty" do
|
9
|
+
subject { DirtyTest.new }
|
10
|
+
|
11
|
+
it "should mark the fields as dirty" do
|
12
|
+
subject.name = 'abc'
|
13
|
+
subject.should be_name_changed
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should mark complex fields" do
|
17
|
+
inner = []
|
18
|
+
outer = [inner]
|
19
|
+
|
20
|
+
subject.complex = outer
|
21
|
+
subject.send :clean!
|
22
|
+
|
23
|
+
subject.complex_will_change!
|
24
|
+
subject.complex[0].push 'abc'
|
25
|
+
|
26
|
+
subject.should be_complex_changed
|
27
|
+
subject.complex.should eq([['abc']])
|
28
|
+
subject.complex_was.should eq([[]])
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not mark the field dirty when the value is the same" do
|
32
|
+
subject.name = "abc"
|
33
|
+
subject.send :clean!
|
34
|
+
|
35
|
+
subject.name = "abc"
|
36
|
+
|
37
|
+
subject.should_not be_name_changed
|
38
|
+
end
|
39
|
+
|
40
|
+
it "doesn't mark any fields as dirty when the model is loaded" do
|
41
|
+
DirtyTest.stubs(bucket: stub)
|
42
|
+
DirtyTest.bucket.expects(:get).with(['abc123'], quiet: false, extended: true).returns({'name' => "Bob"}, {}, 123)
|
43
|
+
|
44
|
+
DirtyTest.find('abc123').should_not be_changed
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "creating" do
|
48
|
+
describe "successfully" do
|
49
|
+
before do
|
50
|
+
subject.stubs(create_without_dirty: true)
|
51
|
+
subject.name = "creating"
|
52
|
+
subject.create
|
53
|
+
end
|
54
|
+
|
55
|
+
it "captures changes" do
|
56
|
+
subject.name.should eq("creating")
|
57
|
+
subject.should_not be_name_changed
|
58
|
+
subject.previous_changes['name'].should eq([nil, 'creating'])
|
59
|
+
end
|
60
|
+
|
61
|
+
it "clears current changes" do
|
62
|
+
subject.changes.should be_blank
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "failing" do
|
67
|
+
before do
|
68
|
+
subject.stubs(create_without_dirty: false)
|
69
|
+
subject.name = "creating"
|
70
|
+
subject.create
|
71
|
+
end
|
72
|
+
|
73
|
+
it "doesn't capture changes" do
|
74
|
+
subject.previous_changes.should be_blank
|
75
|
+
end
|
76
|
+
|
77
|
+
it "doesn't clear current changes" do
|
78
|
+
subject.changed.should eq(["name"])
|
79
|
+
end
|
80
|
+
|
81
|
+
it "doesn't have a previous change" do
|
82
|
+
subject.previous_name.should be_nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "saving" do
|
88
|
+
it "doesn't saves if not changed on request" do
|
89
|
+
subject.stubs(changed?: false)
|
90
|
+
subject.expects(:save).never
|
91
|
+
|
92
|
+
subject.save_if_changed
|
93
|
+
end
|
94
|
+
|
95
|
+
it "saves if changed on request" do
|
96
|
+
subject.stubs(changed?: true)
|
97
|
+
subject.expects(:save)
|
98
|
+
|
99
|
+
subject.save_if_changed
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "successfully" do
|
103
|
+
before do
|
104
|
+
subject.stubs(save_without_dirty: true)
|
105
|
+
subject.name = "save"
|
106
|
+
subject.id = 123
|
107
|
+
subject.save
|
108
|
+
end
|
109
|
+
|
110
|
+
it "captures changes" do
|
111
|
+
subject.name.should eq("save")
|
112
|
+
subject.should_not be_name_changed
|
113
|
+
subject.previous_changes['name'].should eq([nil, 'save'])
|
114
|
+
subject.previous_name.should eq(nil)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "clears current changes" do
|
118
|
+
subject.changes.should be_blank
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "failing" do
|
123
|
+
before do
|
124
|
+
subject.stubs(save_without_dirty: false)
|
125
|
+
subject.name = "save"
|
126
|
+
subject.id = 123
|
127
|
+
subject.save
|
128
|
+
end
|
129
|
+
|
130
|
+
it "doesn't capture changes" do
|
131
|
+
subject.previous_changes.should be_blank
|
132
|
+
end
|
133
|
+
|
134
|
+
it "doesn't clear current changes" do
|
135
|
+
subject.changed.should eq(["name"])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class IdPrefixTest < Couchbase::Model
|
4
|
+
end
|
5
|
+
|
6
|
+
describe "IdPrefix" do
|
7
|
+
subject { IdPrefixTest.new }
|
8
|
+
|
9
|
+
it "knows the proper prefix" do
|
10
|
+
subject.class.id_prefix.should eq("id_prefix_test")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "prefixes an id properly" do
|
14
|
+
subject.class.prefixed_id(123).should eq("id_prefix_test:123")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "unprefixes an id properly" do
|
18
|
+
subject.class.unprefixed_id("klass:123").should eq("123")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "gets the prefix for an id properly" do
|
22
|
+
subject.class.prefix_from_id("class:abc").should eq("class")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "gets the class for an id prperly" do
|
26
|
+
subject.class.class_from_id("id_prefix_test").should eq(IdPrefixTest)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "creating" do
|
30
|
+
before do
|
31
|
+
subject.stubs(create_without_id_prefix: true)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "doesn't set the id if present" do
|
35
|
+
subject.id = 123
|
36
|
+
subject.create
|
37
|
+
subject.id.should eq(123)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "creates a uid with a prefix" do
|
41
|
+
subject.create
|
42
|
+
subject.id.should match(/^id_prefix_test:/)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/parent_spec.rb
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Child < Couchbase::Model
|
4
|
+
attribute :age
|
5
|
+
|
6
|
+
has_parent
|
7
|
+
end
|
8
|
+
|
9
|
+
class Brother < Couchbase::Model
|
10
|
+
has_parent
|
11
|
+
end
|
12
|
+
|
13
|
+
class Sister < Couchbase::Model
|
14
|
+
has_parent
|
15
|
+
end
|
16
|
+
|
17
|
+
class ParentTest < Couchbase::Model
|
18
|
+
attribute :name
|
19
|
+
|
20
|
+
child :child
|
21
|
+
child :dont_load, auto_load: false
|
22
|
+
end
|
23
|
+
|
24
|
+
class AutoSaveTest < Couchbase::Model
|
25
|
+
include ActiveModel::Validations
|
26
|
+
|
27
|
+
attribute :name
|
28
|
+
validates_length_of :name, maximum: 5
|
29
|
+
|
30
|
+
child :child, auto_save: true
|
31
|
+
child :brother, auto_delete: true
|
32
|
+
end
|
33
|
+
|
34
|
+
class MultipleChildTest < Couchbase::Model
|
35
|
+
children :brother, :sister
|
36
|
+
end
|
37
|
+
|
38
|
+
class InvalidTest < Couchbase::Model
|
39
|
+
include ActiveModel::Validations
|
40
|
+
|
41
|
+
attribute :name
|
42
|
+
validates_length_of :name, maximum: 5
|
43
|
+
child :brother
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "parent" do
|
47
|
+
subject { ParentTest.new }
|
48
|
+
|
49
|
+
it "has a setter" do
|
50
|
+
subject.should respond_to(:child=)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "has a getter" do
|
54
|
+
subject.should respond_to(:child)
|
55
|
+
end
|
56
|
+
|
57
|
+
context "the getter" do
|
58
|
+
let(:association) { ParentTest.child_association_for :child }
|
59
|
+
|
60
|
+
it "returns the value is present" do
|
61
|
+
subject.child = (brother = Brother.new)
|
62
|
+
subject.child.should eq(brother)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "tries to load from the db if not loaded" do
|
66
|
+
subject.expects(:build_child).never
|
67
|
+
association.expects(:load).returns((child = Child.new)).once
|
68
|
+
|
69
|
+
subject.child.should eq(child)
|
70
|
+
subject.child.should eq(child)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "builds a new child if child doesn't exist in the db" do
|
74
|
+
association.expects(:load).returns(nil).once
|
75
|
+
|
76
|
+
subject.child.should be_a(Child)
|
77
|
+
subject.child.should be_a(Child)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "can reload itself and all it's children" do
|
82
|
+
subject.child = stub(reload: true)
|
83
|
+
subject.dont_load = stub(reload: true)
|
84
|
+
subject.expects(:reload)
|
85
|
+
|
86
|
+
subject.reload_all
|
87
|
+
end
|
88
|
+
|
89
|
+
it "knows if the child is loaded or not" do
|
90
|
+
subject.should_not be_child_loaded
|
91
|
+
subject.send :child_loaded!
|
92
|
+
subject.should be_child_loaded
|
93
|
+
end
|
94
|
+
|
95
|
+
it "handles multiple children" do
|
96
|
+
MultipleChildTest.new.should respond_to(:brother, :brother=, :sister, :sister=)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "saves dirty children if we want to save them" do
|
100
|
+
subject = MultipleChildTest.new
|
101
|
+
subject.brother = Brother.new
|
102
|
+
subject.brother.stubs(changed?: true)
|
103
|
+
subject.brother.expects(:save)
|
104
|
+
subject.sister = Sister.new
|
105
|
+
subject.sister.stubs(changed?: false)
|
106
|
+
subject.sister.expects(:save).never
|
107
|
+
|
108
|
+
subject.stubs(save: :saved)
|
109
|
+
|
110
|
+
subject.save_with_children.should eq(:saved)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "doesn't save children if the main object isn't valid" do
|
114
|
+
subject = InvalidTest.new
|
115
|
+
subject.brother = Brother.new
|
116
|
+
subject.brother.stubs(changed?: true)
|
117
|
+
subject.brother.expects(:save).never
|
118
|
+
|
119
|
+
subject.name = "123456"
|
120
|
+
|
121
|
+
subject.save_with_children
|
122
|
+
end
|
123
|
+
|
124
|
+
it "auto-saves children marked as autosaved" do
|
125
|
+
subject = AutoSaveTest.new name: "Test"
|
126
|
+
subject.child = Child.new age: 5
|
127
|
+
subject.brother = Brother.new
|
128
|
+
|
129
|
+
subject.stubs(save_without_autosave_children: true)
|
130
|
+
|
131
|
+
subject.child.expects(:save_if_changed)
|
132
|
+
subject.brother.expects(:save_if_changed).never
|
133
|
+
|
134
|
+
subject.save
|
135
|
+
end
|
136
|
+
|
137
|
+
it "doesn't auto-save children if we fail to save" do
|
138
|
+
subject = AutoSaveTest.new name: "Test abc"
|
139
|
+
subject.child = Child.new age: 5
|
140
|
+
subject.brother = Brother.new
|
141
|
+
|
142
|
+
subject.child.expects(:save_if_changed).never
|
143
|
+
subject.brother.expects(:save_if_changed).never
|
144
|
+
|
145
|
+
subject.save
|
146
|
+
end
|
147
|
+
|
148
|
+
it "auto-deletes children marked as auto-delete" do
|
149
|
+
subject = AutoSaveTest.new name: "Test"
|
150
|
+
subject.child = Child.new age: 5
|
151
|
+
subject.brother = Brother.new
|
152
|
+
|
153
|
+
subject.stubs(delete_without_autodelete_children: true)
|
154
|
+
|
155
|
+
subject.child.expects(:delete).never
|
156
|
+
subject.brother.expects(:delete)
|
157
|
+
|
158
|
+
subject.delete
|
159
|
+
end
|
160
|
+
|
161
|
+
it "deletes children when we're deleted" do
|
162
|
+
subject = MultipleChildTest.new
|
163
|
+
subject.brother = Brother.new
|
164
|
+
subject.brother.expects(:delete)
|
165
|
+
subject.sister = Sister.new
|
166
|
+
subject.sister.expects(:delete)
|
167
|
+
|
168
|
+
subject.stubs(delete: :deleted)
|
169
|
+
|
170
|
+
subject.delete_with_children.should eq(:deleted)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "builds a new object properly" do
|
174
|
+
subject.id = "parent_test:123"
|
175
|
+
child = subject.build_child age: 6
|
176
|
+
|
177
|
+
child.should eq(subject.child)
|
178
|
+
child.age.should eq(6)
|
179
|
+
child.parent.should eq(subject)
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "finding objects with children" do
|
183
|
+
subject { ParentTest }
|
184
|
+
let(:bucket) { stub }
|
185
|
+
|
186
|
+
before do
|
187
|
+
subject.stubs(bucket: bucket)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "finds and returns the proper objects" do
|
191
|
+
bucket.expects(:get).with(
|
192
|
+
["parent:1", "child:1"],
|
193
|
+
quiet: true,
|
194
|
+
extended: true
|
195
|
+
).returns({
|
196
|
+
"parent:1" => [{name: "abc"}, 0, :cas],
|
197
|
+
"child:1" => [{age: 5}, 0, :cas]
|
198
|
+
})
|
199
|
+
|
200
|
+
parent = subject.find_with_children("parent:1")
|
201
|
+
parent.name.should eq("abc")
|
202
|
+
parent.child.age.should eq(5)
|
203
|
+
end
|
204
|
+
|
205
|
+
it "finds and returns all the proper objects" do
|
206
|
+
bucket.expects(:get).with(
|
207
|
+
["parent:1", "parent:2", 'child:1', 'child:2'],
|
208
|
+
quiet: true,
|
209
|
+
extended: true
|
210
|
+
).returns({
|
211
|
+
"parent:1" => [{name: "abc"}, 0, :cas],
|
212
|
+
"child:2" => [{age: 5}, 0, :cas],
|
213
|
+
"parent:2" => [{name: "def"}, 0, :cas],
|
214
|
+
"child:1" => [{age: 7}, 0, :cas]
|
215
|
+
})
|
216
|
+
|
217
|
+
objects = subject.find_all_with_children(["parent:1", "parent:2"])
|
218
|
+
objects.size.should eq(2)
|
219
|
+
|
220
|
+
objects.first.id.should eq("parent:1")
|
221
|
+
objects.first.name.should eq("abc")
|
222
|
+
objects.first.child.id.should eq("child:1")
|
223
|
+
objects.first.child.age.should eq(7)
|
224
|
+
|
225
|
+
objects.last.id.should eq("parent:2")
|
226
|
+
objects.last.name.should eq('def')
|
227
|
+
objects.last.child.id.should eq("child:2")
|
228
|
+
objects.last.child.age.should eq(5)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "only finds valid children" do
|
232
|
+
bucket.expects(:get).with(
|
233
|
+
["parent:1"],
|
234
|
+
quiet: true,
|
235
|
+
extended: true
|
236
|
+
).returns({
|
237
|
+
"parent:1" => [{name: "abc"}, 0, :cas],
|
238
|
+
})
|
239
|
+
|
240
|
+
subject.find_with_children "parent:1", :invalid
|
241
|
+
end
|
242
|
+
|
243
|
+
it "raises an error when the parent object isn't found" do
|
244
|
+
bucket.expects(:get).with(
|
245
|
+
["parent:1", "child:1"],
|
246
|
+
quiet: true,
|
247
|
+
extended: true
|
248
|
+
).returns({
|
249
|
+
"child:1" => [{age: 5}, 0, :cas]
|
250
|
+
})
|
251
|
+
|
252
|
+
expect { subject.find_with_children("parent:1") }.to raise_error(Couchbase::Error::NotFound)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "marks all children as loaded even if they're not found" do
|
256
|
+
bucket.expects(:get).with(
|
257
|
+
["parent:1", "child:1"],
|
258
|
+
quiet: true,
|
259
|
+
extended: true
|
260
|
+
).returns({
|
261
|
+
"parent:1" => [{name: "abc"}, 0, :cas],
|
262
|
+
})
|
263
|
+
|
264
|
+
parent = subject.find_with_children("parent:1")
|
265
|
+
parent.should be_child_loaded
|
266
|
+
parent.child.should be_new
|
267
|
+
end
|
268
|
+
|
269
|
+
it "doesn't raise an error when the child object isn't found" do
|
270
|
+
bucket.expects(:get).with(
|
271
|
+
["parent:1", "child:1"],
|
272
|
+
quiet: true,
|
273
|
+
extended: true
|
274
|
+
).returns({
|
275
|
+
"parent:1" => [{name: "abc"}, 0, :cas],
|
276
|
+
})
|
277
|
+
|
278
|
+
parent = subject.find_with_children("parent:1")
|
279
|
+
parent.name.should eq("abc")
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|