mongoid-sleeping_king_studios 0.5.0 → 0.6.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/LICENSE +1 -1
- data/README.md +21 -3
- data/lib/mongoid/sleeping_king_studios.rb +11 -1
- data/lib/mongoid/sleeping_king_studios/concern.rb +69 -0
- data/lib/mongoid/sleeping_king_studios/concern/metadata.rb +61 -0
- data/lib/mongoid/sleeping_king_studios/ext/mongoid/document.rb +15 -0
- data/lib/mongoid/sleeping_king_studios/has_tree.rb +119 -68
- data/lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb +177 -113
- data/lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry/metadata.rb +82 -0
- data/lib/mongoid/sleeping_king_studios/has_tree/children/metadata.rb +40 -0
- data/lib/mongoid/sleeping_king_studios/has_tree/errors.rb +6 -5
- data/lib/mongoid/sleeping_king_studios/has_tree/metadata.rb +64 -0
- data/lib/mongoid/sleeping_king_studios/has_tree/parent/metadata.rb +40 -0
- data/lib/mongoid/sleeping_king_studios/sluggable.rb +125 -46
- data/lib/mongoid/sleeping_king_studios/sluggable/metadata.rb +42 -0
- data/lib/mongoid/sleeping_king_studios/version.rb +2 -1
- metadata +32 -37
@@ -1,6 +1,7 @@
|
|
1
1
|
# lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry.rb
|
2
2
|
|
3
3
|
require 'mongoid/sleeping_king_studios'
|
4
|
+
require 'mongoid/sleeping_king_studios/concern'
|
4
5
|
require 'mongoid/sleeping_king_studios/has_tree/errors'
|
5
6
|
|
6
7
|
module Mongoid::SleepingKingStudios
|
@@ -21,6 +22,142 @@ module Mongoid::SleepingKingStudios
|
|
21
22
|
# @since 0.5.0
|
22
23
|
module CacheAncestry
|
23
24
|
extend ActiveSupport::Concern
|
25
|
+
extend Mongoid::SleepingKingStudios::Concern
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
#
|
29
|
+
# Sets up the ancestry caching concern.
|
30
|
+
#
|
31
|
+
# @param [Class] base The base class into which the concern is mixed in.
|
32
|
+
# @param [Hash] options The options for the relation.
|
33
|
+
#
|
34
|
+
# @since 0.6.0
|
35
|
+
def self.apply base, metadata
|
36
|
+
name = :has_tree_cache_ancestry
|
37
|
+
validate_options name, metadata.properties
|
38
|
+
|
39
|
+
define_fields base, metadata
|
40
|
+
define_accessors base, metadata
|
41
|
+
define_helpers base, metadata
|
42
|
+
end # class method apply
|
43
|
+
|
44
|
+
# @api private
|
45
|
+
#
|
46
|
+
# Overwrites the parent id setter to update the ancestor ids field,
|
47
|
+
# and defines the ancestors and descendents methods.
|
48
|
+
#
|
49
|
+
# @param [Class] base The base class into which the concern is mixed in.
|
50
|
+
# @param [Metadata] metadata The metadata for the relation.
|
51
|
+
#
|
52
|
+
# @since 0.6.0
|
53
|
+
def self.define_accessors base, metadata
|
54
|
+
parent_id_writer = base.instance_method metadata.parent_foreign_key_writer
|
55
|
+
|
56
|
+
base.re_define_method metadata.parent_foreign_key_writer do |value|
|
57
|
+
old_ancestor_ids = send(metadata.foreign_key).dup
|
58
|
+
|
59
|
+
parent_id_writer.bind(self).call value
|
60
|
+
new_ancestor_ids = send(metadata.parent_name) ?
|
61
|
+
send(metadata.parent_name).send(metadata.foreign_key) + [send(metadata.parent_name).id] :
|
62
|
+
[]
|
63
|
+
|
64
|
+
descendents.each do |descendent|
|
65
|
+
ary = descendent.send(metadata.foreign_key).dup
|
66
|
+
ary[0..old_ancestor_ids.count] = new_ancestor_ids + [id]
|
67
|
+
descendent.update_attributes metadata.foreign_key => ary
|
68
|
+
end # each
|
69
|
+
|
70
|
+
send :"#{metadata.foreign_key}=", new_ancestor_ids
|
71
|
+
end # method
|
72
|
+
|
73
|
+
base.send :define_method, metadata.relation_name do
|
74
|
+
begin
|
75
|
+
self.class.find(send(metadata.foreign_key))
|
76
|
+
rescue Mongoid::Errors::DocumentNotFound, Mongoid::Errors::InvalidFind
|
77
|
+
raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new metadata.relation_name, send(metadata.foreign_key)
|
78
|
+
end # begin-rescue
|
79
|
+
end # method
|
80
|
+
|
81
|
+
base.send :define_method, :descendents do
|
82
|
+
criteria = self.class.all
|
83
|
+
|
84
|
+
send(metadata.foreign_key).each_with_index do |ancestor_id, index|
|
85
|
+
criteria = criteria.where(:"#{metadata.foreign_key}.#{index}" => ancestor_id)
|
86
|
+
end # each
|
87
|
+
|
88
|
+
criteria.where(:"#{metadata.foreign_key}.#{send(metadata.foreign_key).count}" => id)
|
89
|
+
end # method descendents
|
90
|
+
end # class method define_fields
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
#
|
94
|
+
# Defines the foreign key field on the base class.
|
95
|
+
#
|
96
|
+
# @param [Class] base The base class into which the concern is mixed in.
|
97
|
+
# @param [Metadata] metadata The metadata for the relation.
|
98
|
+
#
|
99
|
+
# @since 0.6.0
|
100
|
+
def self.define_fields base, metadata
|
101
|
+
base.send :field, metadata.foreign_key, :type => Array, :default => []
|
102
|
+
end # class method define_fields
|
103
|
+
|
104
|
+
# @api private
|
105
|
+
#
|
106
|
+
# Defines the rebuild and validate ancestry helper methods.
|
107
|
+
#
|
108
|
+
# @param [Class] base The base class into which the concern is mixed in.
|
109
|
+
# @param [Metadata] metadata The metadata for the relation.
|
110
|
+
#
|
111
|
+
# @since 0.6.0
|
112
|
+
def self.define_helpers base, metadata
|
113
|
+
base.send :define_method, :rebuild_ancestry! do
|
114
|
+
begin
|
115
|
+
ary, object = [], self
|
116
|
+
while object.send(metadata.parent_name)
|
117
|
+
ary.unshift object.send(metadata.parent_name).id
|
118
|
+
object = object.send(metadata.parent_name)
|
119
|
+
end # while
|
120
|
+
self.send :"#{metadata.foreign_key}=", ary
|
121
|
+
rescue Mongoid::Errors::DocumentNotFound
|
122
|
+
raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new "#{relation_name}", object.send(metadata.parent_foreign_key)
|
123
|
+
end # begin-rescue
|
124
|
+
end # method rebuild_ancestry!
|
125
|
+
|
126
|
+
base.send :define_method, :validate_ancestry! do
|
127
|
+
return if send(metadata.foreign_key).empty?
|
128
|
+
|
129
|
+
ancestors = []
|
130
|
+
send(metadata.foreign_key).each_with_index do |ancestor_id, index|
|
131
|
+
begin
|
132
|
+
ancestor = self.class.find(ancestor_id)
|
133
|
+
ancestors << ancestor
|
134
|
+
|
135
|
+
if index > 0 && ancestor.send(metadata.parent_foreign_key) != send(metadata.foreign_key)[index - 1]
|
136
|
+
# If the ancestor's parent is not the same as the previous
|
137
|
+
# ancestor.
|
138
|
+
raise Mongoid::SleepingKingStudios::HasTree::Errors::UnexpectedAncestor.new "#{metadata.relation_name}", ancestor.send(metadata.parent_foreign_key), send(metadata.foreign_key)[index - 1]
|
139
|
+
end # if
|
140
|
+
rescue Mongoid::Errors::InvalidFind, Mongoid::Errors::DocumentNotFound
|
141
|
+
# If the ancestor id is nil, or the ancestor does not exist.
|
142
|
+
raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new "#{metadata.relation_name}", ancestor_id
|
143
|
+
end # begin-rescue
|
144
|
+
end # each with index
|
145
|
+
end # method validate_ancestry!
|
146
|
+
end # class method define_helpers
|
147
|
+
|
148
|
+
# Get the valid options allowed with this concern.
|
149
|
+
#
|
150
|
+
# @return [ Array<Symbol> ] The valid options.
|
151
|
+
#
|
152
|
+
# @since 0.5.1
|
153
|
+
def self.valid_options
|
154
|
+
%i(
|
155
|
+
children_name # Internal; do not set directly.
|
156
|
+
foreign_key
|
157
|
+
parent_name # Internal; do not set directly.
|
158
|
+
relation_name
|
159
|
+
) # end Array
|
160
|
+
end # class method valid_options
|
24
161
|
|
25
162
|
# @!attribute [r] ancestor_ids
|
26
163
|
# Stores the ids of the object's ancestors, starting from the root
|
@@ -31,120 +168,47 @@ module Mongoid::SleepingKingStudios
|
|
31
168
|
#
|
32
169
|
# @since 0.5.0
|
33
170
|
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# @raise [Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor]
|
73
|
-
# If one or more of the ancestors is not found in the datastore (the id
|
74
|
-
# is wrong, the object is not persisted, there is a nil value in
|
75
|
-
# ancestor_ids, and so on).
|
76
|
-
#
|
77
|
-
# @return [Array] The objects' ancestors
|
78
|
-
def ancestors
|
79
|
-
self.class.find(ancestor_ids)
|
80
|
-
rescue Mongoid::Errors::DocumentNotFound, Mongoid::Errors::InvalidFind
|
81
|
-
raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new ancestor_ids
|
82
|
-
end # method ancestors
|
83
|
-
|
84
|
-
# Returns a scope for all of the descendents of the current object, i.e.
|
85
|
-
# all objects that have the current object as an ancestor.
|
86
|
-
#
|
87
|
-
# @return [Mongoid::Criteria] The criteria for finding the descendents.
|
88
|
-
def descendents
|
89
|
-
criteria = self.class.all
|
90
|
-
|
91
|
-
ancestor_ids.each_with_index do |ancestor_id, index|
|
92
|
-
criteria = criteria.where(:"ancestor_ids.#{index}" => ancestor_id)
|
93
|
-
end # each
|
94
|
-
|
95
|
-
criteria.where(:"ancestor_ids.#{ancestor_ids.count}" => id)
|
96
|
-
end # scope descendents
|
97
|
-
|
98
|
-
# Travels up the tree using the #parent method and saves the ancestors to
|
99
|
-
# the :ancestor_ids field. This overwrites the value of :ancestor_ids on
|
100
|
-
# the current object, but not on any of its ancestors.
|
101
|
-
#
|
102
|
-
# @raise [Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor]
|
103
|
-
# If the current object or an ancestor has an invalid #parent.
|
104
|
-
def rebuild_ancestry!
|
105
|
-
ary, object = [], self
|
106
|
-
while object.parent
|
107
|
-
ary.unshift object.parent.id
|
108
|
-
object = object.parent
|
109
|
-
end # while
|
110
|
-
self.send :ancestor_ids=, ary
|
111
|
-
rescue Mongoid::Errors::DocumentNotFound
|
112
|
-
raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new object.parent_id
|
113
|
-
end # method rebuild_ancestry!
|
114
|
-
|
115
|
-
# Travels up the tree using the :ancestor_ids and ensures that each
|
116
|
-
# ancestor exists and is persisted to the database, and that the
|
117
|
-
# object's parent correctly matches the last value in its own
|
118
|
-
# :ancestor_ids field.
|
119
|
-
#
|
120
|
-
# @raise [Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor]
|
121
|
-
# If any of the ancestors is not found in the datastore (the id is
|
122
|
-
# wrong, the object is not persisted, there is a nil value in
|
123
|
-
# ancestor_ids, and so on).
|
171
|
+
# @!method ancestors
|
172
|
+
# Returns an array of the current object's ancestors, from the root
|
173
|
+
# object to the current parent. If the object has no parent, returns an
|
174
|
+
# empty array. If an error is raised, consider calling
|
175
|
+
# #rebuild_ancestry!
|
176
|
+
#
|
177
|
+
# @raise [Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor]
|
178
|
+
# If one or more of the ancestors is not found in the datastore (the
|
179
|
+
# id is wrong, the object is not persisted, there is a nil value in
|
180
|
+
# ancestor_ids, and so on).
|
181
|
+
#
|
182
|
+
# @return [Array] The objects' ancestors
|
183
|
+
|
184
|
+
# @!method descendents
|
185
|
+
# Returns a scope for all of the descendents of the current object,
|
186
|
+
# i.e. all objects that have the current object as an ancestor.
|
187
|
+
#
|
188
|
+
# @return [Mongoid::Criteria] The criteria for finding the descendents.
|
189
|
+
|
190
|
+
# @!method rebuild_ancestry!
|
191
|
+
# Travels up the tree using the #parent method and saves the ancestors
|
192
|
+
# to the :ancestor_ids field. This overwrites the value of
|
193
|
+
# :ancestor_ids on the current object, but not on any of its ancestors.
|
194
|
+
#
|
195
|
+
# @raise [Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor]
|
196
|
+
# If the current object or an ancestor has an invalid #parent.
|
197
|
+
|
198
|
+
# @!method validate_ancestry!
|
199
|
+
# Travels up the tree using the :ancestor_ids and ensures that each
|
200
|
+
# ancestor exists and is persisted to the database, and that the
|
201
|
+
# object's parent correctly matches the last value in its own
|
202
|
+
# :ancestor_ids field.
|
203
|
+
#
|
204
|
+
# @raise [Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor]
|
205
|
+
# If any of the ancestors is not found in the datastore (the id is
|
206
|
+
# wrong, the object is not persisted, there is a nil value in
|
207
|
+
# ancestor_ids, and so on).
|
124
208
|
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
def validate_ancestry!
|
129
|
-
return if ancestor_ids.empty?
|
130
|
-
|
131
|
-
ancestors = []
|
132
|
-
ancestor_ids.each_with_index do |ancestor_id, index|
|
133
|
-
begin
|
134
|
-
ancestor = self.class.find(ancestor_id)
|
135
|
-
ancestors << ancestor
|
136
|
-
|
137
|
-
if index > 0 && ancestor.parent_id != ancestor_ids[index - 1]
|
138
|
-
# If the ancestor's parent is not the same as the previous
|
139
|
-
# ancestor.
|
140
|
-
raise Mongoid::SleepingKingStudios::HasTree::Errors::UnexpectedAncestor.new ancestor.parent_id, ancestor_ids[index - 1]
|
141
|
-
end # if
|
142
|
-
rescue Mongoid::Errors::InvalidFind, Mongoid::Errors::DocumentNotFound
|
143
|
-
# If the ancestor id is nil, or the ancestor does not exist.
|
144
|
-
raise Mongoid::SleepingKingStudios::HasTree::Errors::MissingAncestor.new ancestor_id
|
145
|
-
end # begin-rescue
|
146
|
-
end # each
|
147
|
-
end # method validate_ancestry!
|
209
|
+
# @raise [Mongoid::SleepingKingStudios::HasTree::Errors::UnexpectedAncestor]
|
210
|
+
# If there is a mismatch between an object's #parent and the last
|
211
|
+
# value in the object's :ancestor_ids field.
|
148
212
|
end # module
|
149
213
|
end # module
|
150
214
|
end # module
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# lib/mongoid/sleeping_king_studios/has_tree/cache_ancestry/metadata.rb
|
2
|
+
|
3
|
+
require 'mongoid/sleeping_king_studios/concern/metadata'
|
4
|
+
|
5
|
+
module Mongoid::SleepingKingStudios
|
6
|
+
module HasTree
|
7
|
+
module CacheAncestry
|
8
|
+
# Stores information about a HasTree::CacheAncestry concern.
|
9
|
+
class Metadata < Mongoid::SleepingKingStudios::Concern::Metadata
|
10
|
+
# The name of the children relation for the tree.
|
11
|
+
#
|
12
|
+
# @return [Symbol] The relation name.
|
13
|
+
def children_name
|
14
|
+
self[:children_name] || :children
|
15
|
+
end # method parent_name
|
16
|
+
|
17
|
+
# @return [Boolean] True if a custom children relation name is set;
|
18
|
+
# otherwise false.
|
19
|
+
def children_name?
|
20
|
+
!!self[:children_name]
|
21
|
+
end # method children_name?
|
22
|
+
|
23
|
+
# The name of the field used to store ancestor references. If no
|
24
|
+
# foreign key is set, uses the relation name and the field name to
|
25
|
+
# generate a key.
|
26
|
+
#
|
27
|
+
# @return [Symbol] The field name.
|
28
|
+
def foreign_key
|
29
|
+
self[:foreign_key] || :"#{relation_name.to_s.singularize}_ids"
|
30
|
+
end # method foreign_key
|
31
|
+
|
32
|
+
# The name of the field used to store the tree's parent relation.
|
33
|
+
#
|
34
|
+
# @return [Symbol] The method name.
|
35
|
+
def parent_foreign_key
|
36
|
+
:"#{parent_name}_id"
|
37
|
+
end # method parent_foreign_key
|
38
|
+
|
39
|
+
# The writer for the tree's parent relation id.
|
40
|
+
#
|
41
|
+
# @return [Symbol] The method name.
|
42
|
+
def parent_foreign_key_writer
|
43
|
+
:"#{parent_name}_id="
|
44
|
+
end # method parent_foreign_key_writer
|
45
|
+
|
46
|
+
# The name of the parent relation for the tree.
|
47
|
+
#
|
48
|
+
# @return [Symbol] The relation name.
|
49
|
+
def parent_name
|
50
|
+
self[:parent_name] || :parent
|
51
|
+
end # method parent_name
|
52
|
+
|
53
|
+
# @return [Boolean] True if a custom children relation name is set;
|
54
|
+
# otherwise false.
|
55
|
+
def parent_name?
|
56
|
+
!!self[:parent_name]
|
57
|
+
end # method parent_name?
|
58
|
+
|
59
|
+
# The writer for the tree's parent relation.
|
60
|
+
#
|
61
|
+
# @return [Symbol] The method name.
|
62
|
+
def parent_writer
|
63
|
+
:"#{parent_name}="
|
64
|
+
end # method parent_name
|
65
|
+
|
66
|
+
# The name of the tree's ancestors method. If no relation name is set,
|
67
|
+
# defaults to :ancestors.
|
68
|
+
#
|
69
|
+
# @return [Symbol] The relation name.
|
70
|
+
def relation_name
|
71
|
+
fetch(:relation_name, :ancestors)
|
72
|
+
end # method relation_name
|
73
|
+
|
74
|
+
# @return [Boolean] True if a custom relation name is set; otherwise
|
75
|
+
# false.
|
76
|
+
def relation_name?
|
77
|
+
!!self[:relation_name]
|
78
|
+
end # method relation_name?
|
79
|
+
end # class
|
80
|
+
end # module
|
81
|
+
end # module
|
82
|
+
end # module
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# lib/mongoid/sleeping_king_studios/has_tree/children/metadata.rb
|
2
|
+
|
3
|
+
require 'mongoid/sleeping_king_studios/concern/metadata'
|
4
|
+
|
5
|
+
module Mongoid::SleepingKingStudios
|
6
|
+
module HasTree
|
7
|
+
module Children
|
8
|
+
# Stores information about a HasTree concern's children relation.
|
9
|
+
class Metadata < Mongoid::SleepingKingStudios::Concern::Metadata
|
10
|
+
# The name of the tree's parent relation. If no relation name is set,
|
11
|
+
# defaults to :parent.
|
12
|
+
#
|
13
|
+
# @return [Symbol] The relation name.
|
14
|
+
def inverse_of
|
15
|
+
fetch(:inverse_of, :parent)
|
16
|
+
end # method inverse_of
|
17
|
+
|
18
|
+
# @return [Boolean] True if a custom inverse relation name is set;
|
19
|
+
# otherwise false.
|
20
|
+
def inverse_of?
|
21
|
+
!!self[:inverse_of]
|
22
|
+
end # method inverse_of?
|
23
|
+
|
24
|
+
# The name of the tree's children relation. If no relation name is set,
|
25
|
+
# defaults to :children.
|
26
|
+
#
|
27
|
+
# @return [Symbol] The relation name.
|
28
|
+
def relation_name
|
29
|
+
fetch(:relation_name, :children)
|
30
|
+
end # method relation_name
|
31
|
+
|
32
|
+
# @return [Boolean] True if a custom relation name is set; otherwise
|
33
|
+
# false.
|
34
|
+
def relation_name?
|
35
|
+
!!self[:relation_name]
|
36
|
+
end # method relation_name?
|
37
|
+
end # class
|
38
|
+
end # module
|
39
|
+
end # module
|
40
|
+
end # module
|
@@ -6,17 +6,18 @@ module Mongoid::SleepingKingStudios
|
|
6
6
|
module HasTree
|
7
7
|
module Errors
|
8
8
|
class MissingAncestor < Mongoid::SleepingKingStudios::Error
|
9
|
-
def initialize ancestor_id
|
9
|
+
def initialize relation_name, ancestor_id
|
10
10
|
message = Array === ancestor_id ?
|
11
|
-
|
12
|
-
|
11
|
+
"#{relation_name.to_s.pluralize} with ids" :
|
12
|
+
"#{relation_name.to_s.singularize} with id"
|
13
13
|
super "unable to find #{message} #{ancestor_id.inspect}"
|
14
14
|
end # constructor
|
15
15
|
end # class
|
16
16
|
|
17
17
|
class UnexpectedAncestor < Mongoid::SleepingKingStudios::Error
|
18
|
-
def initialize expected_id, received_id
|
19
|
-
|
18
|
+
def initialize relation_name, expected_id, received_id
|
19
|
+
message = "expected #{relation_name.to_s.singularize} with id"
|
20
|
+
super "#{message} #{expected_id.inspect}, but received id #{received_id.inspect}"
|
20
21
|
end # constructor
|
21
22
|
end # class
|
22
23
|
end # module Errors
|