mongoid 2.0.0.beta.20 → 2.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. data/README.rdoc +8 -0
  2. data/Rakefile +51 -0
  3. data/lib/config/locales/nl.yml +39 -0
  4. data/lib/config/locales/ro.yml +1 -1
  5. data/lib/mongoid.rb +17 -17
  6. data/lib/mongoid/atomicity.rb +54 -22
  7. data/lib/mongoid/attributes.rb +145 -125
  8. data/lib/mongoid/callbacks.rb +7 -2
  9. data/lib/mongoid/collection.rb +49 -32
  10. data/lib/mongoid/collections.rb +0 -1
  11. data/lib/mongoid/components.rb +34 -29
  12. data/lib/mongoid/config.rb +207 -193
  13. data/lib/mongoid/config/database.rb +167 -0
  14. data/lib/mongoid/contexts.rb +2 -5
  15. data/lib/mongoid/contexts/enumerable.rb +30 -4
  16. data/lib/mongoid/contexts/ids.rb +2 -2
  17. data/lib/mongoid/contexts/mongo.rb +30 -5
  18. data/lib/mongoid/copyable.rb +44 -0
  19. data/lib/mongoid/criteria.rb +110 -56
  20. data/lib/mongoid/criterion/creational.rb +34 -0
  21. data/lib/mongoid/criterion/destructive.rb +37 -0
  22. data/lib/mongoid/criterion/exclusion.rb +3 -1
  23. data/lib/mongoid/criterion/inclusion.rb +59 -64
  24. data/lib/mongoid/criterion/inspection.rb +22 -0
  25. data/lib/mongoid/criterion/optional.rb +42 -54
  26. data/lib/mongoid/criterion/selector.rb +9 -0
  27. data/lib/mongoid/default_scope.rb +28 -0
  28. data/lib/mongoid/deprecation.rb +5 -5
  29. data/lib/mongoid/dirty.rb +4 -5
  30. data/lib/mongoid/document.rb +161 -114
  31. data/lib/mongoid/extensions.rb +7 -11
  32. data/lib/mongoid/extensions/array/parentization.rb +2 -2
  33. data/lib/mongoid/extensions/date/conversions.rb +1 -1
  34. data/lib/mongoid/extensions/hash/conversions.rb +0 -23
  35. data/lib/mongoid/extensions/nil/collectionization.rb +12 -0
  36. data/lib/mongoid/extensions/object/reflections.rb +17 -0
  37. data/lib/mongoid/extensions/object/yoda.rb +27 -0
  38. data/lib/mongoid/extensions/string/conversions.rb +23 -4
  39. data/lib/mongoid/extensions/time_conversions.rb +4 -4
  40. data/lib/mongoid/field.rb +30 -19
  41. data/lib/mongoid/fields.rb +15 -5
  42. data/lib/mongoid/finders.rb +19 -11
  43. data/lib/mongoid/hierarchy.rb +34 -28
  44. data/lib/mongoid/identity.rb +62 -20
  45. data/lib/mongoid/inspection.rb +58 -0
  46. data/lib/mongoid/matchers.rb +20 -0
  47. data/lib/mongoid/multi_database.rb +11 -0
  48. data/lib/mongoid/nested_attributes.rb +41 -0
  49. data/lib/mongoid/paranoia.rb +3 -4
  50. data/lib/mongoid/paths.rb +1 -1
  51. data/lib/mongoid/persistence.rb +89 -90
  52. data/lib/mongoid/persistence/command.rb +20 -4
  53. data/lib/mongoid/persistence/insert.rb +13 -11
  54. data/lib/mongoid/persistence/insert_embedded.rb +8 -6
  55. data/lib/mongoid/persistence/remove.rb +6 -4
  56. data/lib/mongoid/persistence/remove_all.rb +6 -4
  57. data/lib/mongoid/persistence/remove_embedded.rb +8 -6
  58. data/lib/mongoid/persistence/update.rb +12 -10
  59. data/lib/mongoid/railtie.rb +2 -2
  60. data/lib/mongoid/railties/database.rake +10 -9
  61. data/lib/mongoid/relations.rb +104 -0
  62. data/lib/mongoid/relations/accessors.rb +154 -0
  63. data/lib/mongoid/relations/auto_save.rb +34 -0
  64. data/lib/mongoid/relations/binding.rb +24 -0
  65. data/lib/mongoid/relations/bindings.rb +9 -0
  66. data/lib/mongoid/relations/bindings/embedded/in.rb +77 -0
  67. data/lib/mongoid/relations/bindings/embedded/many.rb +93 -0
  68. data/lib/mongoid/relations/bindings/embedded/one.rb +65 -0
  69. data/lib/mongoid/relations/bindings/referenced/in.rb +78 -0
  70. data/lib/mongoid/relations/bindings/referenced/many.rb +93 -0
  71. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +94 -0
  72. data/lib/mongoid/relations/bindings/referenced/one.rb +63 -0
  73. data/lib/mongoid/relations/builder.rb +41 -0
  74. data/lib/mongoid/relations/builders.rb +79 -0
  75. data/lib/mongoid/relations/builders/embedded/in.rb +25 -0
  76. data/lib/mongoid/relations/builders/embedded/many.rb +32 -0
  77. data/lib/mongoid/relations/builders/embedded/one.rb +26 -0
  78. data/lib/mongoid/relations/builders/nested_attributes/many.rb +116 -0
  79. data/lib/mongoid/relations/builders/nested_attributes/one.rb +135 -0
  80. data/lib/mongoid/relations/builders/referenced/in.rb +32 -0
  81. data/lib/mongoid/relations/builders/referenced/many.rb +26 -0
  82. data/lib/mongoid/relations/builders/referenced/many_to_many.rb +29 -0
  83. data/lib/mongoid/relations/builders/referenced/one.rb +30 -0
  84. data/lib/mongoid/relations/cascading.rb +55 -0
  85. data/lib/mongoid/relations/cascading/delete.rb +19 -0
  86. data/lib/mongoid/relations/cascading/destroy.rb +19 -0
  87. data/lib/mongoid/relations/cascading/nullify.rb +18 -0
  88. data/lib/mongoid/relations/cascading/strategy.rb +26 -0
  89. data/lib/mongoid/relations/cyclic.rb +97 -0
  90. data/lib/mongoid/relations/embedded/in.rb +172 -0
  91. data/lib/mongoid/relations/embedded/many.rb +450 -0
  92. data/lib/mongoid/relations/embedded/one.rb +169 -0
  93. data/lib/mongoid/relations/macros.rb +302 -0
  94. data/lib/mongoid/relations/many.rb +185 -0
  95. data/lib/mongoid/relations/metadata.rb +529 -0
  96. data/lib/mongoid/relations/nested_builder.rb +52 -0
  97. data/lib/mongoid/relations/one.rb +29 -0
  98. data/lib/mongoid/relations/polymorphic.rb +54 -0
  99. data/lib/mongoid/relations/proxy.rb +122 -0
  100. data/lib/mongoid/relations/referenced/in.rb +214 -0
  101. data/lib/mongoid/relations/referenced/many.rb +358 -0
  102. data/lib/mongoid/relations/referenced/many_to_many.rb +379 -0
  103. data/lib/mongoid/relations/referenced/one.rb +204 -0
  104. data/lib/mongoid/relations/reflections.rb +45 -0
  105. data/lib/mongoid/safe.rb +11 -1
  106. data/lib/mongoid/safety.rb +122 -97
  107. data/lib/mongoid/scope.rb +14 -9
  108. data/lib/mongoid/state.rb +37 -3
  109. data/lib/mongoid/timestamps.rb +11 -0
  110. data/lib/mongoid/validations.rb +42 -3
  111. data/lib/mongoid/validations/associated.rb +8 -5
  112. data/lib/mongoid/validations/uniqueness.rb +23 -2
  113. data/lib/mongoid/version.rb +1 -1
  114. data/lib/mongoid/versioning.rb +25 -16
  115. data/lib/rails/generators/mongoid/model/templates/model.rb +3 -1
  116. metadata +95 -80
  117. data/lib/mongoid/associations.rb +0 -364
  118. data/lib/mongoid/associations/embedded_in.rb +0 -74
  119. data/lib/mongoid/associations/embeds_many.rb +0 -299
  120. data/lib/mongoid/associations/embeds_one.rb +0 -111
  121. data/lib/mongoid/associations/foreign_key.rb +0 -35
  122. data/lib/mongoid/associations/meta_data.rb +0 -38
  123. data/lib/mongoid/associations/options.rb +0 -78
  124. data/lib/mongoid/associations/proxy.rb +0 -60
  125. data/lib/mongoid/associations/referenced_in.rb +0 -70
  126. data/lib/mongoid/associations/references_many.rb +0 -254
  127. data/lib/mongoid/associations/references_many_as_array.rb +0 -128
  128. data/lib/mongoid/associations/references_one.rb +0 -104
  129. data/lib/mongoid/extensions/array/accessors.rb +0 -17
  130. data/lib/mongoid/extensions/array/assimilation.rb +0 -26
  131. data/lib/mongoid/extensions/hash/accessors.rb +0 -42
  132. data/lib/mongoid/extensions/hash/assimilation.rb +0 -40
  133. data/lib/mongoid/extensions/nil/assimilation.rb +0 -17
  134. data/lib/mongoid/memoization.rb +0 -33
