mongoid-siblings 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Douwe Maan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # mongoid-siblings
2
+
3
+ mongoid-siblings adds methods to enable you to easily access your Mongoid
4
+ document's siblings.
5
+
6
+ ## Requirements
7
+
8
+ * mongoid (~> 3.0)
9
+
10
+ ## Installation
11
+
12
+ Add the following to your Gemfile:
13
+
14
+ ```ruby
15
+ gem "mongoid-siblings"
16
+ ```
17
+
18
+ And tell Bundler to install the new gem:
19
+
20
+ ```
21
+ bundle install
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ Include the `Mongoid::Siblings` module in your document class:
27
+
28
+ ```ruby
29
+ class Book
30
+ include Mongoid::Document
31
+ include Mongoid::Siblings
32
+
33
+ belongs_to :publisher
34
+ belongs_to :author
35
+
36
+ ...
37
+ end
38
+ ```
39
+
40
+ You will now have access to the following methods:
41
+
42
+ ```ruby
43
+ # Find all books by this book's author, not including this book.
44
+ book.siblings(scope: :author)
45
+
46
+ # Find all books by this book's author.
47
+ book.siblings_and_self(scope: :author)
48
+
49
+ # Check whether a certain book was published by the same publisher as this book.
50
+ book.sibling_of?(other_book, scope: :publisher)
51
+
52
+ # Make this book a sibling of a book with another author and publisher.
53
+ # This will set this books author and publisher to match that of the other book.
54
+ book.sibling_of!(other_book, scope: [:author, :publisher])
55
+ ```
56
+
57
+ ## Full documentation
58
+ See [this project's RubyDoc.info page](http://rubydoc.info/github/DouweM/mongoid-siblings/master/frames).
59
+
60
+ ## Known issues
61
+ See [the GitHub Issues page](https://github.com/DouweM/mongoid-siblings/issues).
62
+
63
+ ## License
64
+ Copyright (c) 2012 Douwe Maan
65
+
66
+ Permission is hereby granted, free of charge, to any person obtaining
67
+ a copy of this software and associated documentation files (the
68
+ "Software"), to deal in the Software without restriction, including
69
+ without limitation the rights to use, copy, modify, merge, publish,
70
+ distribute, sublicense, and/or sell copies of the Software, and to
71
+ permit persons to whom the Software is furnished to do so, subject to
72
+ the following conditions:
73
+
74
+ The above copyright notice and this permission notice shall be
75
+ included in all copies or substantial portions of the Software.
76
+
77
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
78
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
79
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
80
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
81
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
82
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
83
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ spec = Gem::Specification.load("mongoid-siblings.gemspec")
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
8
+
9
+ desc "Build the .gem file"
10
+ task :build do
11
+ system "gem build #{spec.name}.gemspec"
12
+ end
13
+
14
+ desc "Push the .gem file to rubygems.org"
15
+ task release: :build do
16
+ system "gem push #{spec.name}-#{spec.version}.gem"
17
+ end
@@ -0,0 +1,202 @@
1
+ module Mongoid
2
+
3
+ # Adds methods to easily access your document's siblings.
4
+ module Siblings
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ cattr_accessor :default_sibling_scope
9
+ end
10
+
11
+ # Returns this document's siblings.
12
+ #
13
+ # @example Retrieve document's siblings
14
+ # book.siblings
15
+ #
16
+ # @see {#siblings_and_self}
17
+ def siblings(options = {})
18
+ self.siblings_and_self(options).excludes(id: self.id)
19
+ end
20
+
21
+ # Returns this document's siblings and itself.
22
+ #
23
+ # @example Retrieve document's siblings and itself within a certain scope.
24
+ # book.siblings_and_self(scope: :author)
25
+ #
26
+ # @example Retrieve what would be document's siblings if it had another scope value.
27
+ #
28
+ # book.siblings_and_self(
29
+ # scope: :author,
30
+ # scope_values: {
31
+ # author: other_author
32
+ # }
33
+ # )
34
+ #
35
+ # @param [ Hash ] options The options.
36
+ #
37
+ # @option options [ Array<Symbol>, Symbol ] scope One or more relations or
38
+ # attributes that siblings of this object need to have in common.
39
+ # @option options [ Hash<Symbol, Object> ] scope_values Optional alternative
40
+ # values to use to determine siblingship.
41
+ #
42
+ # @return [ Mongoid::Criteria ] Criteria to retrieve the document's siblings.
43
+ def siblings_and_self(options = {})
44
+ scopes = options[:scope] || self.default_sibling_scope
45
+ scope_values = options[:scope_values] || {}
46
+
47
+ scopes = Array.wrap(scopes).compact
48
+
49
+
50
+ criteria = base_document_class.all
51
+
52
+ detail_scopes = []
53
+
54
+ # Find out what scope determines the root criteria. This can be
55
+ # [klass].all or self.[relation].
56
+ # It is assumed that for `scopes: [:rel1, :rel2]`, sibling objects always
57
+ # have the same `rel1` *and* `rel2`, and that two objects with the same
58
+ # `rel1` will always have the same `rel2`.
59
+ scopes.reverse_each do |scope|
60
+ scope_value = scope_values.fetch(scope) { self.send(scope) }
61
+
62
+ relation_metadata = self.reflect_on_association(scope)
63
+ if relation_metadata && scope_value
64
+ proxy = self.siblings_through_relation(scope, scope_value)
65
+ next if proxy.nil?
66
+ criteria = proxy.criteria
67
+ else
68
+ detail_scopes << scope
69
+ end
70
+ end
71
+
72
+ # Apply detail criteria, to make sure siblings share every simple
73
+ # attribute or nil-relation.
74
+ detail_scopes.each do |scope|
75
+ scope_value = scope_values.fetch(scope) { self.send(scope) }
76
+
77
+ relation_metadata = self.reflect_on_association(scope)
78
+ scope_key = relation_metadata ? relation_metadata.key : scope
79
+
80
+ criteria = criteria.where(scope_key => scope_value)
81
+ end
82
+
83
+ criteria
84
+ end
85
+
86
+ # Is this document a sibling of the other document?
87
+ #
88
+ # @example Is this document a sibling of the other document?
89
+ # book.sibling_of?(other_book, scope: :author)
90
+ #
91
+ # @param [ Document ] other The document to check against.
92
+ # @param [ Hash ] options The options.
93
+ #
94
+ # @option options [ Array<Symbol>, Symbol ] scope One or more relations and
95
+ # attributes that siblings of this object need to have in common.
96
+ # @option options [ Hash<Symbol, Object> ] scope_values Optional alternative
97
+ # values for this document to use to determine siblings.
98
+ # @option options [ Hash<Symbol, Object> ] other_scope_values Optional
99
+ # alternative values for the other document to use to determine
100
+ # siblingship.
101
+ #
102
+ # @return [ Boolean ] True if the document is a sibling of the other
103
+ # document.
104
+ def sibling_of?(other, options = {})
105
+ scopes = options[:scope] || self.default_sibling_scope
106
+ scope_values = options[:scope_values] || {}
107
+ other_scope_values = options[:other_scope_values] || {}
108
+
109
+ scopes = Array.wrap(scopes).compact
110
+
111
+
112
+ return false if base_document_class != base_document_class(other)
113
+
114
+ scopes.each do |scope|
115
+ scope_value = scope_values.fetch(scope) { self.send(scope) }
116
+ other_scope_value = other_scope_values.fetch(scope) { other.send(scope) }
117
+
118
+ return false if scope_value != other_scope_value
119
+ end
120
+
121
+ true
122
+ end
123
+
124
+ # Makes this document a sibling of the other document.
125
+ #
126
+ # This is done by copying over the values used to determine siblingship
127
+ # from the other document.
128
+ #
129
+ # @example Make document a sibling of the other document.
130
+ # book.sibling_of!(book_of_other_author, scope: :author)
131
+ #
132
+ # @param [ Document ] other The document to become a sibling of.
133
+ # @param [ Hash ] options The options.
134
+ #
135
+ # @option options [ Array<Symbol>, Symbol ] scope One or more relations and
136
+ # attributes that siblings of this object need to have in common.
137
+ # @option options [ Hash<Symbol, Object> ] other_scope_values Optional
138
+ # alternative values to use to determine siblingship.
139
+ #
140
+ # @return [ Boolean ] True if the document was made a sibling of the other
141
+ # document.
142
+ def sibling_of!(other, options = {})
143
+ return true if self.sibling_of?(other, options)
144
+
145
+ scopes = options[:scope] || self.default_sibling_scope
146
+ other_scope_values = options[:other_scope_values] || {}
147
+
148
+ scopes = Array.wrap(scopes).compact
149
+
150
+
151
+ return false if base_document_class != base_document_class(other)
152
+
153
+ scopes.each do |scope|
154
+ other_scope_value = other_scope_values.fetch(scope) { other.send(scope) }
155
+
156
+ relation_metadata = self.reflect_on_association(scope)
157
+ if relation_metadata && other_scope_value
158
+ other.siblings_through_relation(scope, other_scope_value) << self
159
+ else
160
+ self.send("#{scope}=", other_scope_value)
161
+ end
162
+ end
163
+
164
+ self.save!
165
+ end
166
+
167
+ protected
168
+
169
+ def siblings_through_relation(name, other = nil)
170
+ other ||= self.send(name)
171
+
172
+ relation_metadata = self.reflect_on_association(name)
173
+ inverses = relation_metadata.inverses(other)
174
+
175
+ return nil if inverses.nil? || inverses.empty?
176
+
177
+ if inverses.length == 1
178
+ inverse = inverses.first
179
+ elsif relation_metadata.polymorphic?
180
+ inverse = inverses.find { |inverse|
181
+ inverse == self.send(relation_metadata.inverse_of_field)
182
+ }
183
+ else
184
+ inverse = inverses.find { |inverse|
185
+ other.send(inverse).include?(self)
186
+ }
187
+ end
188
+
189
+ return nil if inverse.nil?
190
+
191
+ other.send(inverse)
192
+ end
193
+
194
+ def base_document_class(doc = self)
195
+ base_document_klass = doc.class
196
+ while base_document_klass.superclass.include?(Mongoid::Document)
197
+ base_document_klass = base_document_klass.superclass
198
+ end
199
+ base_document_klass
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,679 @@
1
+ require "spec_helper.rb"
2
+
3
+ describe Mongoid::Siblings do
4
+
5
+ let(:parent) { DummyParentDocument.create }
6
+
7
+ describe "#siblings" do
8
+
9
+ context "when using a fallback scope" do
10
+
11
+ let(:super_parent) { DummySuperParentDocument.create }
12
+ let(:parent) { DummyParentDocument.create }
13
+ subject { DummyReferencedChildDocument.create }
14
+ let!(:main_sibling) { DummyReferencedChildDocument.create(parent: parent, super_parent: super_parent) }
15
+ let!(:fallback_sibling) { DummyReferencedChildDocument.create(super_parent: super_parent) }
16
+ let!(:ultimate_sibling) { DummyReferencedChildDocument.create }
17
+
18
+ context "when providing scope values" do
19
+
20
+ let(:old_super_parent) { DummySuperParentDocument.create }
21
+ let(:old_parent) { DummyParentDocument.create }
22
+
23
+ before(:each) do
24
+ subject.parent = parent
25
+ subject.super_parent = super_parent
26
+ subject.save
27
+ end
28
+
29
+ context "when providing one scope value" do
30
+
31
+ let!(:old_main_sibling) { DummyReferencedChildDocument.create(parent: old_parent, super_parent: super_parent) }
32
+
33
+ it "returns the subject's siblings" do
34
+ subject.siblings(
35
+ scope: [:parent, :super_parent],
36
+ scope_values: {
37
+ parent: old_parent
38
+ }
39
+ ).to_a.should eq([old_main_sibling])
40
+ end
41
+ end
42
+
43
+ context "when providing multiple scope values" do
44
+
45
+ let!(:old_main_sibling) { DummyReferencedChildDocument.create(parent: old_parent, super_parent: old_super_parent) }
46
+ let!(:old_fallback_sibling) { DummyReferencedChildDocument.create(super_parent: old_super_parent) }
47
+
48
+ context "when the main sibling value was nil" do
49
+
50
+ it "returns the subject's siblings" do
51
+ subject.siblings(
52
+ scope: [:parent, :super_parent],
53
+ scope_values: {
54
+ parent: nil,
55
+ super_parent: old_super_parent
56
+ }
57
+ ).to_a.should eq([old_fallback_sibling])
58
+ end
59
+ end
60
+
61
+ context "when the main sibling value wasn't nil" do
62
+
63
+ it "returns the subject's siblings" do
64
+ subject.siblings(
65
+ scope: [:parent, :super_parent],
66
+ scope_values: {
67
+ parent: old_parent,
68
+ super_parent: old_super_parent
69
+ }
70
+ ).to_a.should eq([old_main_sibling])
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ context "when not providing scope values" do
77
+
78
+ context "when the document has a main scope document" do
79
+
80
+ before(:each) do
81
+ subject.parent = parent
82
+ subject.super_parent = super_parent
83
+ subject.save
84
+ end
85
+
86
+ it "returns the subject's siblings" do
87
+ subject.siblings(scope: [:parent, :super_parent]).to_a.should eq([main_sibling])
88
+ end
89
+ end
90
+
91
+ context "when the document has a fallback scope document but not a main scope document" do
92
+
93
+ before(:each) do
94
+ subject.super_parent = super_parent
95
+ subject.save
96
+ end
97
+
98
+ it "returns the subject's siblings" do
99
+ subject.siblings(scope: [:parent, :super_parent]).to_a.should eq([fallback_sibling])
100
+ end
101
+ end
102
+
103
+ context "when the document has neither a main scope document or a fallback scope document" do
104
+
105
+ it "returns the subject's siblings" do
106
+ subject.siblings(scope: [:parent, :super_parent]).to_a.should eq([ultimate_sibling])
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ context "when using a single scope" do
113
+
114
+ context "when providing a scope value" do
115
+
116
+ let(:old_parent) { DummyParentDocument.create }
117
+ subject { DummyReferencedChildDocument.create(parent: parent) }
118
+ let!(:old_sibling) { DummyReferencedChildDocument.create(parent: old_parent) }
119
+ let!(:sibling) { DummyReferencedChildDocument.create(parent: parent) }
120
+
121
+ it "returns the subject's siblings" do
122
+ subject.siblings(scope: :parent, scope_values: { parent: old_parent }).to_a.should eq([old_sibling])
123
+ end
124
+ end
125
+
126
+ context "when not providing a scope value" do
127
+
128
+ context "when using a referenced relation" do
129
+
130
+ subject { DummyReferencedChildDocument.create }
131
+
132
+ context "when using a non-polymorphic relation" do
133
+
134
+ let!(:sibling) { DummyReferencedChildDocument.create(parent: parent) }
135
+
136
+ before(:each) do
137
+ subject.parent = parent
138
+ subject.save
139
+ end
140
+
141
+ it "returns the subject's siblings" do
142
+ subject.siblings(scope: :parent).to_a.should eq([sibling])
143
+ end
144
+ end
145
+
146
+ context "when using multiple polymorphic relations" do
147
+
148
+ let!(:sibling) { DummyReferencedChildDocument.create.tap { |doc| parent.referenced_polymorphic_children1 << doc} }
149
+
150
+ before(:each) do
151
+ parent.referenced_polymorphic_children1 << subject
152
+ subject.save
153
+ end
154
+
155
+ it "returns the subject's siblings" do
156
+ subject.siblings(scope: :polymorphic_parent).to_a.should eq([sibling])
157
+ end
158
+ end
159
+ end
160
+
161
+ context "when using an embedded relation" do
162
+
163
+ context "when using a non-polymorphic relation" do
164
+
165
+ subject { DummyEmbeddedChildDocument.create(parent: parent) }
166
+ let!(:sibling) { DummyEmbeddedChildDocument.create(parent: parent) }
167
+
168
+ it "returns the subject's siblings" do
169
+ subject.siblings(scope: :parent).to_a.should eq([sibling])
170
+ end
171
+ end
172
+
173
+ context "when using a polymorphic relation" do
174
+
175
+ subject { DummyPolymorphicEmbeddedChildDocument.create(parent: parent) }
176
+ let!(:sibling) { DummyPolymorphicEmbeddedChildDocument.create(parent: parent) }
177
+
178
+ it "returns the subject's siblings" do
179
+ subject.siblings(scope: :parent).to_a.should eq([sibling])
180
+ end
181
+ end
182
+ end
183
+
184
+ context "when using a simple attribute" do
185
+
186
+ subject { DummyReferencedChildDocument.create(parent: parent) }
187
+ let!(:sibling) { DummyReferencedChildDocument.create(parent: parent) }
188
+
189
+ it "returns the subject's siblings" do
190
+ subject.siblings(scope: :parent_id).to_a.should eq([sibling])
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ context "when not using a scope" do
197
+
198
+ context "when a default sibling scope is set" do
199
+
200
+ before(:each) do
201
+ DummyReferencedChildDocument.default_sibling_scope = :parent
202
+ end
203
+
204
+ after(:each) do
205
+ DummyReferencedChildDocument.default_sibling_scope = nil
206
+ end
207
+
208
+ subject { DummyReferencedChildDocument.create(parent: parent) }
209
+ let!(:sibling) { DummyReferencedChildDocument.create(parent: parent) }
210
+
211
+ it "returns the subject's siblings through the default sibling scope" do
212
+ subject.siblings.to_a.should eq([sibling])
213
+ end
214
+ end
215
+
216
+ context "when no default sibling scope is set" do
217
+
218
+ subject { DummyReferencedChildDocument.create }
219
+ let!(:sibling) { DummyReferencedChildDocument.create }
220
+
221
+ it "returns all other documents of the subject's type" do
222
+ subject.siblings.to_a.should eq([sibling])
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ describe "#siblings_and_self" do
229
+
230
+ subject { DummyReferencedChildDocument.create(parent: parent) }
231
+ let!(:sibling) { DummyReferencedChildDocument.create(parent: parent) }
232
+
233
+ it "returns the subject's siblings and the subject itself" do
234
+ subject.siblings_and_self(scope: :parent).to_a.sort.should eq([subject, sibling].sort)
235
+ end
236
+ end
237
+
238
+ describe "#sibling_of?" do
239
+
240
+ context "when using a fallback scope" do
241
+
242
+ let(:super_parent) { DummySuperParentDocument.create }
243
+ let(:parent) { DummyParentDocument.create }
244
+ subject { DummyReferencedChildDocument.create }
245
+ let!(:main_sibling) { DummyReferencedChildDocument.create(parent: parent, super_parent: super_parent) }
246
+ let!(:fallback_sibling) { DummyReferencedChildDocument.create(super_parent: super_parent) }
247
+ let!(:ultimate_sibling) { DummyReferencedChildDocument.create }
248
+
249
+ context "when providing scope values" do
250
+
251
+ let(:old_super_parent) { DummySuperParentDocument.create }
252
+ let(:old_parent) { DummyParentDocument.create }
253
+
254
+ before(:each) do
255
+ subject.parent = parent
256
+ subject.super_parent = super_parent
257
+ subject.save
258
+ end
259
+
260
+ context "when providing one scope value" do
261
+
262
+ let!(:old_main_sibling) { DummyReferencedChildDocument.create(parent: old_parent, super_parent: super_parent) }
263
+
264
+ context "when called with a sibling" do
265
+
266
+ it "returns true" do
267
+ subject.should be_sibling_of(
268
+ old_main_sibling,
269
+ scope: [:parent, :super_parent],
270
+ scope_values: {
271
+ parent: old_parent
272
+ }
273
+ )
274
+ end
275
+ end
276
+
277
+ context "when not called with a sibling" do
278
+
279
+ it "returns false" do
280
+ subject.should_not be_sibling_of(
281
+ main_sibling,
282
+ scope: [:parent, :super_parent],
283
+ scope_values: {
284
+ parent: old_parent
285
+ }
286
+ )
287
+ subject.should_not be_sibling_of(
288
+ fallback_sibling,
289
+ scope: [:parent, :super_parent],
290
+ scope_values: {
291
+ parent: old_parent
292
+ }
293
+ )
294
+ subject.should_not be_sibling_of(
295
+ ultimate_sibling,
296
+ scope: [:parent, :super_parent],
297
+ scope_values: {
298
+ parent: old_parent
299
+ }
300
+ )
301
+ end
302
+ end
303
+ end
304
+
305
+ context "when providing multiple scope values" do
306
+
307
+ let!(:old_main_sibling) { DummyReferencedChildDocument.create(parent: old_parent, super_parent: old_super_parent) }
308
+ let!(:old_fallback_sibling) { DummyReferencedChildDocument.create(super_parent: old_super_parent) }
309
+
310
+ context "when the main sibling value was nil" do
311
+
312
+ context "when called with a sibling" do
313
+
314
+ it "returns true" do
315
+ subject.should be_sibling_of(
316
+ old_fallback_sibling,
317
+ scope: [:parent, :super_parent],
318
+ scope_values: {
319
+ parent: nil,
320
+ super_parent: old_super_parent
321
+ }
322
+ )
323
+ end
324
+ end
325
+
326
+ context "when not called with a sibling" do
327
+
328
+ it "returns false" do
329
+ subject.should_not be_sibling_of(
330
+ old_main_sibling,
331
+ scope: [:parent, :super_parent],
332
+ scope_values: {
333
+ parent: nil,
334
+ super_parent: old_super_parent
335
+ }
336
+ )
337
+ subject.should_not be_sibling_of(
338
+ main_sibling,
339
+ scope: [:parent, :super_parent],
340
+ scope_values: {
341
+ parent: nil,
342
+ super_parent: old_super_parent
343
+ }
344
+ )
345
+ subject.should_not be_sibling_of(
346
+ fallback_sibling,
347
+ scope: [:parent, :super_parent],
348
+ scope_values: {
349
+ parent: nil,
350
+ super_parent: old_super_parent
351
+ }
352
+ )
353
+ subject.should_not be_sibling_of(
354
+ ultimate_sibling,
355
+ scope: [:parent, :super_parent],
356
+ scope_values: {
357
+ parent: nil,
358
+ super_parent: old_super_parent
359
+ }
360
+ )
361
+ end
362
+ end
363
+ end
364
+
365
+ context "when the main sibling value was not nil" do
366
+
367
+ context "when called with a sibling" do
368
+
369
+ it "returns true" do
370
+ subject.should be_sibling_of(
371
+ old_main_sibling,
372
+ scope: [:parent, :super_parent],
373
+ scope_values: {
374
+ parent: old_parent,
375
+ super_parent: old_super_parent
376
+ }
377
+ )
378
+ end
379
+ end
380
+
381
+ context "when not called with a sibling" do
382
+
383
+ it "returns false" do
384
+ subject.should_not be_sibling_of(
385
+ old_fallback_sibling,
386
+ scope: [:parent, :super_parent],
387
+ scope_values: {
388
+ parent: old_parent,
389
+ super_parent: old_super_parent
390
+ }
391
+ )
392
+ subject.should_not be_sibling_of(
393
+ main_sibling,
394
+ scope: [:parent, :super_parent],
395
+ scope_values: {
396
+ parent: old_parent,
397
+ super_parent: old_super_parent
398
+ }
399
+ )
400
+ subject.should_not be_sibling_of(
401
+ fallback_sibling,
402
+ scope: [:parent, :super_parent],
403
+ scope_values: {
404
+ parent: old_parent,
405
+ super_parent: old_super_parent
406
+ }
407
+ )
408
+ subject.should_not be_sibling_of(
409
+ ultimate_sibling,
410
+ scope: [:parent, :super_parent],
411
+ scope_values: {
412
+ parent: old_parent,
413
+ super_parent: old_super_parent
414
+ }
415
+ )
416
+ end
417
+ end
418
+ end
419
+ end
420
+ end
421
+
422
+ context "when providing other scope values" do
423
+
424
+ let(:old_super_parent) { DummySuperParentDocument.create }
425
+ let(:old_parent) { DummyParentDocument.create }
426
+
427
+ before(:each) do
428
+ subject.parent = parent
429
+ subject.super_parent = super_parent
430
+ subject.save
431
+ end
432
+
433
+ context "when providing one other scope value" do
434
+
435
+ let!(:old_main_sibling) { DummyReferencedChildDocument.create(parent: old_parent, super_parent: super_parent) }
436
+
437
+ context "when called with a sibling" do
438
+
439
+ it "returns true" do
440
+ subject.should be_sibling_of(
441
+ old_main_sibling,
442
+ scope: [:parent, :super_parent],
443
+ other_scope_values: {
444
+ parent: parent
445
+ }
446
+ )
447
+ end
448
+ end
449
+ end
450
+
451
+ context "when providing multiple other scope values" do
452
+
453
+ let!(:old_main_sibling) { DummyReferencedChildDocument.create(parent: old_parent, super_parent: old_super_parent) }
454
+ let!(:old_fallback_sibling) { DummyReferencedChildDocument.create(super_parent: old_super_parent) }
455
+
456
+ context "when the other main sibling value was nil" do
457
+
458
+ before(:each) do
459
+ subject.parent = nil
460
+ subject.save
461
+ end
462
+
463
+ context "when called with a sibling" do
464
+
465
+ it "returns true" do
466
+ subject.should be_sibling_of(
467
+ old_fallback_sibling,
468
+ scope: [:parent, :super_parent],
469
+ other_scope_values: {
470
+ parent: nil,
471
+ super_parent: super_parent
472
+ }
473
+ )
474
+ end
475
+ end
476
+ end
477
+
478
+ context "when the other main sibling value was not nil" do
479
+
480
+ context "when called with a sibling" do
481
+
482
+ it "returns true" do
483
+ subject.should be_sibling_of(
484
+ old_main_sibling,
485
+ scope: [:parent, :super_parent],
486
+ other_scope_values: {
487
+ parent: parent,
488
+ super_parent: super_parent
489
+ }
490
+ )
491
+ end
492
+ end
493
+ end
494
+ end
495
+ end
496
+
497
+ context "when not providing scope values" do
498
+
499
+ context "when the document has a main scope document" do
500
+
501
+ before(:each) do
502
+ subject.parent = parent
503
+ subject.super_parent = super_parent
504
+ subject.save
505
+ end
506
+
507
+ context "when called with a sibling" do
508
+
509
+ it "returns true" do
510
+ subject.should be_sibling_of(main_sibling, scope: [:parent, :super_parent])
511
+ end
512
+ end
513
+
514
+ context "when not called with a sibling" do
515
+
516
+ it "returns false" do
517
+ subject.should_not be_sibling_of(fallback_sibling, scope: [:parent, :super_parent])
518
+ subject.should_not be_sibling_of(ultimate_sibling, scope: [:parent, :super_parent])
519
+ end
520
+ end
521
+ end
522
+
523
+ context "when the document has a fallback scope document but not a main scope document" do
524
+
525
+ before(:each) do
526
+ subject.super_parent = super_parent
527
+ subject.save
528
+ end
529
+
530
+ context "when called with a sibling" do
531
+
532
+ it "returns true" do
533
+ subject.should be_sibling_of(fallback_sibling, scope: [:parent, :super_parent])
534
+ end
535
+ end
536
+
537
+ context "when not called with a sibling" do
538
+
539
+ it "returns false" do
540
+ subject.should_not be_sibling_of(main_sibling, scope: [:parent, :super_parent])
541
+ subject.should_not be_sibling_of(ultimate_sibling, scope: [:parent, :super_parent])
542
+ end
543
+ end
544
+ end
545
+
546
+ context "when the document has neither a main scope document or a fallback scope document" do
547
+
548
+ context "when called with a sibling" do
549
+
550
+ it "returns true" do
551
+ subject.should be_sibling_of(ultimate_sibling, scope: [:parent, :super_parent])
552
+ end
553
+ end
554
+
555
+ context "when not called with a sibling" do
556
+
557
+ it "returns false" do
558
+ subject.should_not be_sibling_of(main_sibling, scope: [:parent, :super_parent])
559
+ subject.should_not be_sibling_of(fallback_sibling, scope: [:parent, :super_parent])
560
+ end
561
+ end
562
+ end
563
+ end
564
+ end
565
+
566
+ context "when using a scope" do
567
+
568
+ context "when called with a sibling" do
569
+
570
+ subject { DummyReferencedChildDocument.create(parent: parent) }
571
+ let(:sibling) { DummyReferencedChildDocument.create(parent: parent) }
572
+
573
+ it "returns true" do
574
+ subject.should be_sibling_of(sibling, scope: :parent)
575
+ end
576
+ end
577
+
578
+ context "when not called with a sibling" do
579
+
580
+ subject { DummyReferencedChildDocument.create(parent: parent) }
581
+ let(:other_parent) { DummyParentDocument.create }
582
+ let(:non_sibling) { DummyReferencedChildDocument.create(parent: other_parent) }
583
+
584
+ it "returns false" do
585
+ subject.should_not be_sibling_of(non_sibling, scope: :parent)
586
+ end
587
+ end
588
+ end
589
+
590
+ context "when not using a scope" do
591
+
592
+ context "when called with a sibling" do
593
+
594
+ subject { DummyReferencedChildDocument.create }
595
+ let(:sibling) { DummyReferencedChildDocument.create }
596
+
597
+ it "returns true" do
598
+ subject.should be_sibling_of(sibling)
599
+ end
600
+ end
601
+
602
+ context "when not called with a sibling" do
603
+
604
+ subject { DummyReferencedChildDocument.create }
605
+
606
+ it "returns false" do
607
+ subject.should_not be_sibling_of(parent)
608
+ end
609
+ end
610
+ end
611
+ end
612
+
613
+ describe "#sibling_of!" do
614
+
615
+ context "when called with a sibling" do
616
+
617
+ let(:parent) { DummyParentDocument.create }
618
+ subject { DummyReferencedChildDocument.create(parent: parent) }
619
+ let!(:sibling) { DummyReferencedChildDocument.create(parent: parent) }
620
+
621
+ it "returns true" do
622
+ subject.sibling_of!(sibling, scope: :parent).should be_true
623
+ end
624
+
625
+ it "doesn't save the subject" do
626
+ subject.should_not_receive(:save!)
627
+
628
+ subject.sibling_of!(sibling, scope: :parent)
629
+ end
630
+ end
631
+
632
+ context "when not called with a sibling" do
633
+
634
+ context "when called with an object that can never be a sibling" do
635
+
636
+ let(:parent) { DummyParentDocument.create }
637
+ subject { DummyReferencedChildDocument.create(parent: parent) }
638
+ let!(:non_sibling) { DummyParentDocument.create }
639
+
640
+ it "returns false" do
641
+ subject.sibling_of!(non_sibling, scope: :parent).should be_false
642
+ end
643
+ end
644
+
645
+ context "when called with an object that could be a sibling" do
646
+
647
+ let(:super_parent) { DummySuperParentDocument.create }
648
+ let(:parent) { DummyParentDocument.create }
649
+ subject { DummyReferencedChildDocument.create }
650
+ let!(:new_sibling) { DummyReferencedChildDocument.create(parent_id: parent.id, super_parent: super_parent) }
651
+
652
+ let(:options) { [new_sibling, scope: [:parent_id, :super_parent]] }
653
+
654
+ it "copies the scope values from the object to the subject" do
655
+ subject.sibling_of!(*options)
656
+
657
+ subject.parent_id.should eq(new_sibling.parent_id)
658
+ subject.super_parent.should eq(new_sibling.super_parent)
659
+ end
660
+
661
+ it "saves the subject" do
662
+ subject.should_receive(:save!)
663
+
664
+ subject.sibling_of!(*options)
665
+ end
666
+
667
+ it "returns true" do
668
+ subject.sibling_of!(*options).should be_true
669
+ end
670
+
671
+ it "makes the subject a sibling of the object" do
672
+ subject.sibling_of!(*options).should be_true
673
+
674
+ subject.should be_sibling_of(*options)
675
+ end
676
+ end
677
+ end
678
+ end
679
+ end
@@ -0,0 +1,19 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ require "mongoid"
5
+ require "mongoid/siblings"
6
+
7
+ require "rspec"
8
+
9
+ Mongoid.configure do |config|
10
+ config.connect_to "mongoid_siblings_test"
11
+ end
12
+
13
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
14
+
15
+ RSpec.configure do |config|
16
+ config.after :each do
17
+ Mongoid::Config.purge!
18
+ end
19
+ end
@@ -0,0 +1,49 @@
1
+ class DummySuperParentDocument
2
+ include Mongoid::Document
3
+
4
+ has_many :children, class_name: "DummyReferencedChildDocument",
5
+ inverse_of: :super_parent
6
+ end
7
+
8
+ class DummyParentDocument
9
+ include Mongoid::Document
10
+
11
+ has_many :referenced_children, class_name: "DummyReferencedChildDocument",
12
+ inverse_of: :parent
13
+ has_many :referenced_polymorphic_children1, class_name: "DummyReferencedChildDocument",
14
+ as: :polymorphic_parent
15
+ has_many :referenced_polymorphic_children2, class_name: "DummyReferencedChildDocument",
16
+ as: :polymorphic_parent
17
+
18
+ embeds_many :embedded_children, class_name: "DummyEmbeddedChildDocument",
19
+ inverse_of: :parent
20
+ embeds_many :embedded_polymorphic_children, class_name: "DummyPolymorphicEmbeddedChildDocument",
21
+ as: :parent
22
+ end
23
+
24
+ class DummyReferencedChildDocument
25
+ include Mongoid::Document
26
+ include Mongoid::Siblings
27
+
28
+ belongs_to :super_parent, class_name: DummySuperParentDocument.to_s,
29
+ inverse_of: :children
30
+
31
+ belongs_to :parent, class_name: DummyParentDocument.to_s,
32
+ inverse_of: :referenced_children
33
+ belongs_to :polymorphic_parent, polymorphic: true
34
+ end
35
+
36
+ class DummyEmbeddedChildDocument
37
+ include Mongoid::Document
38
+ include Mongoid::Siblings
39
+
40
+ embedded_in :parent, class_name: DummyParentDocument.to_s,
41
+ inverse_of: :embedded_children
42
+ end
43
+
44
+ class DummyPolymorphicEmbeddedChildDocument
45
+ include Mongoid::Document
46
+ include Mongoid::Siblings
47
+
48
+ embedded_in :parent, polymorphic: true
49
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-siblings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Douwe Maan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongoid
16
+ requirement: &70235283618700 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70235283618700
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70235283618220 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70235283618220
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70235283617640 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70235283617640
47
+ description: mongoid-siblings adds methods to enable you to easily access your Mongoid
48
+ document's siblings.
49
+ email: douwe@selenight.nl
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/mongoid/siblings.rb
55
+ - LICENSE
56
+ - README.md
57
+ - Rakefile
58
+ - Gemfile
59
+ - spec/mongoid/siblings_spec.rb
60
+ - spec/spec_helper.rb
61
+ - spec/support/models.rb
62
+ homepage: https://github.com/DouweM/mongoid-siblings
63
+ licenses:
64
+ - MIT
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ segments:
76
+ - 0
77
+ hash: -1413753754356574329
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ segments:
85
+ - 0
86
+ hash: -1413753754356574329
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.6
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Easy access to your Mongoid document's siblings.
93
+ test_files:
94
+ - spec/mongoid/siblings_spec.rb
95
+ - spec/spec_helper.rb
96
+ - spec/support/models.rb