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.
@@ -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
- # Class methods added to the base class via #extend.
35
- module ClassMethods
36
- # @overload cache_ancestry()
37
- # Adds the :ancestry_id field, the #ancestors and #descendents
38
- # scopes, and redefines #parent_id= to update the :ancestry_id
39
- # field on the object and its descendents.
40
- #
41
- # Do not call this method directly; rather, add a
42
- # :cache_ancestry => true options to the call to ::has_tree.
43
- def cache_ancestry **options
44
- parent_name = options[:children][:inverse_of]
45
- children_name = options[:parent][:inverse_of]
46
-
47
- field :ancestor_ids, :type => Array, :default => []
48
-
49
- alias_method :"set_parent_id", :"#{parent_name}_id="
50
- private :set_parent_id
51
- re_define_method "#{parent_name}_id=" do |value|
52
- old_ancestor_ids = ancestor_ids.dup
53
-
54
- set_parent_id value
55
- new_ancestor_ids = parent ? parent.ancestor_ids + [parent.id] : []
56
-
57
- descendents.each do |descendent|
58
- ary = descendent.ancestor_ids.dup
59
- ary[0..old_ancestor_ids.count] = new_ancestor_ids + [id]
60
- descendent.update_attributes :ancestor_ids => ary
61
- end # each
62
-
63
- self.send :ancestor_ids=, new_ancestor_ids
64
- end # method #{parent_name}_id=
65
- end # class method cache_ancestry
66
- end # module ClassMethods
67
-
68
- # Returns an array of the current object's ancestors, from the root
69
- # object to the current parent. If the object has no parent, returns an
70
- # empty array. If an error is raised, consider calling #rebuild_ancestry!
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
- # @raise [Mongoid::SleepingKingStudios::HasTree::Errors::UnexpectedAncestor]
126
- # If there is a mismatch between an object's #parent and the last value
127
- # in the object's :ancestor_ids field.
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
- 'ancestors with ids' :
12
- 'ancestor with id'
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
- super "expected ancestor with id #{expected_id.inspect}, but received id #{received_id.inspect}"
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