mongoid-ordering 0.1.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/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +139 -0
- data/Rakefile +17 -0
- data/lib/mongoid/ordering.rb +255 -0
- data/spec/mongoid/ordering_spec.rb +291 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/models.rb +19 -0
- metadata +106 -0
data/Gemfile
ADDED
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,139 @@
|
|
1
|
+
# mongoid-ordering [](http://travis-ci.org/DouweM/mongoid-ordering)
|
2
|
+
|
3
|
+
mongoid-ordering makes it easy to keep your Mongoid documents in order.
|
4
|
+
|
5
|
+
Most of mongoid-ordering is based on the
|
6
|
+
`Mongoid::Tree::Ordering` module from the
|
7
|
+
[mongoid-tree](https://github.com/benedikt/mongoid-tree) gem. I thought
|
8
|
+
the ordering logic would be useful outside of the tree context as well, so I
|
9
|
+
extracted it into the gem you're looking at right now.
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
* Automatically order your query results by a new `position` attribute.
|
14
|
+
* Allow documents to be ordered within a certain scope.
|
15
|
+
* Handle changes in position when a document is destroyed or when a document is
|
16
|
+
moved outside of this scope.
|
17
|
+
* Tons of utility methods to make working with ordered documents incredibly easy.
|
18
|
+
|
19
|
+
## Requirements
|
20
|
+
|
21
|
+
* mongoid (~> 3.0)
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Add the following to your Gemfile:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem "mongoid-ordering"
|
29
|
+
```
|
30
|
+
|
31
|
+
And tell Bundler to install the new gem:
|
32
|
+
|
33
|
+
```
|
34
|
+
bundle install
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
Include the `Mongoid::Ordering` module in your document class:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class Book
|
43
|
+
include Mongoid::Document
|
44
|
+
include Mongoid::Ordering
|
45
|
+
|
46
|
+
...
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
This will take care of everything to get you going.
|
51
|
+
|
52
|
+
If you want to specify a scope within which to keep the documents in order,
|
53
|
+
you can like this:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class Book
|
57
|
+
include Mongoid::Document
|
58
|
+
include Mongoid::Ordering
|
59
|
+
|
60
|
+
belongs_to :author
|
61
|
+
|
62
|
+
ordered scope: :author
|
63
|
+
|
64
|
+
...
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
You will now have access to the following methods:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# Retrieve siblings positioned above this document.
|
72
|
+
book.higher_siblings
|
73
|
+
# Retrieve siblings positioned below this document.
|
74
|
+
book.lower_siblings
|
75
|
+
# Retrieve the highest sibling.
|
76
|
+
book.highest_sibling
|
77
|
+
# Retrieve the lowest sibling.
|
78
|
+
book.lowest_sibling
|
79
|
+
|
80
|
+
# Is this the highest sibling?
|
81
|
+
book.at_top?
|
82
|
+
# Is this the lowest sibling?
|
83
|
+
book.at_bottom?
|
84
|
+
|
85
|
+
# Move document to the top.
|
86
|
+
book.move_to_top
|
87
|
+
# Move document to the bottom.
|
88
|
+
book.move_to_bottom
|
89
|
+
# Move document one position up.
|
90
|
+
book.move_up
|
91
|
+
# Move document one position down.
|
92
|
+
book.move_down
|
93
|
+
# Move document above another document.
|
94
|
+
book.move_above(other_book)
|
95
|
+
# Move document below another document.
|
96
|
+
book.move_below(other_book)
|
97
|
+
```
|
98
|
+
|
99
|
+
mongoid-ordering uses [mongoid-siblings](https://github.com/DouweM/mongoid-siblings) to get all of this to work, so you'll get the following methods as a bonus:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
# Retrieve document's siblings
|
103
|
+
book.siblings
|
104
|
+
# Retrieve document's siblings and itself
|
105
|
+
book.siblings_and_self
|
106
|
+
# Is this document a sibling of the other document?
|
107
|
+
book.sibling_of?(other_book)
|
108
|
+
# Make document a sibling of the other document.
|
109
|
+
# This will move this book to the same scope as the other book.
|
110
|
+
book.sibling_of!(other_book)
|
111
|
+
```
|
112
|
+
|
113
|
+
## Full documentation
|
114
|
+
See [this project's RubyDoc.info page](http://rubydoc.info/github/DouweM/mongoid-ordering/master/frames).
|
115
|
+
|
116
|
+
## Known issues
|
117
|
+
See [the GitHub Issues page](https://github.com/DouweM/mongoid-ordering/issues).
|
118
|
+
|
119
|
+
## License
|
120
|
+
Copyright (c) 2012 Douwe Maan
|
121
|
+
|
122
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
123
|
+
a copy of this software and associated documentation files (the
|
124
|
+
"Software"), to deal in the Software without restriction, including
|
125
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
126
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
127
|
+
permit persons to whom the Software is furnished to do so, subject to
|
128
|
+
the following conditions:
|
129
|
+
|
130
|
+
The above copyright notice and this permission notice shall be
|
131
|
+
included in all copies or substantial portions of the Software.
|
132
|
+
|
133
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
134
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
135
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
136
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
137
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
138
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
139
|
+
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-ordering.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,255 @@
|
|
1
|
+
module Mongoid
|
2
|
+
|
3
|
+
# Add 'position' field, multiple methods and multiple callbacks to help with
|
4
|
+
# ordering your documents.
|
5
|
+
module Ordering
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include Mongoid::Siblings
|
8
|
+
|
9
|
+
included do
|
10
|
+
cattr_accessor :ordering_scopes
|
11
|
+
self.ordering_scopes = []
|
12
|
+
|
13
|
+
field :position, type: Integer
|
14
|
+
|
15
|
+
default_scope asc(:position)
|
16
|
+
|
17
|
+
before_save :assign_default_position
|
18
|
+
before_save :reposition_former_siblings, if: :sibling_reposition_required?
|
19
|
+
after_destroy :move_lower_siblings_up
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# Sets options used for ordering.
|
24
|
+
#
|
25
|
+
# @example Set options.
|
26
|
+
# class Book
|
27
|
+
# include Mongoid::Document
|
28
|
+
# include Mongoid::Ordering
|
29
|
+
#
|
30
|
+
# belongs_to :author
|
31
|
+
#
|
32
|
+
# ordered scope: :author
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @param [ Hash ] options The options.
|
36
|
+
#
|
37
|
+
# @option options [ Array<Symbol>, Symbol ] scope One or more relations or
|
38
|
+
# attributes that will determine the scope within which to keep the
|
39
|
+
# documents in order.
|
40
|
+
def ordered(options = {})
|
41
|
+
self.default_sibling_scope = self.ordering_scopes = Array.wrap(options[:scope]).compact
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns siblings positioned above this document.
|
46
|
+
# Siblings with a position lower than this document's position.
|
47
|
+
#
|
48
|
+
# @example Retrieve siblings positioned above this document.
|
49
|
+
# book.higher_siblings
|
50
|
+
#
|
51
|
+
# @return [ Mongoid::Criteria ] Criteria to retrieve the document's higher siblings.
|
52
|
+
def higher_siblings
|
53
|
+
self.siblings.where(:position.lt => self.position)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns siblings positioned below this document.
|
57
|
+
# Siblings with a position greater than this document's position.
|
58
|
+
#
|
59
|
+
# @example Retrieve siblings positioned below this document.
|
60
|
+
# book.lower_siblings
|
61
|
+
#
|
62
|
+
# @return [ Mongoid::Criteria ] Criteria to retrieve the document's lower siblings.
|
63
|
+
def lower_siblings
|
64
|
+
self.siblings.where(:position.gt => self.position)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the highest sibling (could be self).
|
68
|
+
#
|
69
|
+
# @example Retrieve the highest sibling.
|
70
|
+
# book.highest_sibling
|
71
|
+
#
|
72
|
+
# @return [ Mongoid::Document ] The highest sibling.
|
73
|
+
def highest_sibling
|
74
|
+
self.siblings_and_self.first
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the lowest sibling (could be self).
|
78
|
+
#
|
79
|
+
# @example Retrieve the lowest sibling.
|
80
|
+
# book.lowest_sibling
|
81
|
+
#
|
82
|
+
# @return [ Mongoid::Document ] The lowest sibling
|
83
|
+
def lowest_sibling
|
84
|
+
self.siblings_and_self.last
|
85
|
+
end
|
86
|
+
|
87
|
+
# Is this the highest sibling?
|
88
|
+
#
|
89
|
+
# @example Is this the highest sibling?
|
90
|
+
# book.at_top?
|
91
|
+
#
|
92
|
+
# @return [ Boolean ] True if this document is the highest sibling.
|
93
|
+
def at_top?
|
94
|
+
self.higher_siblings.empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
# Is this the lowest sibling?
|
98
|
+
#
|
99
|
+
# @example Is this the lowest sibling?
|
100
|
+
# book.at_bottom?
|
101
|
+
#
|
102
|
+
# @return [ Boolean ] True if this document is the lowest sibling.
|
103
|
+
def at_bottom?
|
104
|
+
self.lower_siblings.empty?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Moves this document above all of its siblings.
|
108
|
+
#
|
109
|
+
# @example Move document to the top.
|
110
|
+
# book.move_to_top
|
111
|
+
#
|
112
|
+
# @return [ Boolean ] True if the document was moved to the top or was
|
113
|
+
# already there.
|
114
|
+
def move_to_top
|
115
|
+
return true if at_top?
|
116
|
+
self.move_above(self.highest_sibling)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Moves this document below all of its siblings.
|
120
|
+
#
|
121
|
+
# @example Move document to the bottom.
|
122
|
+
# book.move_to_bottom
|
123
|
+
#
|
124
|
+
# @return [ Boolean ] True if the document was moved to the bottom or was
|
125
|
+
# already there.
|
126
|
+
def move_to_bottom
|
127
|
+
return true if at_bottom?
|
128
|
+
self.move_below(self.lowest_sibling)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Moves this document one position up.
|
132
|
+
#
|
133
|
+
# @example Move document one position up.
|
134
|
+
# book.move_up
|
135
|
+
def move_up
|
136
|
+
return if at_top?
|
137
|
+
self.siblings.where(position: self.position - 1).first.inc(:position, 1)
|
138
|
+
self.inc(:position, -1)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Moves this document one position down.
|
142
|
+
#
|
143
|
+
# @example Move document one position down.
|
144
|
+
# book.move_down
|
145
|
+
def move_down
|
146
|
+
return if at_bottom?
|
147
|
+
self.siblings.where(position: self.position + 1).first.inc(:position, -1)
|
148
|
+
self.inc(:position, 1)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Moves this document above the specified document.
|
152
|
+
#
|
153
|
+
# This method changes this document's scope values if necessary.
|
154
|
+
#
|
155
|
+
# @example Move document above another document.
|
156
|
+
# book.move_above(other_book)
|
157
|
+
#
|
158
|
+
# @param [ Mongoid::Document ] other The document to Moves this document
|
159
|
+
# above.
|
160
|
+
def move_above(other)
|
161
|
+
return false unless self.sibling_of!(other)
|
162
|
+
|
163
|
+
if self.position > other.position
|
164
|
+
new_position = other.position
|
165
|
+
other.lower_siblings.and(:position.lt => self.position).each { |s| s.inc(:position, 1) }
|
166
|
+
other.inc(:position, 1)
|
167
|
+
else
|
168
|
+
new_position = other.position - 1
|
169
|
+
other.higher_siblings.and(:position.gt => self.position).each { |s| s.inc(:position, -1) }
|
170
|
+
end
|
171
|
+
self.position = new_position
|
172
|
+
|
173
|
+
self.save!
|
174
|
+
end
|
175
|
+
|
176
|
+
# Moves this document below the specified document.
|
177
|
+
#
|
178
|
+
# This method changes this document's scope values if necessary.
|
179
|
+
#
|
180
|
+
# @example Move document below another document.
|
181
|
+
# book.move_below(other_book)
|
182
|
+
#
|
183
|
+
# @param [ Mongoid::Document ] other The document to Moves this document
|
184
|
+
# below.
|
185
|
+
def move_below(other)
|
186
|
+
return false unless self.sibling_of!(other)
|
187
|
+
|
188
|
+
if self.position > other.position
|
189
|
+
new_position = other.position + 1
|
190
|
+
other.lower_siblings.and(:position.lt => self.position).each { |s| s.inc(:position, 1) }
|
191
|
+
else
|
192
|
+
new_position = other.position
|
193
|
+
other.higher_siblings.and(:position.gt => self.position).each { |s| s.inc(:position, -1) }
|
194
|
+
other.inc(:position, -1)
|
195
|
+
end
|
196
|
+
self.position = new_position
|
197
|
+
|
198
|
+
self.save!
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def move_lower_siblings_up
|
204
|
+
self.lower_siblings.each { |s| s.inc(:position, -1) }
|
205
|
+
end
|
206
|
+
|
207
|
+
def sibling_reposition_required?
|
208
|
+
return false if self.ordering_scopes.empty?
|
209
|
+
self.ordering_scopes.any? { |scope| attribute_changed?(key_for_scope(scope)) } && persisted?
|
210
|
+
end
|
211
|
+
|
212
|
+
def reposition_former_siblings
|
213
|
+
return if self.ordering_scopes.empty?
|
214
|
+
|
215
|
+
old_scope_values = {}
|
216
|
+
self.ordering_scopes.each do |scope|
|
217
|
+
scope_metadata = self.reflect_on_association(scope)
|
218
|
+
scope_key = scope_metadata ? scope_metadata.key : scope.to_s
|
219
|
+
|
220
|
+
if attribute_changed?(scope_key)
|
221
|
+
old_value = attribute_was(scope_key)
|
222
|
+
|
223
|
+
old_scope_values[scope] = if scope_metadata && old_value
|
224
|
+
model = scope_metadata.inverse_type ? attribute_was(scope_metadata.inverse_type) : scope_metadata.klass
|
225
|
+
scope_metadata.criteria(old_value, model).first
|
226
|
+
else
|
227
|
+
old_value
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
former_siblings = self.siblings(scope_values: old_scope_values).where(:position.gt => (attribute_was("position") || 0))
|
233
|
+
former_siblings.each { |s| s.inc(:position, -1) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def assign_default_position
|
237
|
+
return unless self.position.nil? ||
|
238
|
+
(self.ordering_scopes.any? { |scope| attribute_changed?(key_for_scope(scope)) } &&
|
239
|
+
!new_record?)
|
240
|
+
|
241
|
+
siblings = self.siblings
|
242
|
+
self.position = if siblings.empty? || siblings.map(&:position).compact.empty?
|
243
|
+
0
|
244
|
+
else
|
245
|
+
siblings.max(:position).to_i + 1
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def key_for_scope(scope)
|
250
|
+
return nil if scope.nil?
|
251
|
+
metadata = self.reflect_on_association(scope)
|
252
|
+
metadata ? metadata.key : scope.to_s
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
require "spec_helper.rb"
|
2
|
+
|
3
|
+
describe Mongoid::Ordering do
|
4
|
+
|
5
|
+
describe ".ordered" do
|
6
|
+
|
7
|
+
let(:test_class) {
|
8
|
+
Class.new do
|
9
|
+
include Mongoid::Document
|
10
|
+
include Mongoid::Ordering
|
11
|
+
|
12
|
+
ordered scope: [:main, :fallback]
|
13
|
+
end
|
14
|
+
}
|
15
|
+
|
16
|
+
it "sets the default sibling scope and the ordering scopes to the specified scope" do
|
17
|
+
test_class.default_sibling_scope.should eq([:main, :fallback])
|
18
|
+
test_class.ordering_scopes.should eq([:main, :fallback])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "Instance methods" do
|
23
|
+
|
24
|
+
let!(:sibling1) { DummyParentDocument.create }
|
25
|
+
let!(:sibling2) { DummyParentDocument.create }
|
26
|
+
let!(:sibling3) { DummyParentDocument.create }
|
27
|
+
|
28
|
+
describe "#lower_siblings" do
|
29
|
+
|
30
|
+
it "returns the siblings with a higher position" do
|
31
|
+
sibling1.lower_siblings.should eq([sibling2, sibling3])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#higher_siblings" do
|
36
|
+
|
37
|
+
it "returns the siblings with a lower position" do
|
38
|
+
sibling3.higher_siblings.should eq([sibling1, sibling2])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#highest_sibling" do
|
43
|
+
|
44
|
+
it "returns the first sibling" do
|
45
|
+
sibling2.highest_sibling.should eq(sibling1)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#lowest_sibling" do
|
50
|
+
|
51
|
+
it "returns the last sibling" do
|
52
|
+
sibling2.lowest_sibling.should eq(sibling3)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#at_top?" do
|
57
|
+
|
58
|
+
context "when the subject is the first sibling" do
|
59
|
+
|
60
|
+
it "returns true" do
|
61
|
+
sibling1.should be_at_top
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when the subject is not the first sibling" do
|
66
|
+
|
67
|
+
it "returns false" do
|
68
|
+
sibling2.should_not be_at_top
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#at_bottom?" do
|
74
|
+
|
75
|
+
context "when the subject is the last sibling" do
|
76
|
+
|
77
|
+
it "returns true" do
|
78
|
+
sibling3.should be_at_bottom
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when the subject is not the last sibling" do
|
83
|
+
|
84
|
+
it "returns false" do
|
85
|
+
sibling2.should_not be_at_bottom
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#move_to_top" do
|
91
|
+
|
92
|
+
it "rearranges the siblings" do
|
93
|
+
sibling3.move_to_top
|
94
|
+
|
95
|
+
sibling3.siblings_and_self.should eq([sibling3, sibling1, sibling2])
|
96
|
+
end
|
97
|
+
|
98
|
+
it "properly sets the positions" do
|
99
|
+
sibling3.move_to_top
|
100
|
+
|
101
|
+
sibling3.reload.position.should eq(0)
|
102
|
+
sibling1.reload.position.should eq(1)
|
103
|
+
sibling2.reload.position.should eq(2)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#move_to_bottom" do
|
108
|
+
|
109
|
+
it "rearranges the siblings" do
|
110
|
+
sibling1.move_to_bottom
|
111
|
+
|
112
|
+
sibling1.siblings_and_self.should eq([sibling2, sibling3, sibling1])
|
113
|
+
end
|
114
|
+
|
115
|
+
it "properly sets the positions" do
|
116
|
+
sibling1.move_to_bottom
|
117
|
+
|
118
|
+
sibling2.reload.position.should eq(0)
|
119
|
+
sibling3.reload.position.should eq(1)
|
120
|
+
sibling1.reload.position.should eq(2)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#move_up" do
|
125
|
+
|
126
|
+
it "rearranges the siblings" do
|
127
|
+
sibling3.move_up
|
128
|
+
|
129
|
+
sibling3.siblings_and_self.should eq([sibling1, sibling3, sibling2])
|
130
|
+
end
|
131
|
+
|
132
|
+
it "properly sets the positions" do
|
133
|
+
sibling3.move_up
|
134
|
+
|
135
|
+
sibling1.reload.position.should eq(0)
|
136
|
+
sibling3.reload.position.should eq(1)
|
137
|
+
sibling2.reload.position.should eq(2)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#move_down" do
|
142
|
+
|
143
|
+
it "rearranges the siblings" do
|
144
|
+
sibling1.move_down
|
145
|
+
|
146
|
+
sibling1.siblings_and_self.should eq([sibling2, sibling1, sibling3])
|
147
|
+
end
|
148
|
+
|
149
|
+
it "properly sets the positions" do
|
150
|
+
sibling1.move_down
|
151
|
+
|
152
|
+
sibling2.reload.position.should eq(0)
|
153
|
+
sibling1.reload.position.should eq(1)
|
154
|
+
sibling3.reload.position.should eq(2)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "#move_above" do
|
159
|
+
|
160
|
+
context "when the subject was somewhere above the other object" do
|
161
|
+
|
162
|
+
it "rearranges the siblings" do
|
163
|
+
sibling1.move_above(sibling3)
|
164
|
+
|
165
|
+
sibling1.siblings_and_self.should eq([sibling2, sibling1, sibling3])
|
166
|
+
end
|
167
|
+
|
168
|
+
it "properly sets the positions" do
|
169
|
+
sibling1.move_above(sibling3)
|
170
|
+
|
171
|
+
sibling2.reload.position.should eq(0)
|
172
|
+
sibling1.reload.position.should eq(1)
|
173
|
+
sibling3.reload.position.should eq(2)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "when the subject was somewhere below the other object" do
|
178
|
+
|
179
|
+
it "rearranges the siblings" do
|
180
|
+
sibling3.move_above(sibling2)
|
181
|
+
|
182
|
+
sibling3.siblings_and_self.should eq([sibling1, sibling3, sibling2])
|
183
|
+
end
|
184
|
+
|
185
|
+
it "properly sets the positions" do
|
186
|
+
sibling3.move_above(sibling2)
|
187
|
+
|
188
|
+
sibling1.reload.position.should eq(0)
|
189
|
+
sibling3.reload.position.should eq(1)
|
190
|
+
sibling2.reload.position.should eq(2)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "#move_below" do
|
196
|
+
|
197
|
+
context "when the subject was somewhere above the other object" do
|
198
|
+
|
199
|
+
it "rearranges the siblings" do
|
200
|
+
sibling1.move_below(sibling2)
|
201
|
+
|
202
|
+
sibling1.siblings_and_self.should eq([sibling2, sibling1, sibling3])
|
203
|
+
end
|
204
|
+
|
205
|
+
it "properly sets the positions" do
|
206
|
+
sibling1.move_below(sibling2)
|
207
|
+
|
208
|
+
sibling2.reload.position.should eq(0)
|
209
|
+
sibling1.reload.position.should eq(1)
|
210
|
+
sibling3.reload.position.should eq(2)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when the subject was somewhere below the other object" do
|
216
|
+
|
217
|
+
it "rearranges the siblings" do
|
218
|
+
sibling3.move_below(sibling1)
|
219
|
+
|
220
|
+
sibling3.siblings_and_self.should eq([sibling1, sibling3, sibling2])
|
221
|
+
end
|
222
|
+
|
223
|
+
it "properly sets the positions" do
|
224
|
+
sibling3.move_below(sibling1)
|
225
|
+
|
226
|
+
sibling1.reload.position.should eq(0)
|
227
|
+
sibling3.reload.position.should eq(1)
|
228
|
+
sibling2.reload.position.should eq(2)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe "creating a document" do
|
234
|
+
|
235
|
+
context "when no siblings exist yet" do
|
236
|
+
|
237
|
+
it "sets the subject's position to 0" do
|
238
|
+
DummyParentDocument.create.position.should eq(0)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
context "when a sibling already exist" do
|
243
|
+
|
244
|
+
let!(:sibling) { DummyParentDocument.create }
|
245
|
+
|
246
|
+
it "sets the subject's position to the highest position plus 1" do
|
247
|
+
DummyParentDocument.create.position.should eq(1)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
describe "moving a document to another parent" do
|
253
|
+
|
254
|
+
let!(:parent1) { DummyParentDocument.create }
|
255
|
+
let!(:parent1_child1) { DummyChildDocument.create(parent: parent1) }
|
256
|
+
let!(:parent1_child2) { DummyChildDocument.create(parent: parent1) }
|
257
|
+
let!(:parent1_child3) { DummyChildDocument.create(parent: parent1) }
|
258
|
+
let!(:parent2) { DummyParentDocument.create }
|
259
|
+
let!(:parent2_child1) { DummyChildDocument.create(parent: parent2) }
|
260
|
+
let!(:parent2_child2) { DummyChildDocument.create(parent: parent2) }
|
261
|
+
let!(:parent2_child3) { DummyChildDocument.create(parent: parent2) }
|
262
|
+
|
263
|
+
before(:each) do
|
264
|
+
parent1_child2.parent = parent2
|
265
|
+
parent1_child2.save
|
266
|
+
end
|
267
|
+
|
268
|
+
it "moves lower siblings up" do
|
269
|
+
parent1_child1.reload.position.should eq(0)
|
270
|
+
parent1_child3.reload.position.should eq(1)
|
271
|
+
end
|
272
|
+
|
273
|
+
it "sets the subject's position to the highest position under the new parent plus 1" do
|
274
|
+
parent1_child2.reload.position.should eq(3)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe "destroying a document" do
|
279
|
+
|
280
|
+
let!(:sibling1) { DummyParentDocument.create }
|
281
|
+
let!(:sibling2) { DummyParentDocument.create }
|
282
|
+
let!(:sibling3) { DummyParentDocument.create }
|
283
|
+
|
284
|
+
it "moves lower siblings up" do
|
285
|
+
sibling2.destroy
|
286
|
+
|
287
|
+
sibling1.reload.position.should eq(0)
|
288
|
+
sibling3.reload.position.should eq(1)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
require "mongoid"
|
5
|
+
require "mongoid/siblings"
|
6
|
+
require "mongoid/ordering"
|
7
|
+
|
8
|
+
require "rspec"
|
9
|
+
|
10
|
+
Mongoid.configure do |config|
|
11
|
+
config.connect_to "mongoid_ordering_test"
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.after :each do
|
18
|
+
Mongoid::Config.purge!
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class DummyParentDocument
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Ordering
|
4
|
+
|
5
|
+
ordered scope: nil
|
6
|
+
|
7
|
+
has_many :children, class_name: "DummyChildDocument",
|
8
|
+
inverse_of: :parent
|
9
|
+
end
|
10
|
+
|
11
|
+
class DummyChildDocument
|
12
|
+
include Mongoid::Document
|
13
|
+
include Mongoid::Ordering
|
14
|
+
|
15
|
+
ordered scope: :parent
|
16
|
+
|
17
|
+
belongs_to :parent, class_name: DummyParentDocument.to_s,
|
18
|
+
inverse_of: :children
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid-ordering
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
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: &70134351207600 !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: *70134351207600
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mongoid-siblings
|
27
|
+
requirement: &70134351205960 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.1.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70134351205960
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70134351204840 !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: *70134351204840
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &70134351203100 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70134351203100
|
58
|
+
description: mongoid-ordering makes it easy to keep your Mongoid documents in order.
|
59
|
+
email: douwe@selenight.nl
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- lib/mongoid/ordering.rb
|
65
|
+
- LICENSE
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- Gemfile
|
69
|
+
- spec/mongoid/ordering_spec.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
- spec/support/models.rb
|
72
|
+
homepage: https://github.com/DouweM/mongoid-ordering
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
hash: -533634391627392618
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
hash: -533634391627392618
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.6
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Easy ordering of your Mongoid documents.
|
103
|
+
test_files:
|
104
|
+
- spec/mongoid/ordering_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
- spec/support/models.rb
|