vidibus-inheritance 0.3.6 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@ module Vidibus
3
3
  module Validators
4
4
  class AncestorValidator < ActiveModel::EachValidator
5
5
  def validate_each(record, attribute, value)
6
- unless value.is_a?(record.class)
6
+ unless value.class == record.class
7
7
  record.errors[attribute] << "must be a #{record.class}"
8
8
  end
9
9
  end
data/spec/models.rb ADDED
@@ -0,0 +1,70 @@
1
+ class Model
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+ include Vidibus::Uuid::Mongoid
5
+ include Vidibus::Inheritance::Mongoid
6
+ field :name
7
+ field :age, :type => Integer
8
+ embeds_many :children
9
+ embeds_many :puppets
10
+ embeds_one :location
11
+ end
12
+
13
+ class Trude < Model
14
+ before_validation :trudify
15
+ def trudify; self.name = "Trude"; end
16
+ end
17
+
18
+ class Child
19
+ include Mongoid::Document
20
+ field :name
21
+ field :mutated, :type => Boolean
22
+ validates :name, :presence => true
23
+ embedded_in :model, :inverse_of => :children
24
+ embeds_many :puppets
25
+ embeds_one :location
26
+ end
27
+
28
+ class Puppet
29
+ include Mongoid::Document
30
+ field :name
31
+ validates :name, :presence => true
32
+ embedded_in :child, :inverse_of => :puppets
33
+ embedded_in :model, :inverse_of => :puppets
34
+ embedded_in :location, :inverse_of => :puppets
35
+ embeds_one :location
36
+ end
37
+
38
+ class Location
39
+ include Mongoid::Document
40
+ field :name
41
+ field :mutated, :type => Boolean
42
+ validates :name, :presence => true
43
+ embedded_in :model, :inverse_of => :location
44
+ embedded_in :child, :inverse_of => :location
45
+ embedded_in :puppet, :inverse_of => :location
46
+ embeds_many :puppets
47
+ end
48
+
49
+ class Manager
50
+ include Mongoid::Document
51
+ include Vidibus::Uuid::Mongoid
52
+ include Vidibus::Inheritance::Mongoid
53
+ field :name
54
+ validates :name, :presence => true
55
+ end
56
+
57
+ class Clerk
58
+ include Mongoid::Document
59
+ field :name
60
+ validates :name, :presence => true
61
+ end
62
+
63
+ class ValidatedModel
64
+ include ActiveModel::Validations
65
+ attr_accessor :ancestor
66
+ validates :ancestor, :ancestor => true
67
+ end
68
+
69
+ class ValidatedModelSubclass < ValidatedModel
70
+ end
data/spec/spec_helper.rb CHANGED
@@ -4,10 +4,11 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
4
4
  require "rubygems"
5
5
  require "active_support/core_ext"
6
6
  require "spec"
7
+ require "rr"
7
8
  require "mongoid"
8
9
  require "vidibus-uuid"
9
10
  require "vidibus-inheritance"
10
- require "rr"
11
+ require "models"
11
12
 
12
13
  Mongoid.configure do |config|
13
14
  name = "vidibus-inheritance_test"
