mongoid-ancestry 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -2
- data/.travis.yml +3 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +22 -19
- data/README.md +23 -15
- data/Rakefile +1 -1
- data/lib/mongoid-ancestry/instance_methods.rb +186 -186
- data/lib/mongoid-ancestry/version.rb +1 -1
- data/lib/mongoid-ancestry.rb +2 -1
- data/log/.gitignore +4 -0
- data/mongoid-ancestry.gemspec +2 -2
- metadata +23 -13
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,30 +1,33 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mongoid-ancestry (0.2.
|
5
|
-
bson_ext (
|
6
|
-
mongoid (
|
4
|
+
mongoid-ancestry (0.2.3)
|
5
|
+
bson_ext (>= 1.3)
|
6
|
+
mongoid (>= 2.0)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: http://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (3.
|
12
|
-
activesupport (= 3.
|
13
|
-
builder (~>
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
activemodel (3.2.5)
|
12
|
+
activesupport (= 3.2.5)
|
13
|
+
builder (~> 3.0.0)
|
14
|
+
activesupport (3.2.5)
|
15
|
+
i18n (~> 0.6)
|
16
|
+
multi_json (~> 1.0)
|
17
|
+
bson (1.6.4)
|
18
|
+
bson_ext (1.6.4)
|
19
|
+
bson (~> 1.6.4)
|
20
|
+
builder (3.0.0)
|
19
21
|
diff-lcs (1.1.2)
|
20
|
-
i18n (0.
|
21
|
-
mongo (1.
|
22
|
-
bson (
|
23
|
-
mongoid (2.
|
24
|
-
activemodel (~> 3.
|
22
|
+
i18n (0.6.0)
|
23
|
+
mongo (1.6.2)
|
24
|
+
bson (~> 1.6.2)
|
25
|
+
mongoid (2.4.7)
|
26
|
+
activemodel (~> 3.1)
|
25
27
|
mongo (~> 1.3)
|
26
28
|
tzinfo (~> 0.3.22)
|
27
|
-
|
29
|
+
multi_json (1.3.6)
|
30
|
+
rake (0.9.2.2)
|
28
31
|
rspec (2.5.0)
|
29
32
|
rspec-core (~> 2.5.0)
|
30
33
|
rspec-expectations (~> 2.5.0)
|
@@ -33,12 +36,12 @@ GEM
|
|
33
36
|
rspec-expectations (2.5.0)
|
34
37
|
diff-lcs (~> 1.1.2)
|
35
38
|
rspec-mocks (2.5.0)
|
36
|
-
tzinfo (0.3.
|
37
|
-
will_paginate (3.0.pre2)
|
39
|
+
tzinfo (0.3.33)
|
38
40
|
|
39
41
|
PLATFORMS
|
40
42
|
ruby
|
41
43
|
|
42
44
|
DEPENDENCIES
|
43
45
|
mongoid-ancestry!
|
46
|
+
rake
|
44
47
|
rspec (~> 2.5)
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
Mongoid-ancestry
|
2
2
|
================
|
3
3
|
|
4
|
+
[![travis](https://secure.travis-ci.org/joe1chen/mongoid-ancestry.png)](http://travis-ci.org/joe1chen/mongoid-ancestry)
|
5
|
+
|
4
6
|
Mongoid-ancestry is a gem/plugin that allows the records of a Ruby on Rails Mongoid model to be organised as a tree structure (or hierarchy). It uses a single, intuitively formatted database column, using a variation on the materialised path pattern. It exposes all the standard tree structure relations (ancestors, parent, root, children, siblings, descendants) and all of them can be fetched in a single query. Additional features are STI support, scopes, depth caching, depth constraints, easy migration from older plugins/gems, integrity checking, integrity restoration, arrangement of (sub)tree into hashes and different strategies for dealing with orphaned records.
|
5
7
|
|
6
8
|
## Installation
|
@@ -23,9 +25,9 @@ Your model is now a tree!
|
|
23
25
|
|
24
26
|
## Organising records into a tree
|
25
27
|
You can use the parent attribute to organise your records into a tree. If you have the id of the record you want
|
26
|
-
to use as a parent and don't want to fetch it, you can also use parent_id
|
27
|
-
parent and parent_id can be set using parent
|
28
|
-
to new, create, create!, update_attributes and update_attributes
|
28
|
+
to use as a parent and don't want to fetch it, you can also use `parent_id`. Like any virtual model attributes,
|
29
|
+
parent and `parent_id` can be set using `parent=` and `parent_id=` on a record or by including them in the hash passed
|
30
|
+
to new, create, create!, `update_attributes` and `update_attributes!`. For example:
|
29
31
|
|
30
32
|
TreeNode.create :name => 'Stinky', :parent => TreeNode.create(:name => 'Squeeky')
|
31
33
|
|
@@ -69,15 +71,18 @@ To navigate an Ancestry model, use the following methods on any instance / recor
|
|
69
71
|
|
70
72
|
## Options for has_ancestry
|
71
73
|
|
72
|
-
The has_ancestry methods supports the following options:
|
74
|
+
The `has_ancestry` methods supports the following options:
|
73
75
|
|
74
76
|
:ancestry_field Pass in a symbol to store ancestry in a different field
|
75
77
|
:orphan_strategy Instruct Ancestry what to do with children of a node that is destroyed:
|
76
78
|
:destroy All children are destroyed as well (default)
|
77
79
|
:rootify The children of the destroyed node become root nodes
|
78
80
|
:restrict An Error is raised if any children exist
|
79
|
-
:cache_depth Cache the depth of each node in the
|
81
|
+
:cache_depth Cache the depth of each node in the `ancestry_depth` field (default: false)
|
80
82
|
If you turn depth_caching on for an existing model:
|
83
|
+
- Mongoid has default configuration attribute `allow_dynamic_fields` as `true`.
|
84
|
+
You should manually add this depth field with `Integer` type and `0` as default value
|
85
|
+
into your model if `allow_dynamic_fields` is disabled in your configuration.
|
81
86
|
- Build cache: TreeNode.rebuild_depth_cache!
|
82
87
|
:depth_cache_field Pass in a symbol to store depth cache in a different field
|
83
88
|
|
@@ -107,7 +112,7 @@ Thanks to some convenient rails magic, it is even possible to create nodes throu
|
|
107
112
|
|
108
113
|
## Selecting nodes by depth
|
109
114
|
|
110
|
-
When depth caching is enabled (see has_ancestry options), five more named scopes can be used to select nodes on their depth:
|
115
|
+
When depth caching is enabled (see `has_ancestry` options), five more named scopes can be used to select nodes on their depth:
|
111
116
|
|
112
117
|
before_depth(depth) Return nodes that are less deep than depth (node.depth < depth)
|
113
118
|
to_depth(depth) Return nodes up to a certain depth (node.depth <= depth)
|
@@ -115,7 +120,7 @@ When depth caching is enabled (see has_ancestry options), five more named scopes
|
|
115
120
|
from_depth(depth) Return nodes starting from a certain depth (node.depth >= depth)
|
116
121
|
after_depth(depth) Return nodes that are deeper than depth (node.depth > depth)
|
117
122
|
|
118
|
-
The depth scopes are also available through calls to descendants
|
123
|
+
The depth scopes are also available through calls to `descendants`, `descendant_ids`, `subtree`, `subtree_ids`, `path` and `ancestors`. In this case, depth values are interpreted relatively. Some examples:
|
119
124
|
|
120
125
|
node.subtree(:to_depth => 2) Subtree of node, to a depth of node.depth + 2 (self, children and grandchildren)
|
121
126
|
node.subtree.to_depth(5) Subtree of node to an absolute depth of 5
|
@@ -129,7 +134,10 @@ The depth scopes are also available through calls to descendants, descendant_ids
|
|
129
134
|
node.descendants(:from_depth => 2, :to_depth => 4)
|
130
135
|
node.subtree.from_depth(10).to_depth(12)
|
131
136
|
|
132
|
-
Please note that depth constraints cannot be passed to ancestor_ids and path_ids
|
137
|
+
Please note that depth constraints cannot be passed to `ancestor_ids` and `path_ids`. The reason for this is that
|
138
|
+
both these relations can be fetched directly from the ancestry column without performing a database query. It would
|
139
|
+
require an entirely different method of applying the depth constraints which isn't worth the effort of implementing.
|
140
|
+
You can use `ancestors(depth_options).map(&:id)` or `ancestor_ids.slice(min_depth..max_depth)` instead.
|
133
141
|
|
134
142
|
## STI support
|
135
143
|
|
@@ -157,11 +165,11 @@ The arrange method takes Mongoid find options. If you want your hashes to be ord
|
|
157
165
|
|
158
166
|
## Migrating from plugin that uses parent_id column
|
159
167
|
|
160
|
-
With Mongoid-ancestry its easy to migrate from any of these plugins, to do so, use the build_ancestry_from_parent_ids
|
168
|
+
With Mongoid-ancestry its easy to migrate from any of these plugins, to do so, use the `Model.build_ancestry_from_parent_ids!` method on your model. These steps provide a more detailed explanation:
|
161
169
|
|
162
170
|
1. Remove old tree plugin or gem and add in Mongoid-ancestry
|
163
171
|
* See 'Installation' for more info on installing and configuring gem
|
164
|
-
* Add to app/models/
|
172
|
+
* Add to app/models/model.rb:
|
165
173
|
|
166
174
|
include Mongoid::Ancestry
|
167
175
|
has_ancestry
|
@@ -173,13 +181,13 @@ Most tree calls will probably work fine with ancestry
|
|
173
181
|
Others must be changed or proxied
|
174
182
|
Check if all your data is intact and all tests pass
|
175
183
|
|
176
|
-
3. Drop parent_id field
|
184
|
+
3. Drop `parent_id` field
|
177
185
|
|
178
186
|
## Integrity checking and restoration
|
179
187
|
|
180
|
-
I don't see any way Mongoid-ancestry tree integrity could get compromised without explicitly setting cyclic parents or invalid ancestry and circumventing validation with update_attribute
|
188
|
+
I don't see any way Mongoid-ancestry tree integrity could get compromised without explicitly setting cyclic parents or invalid ancestry and circumventing validation with `update_attribute`, if you do, please let me know.
|
181
189
|
|
182
|
-
Mongoid-ancestry includes some methods for detecting integrity problems and restoring integrity just to be sure. To check integrity use:
|
190
|
+
Mongoid-ancestry includes some methods for detecting integrity problems and restoring integrity just to be sure. To check integrity use: `Model.check_ancestry_integrity!`. An Mongoid::Ancestry::Error will be raised if there are any problems. You can also specify `:report => :list` to return an array of exceptions or `:report => :echo` to echo any error messages. To restore integrity use: `Model.restore_ancestry_integrity!`.
|
183
191
|
|
184
192
|
For example, from IRB:
|
185
193
|
|
@@ -202,13 +210,13 @@ Additionally, if you think something is wrong with your depth cache:
|
|
202
210
|
|
203
211
|
## Tests
|
204
212
|
|
205
|
-
The Mongoid-ancestry gem comes with rspec and guard(for automatically specs running) suite consisting of about 40 specs. It takes about 10 seconds to run. To run it yourself check out the repository from GitHub, run `bundle install`, run `guard` and press `Ctrl
|
213
|
+
The Mongoid-ancestry gem comes with rspec and guard(for automatically specs running) suite consisting of about 40 specs. It takes about 10 seconds to run. To run it yourself check out the repository from GitHub, run `bundle install`, run `guard` and press `Ctrl+\ ` or just `rake spec`.
|
206
214
|
|
207
215
|
## Internals
|
208
216
|
|
209
217
|
As can be seen in the previous section, Mongoid-ancestry stores a path from the root to the parent for every node. This is a variation on the materialised path database pattern. It allows to fetch any relation (siblings, descendants, etc.) in a single query without the complicated algorithms and incomprehensibility associated with left and right values. Additionally, any inserts, deletes and updates only affect nodes within the affected node's own subtree.
|
210
218
|
|
211
|
-
The materialised path pattern requires Mongoid-ancestry to use a
|
219
|
+
The materialised path pattern requires Mongoid-ancestry to use a `regexp` condition in order to fetch descendants. This should not be particularly slow however since the the condition never starts with a wildcard which allows the DBMS to use the column index. If you have any data on performance with a large number of records, please drop me line.
|
212
220
|
|
213
221
|
## Contact and copyright
|
214
222
|
|
data/Rakefile
CHANGED
@@ -1,243 +1,243 @@
|
|
1
1
|
module Mongoid
|
2
2
|
module Ancestry
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
3
|
+
|
4
|
+
# Validate that the ancestors don't include itself
|
5
|
+
def ancestry_exclude_self
|
6
|
+
if ancestor_ids.include? id
|
7
|
+
errors.add(:base, "#{self.class.name.humanize} cannot be a descendant of itself.")
|
9
8
|
end
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
11
|
+
# Update descendants with new ancestry
|
12
|
+
def update_descendants_with_new_ancestry
|
13
|
+
# Skip this if callbacks are disabled
|
14
|
+
unless ancestry_callbacks_disabled?
|
15
|
+
# If node is valid, not a new record and ancestry was updated ...
|
16
|
+
if changed.include?(self.base_class.ancestry_field.to_s) && !new_record? && valid?
|
17
|
+
# ... for each descendant ...
|
18
|
+
descendants.each do |descendant|
|
19
|
+
# ... replace old ancestry with new ancestry
|
20
|
+
descendant.without_ancestry_callbacks do
|
21
|
+
for_replace = \
|
22
|
+
if read_attribute(self.class.ancestry_field).blank?
|
23
|
+
id.to_s
|
24
|
+
else
|
25
|
+
"#{read_attribute self.class.ancestry_field}/#{id}"
|
26
|
+
end
|
27
|
+
new_ancestry = descendant.read_attribute(descendant.class.ancestry_field).gsub(/^#{self.child_ancestry}/, for_replace)
|
28
|
+
descendant.update_attribute(self.base_class.ancestry_field, new_ancestry)
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
33
|
+
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
end
|
52
|
-
# ... destroy all descendants if orphan strategy is destroy
|
53
|
-
elsif self.base_class.orphan_strategy == :destroy
|
54
|
-
descendants.all.each do |descendant|
|
55
|
-
descendant.without_ancestry_callbacks { descendant.destroy }
|
35
|
+
# Apply orphan strategy
|
36
|
+
def apply_orphan_strategy
|
37
|
+
# Skip this if callbacks are disabled
|
38
|
+
unless ancestry_callbacks_disabled?
|
39
|
+
# If this isn't a new record ...
|
40
|
+
unless new_record?
|
41
|
+
# ... make al children root if orphan strategy is rootify
|
42
|
+
if self.base_class.orphan_strategy == :rootify
|
43
|
+
descendants.each do |descendant|
|
44
|
+
descendant.without_ancestry_callbacks do
|
45
|
+
val = \
|
46
|
+
unless descendant.ancestry == child_ancestry
|
47
|
+
descendant.read_attribute(descendant.class.ancestry_field).gsub(/^#{child_ancestry}\//, '')
|
48
|
+
end
|
49
|
+
descendant.update_attribute descendant.class.ancestry_field, val
|
56
50
|
end
|
57
|
-
# ... throw an exception if it has children and orphan strategy is restrict
|
58
|
-
elsif self.base_class.orphan_strategy == :restrict
|
59
|
-
raise Error.new('Cannot delete record because it has descendants.') unless is_childless?
|
60
51
|
end
|
52
|
+
# ... destroy all descendants if orphan strategy is destroy
|
53
|
+
elsif self.base_class.orphan_strategy == :destroy
|
54
|
+
descendants.all.each do |descendant|
|
55
|
+
descendant.without_ancestry_callbacks { descendant.destroy }
|
56
|
+
end
|
57
|
+
# ... throw an exception if it has children and orphan strategy is restrict
|
58
|
+
elsif self.base_class.orphan_strategy == :restrict
|
59
|
+
raise Error.new('Cannot delete record because it has descendants.') unless is_childless?
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
63
|
+
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
# The ancestry value for this record's children
|
66
|
+
def child_ancestry
|
67
|
+
# New records cannot have children
|
68
|
+
raise Error.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
70
|
+
if self.send("#{self.base_class.ancestry_field}_was").blank?
|
71
|
+
id.to_s
|
72
|
+
else
|
73
|
+
"#{self.send "#{self.base_class.ancestry_field}_was"}/#{id}"
|
75
74
|
end
|
75
|
+
end
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
77
|
+
# Ancestors
|
78
|
+
def ancestor_ids
|
79
|
+
read_attribute(self.base_class.ancestry_field).to_s.split('/').map { |id| cast_primary_key(id) }
|
80
|
+
end
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
def ancestor_conditions
|
83
|
+
{ :_id.in => ancestor_ids }
|
84
|
+
end
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
86
|
+
def ancestors depth_options = {}
|
87
|
+
self.base_class.scope_depth(depth_options, depth).where(ancestor_conditions)
|
88
|
+
end
|
89
89
|
|
90
|
-
|
91
|
-
|
92
|
-
|
90
|
+
def path_ids
|
91
|
+
ancestor_ids + [id]
|
92
|
+
end
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
def path_conditions
|
95
|
+
{ :_id.in => path_ids }
|
96
|
+
end
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
def path depth_options = {}
|
99
|
+
self.base_class.scope_depth(depth_options, depth).where(path_conditions)
|
100
|
+
end
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
|
102
|
+
def depth
|
103
|
+
ancestor_ids.size
|
104
|
+
end
|
105
105
|
|
106
|
-
|
107
|
-
|
108
|
-
|
106
|
+
def cache_depth
|
107
|
+
write_attribute self.base_class.depth_cache_field, depth
|
108
|
+
end
|
109
109
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
110
|
+
# Parent
|
111
|
+
def parent= parent
|
112
|
+
write_attribute(self.base_class.ancestry_field, parent.blank? ? nil : parent.child_ancestry)
|
113
|
+
end
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
def parent_id= parent_id
|
116
|
+
self.parent = parent_id.blank? ? nil : self.base_class.find(parent_id)
|
117
|
+
end
|
118
118
|
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
def parent_id
|
120
|
+
ancestor_ids.empty? ? nil : ancestor_ids.last
|
121
|
+
end
|
122
122
|
|
123
|
-
|
124
|
-
|
125
|
-
|
123
|
+
def parent
|
124
|
+
parent_id.blank? ? nil : self.base_class.find(parent_id)
|
125
|
+
end
|
126
126
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
127
|
+
# Root
|
128
|
+
def root_id
|
129
|
+
ancestor_ids.empty? ? id : ancestor_ids.first
|
130
|
+
end
|
131
131
|
|
132
|
-
|
133
|
-
|
134
|
-
|
132
|
+
def root
|
133
|
+
(root_id == id) ? self : self.base_class.find(root_id)
|
134
|
+
end
|
135
135
|
|
136
|
-
|
137
|
-
|
138
|
-
|
136
|
+
def is_root?
|
137
|
+
read_attribute(self.base_class.ancestry_field).blank?
|
138
|
+
end
|
139
139
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
140
|
+
# Children
|
141
|
+
def child_conditions
|
142
|
+
{self.base_class.ancestry_field => child_ancestry}
|
143
|
+
end
|
144
144
|
|
145
|
-
|
146
|
-
|
147
|
-
|
145
|
+
def children
|
146
|
+
self.base_class.where(child_conditions)
|
147
|
+
end
|
148
148
|
|
149
|
-
|
150
|
-
|
151
|
-
|
149
|
+
def child_ids
|
150
|
+
children.only(:_id).map(&:id)
|
151
|
+
end
|
152
152
|
|
153
|
-
|
154
|
-
|
155
|
-
|
153
|
+
def has_children?
|
154
|
+
self.children.present?
|
155
|
+
end
|
156
156
|
|
157
|
-
|
158
|
-
|
159
|
-
|
157
|
+
def is_childless?
|
158
|
+
!has_children?
|
159
|
+
end
|
160
160
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
161
|
+
# Siblings
|
162
|
+
def sibling_conditions
|
163
|
+
{self.base_class.ancestry_field => read_attribute(self.base_class.ancestry_field)}
|
164
|
+
end
|
165
165
|
|
166
|
-
|
167
|
-
|
168
|
-
|
166
|
+
def siblings
|
167
|
+
self.base_class.where sibling_conditions
|
168
|
+
end
|
169
169
|
|
170
|
-
|
171
|
-
|
172
|
-
|
170
|
+
def sibling_ids
|
171
|
+
siblings.only(:_id).map(&:id)
|
172
|
+
end
|
173
173
|
|
174
|
-
|
175
|
-
|
176
|
-
|
174
|
+
def has_siblings?
|
175
|
+
self.siblings.count > 1
|
176
|
+
end
|
177
177
|
|
178
|
-
|
179
|
-
|
180
|
-
|
178
|
+
def is_only_child?
|
179
|
+
!has_siblings?
|
180
|
+
end
|
181
181
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
182
|
+
# Descendants
|
183
|
+
def descendant_conditions
|
184
|
+
[
|
185
|
+
{ self.base_class.ancestry_field => /^#{child_ancestry}\// },
|
186
|
+
{ self.base_class.ancestry_field => child_ancestry }
|
187
|
+
]
|
188
|
+
end
|
189
189
|
|
190
|
-
|
191
|
-
|
192
|
-
|
190
|
+
def descendants depth_options = {}
|
191
|
+
self.base_class.scope_depth(depth_options, depth).any_of(descendant_conditions)
|
192
|
+
end
|
193
193
|
|
194
|
-
|
195
|
-
|
196
|
-
|
194
|
+
def descendant_ids depth_options = {}
|
195
|
+
descendants(depth_options).only(:_id).map(&:id)
|
196
|
+
end
|
197
197
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
198
|
+
# Subtree
|
199
|
+
def subtree_conditions
|
200
|
+
[
|
201
|
+
{ :_id => id },
|
202
|
+
{ self.base_class.ancestry_field => /^#{child_ancestry}\// },
|
203
|
+
{ self.base_class.ancestry_field => child_ancestry }
|
204
|
+
]
|
205
|
+
end
|
206
206
|
|
207
|
-
|
208
|
-
|
209
|
-
|
207
|
+
def subtree depth_options = {}
|
208
|
+
self.base_class.scope_depth(depth_options, depth).any_of(subtree_conditions)
|
209
|
+
end
|
210
210
|
|
211
|
-
|
212
|
-
|
213
|
-
|
211
|
+
def subtree_ids depth_options = {}
|
212
|
+
subtree(depth_options).only(:_id).map(&:id)
|
213
|
+
end
|
214
214
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
215
|
+
# Callback disabling
|
216
|
+
def without_ancestry_callbacks
|
217
|
+
@disable_ancestry_callbacks = true
|
218
|
+
yield
|
219
|
+
@disable_ancestry_callbacks = false
|
220
|
+
end
|
221
221
|
|
222
|
-
|
223
|
-
|
224
|
-
|
222
|
+
def ancestry_callbacks_disabled?
|
223
|
+
!!@disable_ancestry_callbacks
|
224
|
+
end
|
225
225
|
|
226
|
-
|
226
|
+
private
|
227
227
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
end
|
228
|
+
def cast_primary_key(key)
|
229
|
+
if primary_key_type == Integer
|
230
|
+
key.to_i
|
231
|
+
elsif primary_key_type == BSON::ObjectId && key =~ /[a-z0-9]{24}/
|
232
|
+
BSON::ObjectId.convert(self, key)
|
233
|
+
else
|
234
|
+
key
|
236
235
|
end
|
236
|
+
end
|
237
237
|
|
238
|
-
|
239
|
-
|
240
|
-
end
|
238
|
+
def primary_key_type
|
239
|
+
@primary_key_type ||= self.base_class.fields['_id'].options[:type]
|
241
240
|
end
|
242
241
|
end
|
242
|
+
|
243
243
|
end
|
data/lib/mongoid-ancestry.rb
CHANGED
@@ -3,12 +3,13 @@ module Mongoid
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
autoload :ClassMethods, 'mongoid-ancestry/class_methods'
|
6
|
-
autoload :InstanceMethods, 'mongoid-ancestry/instance_methods'
|
7
6
|
autoload :Error, 'mongoid-ancestry/exceptions'
|
8
7
|
|
9
8
|
included do
|
10
9
|
cattr_accessor :base_class
|
11
10
|
self.base_class = self
|
12
11
|
end
|
12
|
+
|
13
|
+
require 'mongoid-ancestry/instance_methods'
|
13
14
|
end
|
14
15
|
end
|
data/log/.gitignore
ADDED
data/mongoid-ancestry.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid-ancestry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,31 +10,40 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
14
|
-
default_executable:
|
13
|
+
date: 2012-06-25 00:00:00.000000000 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: mongoid
|
18
|
-
requirement:
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
19
18
|
none: false
|
20
19
|
requirements:
|
21
|
-
- -
|
20
|
+
- - ! '>='
|
22
21
|
- !ruby/object:Gem::Version
|
23
22
|
version: '2.0'
|
24
23
|
type: :runtime
|
25
24
|
prerelease: false
|
26
|
-
version_requirements:
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '2.0'
|
27
31
|
- !ruby/object:Gem::Dependency
|
28
32
|
name: bson_ext
|
29
|
-
requirement:
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
30
34
|
none: false
|
31
35
|
requirements:
|
32
|
-
- -
|
36
|
+
- - ! '>='
|
33
37
|
- !ruby/object:Gem::Version
|
34
38
|
version: '1.3'
|
35
39
|
type: :runtime
|
36
40
|
prerelease: false
|
37
|
-
version_requirements:
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.3'
|
38
47
|
description: Organise Mongoid model into a tree structure
|
39
48
|
email:
|
40
49
|
- eagle.anton@gmail.com
|
@@ -44,6 +53,7 @@ extra_rdoc_files:
|
|
44
53
|
- README.md
|
45
54
|
files:
|
46
55
|
- .gitignore
|
56
|
+
- .travis.yml
|
47
57
|
- Gemfile
|
48
58
|
- Gemfile.lock
|
49
59
|
- Guardfile
|
@@ -57,13 +67,13 @@ files:
|
|
57
67
|
- lib/mongoid-ancestry/exceptions.rb
|
58
68
|
- lib/mongoid-ancestry/instance_methods.rb
|
59
69
|
- lib/mongoid-ancestry/version.rb
|
70
|
+
- log/.gitignore
|
60
71
|
- mongoid-ancestry.gemspec
|
61
72
|
- spec/lib/ancestry_spec.rb
|
62
73
|
- spec/lib/mongoid-ancestry/class_methods_spec.rb
|
63
74
|
- spec/lib/mongoid-ancestry/instance_methods_spec.rb
|
64
75
|
- spec/spec_helper.rb
|
65
76
|
- spec/support/models.rb
|
66
|
-
has_rdoc: true
|
67
77
|
homepage: http://github.com/skyeagle/mongoid-ancestry
|
68
78
|
licenses:
|
69
79
|
- MIT
|
@@ -79,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
89
|
version: '0'
|
80
90
|
segments:
|
81
91
|
- 0
|
82
|
-
hash:
|
92
|
+
hash: -4473599239750644479
|
83
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
94
|
none: false
|
85
95
|
requirements:
|
@@ -88,10 +98,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
98
|
version: '0'
|
89
99
|
segments:
|
90
100
|
- 0
|
91
|
-
hash:
|
101
|
+
hash: -4473599239750644479
|
92
102
|
requirements: []
|
93
103
|
rubyforge_project: mongoid-ancestry
|
94
|
-
rubygems_version: 1.
|
104
|
+
rubygems_version: 1.8.24
|
95
105
|
signing_key:
|
96
106
|
specification_version: 3
|
97
107
|
summary: Ancestry allows the records of a Mongoid model to be organised in a tree
|