@@ -0,0 +1,185 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Relations #:nodoc:
4
+
5
+ # This is the superclass for all many to one and many to many relation
6
+ # proxies.
7
+ class Many < Proxy
8
+
9
+ # Appends a document or array of documents to the relation. Will set
10
+ # the parent and update the index in the process.
11
+ #
12
+ # @example Append a document.
13
+ # person.addresses << address
14
+ #
15
+ # @example Push a document.
16
+ # person.addresses.push(address)
17
+ #
18
+ # @example Concat with other documents.
19
+ # perosn.addresses.concat([ address_one, address_two ])
20
+ #
21
+ # @param [ Document, Array<Document> ] *args Any number of documents.
22
+ def <<(*args)
23
+ options = default_options(args)
24
+ args.flatten.each do |doc|
25
+ unless target.include?(doc)
26
+ append(doc, options)
27
+ doc.save if base.persisted? && !options[:building]
28
+ end
29
+ end
30
+ end
31
+ alias :concat :<<
32
+ alias :push :<<
33
+
34
+ # Builds a new document in the relation and appends it to the target.
35
+ # Takes an optional type if you want to specify a subclass.
36
+ #
37
+ # @example Build a new document on the relation.
38
+ # person.people.build(:name => "Bozo")
39
+ #
40
+ # @param [ Hash ] attributes The attributes to build the document with.
41
+ # @param [ Class ] type Optional class to build the document with.
42
+ #
43
+ # @return [ Document ] The new document.
44
+ def build(attributes = {}, type = nil)
45
+ instantiated(type).tap do |doc|
46
+ append(doc, default_options(:building => true))
47
+ doc.write_attributes(attributes)
48
+ doc.identify
49
+ end
50
+ end
51
+ alias :new :build
52
+
53
+ # Returns a count of the number of documents in the association that have
54
+ # actually been persisted to the database.
55
+ #
56
+ # Use #size if you want the total number of documents.
57
+ #
58
+ # @example Get the count of persisted documents.
59
+ # person.addresses.count
60
+ #
61
+ # @return [ Integer ] The total number of persisted embedded docs, as
62
+ # flagged by the #persisted? method.
63
+ def count
64
+ target.select(&:persisted?).size
65
+ end
66
+
67
+ # Creates a new document on the references many relation. This will
68
+ # save the document if the parent has been persisted.
69
+ #
70
+ # @example Create and save the new document.
71
+ # person.posts.create(:text => "Testing")
72
+ #
73
+ # @param [ Hash ] attributes The attributes to create with.
74
+ # @param [ Class ] type The optional type of document to create.
75
+ #
76
+ # @return [ Document ] The newly created document.
77
+ def create(attributes = nil, type = nil)
78
+ build(attributes, type).tap do |doc|
79
+ doc.save if base.persisted?
80
+ end
81
+ end
82
+
83
+ # Creates a new document on the references many relation. This will
84
+ # save the document if the parent has been persisted and will raise an
85
+ # error if validation fails.
86
+ #
87
+ # @example Create and save the new document.
88
+ # person.posts.create!(:text => "Testing")
89
+ #
90
+ # @param [ Hash ] attributes The attributes to create with.
91
+ # @param [ Class ] type The optional type of document to create.
92
+ #
93
+ # @raise [ Errors::Validations ] If validation failed.
94
+ #
95
+ # @return [ Document ] The newly created document.
96
+ def create!(attributes = nil, type = nil)
97
+ build(attributes, type).tap do |doc|
98
+ doc.save! if base.persisted?
99
+ end
100
+ end
101
+
102
+ # Determine if any documents in this relation exist in the database.
103
+ #
104
+ # @example Are there persisted documents?
105
+ # person.posts.exists?
106
+ #
107
+ # @return [ true, false ] True is persisted documents exist, false if not.
108
+ def exists?
109
+ count > 0
110
+ end
111
+
112
+ # Find the first document given the conditions, or creates a new document
113
+ # with the conditions that were supplied.
114
+ #
115
+ # @example Find or create.
116
+ # person.posts.find_or_create_by(:title => "Testing")
117
+ #
118
+ # @param [ Hash ] attrs The attributes to search or create with.
119
+ #
120
+ # @return [ Document ] An existing document or newly created one.
121
+ def find_or_create_by(attrs = {})
122
+ find_or(:create, attrs)
123
+ end
124
+
125
+ # Find the first +Document+ given the conditions, or instantiates a new document
126
+ # with the conditions that were supplied
127
+ #
128
+ # @example Find or initialize.
129
+ # person.posts.find_or_initialize_by(:title => "Test")
130
+ #
131
+ # @param [ Hash ] attrs The attributes to search or initialize with.
132
+ #
133
+ # @return [ Document ] An existing document or newly instantiated one.
134
+ def find_or_initialize_by(attrs = {})
135
+ find_or(:build, attrs)
136
+ end
137
+
138
+ private
139
+
140
+ # Get the default options used in binding functions.
141
+ #
142
+ # @example Get the default options.
143
+ # relation.default_options(:continue => true)
144
+ #
145
+ # @param [ Hash, Array ] args The arguments to parse from.
146
+ #
147
+ # @return [ Hash ] The options merged with the actuals.
148
+ def default_options(args = {})
149
+ options = args.is_a?(Hash) ? args : args.extract_options!
150
+ DEFAULT_OPTIONS.merge(options)
151
+ end
152
+
153
+ # Find the first object given the supplied attributes or create/initialize it.
154
+ #
155
+ # @example Find or create|initialize.
156
+ # person.addresses.find_or(:create, :street => "Bond")
157
+ #
158
+ # @param [ Symbol ] method The method name, create or new.
159
+ # @param [ Hash ] attrs The attributes to build with.
160
+ #
161
+ # @return [ Document ] A matching document or a new/created one.
162
+ def find_or(method, attrs = {})
163
+ find(:first, :conditions => attrs) || send(method, attrs)
164
+ end
165
+
166
+ # If the target array does not respond to the supplied method then try to
167
+ # find a named scope or criteria on the class and send the call there.
168
+ #
169
+ # If the method exists on the array, use the default proxy behavior.
170
+ #
171
+ # @param [ Symbol, String ] name The name of the method.
172
+ # @param [ Array ] args The method args
173
+ # @param [ Proc ] block Optional block to pass.
174
+ #
175
+ # @return [ Criteria, Object ] A Criteria or return value from the target.
176
+ def method_missing(name, *args, &block)
177
+ return super if target.respond_to?(name) || [].respond_to?(name)
178
+ klass = metadata.klass
179
+ klass.send(:with_scope, criteria) do
180
+ klass.send(name, *args)
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,529 @@
1
+ # encoding: utf-8
2
+ module Mongoid # :nodoc:
3
+ module Relations #:nodoc:
4
+
5
+ # The "Grand Poobah" of information about any relation is this class. It
6
+ # contains everything you could ever possible want to know.
7
+ class Metadata < Hash
8
+
9
+ delegate :foreign_key_default, :to => :relation
10
+
11
+ # Gets a relation builder associated with the relation this metadata is
12
+ # for.
13
+ #
14
+ # @example Get the builder.
15
+ # metadata.builder(document)
16
+ #
17
+ # @param [ Object ] object A document or attributes to give the builder.
18
+ #
19
+ # @return [ Builder ] The builder for the relation.
20
+ #
21
+ # @since 2.0.0.rc.1
22
+ def builder(object)
23
+ relation.builder(self, object)
24
+ end
25
+
26
+ # Returns the name of the strategy used for handling dependent relations.
27
+ #
28
+ # @example Get the strategy.
29
+ # metadata.cascade_strategy
30
+ #
31
+ # @return [ Object ] The cascading strategy to use.
32
+ #
33
+ # @since 2.0.0.rc.1
34
+ def cascade_strategy
35
+ if dependent?
36
+ strategy =
37
+ %{Mongoid::Relations::Cascading::#{dependent.to_s.classify}}
38
+ strategy.constantize
39
+ else
40
+ return nil
41
+ end
42
+ end
43
+
44
+ # Returns the name of the class that this relation contains. If the
45
+ # class_name was provided as an option this will return that, otherwise
46
+ # it will determine the name from the name property.
47
+ #
48
+ # @example Get the class name.
49
+ # metadata.class_name
50
+ #
51
+ # @return [ String ] The name of the relation's proxied class.
52
+ #
53
+ # @since 2.0.0.rc.1
54
+ def class_name
55
+ @class_name ||= (self[:class_name] || classify)
56
+ end
57
+
58
+ # Will determine if the relation is an embedded one or not. Currently
59
+ # only checks against embeds one and many.
60
+ #
61
+ # @example Is the document embedded.
62
+ # metadata.embedded?
63
+ #
64
+ # @return [ true, false ] True if embedded, false if not.
65
+ #
66
+ # @since 2.0.0.rc.1
67
+ def embedded?
68
+ @embedded ||= (macro == :embeds_one || macro == :embeds_many)
69
+ end
70
+
71
+ # Returns the extension of the relation. This can be a proc or module.
72
+ #
73
+ # @example Get the relation extension.
74
+ # metadata.extension
75
+ #
76
+ # @return [ Proc ] The extension or nil.
77
+ #
78
+ # @since 2.0.0.rc.1
79
+ def extension
80
+ self[:extend]
81
+ end
82
+
83
+ # Tells whether an extension definition exist for this relation.
84
+ #
85
+ # @example Is an extension defined?
86
+ # metadata.extension?
87
+ #
88
+ # @return [ true, false ] True if an extension exists, false if not.
89
+ #
90
+ # @since 2.0.0.rc.1
91
+ def extension?
92
+ !!extension
93
+ end
94
+
95
+ # Handles all the logic for figuring out what the foreign_key is for each
96
+ # relations query. The logic is as follows:
97
+ #
98
+ # 1. If the developer defined a custom key, use that.
99
+ # 2. If the relation stores a foreign key,
100
+ # use the class_name_id strategy.
101
+ # 3. If the relation does not store the key,
102
+ # use the inverse_class_name_id strategy.
103
+ #
104
+ # @example Get the foreign key.
105
+ # metadata.foreign_key
106
+ #
107
+ # @return [ String ] The foreign key for the relation.
108
+ #
109
+ # @since 2.0.0.rc.1
110
+ def foreign_key
111
+ @foreign_key ||= determine_foreign_key
112
+ end
113
+
114
+ # Returns the name of the method used to set the foreign key on a
115
+ # document.
116
+ #
117
+ # @example Get the setter for the foreign key.
118
+ # metadata.foreign_key_setter
119
+ #
120
+ # @return [ String ] The foreign_key plus =.
121
+ #
122
+ # @since 2.0.0.rc.1
123
+ def foreign_key_setter
124
+ @foreign_key_setter ||= "#{foreign_key}="
125
+ end
126
+
127
+ # Tells whether a foreign key index exists on the relation.
128
+ #
129
+ # @example Is the key indexed?
130
+ # metadata.indexed?
131
+ #
132
+ # @return [ true, false ] True if an index exists, false if not.
133
+ #
134
+ # @since 2.0.0.rc.1
135
+ def indexed?
136
+ !!self[:index]
137
+ end
138
+
139
+ # Instantiate new metadata for a relation.
140
+ #
141
+ # @example Create the new metadata.
142
+ # Metadata.new(:name => :addresses)
143
+ #
144
+ # @param [ Hash ] properties The relation options.
145
+ #
146
+ # @since 2.0.0.rc.1
147
+ def initialize(properties = {})
148
+ merge!(properties)
149
+ end
150
+
151
+ # Since a lot of the information from the metadata is inferred and not
152
+ # explicitly stored in the hash, the inspection needs to be much more
153
+ # detailed.
154
+ #
155
+ # @example Inspect the metadata.
156
+ # metadata.inspect
157
+ #
158
+ # @return [ String ] Oodles of information in a nice format.
159
+ #
160
+ # @since 2.0.0.rc.1
161
+ def inspect
162
+ "#<Mongoid::Relations::Metadata\n" <<
163
+ " class_name: #{class_name},\n" <<
164
+ " cyclic: #{cyclic || "No"},\n" <<
165
+ " dependent: #{dependent || "None"},\n" <<
166
+ " inverse_of: #{inverse_of || "N/A"},\n" <<
167
+ " inverse_setter: #{inverse_setter},\n" <<
168
+ " inverse_type: #{inverse_type || "N/A"},\n" <<
169
+ " inverse_type_setter: #{inverse_type_setter || "N/A"},\n" <<
170
+ " key: #{key},\n" <<
171
+ " macro: #{macro},\n" <<
172
+ " name: #{name},\n" <<
173
+ " polymorphic: #{polymorphic? ? "Yes" : "No"},\n" <<
174
+ " relation: #{relation},\n" <<
175
+ " setter: #{setter}>\n"
176
+ end
177
+
178
+ # Get the name of the inverse relation if it exists. If this is a
179
+ # polymorphic relation then just return the :as option that was defined.
180
+ #
181
+ # @example Get the name of the inverse.
182
+ # metadata.inverse
183
+ #
184
+ # @param [ Document ] other The document to aid in the discovery.
185
+ #
186
+ # @return [ Symbol ] The inverse name.
187
+ #
188
+ # @since 2.0.0.rc.1
189
+ def inverse(other = nil)
190
+ return self[:inverse_of] if inverse_of?
191
+ return self[:as] || lookup_inverse(other) if polymorphic?
192
+ @inverse ||= (cyclic? ? cyclic_inverse : inverse_relation)
193
+ end
194
+
195
+ # Used for relational many to many only. This determines the name of the
196
+ # foreign key field on the inverse side of the relation, since in this
197
+ # case there are keys on both sides.
198
+ #
199
+ # @example Find the inverse foreign key
200
+ # metadata.inverse_foreign_key
201
+ #
202
+ # @return [ String ] The foreign key on the inverse.
203
+ #
204
+ # @since 2.0.0.rc.1
205
+ def inverse_foreign_key
206
+ @inverse_foreign_key ||=
207
+ (inverse_class_name.underscore << relation.foreign_key_suffix)
208
+ end
209
+
210
+ # Returns the inverse class of the proxied relation.
211
+ #
212
+ # @example Get the inverse class.
213
+ # metadata.inverse_klass
214
+ #
215
+ # @return [ Class ] The class of the inverse of the relation.
216
+ #
217
+ # @since 2.0.0.rc.1
218
+ def inverse_klass
219
+ @inverse_klass ||= inverse_class_name.constantize
220
+ end
221
+
222
+ # Returns the setter for the inverse side of the relation.
223
+ #
224
+ # @example Get the inverse setter.
225
+ # metadata.inverse_setter
226
+ #
227
+ # @param [ Document ] other A document to aid in the discovery.
228
+ #
229
+ # @return [ String ] The inverse setter name.
230
+ #
231
+ # @since 2.0.0.rc.1
232
+ def inverse_setter(other = nil)
233
+ inverse(other).to_s << "="
234
+ end
235
+
236
+ # Returns the name of the field in which to store the name of the class
237
+ # for the polymorphic relation.
238
+ #
239
+ # @example Get the name of the field.
240
+ # metadata.inverse_type
241
+ #
242
+ # @return [ String ] The name of the field for storing the type.
243
+ #
244
+ # @since 2.0.0.rc.1
245
+ def inverse_type
246
+ if relation.stores_foreign_key? && polymorphic?
247
+ (polymorphic? ? name.to_s : class_name.underscore) << "_type"
248
+ else
249
+ return nil
250
+ end
251
+ end
252
+
253
+ # Gets the setter for the field that sets the type of document on a
254
+ # polymorphic relation.
255
+ #
256
+ # @example Get the inverse type setter.
257
+ # metadata.inverse_type_setter
258
+ #
259
+ # @return [ String ] The name of the setter.
260
+ #
261
+ # @since 2.0.0.rc.1
262
+ def inverse_type_setter
263
+ inverse_type ? inverse_type << "=" : nil
264
+ end
265
+
266
+ # This returns the key that is to be used to grab the attributes for the
267
+ # relation or the foreign key or id that a referenced relation will use
268
+ # to query for the object.
269
+ #
270
+ # @example Get the lookup key.
271
+ # metadata.key
272
+ #
273
+ # @return [ String ] The association name, foreign key name, or _id.
274
+ #
275
+ # @since 2.0.0.rc.1
276
+ def key
277
+ @key ||= determine_key
278
+ end
279
+
280
+ # Returns the class of the proxied relation.
281
+ #
282
+ # @example Get the class.
283
+ # metadata.klass
284
+ #
285
+ # @return [ Class ] The class of the relation.
286
+ #
287
+ # @since 2.0.0.rc.1
288
+ def klass
289
+ @klass ||= class_name.constantize
290
+ end
291
+
292
+ # Returns the macro for the relation of this metadata.
293
+ #
294
+ # @example Get the macro.
295
+ # metadata.macro
296
+ #
297
+ # @return [ Symbol ] The macro.
298
+ #
299
+ # @since 2.0.0.rc.1
300
+ def macro
301
+ relation.macro
302
+ end
303
+
304
+ # Gets a relation nested builder associated with the relation this metadata
305
+ # is for. Nested builders are used in conjunction with nested attributes.
306
+ #
307
+ # @example Get the nested builder.
308
+ # metadata.nested_builder(attributes, options)
309
+ #
310
+ # @param [ Hash ] attributes The attributes to build the relation with.
311
+ # @param [ Hash ] options Options for the nested builder.
312
+ #
313
+ # @return [ NestedBuilder ] The nested builder for the relation.
314
+ #
315
+ # @since 2.0.0.rc.1
316
+ def nested_builder(attributes, options)
317
+ relation.nested_builder(self, attributes, options)
318
+ end
319
+
320
+ # Returns true if the relation is polymorphic.
321
+ #
322
+ # @example Is the relation polymorphic?
323
+ # metadata.polymorphic?
324
+ #
325
+ # @return [ true, false ] True if the relation is polymorphic, false if not.
326
+ #
327
+ # @since 2.0.0.rc.1
328
+ def polymorphic?
329
+ @polymorphic ||= (!!self[:as] || !!self[:polymorphic])
330
+ end
331
+
332
+ # Gets the method name used to set this relation.
333
+ #
334
+ # @example Get the setter.
335
+ # metadata = Metadata.new(:name => :person)
336
+ # metadata.setter # => "person="
337
+ #
338
+ # @return [ String ] The name plus "=".
339
+ #
340
+ # @since 2.0.0.rc.1
341
+ def setter
342
+ @setter ||= "#{name.to_s}="
343
+ end
344
+
345
+ # Are we validating this relation automatically?
346
+ #
347
+ # @example Is automatic validation on?
348
+ # metadata.validate?
349
+ #
350
+ # @return [ true, false ] True unless explictly set to false.
351
+ #
352
+ # @since 2.0.0.rc.1
353
+ def validate?
354
+ self[:validate] != false
355
+ end
356
+
357
+ private
358
+
359
+ # Returns the class name for the relation.
360
+ #
361
+ # @example Get the class name.
362
+ # metadata.classify
363
+ #
364
+ # @return [ String ] If embedded_in, the camelized, else classified.
365
+ #
366
+ # @since 2.0.0.rc.1
367
+ def classify
368
+ macro == :embedded_in ? name.to_s.camelize : name.to_s.classify
369
+ end
370
+
371
+ # Get the name of the inverse relation in a cyclic relation.
372
+ #
373
+ # @example Get the cyclic inverse name.
374
+ #
375
+ # class Role
376
+ # include Mongoid::Document
377
+ # embedded_in :parent_role, :cyclic => true
378
+ # embeds_many :child_roles, :cyclic => true
379
+ # end
380
+ #
381
+ # metadata = Metadata.new(:name => :parent_role)
382
+ # metadata.cyclic_inverse # => "child_roles"
383
+ #
384
+ # @return [ String ] The cyclic inverse name.
385
+ #
386
+ # @since 2.0.0.rc.1
387
+ def cyclic_inverse
388
+ @cyclic_inverse ||= determine_cyclic_inverse
389
+ end
390
+
391
+ # Determine the cyclic inverse. Performance improvement with the
392
+ # memoization.
393
+ #
394
+ # @example Determine the inverse.
395
+ # metadata.determine_cyclic_inverse
396
+ #
397
+ # @return [ String ] The cyclic inverse name.
398
+ #
399
+ # @since 2.0.0.rc.1
400
+ def determine_cyclic_inverse
401
+ klass.relations.each_pair do |key, meta|
402
+ if key =~ /#{inverse_klass.name.underscore}/ &&
403
+ meta.relation != relation
404
+ return key.to_sym
405
+ end
406
+ end
407
+ end
408
+
409
+ # Determine the value for the relation's foreign key. Performance
410
+ # improvement.
411
+ #
412
+ # @example Determine the foreign key.
413
+ # metadata.determine_foreign_key
414
+ #
415
+ # @return [ String ] The foreign key.
416
+ #
417
+ # @since 2.0.0.rc.1
418
+ def determine_foreign_key
419
+ return self[:foreign_key] if self[:foreign_key]
420
+ suffix = relation.foreign_key_suffix
421
+ if relation.stores_foreign_key?
422
+ if relation.macro == :references_and_referenced_in_many
423
+ class_name.underscore << suffix
424
+ else
425
+ name.to_s << suffix
426
+ end
427
+ else
428
+ (polymorphic? ? self[:as].to_s : inverse_class_name.underscore) << suffix
429
+ end
430
+ end
431
+
432
+ # Determine the inverse relation. Memoizing #inverse_relation and adding
433
+ # this method dropped 5 seconds off the test suite as a performance
434
+ # improvement.
435
+ #
436
+ # @example Determine the inverse.
437
+ # metadata.determine_inverse_relation
438
+ #
439
+ # @return [ Symbol ] The name of the inverse.
440
+ #
441
+ # @since 2.0.0.rc.1
442
+ def determine_inverse_relation
443
+ klass.relations.each_pair do |key, meta|
444
+ if key == inverse_klass.name.underscore ||
445
+ meta.class_name == inverse_class_name
446
+ return key.to_sym
447
+ end
448
+ end
449
+ return nil
450
+ end
451
+
452
+ # Determine the key for the relation in the attributes.
453
+ #
454
+ # @example Get the key.
455
+ # metadata.determine_key
456
+ #
457
+ # @return [ String ] The key in the attributes.
458
+ #
459
+ # @since 2.0.0.rc.1
460
+ def determine_key
461
+ return name.to_s if relation.embedded?
462
+ relation.stores_foreign_key? ? foreign_key : "_id"
463
+ end
464
+
465
+ # Determine the name of the inverse relation.
466
+ #
467
+ # @example Get the inverse name.
468
+ # metadata.inverse_relation
469
+ #
470
+ # @return [ Symbol ] The name of the inverse relation.
471
+ #
472
+ # @since 2.0.0.rc.1
473
+ def inverse_relation
474
+ @inverse_relation ||= determine_inverse_relation
475
+ end
476
+
477
+ # Infer the name of the inverse relation from the class.
478
+ #
479
+ # @example Get the inverse name
480
+ # metadata.inverse_name
481
+ #
482
+ # @return [ String ] The inverse class name underscored.
483
+ #
484
+ # @since 2.0.0.rc.1
485
+ def inverse_name
486
+ @inverse_name ||= inverse_klass.name.underscore
487
+ end
488
+
489
+ # For polymorphic children, we need to figure out the inverse from the
490
+ # actual instance on the other side, since we cannot know the exact class
491
+ # name to infer it from at load time.
492
+ #
493
+ # @example Find the inverse.
494
+ # metadata.lookup_inverse(other)
495
+ #
496
+ # @param [ Document ] : The inverse document.
497
+ #
498
+ # @return [ String ] The inverse name.
499
+ #
500
+ # @since 2.0.0.rc.1
501
+ def lookup_inverse(other)
502
+ return nil unless other
503
+ other.to_a.first.relations.each_pair do |key, meta|
504
+ return meta.name if meta.as == name
505
+ end
506
+ end
507
+
508
+ # Handles two different cases - the first is a convenience for JSON like
509
+ # access to the hash instead of having to call []. The second is a
510
+ # delegation of the "*?" methods to has_key? as a convenience to check
511
+ # for existence of a value.
512
+ #
513
+ # @example Extras provided by this method.
514
+ # metadata.name
515
+ # metadata.name?
516
+ #
517
+ # @param [ Symbol ] name The name of the method.
518
+ # @param [ Array ] args The arguments passed to the method.
519
+ #
520
+ # @return [ Object ] Either the value or a boolen.
521
+ #
522
+ # @since 2.0.0.rc.1
523
+ def method_missing(name, *args)
524
+ method = name.to_s
525
+ method =~ /\?/ ? has_key?(method.sub('?', '').to_sym) : self[name]
526
+ end
527
+ end
528
+ end
529
+ end