@@ -0,0 +1,396 @@
1
+ require "spec_helper"
2
+
3
+ describe "Inheritance" do
4
+ let(:ancestor) { Model.create }
5
+ let(:inheritor) { Model.new }
6
+ let(:anna) { Model.create!(:name => "Anna", :age => 35) }
7
+
8
+ it "should happen when creating objects" do
9
+ ancestor # trigger object creation before mocking
10
+ mock.instance_of(Model).inherit_attributes
11
+ Model.create!(:ancestor => ancestor)
12
+ end
13
+
14
+ it "should happen when ancestor did change" do
15
+ inheritor = Model.create!
16
+ inheritor.ancestor = ancestor
17
+ stub(inheritor).inherit_attributes
18
+ inheritor.save
19
+ inheritor.should have_received.inherit_attributes
20
+ end
21
+
22
+ it "should not happen when ancestor did not change" do
23
+ inheritor = Model.create!(:ancestor => ancestor)
24
+ dont_allow(inheritor).inherit
25
+ inheritor.save
26
+ # Does not work with RR:
27
+ # inheritor.should_not have_received.inherit
28
+ end
29
+
30
+ it "should apply ancestor's attributes to inheritor" do
31
+ inheritor.update_attributes(:ancestor => anna)
32
+ inheritor.name.should eql("Anna")
33
+ inheritor.age.should eql(35)
34
+ end
35
+
36
+ it "should not inherit acquired attributes" do
37
+ inheritor.update_attributes(:ancestor => ancestor)
38
+ Model::ACQUIRED_ATTRIBUTES.should include("uuid")
39
+ inheritor.uuid.should_not eql(ancestor.uuid)
40
+ end
41
+
42
+ it "should apply ancestor's attributes to inheritor but keep previously mutated attributes" do
43
+ inheritor.update_attributes(:name => "Jenny")
44
+ inheritor.update_attributes(:ancestor => anna)
45
+ inheritor.name.should eql("Jenny")
46
+ inheritor.age.should eql(35)
47
+ end
48
+
49
+ it "should apply ancestor's attributes to inheritor but keep recently mutated attributes" do
50
+ inheritor.update_attributes(:ancestor => anna, :name => "Jenny")
51
+ inheritor.name.should eql("Jenny")
52
+ inheritor.age.should eql(35)
53
+ end
54
+
55
+ it "should allow switching the ancestor" do
56
+ inheritor.inherit_from!(anna)
57
+ another_ancestor = Model.create!(:name => "Leah", :age => 30)
58
+ inheritor.inherit_from!(another_ancestor)
59
+ inheritor.ancestor.should eql(another_ancestor)
60
+ inheritor.name.should eql("Leah")
61
+ inheritor.age.should eql(30)
62
+ end
63
+
64
+ it "should apply changes on ancestor to inheritor" do
65
+ inheritor.inherit_from!(anna)
66
+ inheritor.name.should eql("Anna")
67
+ anna.update_attributes(:name => "Leah")
68
+ inheritor.reload
69
+ inheritor.name.should eql("Leah")
70
+ end
71
+
72
+ it "should preserve changes on inheritor" do
73
+ inheritor = Model.create(:ancestor => anna)
74
+ inheritor.update_attributes(:name => "Sara")
75
+ inheritor.mutated_attributes.should eql(["name"])
76
+ anna.update_attributes(:name => "Leah")
77
+ inheritor.reload
78
+ inheritor.name.should eql("Sara")
79
+ end
80
+
81
+ it "should not update inheritor if acquired attributes were changed on ancestor" do
82
+ inheritor.inherit_from!(ancestor)
83
+ Model::ACQUIRED_ATTRIBUTES.should include("updated_at")
84
+ dont_allow(ancestor.inheritors.first).inherit!
85
+ ancestor.update_attributes(:updated_at => Time.now)
86
+ end
87
+
88
+ it "should not update inheritor if no inheritable attributes were changed on ancestor" do
89
+ inheritor.inherit_from!(anna)
90
+ dont_allow(anna.inheritors.first).inherit!
91
+ anna.update_attributes(:name => "Anna")
92
+ end
93
+
94
+ it "should be applied before validation" do
95
+ ancestor = Manager.create!(:name => "John")
96
+ invalid = Manager.new
97
+ invalid.should_not be_valid
98
+ valid = Manager.new(:ancestor => ancestor)
99
+ valid.should be_valid
100
+ end
101
+
102
+ it "should destroy inheritor when destroying ancestor" do
103
+ inheritor.inherit_from!(ancestor)
104
+ ancestor.destroy
105
+ expect { inheritor.reload }.to raise_error(Mongoid::Errors::DocumentNotFound)
106
+ end
107
+
108
+ context "with embedded collections" do
109
+ before do
110
+ inheritor.inherit_from!(ancestor)
111
+ ancestor.children.create(:name => "Han")
112
+ ancestor.save
113
+ inheritor.reload
114
+ end
115
+
116
+ it "should inherit subobjects on existing relationship" do
117
+ inheritor.children.should have(1).child
118
+ end
119
+
120
+ it "should inherit subobjects when relationship gets established" do
121
+ inheritor = Model.new
122
+ inheritor.inherit_from!(ancestor)
123
+ inheritor.children.should have(1).child
124
+ inheritor.reload.children.should have(1).child
125
+ end
126
+
127
+ it "should add subobjects" do
128
+ ancestor.children << Child.new(:name => "Leah")
129
+ ancestor.save
130
+ ancestor.children.should have(2).children
131
+ inheritor.reload
132
+ inheritor.children.should have(2).children
133
+ end
134
+
135
+ it "should add subobjects with saving" do
136
+ ancestor.children << Child.new(:name => "Leah")
137
+ ancestor.save
138
+ ancestor.children.should have(2).children
139
+ inheritor.save
140
+ inheritor.reload
141
+ inheritor.children.should have(2).children
142
+ end
143
+
144
+ it "should not add existing subobjects twice" do
145
+ inheritor.inherit_from!(ancestor)
146
+ inheritor.children.should have(1).child
147
+ inheritor.reload.children.should have(1).child
148
+ end
149
+
150
+ it "should remove subobjects" do
151
+ inheritor.children.should have(1).child
152
+ ancestor.children.first.destroy
153
+ ancestor.save
154
+ inheritor.reload
155
+ ancestor.children.should have(0).children
156
+ inheritor.children.should have(0).children
157
+ end
158
+
159
+ it "should remove a single suboject without removing others on inheritor" do
160
+ inheritor.children.create(:name => "Leah")
161
+ inheritor.children.should have(2).children
162
+ ancestor.children.first.destroy
163
+ ancestor.save
164
+ ancestor.children.should have(0).children
165
+ inheritor.reload
166
+ inheritor.children.should have(1).child
167
+ end
168
+
169
+ it "should update subobjects" do
170
+ ancestor.children.first.name = "Luke"
171
+ ancestor.save
172
+ inheritor.reload
173
+ inheritor.children.first.name.should eql("Luke")
174
+ end
175
+
176
+ it "should call #update_inherited_attributes for updating subobjects, if available" do
177
+ Child.send(:define_method, :update_inherited_attributes) do
178
+ self.update_attributes(:name => "Callback")
179
+ end
180
+ Child.send(:protected, :update_inherited_attributes)
181
+ ancestor.children.first.name = "Luke"
182
+ ancestor.save
183
+ Child.send(:remove_method, :update_inherited_attributes)
184
+ inheritor.reload
185
+ inheritor.children.first.name.should eql("Callback")
186
+ end
187
+
188
+ it "should exclude acquired attributes of subobjects" do
189
+ ancestor.children.first.mutated = true
190
+ ancestor.save
191
+ ancestor.children.first.mutated.should be_true
192
+ inheritor.reload
193
+ inheritor.children.first.mutated.should be_false
194
+ end
195
+
196
+ it "should inherit embedded documents of subobjects" do
197
+ ancestor.children.first.puppets.create(:name => "Goofy")
198
+ ancestor.save
199
+ inheritor.reload
200
+ inheritor.children.first.puppets.should have(1).puppet
201
+ end
202
+ end
203
+
204
+ context "with embedded items" do
205
+ before do
206
+ inheritor.inherit_from!(ancestor)
207
+ ancestor.create_location(:name => "Home")
208
+ ancestor.save
209
+ inheritor.reload
210
+ end
211
+
212
+ it "should inherit subobject on existing relationship" do
213
+ inheritor.location.should_not be_nil
214
+ end
215
+
216
+ it "should inherit subobjects when relationship gets established" do
217
+ inheritor = Model.new
218
+ inheritor.inherit_from!(ancestor)
219
+ inheritor.location.should_not be_nil
220
+ end
221
+
222
+ it "should update subobject" do
223
+ ancestor.location.name = "Studio"
224
+ ancestor.save
225
+ inheritor.reload
226
+ ancestor.location.name.should eql("Studio")
227
+ inheritor.location.name.should eql("Studio")
228
+ end
229
+
230
+ it "should remove subobject" do
231
+ ancestor.location.destroy
232
+ ancestor.save
233
+ inheritor.reload
234
+ ancestor.location.should be_nil
235
+ inheritor.location.should be_nil
236
+ end
237
+
238
+ it "should exclude acquired attributes of subobject" do
239
+ ancestor.location.mutated = true
240
+ ancestor.save
241
+ ancestor.location.mutated.should be_true
242
+ inheritor.reload
243
+ inheritor.location.mutated.should be_false
244
+ end
245
+
246
+ it "should inherit embedded documents of subobject" do
247
+ ancestor.location.puppets.create(:name => "Goofy")
248
+ ancestor.save
249
+ inheritor.reload
250
+ inheritor.location.puppets.should have(1).puppet
251
+ end
252
+ end
253
+
254
+ context "across several generations" do
255
+ let(:grand_ancestor) { Model.create!(:name => "Anna", :age => 97) }
256
+
257
+ before do
258
+ ancestor.inherit_from!(grand_ancestor)
259
+ inheritor.inherit_from!(ancestor)
260
+ end
261
+
262
+ it "should apply changes on grand ancestor to inheritor" do
263
+ inheritor.name.should eql("Anna")
264
+ grand_ancestor.update_attributes(:name => "Leah")
265
+ inheritor.reload
266
+ inheritor.name.should eql("Leah")
267
+ end
268
+
269
+ it "should not apply changes on grand ancestor to inheritor if predecessor has mutations" do
270
+ ancestor.update_attributes(:name => "Jenny")
271
+ grand_ancestor.update_attributes(:name => "Leah")
272
+ inheritor.reload
273
+ inheritor.name.should eql("Jenny")
274
+ end
275
+
276
+ it "should allow resetting mutated attributes" do
277
+ ancestor.update_attributes(:name => "Sara")
278
+ ancestor.name.should eql("Sara")
279
+ inheritor.reload
280
+ inheritor.name.should eql("Sara")
281
+ ancestor.inherit!(:reset => :name)
282
+ ancestor.name.should eql("Anna")
283
+ inheritor.reload
284
+ inheritor.name.should eql("Anna")
285
+ end
286
+
287
+ it "should destroy all inheritors when destroying ancestor" do
288
+ grand_ancestor.destroy
289
+ expect { ancestor.reload }.to raise_error(Mongoid::Errors::DocumentNotFound)
290
+ expect { inheritor.reload }.to raise_error(Mongoid::Errors::DocumentNotFound)
291
+ end
292
+
293
+ context "with embedded collections" do
294
+ before do
295
+ grand_ancestor.children.create(:name => "Han")
296
+ grand_ancestor.save
297
+ ancestor.reload
298
+ inheritor.reload
299
+ end
300
+
301
+ it "should inherit subobjects" do
302
+ inheritor.children.should have(1).child
303
+ end
304
+
305
+ it "should add subobjects" do
306
+ grand_ancestor.children << Child.new(:name => "Leah")
307
+ grand_ancestor.save
308
+ inheritor.reload
309
+ inheritor.children.should have(2).children
310
+ end
311
+
312
+ it "should not add existing subobjects twice" do
313
+ ancestor.inherit_from!(grand_ancestor)
314
+ inheritor.reload
315
+ inheritor.children.should have(1).child
316
+ end
317
+
318
+ it "should remove subobjects" do
319
+ inheritor.children.should have(1).child
320
+ grand_ancestor.children.first.destroy
321
+ grand_ancestor.save
322
+ grand_ancestor.children.should have(0).children
323
+ inheritor.reload
324
+ inheritor.children.should have(0).children
325
+ end
326
+
327
+ it "should remove a single suboject without removing others on inheritor" do
328
+ inheritor.children.create(:name => "Leah")
329
+ inheritor.children.should have(2).children
330
+ grand_ancestor.children.first.destroy
331
+ grand_ancestor.save
332
+ grand_ancestor.children.should have(0).children
333
+ inheritor.reload
334
+ inheritor.children.should have(1).child
335
+ end
336
+
337
+ it "should update subobjects" do
338
+ grand_ancestor.children.first.name = "Luke"
339
+ grand_ancestor.save
340
+ grand_ancestor.children.first.name.should eql("Luke")
341
+ inheritor.reload
342
+ inheritor.children.first.name.should eql("Luke")
343
+ end
344
+
345
+ it "should inherit embedded documents of subobjects" do
346
+ grand_ancestor.children.first.puppets.create(:name => "Goofy")
347
+ grand_ancestor.save
348
+ inheritor.reload
349
+ inheritor.children.first.puppets.should have(1).puppet
350
+ end
351
+ end
352
+
353
+ context "with embedded items" do
354
+ before do
355
+ grand_ancestor.create_location(:name => "Home")
356
+ grand_ancestor.save
357
+ ancestor.reload
358
+ inheritor.reload
359
+ end
360
+
361
+ it "should inherit subobject on existing relationship" do
362
+ inheritor.location.should_not be_nil
363
+ end
364
+
365
+ it "should inherit subobject when relationship gets established" do
366
+ ancestor = Model.new
367
+ ancestor.inherit_from!(grand_ancestor)
368
+ inheritor = Model.new
369
+ inheritor.inherit_from!(ancestor)
370
+ inheritor.location.should_not be_nil
371
+ end
372
+
373
+ it "should update subobject" do
374
+ grand_ancestor.location.name = "Studio"
375
+ grand_ancestor.save
376
+ inheritor.reload
377
+ inheritor.location.name.should eql("Studio")
378
+ end
379
+
380
+ it "should remove subobject" do
381
+ grand_ancestor.location.destroy
382
+ grand_ancestor.save
383
+ inheritor.reload
384
+ grand_ancestor.location.should be_nil
385
+ inheritor.location.should be_nil
386
+ end
387
+
388
+ it "should inherit embedded documents of subobject" do
389
+ grand_ancestor.location.puppets.create(:name => "Goofy")
390
+ grand_ancestor.save
391
+ inheritor.reload
392
+ inheritor.location.puppets.should have(1).puppet
393
+ end
394
+ end
395
+ end
396
+ end