vidibus-inheritance 0.3.6 → 0.3.9

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