mongoid 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (240) hide show
  1. data/README.rdoc +3 -1
  2. data/Rakefile +3 -2
  3. data/lib/config/locales/bg.yml +6 -0
  4. data/lib/config/locales/de.yml +6 -0
  5. data/lib/config/locales/en-GB.yml +48 -0
  6. data/lib/config/locales/en.yml +6 -3
  7. data/lib/config/locales/es.yml +6 -0
  8. data/lib/config/locales/fr.yml +6 -0
  9. data/lib/config/locales/hi.yml +39 -0
  10. data/lib/config/locales/hu.yml +13 -7
  11. data/lib/config/locales/id.yml +3 -0
  12. data/lib/config/locales/it.yml +7 -1
  13. data/lib/config/locales/ja.yml +4 -1
  14. data/lib/config/locales/kr.yml +9 -34
  15. data/lib/config/locales/nl.yml +6 -0
  16. data/lib/config/locales/pl.yml +6 -0
  17. data/lib/config/locales/pt-BR.yml +6 -0
  18. data/lib/config/locales/pt.yml +6 -0
  19. data/lib/config/locales/ro.yml +6 -0
  20. data/lib/config/locales/ru.yml +6 -0
  21. data/lib/config/locales/sv.yml +6 -0
  22. data/lib/config/locales/vi.yml +3 -0
  23. data/lib/config/locales/zh-CN.yml +6 -0
  24. data/lib/mongoid.rb +51 -45
  25. data/lib/mongoid/atomic.rb +145 -0
  26. data/lib/mongoid/atomic/modifiers.rb +109 -0
  27. data/lib/mongoid/atomic/paths.rb +3 -0
  28. data/lib/mongoid/atomic/paths/embedded.rb +43 -0
  29. data/lib/mongoid/atomic/paths/embedded/many.rb +44 -0
  30. data/lib/mongoid/atomic/paths/embedded/one.rb +43 -0
  31. data/lib/mongoid/atomic/paths/root.rb +40 -0
  32. data/lib/mongoid/attributes.rb +12 -23
  33. data/lib/mongoid/attributes/processing.rb +5 -5
  34. data/lib/mongoid/callbacks.rb +2 -0
  35. data/lib/mongoid/collection.rb +12 -59
  36. data/lib/mongoid/collections.rb +23 -20
  37. data/lib/mongoid/collections/master.rb +6 -4
  38. data/lib/mongoid/collections/operations.rb +1 -0
  39. data/lib/mongoid/collections/retry.rb +7 -0
  40. data/lib/mongoid/components.rb +2 -2
  41. data/lib/mongoid/config.rb +42 -55
  42. data/lib/mongoid/config/database.rb +6 -2
  43. data/lib/mongoid/config/replset_database.rb +7 -3
  44. data/lib/mongoid/contexts.rb +9 -3
  45. data/lib/mongoid/contexts/enumerable.rb +7 -3
  46. data/lib/mongoid/contexts/mongo.rb +139 -101
  47. data/lib/mongoid/criteria.rb +86 -69
  48. data/lib/mongoid/criterion/complex.rb +32 -5
  49. data/lib/mongoid/criterion/inclusion.rb +4 -2
  50. data/lib/mongoid/criterion/optional.rb +111 -86
  51. data/lib/mongoid/criterion/selector.rb +8 -4
  52. data/lib/mongoid/cursor.rb +27 -27
  53. data/lib/mongoid/dirty.rb +54 -214
  54. data/lib/mongoid/document.rb +37 -39
  55. data/lib/mongoid/errors/document_not_found.rb +3 -4
  56. data/lib/mongoid/errors/invalid_collection.rb +2 -3
  57. data/lib/mongoid/errors/invalid_database.rb +2 -3
  58. data/lib/mongoid/errors/invalid_field.rb +2 -3
  59. data/lib/mongoid/errors/invalid_options.rb +19 -7
  60. data/lib/mongoid/errors/invalid_type.rb +2 -3
  61. data/lib/mongoid/errors/mongoid_error.rb +5 -6
  62. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +2 -3
  63. data/lib/mongoid/errors/unsupported_version.rb +2 -3
  64. data/lib/mongoid/errors/validations.rb +2 -3
  65. data/lib/mongoid/extensions.rb +8 -62
  66. data/lib/mongoid/extensions/array/deletion.rb +29 -0
  67. data/lib/mongoid/extensions/false_class/equality.rb +14 -1
  68. data/lib/mongoid/extensions/hash/criteria_helpers.rb +21 -10
  69. data/lib/mongoid/extensions/hash/scoping.rb +14 -1
  70. data/lib/mongoid/extensions/nil/collectionization.rb +12 -1
  71. data/lib/mongoid/extensions/object/reflections.rb +33 -2
  72. data/lib/mongoid/extensions/object_id/conversions.rb +2 -36
  73. data/lib/mongoid/extensions/proc/scoping.rb +14 -1
  74. data/lib/mongoid/extensions/string/conversions.rb +4 -16
  75. data/lib/mongoid/extensions/string/inflections.rb +35 -14
  76. data/lib/mongoid/extensions/symbol/inflections.rb +38 -12
  77. data/lib/mongoid/extensions/true_class/equality.rb +14 -1
  78. data/lib/mongoid/extras.rb +11 -30
  79. data/lib/mongoid/factory.rb +1 -1
  80. data/lib/mongoid/fields.rb +121 -29
  81. data/lib/mongoid/fields/mappings.rb +36 -0
  82. data/lib/mongoid/fields/serializable.rb +131 -0
  83. data/lib/mongoid/fields/serializable/array.rb +64 -0
  84. data/lib/mongoid/fields/serializable/big_decimal.rb +42 -0
  85. data/lib/mongoid/fields/serializable/bignum.rb +10 -0
  86. data/lib/mongoid/fields/serializable/binary.rb +11 -0
  87. data/lib/mongoid/fields/serializable/boolean.rb +44 -0
  88. data/lib/mongoid/fields/serializable/date.rb +51 -0
  89. data/lib/mongoid/fields/serializable/date_time.rb +28 -0
  90. data/lib/mongoid/fields/serializable/fixnum.rb +10 -0
  91. data/lib/mongoid/fields/serializable/float.rb +33 -0
  92. data/lib/mongoid/fields/serializable/foreign_keys/array.rb +56 -0
  93. data/lib/mongoid/fields/serializable/foreign_keys/object.rb +43 -0
  94. data/lib/mongoid/fields/serializable/hash.rb +25 -0
  95. data/lib/mongoid/fields/serializable/integer.rb +33 -0
  96. data/lib/mongoid/fields/serializable/object.rb +11 -0
  97. data/lib/mongoid/fields/serializable/object_id.rb +32 -0
  98. data/lib/mongoid/fields/serializable/range.rb +42 -0
  99. data/lib/mongoid/fields/serializable/set.rb +42 -0
  100. data/lib/mongoid/fields/serializable/string.rb +28 -0
  101. data/lib/mongoid/fields/serializable/symbol.rb +28 -0
  102. data/lib/mongoid/fields/serializable/time.rb +12 -0
  103. data/lib/mongoid/fields/serializable/time_with_zone.rb +12 -0
  104. data/lib/mongoid/fields/serializable/timekeeping.rb +102 -0
  105. data/lib/mongoid/finders.rb +61 -37
  106. data/lib/mongoid/hierarchy.rb +43 -8
  107. data/lib/mongoid/identity_map.rb +106 -0
  108. data/lib/mongoid/indexes.rb +17 -1
  109. data/lib/mongoid/javascript.rb +2 -3
  110. data/lib/mongoid/keys.rb +10 -21
  111. data/lib/mongoid/logger.rb +22 -1
  112. data/lib/mongoid/matchers/all.rb +10 -0
  113. data/lib/mongoid/matchers/default.rb +1 -1
  114. data/lib/mongoid/matchers/exists.rb +10 -0
  115. data/lib/mongoid/matchers/gt.rb +10 -0
  116. data/lib/mongoid/matchers/gte.rb +10 -0
  117. data/lib/mongoid/matchers/in.rb +10 -0
  118. data/lib/mongoid/matchers/lt.rb +10 -0
  119. data/lib/mongoid/matchers/lte.rb +10 -0
  120. data/lib/mongoid/matchers/ne.rb +10 -0
  121. data/lib/mongoid/matchers/nin.rb +10 -0
  122. data/lib/mongoid/matchers/or.rb +7 -4
  123. data/lib/mongoid/matchers/size.rb +10 -0
  124. data/lib/mongoid/multi_database.rb +26 -6
  125. data/lib/mongoid/multi_parameter_attributes.rb +40 -17
  126. data/lib/mongoid/named_scope.rb +1 -2
  127. data/lib/mongoid/nested_attributes.rb +4 -1
  128. data/lib/mongoid/observer.rb +108 -5
  129. data/lib/mongoid/paranoia.rb +26 -26
  130. data/lib/mongoid/persistence.rb +15 -21
  131. data/lib/mongoid/persistence/atomic.rb +135 -0
  132. data/lib/mongoid/persistence/atomic/add_to_set.rb +11 -8
  133. data/lib/mongoid/persistence/atomic/bit.rb +37 -0
  134. data/lib/mongoid/persistence/atomic/inc.rb +9 -6
  135. data/lib/mongoid/persistence/atomic/operation.rb +48 -7
  136. data/lib/mongoid/persistence/atomic/pop.rb +34 -0
  137. data/lib/mongoid/persistence/atomic/pull.rb +34 -0
  138. data/lib/mongoid/persistence/atomic/pull_all.rb +10 -9
  139. data/lib/mongoid/persistence/atomic/push.rb +8 -5
  140. data/lib/mongoid/persistence/atomic/push_all.rb +31 -0
  141. data/lib/mongoid/persistence/atomic/rename.rb +31 -0
  142. data/lib/mongoid/persistence/atomic/set.rb +30 -0
  143. data/lib/mongoid/persistence/atomic/unset.rb +28 -0
  144. data/lib/mongoid/persistence/deletion.rb +32 -0
  145. data/lib/mongoid/persistence/insertion.rb +41 -0
  146. data/lib/mongoid/persistence/modification.rb +37 -0
  147. data/lib/mongoid/persistence/operations.rb +214 -0
  148. data/lib/mongoid/persistence/operations/embedded/insert.rb +42 -0
  149. data/lib/mongoid/persistence/operations/embedded/remove.rb +40 -0
  150. data/lib/mongoid/persistence/operations/insert.rb +34 -0
  151. data/lib/mongoid/persistence/operations/remove.rb +33 -0
  152. data/lib/mongoid/persistence/operations/update.rb +53 -0
  153. data/lib/mongoid/railtie.rb +21 -33
  154. data/lib/mongoid/railties/database.rake +12 -12
  155. data/lib/mongoid/relations.rb +9 -5
  156. data/lib/mongoid/relations/accessors.rb +15 -36
  157. data/lib/mongoid/relations/auto_save.rb +2 -2
  158. data/lib/mongoid/relations/binding.rb +28 -1
  159. data/lib/mongoid/relations/bindings/embedded/in.rb +17 -30
  160. data/lib/mongoid/relations/bindings/embedded/many.rb +16 -21
  161. data/lib/mongoid/relations/bindings/embedded/one.rb +11 -16
  162. data/lib/mongoid/relations/bindings/referenced/in.rb +31 -32
  163. data/lib/mongoid/relations/bindings/referenced/many.rb +19 -61
  164. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +15 -63
  165. data/lib/mongoid/relations/bindings/referenced/one.rb +18 -26
  166. data/lib/mongoid/relations/builder.rb +4 -2
  167. data/lib/mongoid/relations/builders.rb +21 -2
  168. data/lib/mongoid/relations/builders/embedded/in.rb +5 -1
  169. data/lib/mongoid/relations/builders/embedded/many.rb +12 -4
  170. data/lib/mongoid/relations/builders/embedded/one.rb +5 -1
  171. data/lib/mongoid/relations/builders/nested_attributes/many.rb +2 -2
  172. data/lib/mongoid/relations/builders/nested_attributes/one.rb +1 -1
  173. data/lib/mongoid/relations/builders/referenced/in.rb +2 -5
  174. data/lib/mongoid/relations/builders/referenced/many.rb +2 -3
  175. data/lib/mongoid/relations/builders/referenced/many_to_many.rb +14 -5
  176. data/lib/mongoid/relations/builders/referenced/one.rb +2 -3
  177. data/lib/mongoid/relations/embedded/atomic.rb +2 -2
  178. data/lib/mongoid/relations/embedded/in.rb +72 -41
  179. data/lib/mongoid/relations/embedded/many.rb +116 -120
  180. data/lib/mongoid/relations/embedded/one.rb +59 -41
  181. data/lib/mongoid/relations/embedded/sort.rb +31 -0
  182. data/lib/mongoid/relations/macros.rb +28 -24
  183. data/lib/mongoid/relations/many.rb +10 -103
  184. data/lib/mongoid/relations/metadata.rb +335 -38
  185. data/lib/mongoid/relations/one.rb +7 -32
  186. data/lib/mongoid/relations/options.rb +47 -0
  187. data/lib/mongoid/relations/proxy.rb +29 -28
  188. data/lib/mongoid/relations/referenced/batch.rb +2 -3
  189. data/lib/mongoid/relations/referenced/in.rb +66 -53
  190. data/lib/mongoid/relations/referenced/many.rb +216 -143
  191. data/lib/mongoid/relations/referenced/many_to_many.rb +132 -163
  192. data/lib/mongoid/relations/referenced/one.rb +76 -58
  193. data/lib/mongoid/relations/synchronization.rb +113 -0
  194. data/lib/mongoid/relations/targets.rb +2 -0
  195. data/lib/mongoid/relations/targets/enumerable.rb +329 -0
  196. data/lib/mongoid/safety.rb +24 -156
  197. data/lib/mongoid/serialization.rb +21 -0
  198. data/lib/mongoid/state.rb +34 -0
  199. data/lib/mongoid/threaded.rb +175 -0
  200. data/lib/mongoid/timestamps/updated.rb +1 -1
  201. data/lib/mongoid/validations.rb +3 -7
  202. data/lib/mongoid/version.rb +1 -1
  203. data/lib/mongoid/versioning.rb +61 -7
  204. data/lib/rack/mongoid.rb +2 -0
  205. data/lib/rack/mongoid/middleware/identity_map.rb +38 -0
  206. data/lib/rails/generators/mongoid/model/model_generator.rb +1 -1
  207. data/lib/rails/generators/mongoid/model/templates/{model.rb → model.rb.tt} +0 -0
  208. data/lib/rails/generators/mongoid/observer/observer_generator.rb +1 -1
  209. data/lib/rails/generators/mongoid/observer/templates/{observer.rb → observer.rb.tt} +0 -0
  210. data/lib/rails/mongoid.rb +17 -17
  211. metadata +136 -102
  212. data/lib/mongoid/atomicity.rb +0 -111
  213. data/lib/mongoid/collections/cyclic_iterator.rb +0 -34
  214. data/lib/mongoid/collections/slaves.rb +0 -61
  215. data/lib/mongoid/extensions/array/conversions.rb +0 -23
  216. data/lib/mongoid/extensions/array/parentization.rb +0 -13
  217. data/lib/mongoid/extensions/big_decimal/conversions.rb +0 -19
  218. data/lib/mongoid/extensions/binary/conversions.rb +0 -17
  219. data/lib/mongoid/extensions/boolean/conversions.rb +0 -27
  220. data/lib/mongoid/extensions/date/conversions.rb +0 -25
  221. data/lib/mongoid/extensions/datetime/conversions.rb +0 -12
  222. data/lib/mongoid/extensions/float/conversions.rb +0 -20
  223. data/lib/mongoid/extensions/hash/conversions.rb +0 -19
  224. data/lib/mongoid/extensions/integer/conversions.rb +0 -20
  225. data/lib/mongoid/extensions/object/conversions.rb +0 -25
  226. data/lib/mongoid/extensions/range/conversions.rb +0 -25
  227. data/lib/mongoid/extensions/set/conversions.rb +0 -20
  228. data/lib/mongoid/extensions/symbol/conversions.rb +0 -21
  229. data/lib/mongoid/extensions/time_conversions.rb +0 -38
  230. data/lib/mongoid/field.rb +0 -162
  231. data/lib/mongoid/paths.rb +0 -61
  232. data/lib/mongoid/persistence/command.rb +0 -71
  233. data/lib/mongoid/persistence/insert.rb +0 -53
  234. data/lib/mongoid/persistence/insert_embedded.rb +0 -43
  235. data/lib/mongoid/persistence/remove.rb +0 -44
  236. data/lib/mongoid/persistence/remove_all.rb +0 -40
  237. data/lib/mongoid/persistence/remove_embedded.rb +0 -48
  238. data/lib/mongoid/persistence/update.rb +0 -77
  239. data/lib/mongoid/safe.rb +0 -23
  240. data/lib/mongoid/validations/referenced.rb +0 -58
@@ -6,41 +6,16 @@ module Mongoid # :nodoc:
6
6
  # behaviour or those proxies.
7
7
  class One < Proxy
8
8
 
9
- # Will load the target into an array if the target had not already been
10
- # loaded.
9
+ # Get all the documents in the relation that are loaded into memory.
11
10
  #
12
- # @example Load the relation into memory.
13
- # relation.load!
11
+ # @example Get the in memory documents.
12
+ # relation.in_memory
14
13
  #
15
- # @return [ One ] The relation.
14
+ # @return [ Array<Document> ] The documents in memory.
16
15
  #
17
- # @since 2.0.0.rc.5
18
- def load!(options = {})
19
- tap do |relation|
20
- unless relation.loaded?
21
- relation.bind(options)
22
- relation.loaded = true
23
- end
24
- end
25
- end
26
-
27
- # Substitutes the supplied target documents for the existing document
28
- # in the relation.
29
- #
30
- # @example Substitute the new document.
31
- # person.name.substitute(new_name)
32
- #
33
- # @param [ Document ] other A document to replace the target.
34
- #
35
- # @return [ Document, nil ] The relation or nil.
36
- #
37
- # @since 2.0.0.rc.1
38
- def substitute(new_target, options = {})
39
- old_target = target
40
- tap do |relation|
41
- relation.target = new_target
42
- new_target ? bind(options) : (unbind(old_target, options) and return nil)
43
- end
16
+ # @since 2.1.0
17
+ def in_memory
18
+ [ target ]
44
19
  end
45
20
  end
46
21
  end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ module Mongoid # :nodoc:
3
+ module Relations #:nodoc:
4
+
5
+ # This module contains the validating logic for options passed to relation
6
+ # macros.
7
+ module Options
8
+ extend self
9
+
10
+ # These options are available to all relations.
11
+ COMMON = [
12
+ :class_name,
13
+ :extend,
14
+ :inverse_class_name,
15
+ :inverse_of,
16
+ :name,
17
+ :relation,
18
+ :validate
19
+ ]
20
+
21
+ # Determine if the provided options are valid for the relation.
22
+ #
23
+ # @example Check the options.
24
+ # Options.validate!(:name => :comments)
25
+ #
26
+ # @param [ Hash ] options The options to check.
27
+ #
28
+ # @raise [ ArgumentError ] If the options are invalid.
29
+ #
30
+ # @return [ true, false ] If the options are valid.
31
+ #
32
+ # @since 2.1.0
33
+ def validate!(options)
34
+ valid_options = options[:relation].valid_options.concat(COMMON)
35
+ options.keys.each do |key|
36
+ if !valid_options.include?(key)
37
+ raise Errors::InvalidOptions.new(
38
+ options[:name],
39
+ key,
40
+ valid_options
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -17,6 +17,8 @@ module Mongoid # :nodoc:
17
17
  # Backwards compatibility with Mongoid beta releases.
18
18
  delegate :klass, :to => :metadata
19
19
 
20
+ delegate :bind_one, :unbind_one, :to => :binding
21
+
20
22
  # Convenience for setting the target and the metadata properties since
21
23
  # all proxies will need to do this.
22
24
  #
@@ -28,14 +30,38 @@ module Mongoid # :nodoc:
28
30
  # @param [ Metadata ] metadata The relation's metadata.
29
31
  #
30
32
  # @since 2.0.0.rc.1
31
- def init(base, target, metadata, &block)
33
+ def init(base, target, metadata)
32
34
  @base, @target, @metadata = base, target, metadata
33
- block.call if block
34
- extend Module.new(&metadata.extension) if metadata.extension?
35
+ yield(self) if block_given?
36
+ extend metadata.extension if metadata.extension?
35
37
  end
36
38
 
37
39
  protected
38
40
 
41
+ # Is the current thread in binding mode?
42
+ #
43
+ # @example Is the current thread in binding mode?
44
+ # proxy.binding?
45
+ #
46
+ # @return [ true, false ] If the thread is binding.
47
+ #
48
+ # @since 2.1.0
49
+ def binding?
50
+ Threaded.binding?
51
+ end
52
+
53
+ # Is the current thread in building mode?
54
+ #
55
+ # @example Is the current thread in building mode?
56
+ # proxy.building?
57
+ #
58
+ # @return [ true, false ] If the thread is building.
59
+ #
60
+ # @since 2.1.0
61
+ def building?
62
+ Threaded.building?
63
+ end
64
+
39
65
  # Get the collection from the root of the hierarchy.
40
66
  #
41
67
  # @example Get the collection.
@@ -65,31 +91,6 @@ module Mongoid # :nodoc:
65
91
  type ? type.new : metadata.klass.new
66
92
  end
67
93
 
68
- # Determines if the target been loaded into memory or not.
69
- #
70
- # @example Is the proxy loaded?
71
- # proxy.loaded?
72
- #
73
- # @return [ true, false ] True if loaded, false if not.
74
- #
75
- # @since 2.0.0.rc.1
76
- def loaded?
77
- !!@loaded
78
- end
79
-
80
- # Takes the supplied documents and sets the metadata on them. Used when
81
- # creating new documents and adding them to the relation.
82
- #
83
- # @example Set the metadata.
84
- # proxy.characterize(addresses)
85
- #
86
- # @param [ Array<Document> ] documents The documents to set metadata on.
87
- #
88
- # @since 2.0.0.rc.4
89
- def characterize(documents)
90
- documents.each { |doc| characterize_one(doc) }
91
- end
92
-
93
94
  # Takes the supplied document and sets the metadata on it.
94
95
  #
95
96
  # @example Set the metadata.
@@ -36,12 +36,11 @@ module Mongoid #:nodoc:
36
36
  #
37
37
  # @since 2.0.2, batch-relational-insert
38
38
  def batched(&block)
39
- inserter = Thread.current[:mongoid_batch_insert] ||= Insert.new
39
+ inserter = Threaded.insert ||= Insert.new
40
40
  count_executions(&block)
41
41
  ensure
42
42
  if @executions.zero?
43
- Thread.current[:mongoid_batch_insert] = nil
44
- raise_mixed if embedded?
43
+ Threaded.insert = nil
45
44
  inserter.execute(collection)
46
45
  end
47
46
  end
@@ -9,27 +9,6 @@ module Mongoid # :nodoc:
9
9
  # collection.
10
10
  class In < Relations::One
11
11
 
12
- # Binds the base object to the inverse of the relation. This is so we
13
- # are referenced to the actual objects themselves and dont hit the
14
- # database twice when setting the relations up.
15
- #
16
- # This is called after first creating the relation, or if a new object
17
- # is set on the relation.
18
- #
19
- # @example Bind the relation.
20
- # game.person.bind
21
- #
22
- # @param [ Hash ] options The options to bind with.
23
- #
24
- # @option options [ true, false ] :binding Are we in build mode?
25
- # @option options [ true, false ] :continue Continue binding the
26
- # inverse?
27
- #
28
- # @since 2.0.0.rc.1
29
- def bind(options = {})
30
- binding.bind(options)
31
- end
32
-
33
12
  # Instantiate a new referenced_in relation.
34
13
  #
35
14
  # @example Create the new relation.
@@ -42,6 +21,7 @@ module Mongoid # :nodoc:
42
21
  def initialize(base, target, metadata)
43
22
  init(base, target, metadata) do
44
23
  characterize_one(target)
24
+ bind_one
45
25
  end
46
26
  end
47
27
 
@@ -57,37 +37,16 @@ module Mongoid # :nodoc:
57
37
  # @return [ In, nil ] The relation or nil.
58
38
  #
59
39
  # @since 2.0.0.rc.1
60
- def substitute(new_target, options = {})
61
- old_target = target
62
- tap do |relation|
63
- relation.target = new_target
64
- if new_target
65
- bind(options)
66
- else
67
- unbind(old_target, options)
68
- nil
69
- end
40
+ def substitute(replacement)
41
+ tap do |proxy|
42
+ proxy.unbind_one
43
+ proxy.target.delete if persistable?
44
+ return nil unless replacement
45
+ proxy.target = replacement
46
+ proxy.bind_one
70
47
  end
71
48
  end
72
49
 
73
- # Unbinds the base object to the inverse of the relation. This occurs
74
- # when setting a side of the relation to nil.
75
- #
76
- # @example Unbind the relation.
77
- # game.person.unbind
78
- #
79
- # @param [ Document, Array<Document> ] old_target The previous target.
80
- # @param [ Hash ] options The options to bind with.
81
- #
82
- # @option options [ true, false ] :binding Are we in build mode?
83
- # @option options [ true, false ] :continue Continue binding the
84
- # inverse?
85
- #
86
- # @since 2.0.0.rc.1
87
- def unbind(old_target, options = {})
88
- binding(old_target).unbind(options)
89
- end
90
-
91
50
  private
92
51
 
93
52
  # Instantiate the binding associated with this relation.
@@ -100,8 +59,20 @@ module Mongoid # :nodoc:
100
59
  # @return [ Binding ] The binding object.
101
60
  #
102
61
  # @since 2.0.0.rc.1
103
- def binding(new_target = nil)
104
- Bindings::Referenced::In.new(base, new_target || target, metadata)
62
+ def binding
63
+ Bindings::Referenced::In.new(base, target, metadata)
64
+ end
65
+
66
+ # Are we able to persist this relation?
67
+ #
68
+ # @example Can we persist the relation?
69
+ # relation.persistable?
70
+ #
71
+ # @return [ true, false ] If the relation is persistable.
72
+ #
73
+ # @since 2.1.0
74
+ def persistable?
75
+ target.persisted? && !binding? && !building?
105
76
  end
106
77
 
107
78
  class << self
@@ -119,8 +90,24 @@ module Mongoid # :nodoc:
119
90
  # @return [ Builder ] A new builder object.
120
91
  #
121
92
  # @since 2.0.0.rc.1
122
- def builder(meta, object)
123
- Builders::Referenced::In.new(meta, object)
93
+ def builder(meta, object, loading = false)
94
+ Builders::Referenced::In.new(meta, object, loading)
95
+ end
96
+
97
+ # Get the standard criteria used for querying this relation.
98
+ #
99
+ # @example Get the criteria.
100
+ # Proxy.criteria(meta, id, Model)
101
+ #
102
+ # @param [ Metadata ] metadata The metadata.
103
+ # @param [ Object ] object The value of the foreign key.
104
+ # @param [ Class ] type The optional type.
105
+ #
106
+ # @return [ Criteria ] The criteria.
107
+ #
108
+ # @since 2.1.0
109
+ def criteria(metadata, object, type = nil)
110
+ type.where(:_id => object)
124
111
  end
125
112
 
126
113
  # Returns true if the relation is an embedded one. In this case
@@ -197,6 +184,20 @@ module Mongoid # :nodoc:
197
184
  Builders::NestedAttributes::One.new(metadata, attributes, options)
198
185
  end
199
186
 
187
+ # Get the path calculator for the supplied document.
188
+ #
189
+ # @example Get the path calculator.
190
+ # Proxy.path(document)
191
+ #
192
+ # @param [ Document ] document The document to calculate on.
193
+ #
194
+ # @return [ Root ] The root atomic path calculator.
195
+ #
196
+ # @since 2.1.0
197
+ def path(document)
198
+ Mongoid::Atomic::Paths::Root.new(document)
199
+ end
200
+
200
201
  # Tells the caller if this relation is one that stores the foreign
201
202
  # key on its own objects.
202
203
  #
@@ -209,6 +210,18 @@ module Mongoid # :nodoc:
209
210
  def stores_foreign_key?
210
211
  true
211
212
  end
213
+
214
+ # Get the valid options allowed with this relation.
215
+ #
216
+ # @example Get the valid options.
217
+ # Relation.valid_options
218
+ #
219
+ # @return [ Array<Symbol> ] The valid options.
220
+ #
221
+ # @since 2.1.0
222
+ def valid_options
223
+ [ :autosave, :foreign_key, :index, :polymorphic ]
224
+ end
212
225
  end
213
226
  end
214
227
  end
@@ -8,6 +8,9 @@ module Mongoid #:nodoc:
8
8
  class Many < Relations::Many
9
9
  include Batch
10
10
 
11
+ delegate :count, :to => :criteria
12
+ delegate :first, :in_memory, :last, :reset, :uniq, :to => :target
13
+
11
14
  # Appends a document or array of documents to the relation. Will set
12
15
  # the parent and update the index in the process.
13
16
  #
@@ -21,43 +24,41 @@ module Mongoid #:nodoc:
21
24
  # person.posts.concat([ post_one, post_two ])
22
25
  #
23
26
  # @param [ Document, Array<Document> ] *args Any number of documents.
27
+ #
28
+ # @return [ Array<Document> ] The loaded docs.
29
+ #
30
+ # @since 2.0.0.beta.1
24
31
  def <<(*args)
25
- options = default_options(args)
26
32
  batched do
27
33
  args.flatten.each do |doc|
28
- return doc unless doc
29
- append(doc, options)
30
- doc.save if base.persisted? && !options[:binding]
34
+ next unless doc
35
+ append(doc)
36
+ doc.save if persistable?
31
37
  end
32
38
  end
33
39
  end
34
40
  alias :concat :<<
35
41
  alias :push :<<
36
42
 
37
- # Binds the base object to the inverse of the relation. This is so we
38
- # are referenced to the actual objects themselves and dont hit the
39
- # database twice when setting the relations up.
40
- #
41
- # This is called after first creating the relation, or if a new object
42
- # is set on the relation.
43
+ # Build a new document from the attributes and append it to this
44
+ # relation without saving.
43
45
  #
44
- # @example Bind the relation.
45
- # person.posts.bind
46
+ # @example Build a new document on the relation.
47
+ # person.posts.build(:title => "A new post")
46
48
  #
47
- # @param [ Hash ] options The options to bind with.
49
+ # @param [ Hash ] attributes The attributes of the new document.
50
+ # @param [ Class ] type The optional subclass to build.
48
51
  #
49
- # @option options [ true, false ] :binding Are we in build mode?
50
- # @option options [ true, false ] :continue Continue binding the
51
- # inverse?
52
+ # @return [ Document ] The new document.
52
53
  #
53
- # @since 2.0.0.rc.1
54
- def bind(options = {})
55
- binding.bind(options)
56
- target.map(&:save) if base.persisted? && !options[:binding]
54
+ # @since 2.0.0.beta.1
55
+ def build(attributes = {}, type = nil)
56
+ Factory.build(type || klass, attributes).tap do |doc|
57
+ append(doc)
58
+ yield(doc) if block_given?
59
+ end
57
60
  end
58
-
59
- alias :concat :<<
60
- alias :push :<<
61
+ alias :new :build
61
62
 
62
63
  # Clear the relation. Will delete the documents from the db if they are
63
64
  # already persisted.
@@ -66,24 +67,14 @@ module Mongoid #:nodoc:
66
67
  # person.posts.clear
67
68
  #
68
69
  # @return [ Many ] The relation emptied.
69
- def clear(options = {})
70
- load! and tap do |relation|
71
- relation.unbind(default_options(options))
72
- target.clear
73
- end
74
- end
75
-
76
- # Returns a count of the number of documents in the association that have
77
- # actually been persisted to the database.
78
- #
79
- # Use #size if you want the total number of documents in memory.
80
- #
81
- # @example Get the count of persisted documents.
82
- # person.posts.count
83
70
  #
84
- # @return [ Integer ] The total number of persisted documents.
85
- def count
86
- criteria.count
71
+ # @since 2.0.0.beta.1
72
+ def clear
73
+ criteria.delete_all
74
+ target.clear do |doc|
75
+ unbind_one(doc)
76
+ doc.destroyed = true
77
+ end
87
78
  end
88
79
 
89
80
  # Creates a new document on the references many relation. This will
@@ -96,6 +87,8 @@ module Mongoid #:nodoc:
96
87
  # @param [ Class ] type The optional type of document to create.
97
88
  #
98
89
  # @return [ Document ] The newly created document.
90
+ #
91
+ # @since 2.0.0.beta.1
99
92
  def create(attributes = nil, type = nil, &block)
100
93
  build(attributes, type, &block).tap do |doc|
101
94
  base.persisted? ? doc.save : raise_unsaved(doc)
@@ -115,12 +108,35 @@ module Mongoid #:nodoc:
115
108
  # @raise [ Errors::Validations ] If validation failed.
116
109
  #
117
110
  # @return [ Document ] The newly created document.
111
+ #
112
+ # @since 2.0.0.beta.1
118
113
  def create!(attributes = nil, type = nil, &block)
119
114
  build(attributes, type, &block).tap do |doc|
120
115
  base.persisted? ? doc.save! : raise_unsaved(doc)
121
116
  end
122
117
  end
123
118
 
119
+ # Delete the document from the relation. This will set the foreign key
120
+ # on the document to nil. If the dependent options on the relation are
121
+ # :delete or :destroy the appropriate removal will occur.
122
+ #
123
+ # @example Delete the document.
124
+ # person.posts.delete(post)
125
+ #
126
+ # @param [ Document ] document The document to remove.
127
+ #
128
+ # @return [ Document ] The matching document.
129
+ #
130
+ # @since 2.1.0
131
+ def delete(document)
132
+ target.delete(document) do |doc|
133
+ if doc
134
+ unbind_one(doc)
135
+ cascade!(doc)
136
+ end
137
+ end
138
+ end
139
+
124
140
  # Deletes all related documents from the database given the supplied
125
141
  # conditions.
126
142
  #
@@ -133,13 +149,10 @@ module Mongoid #:nodoc:
133
149
  # @param [ Hash ] conditions Optional conditions to delete with.
134
150
  #
135
151
  # @return [ Integer ] The number of documents deleted.
152
+ #
153
+ # @since 2.0.0.beta.1
136
154
  def delete_all(conditions = nil)
137
- raise_mixed if klass.embedded?
138
- selector = (conditions || {})[:conditions] || {}
139
- target.delete_if { |doc| doc.matches?(selector) }
140
- metadata.klass.delete_all(
141
- :conditions => criteria.selector.merge(selector)
142
- )
155
+ remove_all(conditions, :delete_all)
143
156
  end
144
157
 
145
158
  # Destroys all related documents from the database given the supplied
@@ -154,13 +167,27 @@ module Mongoid #:nodoc:
154
167
  # @param [ Hash ] conditions Optional conditions to destroy with.
155
168
  #
156
169
  # @return [ Integer ] The number of documents destroyd.
170
+ #
171
+ # @since 2.0.0.beta.1
157
172
  def destroy_all(conditions = nil)
158
- raise_mixed if klass.embedded?
159
- selector = (conditions || {})[:conditions] || {}
160
- target.delete_if { |doc| doc.matches?(selector) }
161
- metadata.klass.destroy_all(
162
- :conditions => criteria.selector.merge(selector)
163
- )
173
+ remove_all(conditions, :destroy_all)
174
+ end
175
+
176
+ # Iterate over each document in the relation and yield to the provided
177
+ # block.
178
+ #
179
+ # @note This will load the entire relation into memory.
180
+ #
181
+ # @example Iterate over the documents.
182
+ # person.posts.each do |post|
183
+ # post.save
184
+ # end
185
+ #
186
+ # @return [ Array<Document> ] The loaded docs.
187
+ #
188
+ # @since 2.1.0
189
+ def each
190
+ target.each { |doc| yield(doc) if block_given? }
164
191
  end
165
192
 
166
193
  # Find the matchind document on the association, either based on id or
@@ -186,6 +213,8 @@ module Mongoid #:nodoc:
186
213
  # @param [ Hash ] options The options to search with.
187
214
  #
188
215
  # @return [ Document, Criteria ] The matching document(s).
216
+ #
217
+ # @since 2.0.0.beta.1
189
218
  def find(*args)
190
219
  criteria.find(*args)
191
220
  end
@@ -199,26 +228,17 @@ module Mongoid #:nodoc:
199
228
  # @param [ Document ] base The document this relation hangs off of.
200
229
  # @param [ Array<Document> ] target The target of the relation.
201
230
  # @param [ Metadata ] metadata The relation's metadata.
202
- def initialize(base, target, metadata)
203
- init(base, target, metadata)
204
- end
205
-
206
- # Will load the target into an array if the target had not already been
207
- # loaded.
208
- #
209
- # @example Load the relation into memory.
210
- # relation.load!
211
231
  #
212
- # @return [ Many ] The relation.
213
- #
214
- # @since 2.0.0.rc.5
215
- def load!(options = {})
216
- raise_mixed if klass.embedded?
217
- tap do |relation|
218
- unless relation.loaded?
219
- relation.target = target.entries
220
- relation.bind(options)
221
- relation.loaded = true
232
+ # @since 2.0.0.beta.1
233
+ def initialize(base, target, metadata)
234
+ init(base, Targets::Enumerable.new(target), metadata) do |proxy|
235
+ raise_mixed if klass.embedded?
236
+ batched do
237
+ proxy.in_memory do |doc|
238
+ characterize_one(doc)
239
+ bind_one(doc)
240
+ doc.save if persistable?
241
+ end
222
242
  end
223
243
  end
224
244
  end
@@ -232,7 +252,10 @@ module Mongoid #:nodoc:
232
252
  #
233
253
  # @since 2.0.0.rc.1
234
254
  def nullify
235
- clear(:binding => true, :continue => true, :nullify => true)
255
+ criteria.update(metadata.foreign_key => nil)
256
+ target.clear do |doc|
257
+ unbind_one(doc)
258
+ end
236
259
  end
237
260
  alias :nullify_all :nullify
238
261
 
@@ -241,44 +264,18 @@ module Mongoid #:nodoc:
241
264
  # deletion.
242
265
  #
243
266
  # @example Replace the relation.
244
- # person.posts.substitute(new_name)
267
+ # person.posts.substitute([ new_post ])
245
268
  #
246
- # @param [ Array<Document> ] target The replacement target.
247
- # @param [ Hash ] options The options to bind with.
248
- #
249
- # @option options [ true, false ] :binding Are we in build mode?
250
- # @option options [ true, false ] :continue Continue binding the
251
- # inverse?
269
+ # @param [ Array<Document> ] replacement The replacement target.
252
270
  #
253
271
  # @return [ Many ] The relation.
254
272
  #
255
273
  # @since 2.0.0.rc.1
256
- def substitute(target, options = {})
257
- tap { target ? (@target = target.to_a; bind(options)) : (@target = unbind(options)) }
258
- end
259
-
260
- # Unbinds the base object to the inverse of the relation. This occurs
261
- # when setting a side of the relation to nil.
262
- #
263
- # Will delete the object if necessary.
264
- #
265
- # @example Unbind the target.
266
- # person.posts.unbind
267
- #
268
- # @param [ Hash ] options The options to bind with.
269
- #
270
- # @option options [ true, false ] :binding Are we in build mode?
271
- # @option options [ true, false ] :continue Continue binding the
272
- # inverse?
273
- #
274
- # @since 2.0.0.rc.1
275
- def unbind(options = {})
276
- binding.unbind(options)
277
- unless base.new_record?
278
- target.each(&:delete) unless options[:binding]
279
- target.each(&:save) if options[:nullify]
274
+ def substitute(replacement)
275
+ tap do |proxy|
276
+ proxy.clear
277
+ proxy.push(replacement) if replacement
280
278
  end
281
- []
282
279
  end
283
280
 
284
281
  private
@@ -292,11 +289,10 @@ module Mongoid #:nodoc:
292
289
  # @param [ Document ] document The document to append to the target.
293
290
  #
294
291
  # @since 2.0.0.rc.1
295
- def append(document, options = {})
296
- init_target if !initialized? && !loaded?
292
+ def append(document)
297
293
  target.push(document)
298
294
  characterize_one(document)
299
- binding.bind_one(document, options)
295
+ bind_one(document)
300
296
  end
301
297
 
302
298
  # Instantiate the binding associated with this relation.
@@ -309,8 +305,8 @@ module Mongoid #:nodoc:
309
305
  # @return [ Binding ] The binding.
310
306
  #
311
307
  # @since 2.0.0.rc.1
312
- def binding(new_target = nil)
313
- Bindings::Referenced::Many.new(base, new_target || target, metadata)
308
+ def binding
309
+ Bindings::Referenced::Many.new(base, target, metadata)
314
310
  end
315
311
 
316
312
  # Get the collection of the relation in question.
@@ -320,9 +316,9 @@ module Mongoid #:nodoc:
320
316
  #
321
317
  # @return [ Collection ] The collection of the relation.
322
318
  #
323
- # @since 2.0.2, batch-relational-insert
319
+ # @since 2.0.2
324
320
  def collection
325
- metadata.klass.collection
321
+ klass.collection
326
322
  end
327
323
 
328
324
  # Get the value for the foreign key in convertable or unconvertable
@@ -341,7 +337,7 @@ module Mongoid #:nodoc:
341
337
  if inverse.using_object_ids? || base.id.is_a?(BSON::ObjectId)
342
338
  base.id
343
339
  else
344
- Mongoid::Criterion::Unconvertable.new(base.id)
340
+ Criterion::Unconvertable.new(base.id)
345
341
  end
346
342
  end
347
343
 
@@ -352,38 +348,31 @@ module Mongoid #:nodoc:
352
348
  # relation.criteria
353
349
  #
354
350
  # @return [ Criteria ] A new criteria.
355
- def criteria
356
- raise_mixed if klass.embedded?
357
- metadata.klass.where(metadata.foreign_key => convertable)
358
- end
359
-
360
- # Tells if the target array been initialized.
361
351
  #
362
- # @example Is the target initialized?
363
- # relation.initialized?
364
- #
365
- # @return [ true, false ] If the target is an array.
366
- #
367
- # @since 2.0.0
368
- def initialized?
369
- !!@initialized
352
+ # @since 2.0.0.beta.1
353
+ def criteria
354
+ Many.criteria(metadata, convertable)
370
355
  end
371
356
 
372
- # Initializes the target of the proxy as an empty array instead of
373
- # hitting the database.
357
+ # Perform the necessary cascade operations for documents that just got
358
+ # deleted or nullified.
374
359
  #
375
- # @example Initialize the target.
376
- # relation.init_target
360
+ # @example Cascade the change.
361
+ # relation.cascade!(document)
377
362
  #
378
- # @raise [ Errors::MixedRelations ] If the class is embedded.
363
+ # @param [ Document ] document The document to cascade on.
379
364
  #
380
- # @return [ true ] Always true.
365
+ # @return [ true, false ] If the metadata is destructive.
381
366
  #
382
- # @since 2.0.0
383
- def init_target
384
- raise_mixed if klass.embedded?
385
- @target = []
386
- @initialized = true
367
+ # @since 2.1.0
368
+ def cascade!(document)
369
+ if persistable?
370
+ if metadata.destructive?
371
+ document.send(metadata.dependent)
372
+ else
373
+ document.save
374
+ end
375
+ end
387
376
  end
388
377
 
389
378
  # If the target array does not respond to the supplied method then try to
@@ -396,11 +385,53 @@ module Mongoid #:nodoc:
396
385
  # @param [ Proc ] block Optional block to pass.
397
386
  #
398
387
  # @return [ Criteria, Object ] A Criteria or return value from the target.
388
+ #
389
+ # @since 2.0.0.beta.1
399
390
  def method_missing(name, *args, &block)
400
- load!(:binding => true) and return super if [].respond_to?(name)
401
- klass = metadata.klass
402
- klass.send(:with_scope, criteria) do
403
- criteria.send(name, *args, &block)
391
+ if target.respond_to?(name)
392
+ target.send(name, *args, &block)
393
+ else
394
+ klass.send(:with_scope, criteria) do
395
+ criteria.send(name, *args, &block)
396
+ end
397
+ end
398
+ end
399
+
400
+ # Are we able to persist this relation?
401
+ #
402
+ # @example Can we persist the relation?
403
+ # relation.persistable?
404
+ #
405
+ # @return [ true, false ] If the relation is persistable.
406
+ #
407
+ # @since 2.1.0
408
+ def persistable?
409
+ base.persisted? && !binding?
410
+ end
411
+
412
+ # Deletes all related documents from the database given the supplied
413
+ # conditions.
414
+ #
415
+ # @example Delete all documents in the relation.
416
+ # person.posts.delete_all
417
+ #
418
+ # @example Conditonally delete all documents in the relation.
419
+ # person.posts.delete_all(:conditions => { :title => "Testing" })
420
+ #
421
+ # @param [ Hash ] conditions Optional conditions to delete with.
422
+ # @param [ Symbol ] The deletion method to call.
423
+ #
424
+ # @return [ Integer ] The number of documents deleted.
425
+ #
426
+ # @since 2.1.0
427
+ def remove_all(conditions = nil, method = :delete_all)
428
+ selector = (conditions || {})[:conditions] || {}
429
+ klass.send(method, :conditions => selector.merge!(criteria.selector)).tap do
430
+ target.delete_if do |doc|
431
+ if doc.matches?(selector)
432
+ unbind_one(doc) and true
433
+ end
434
+ end
404
435
  end
405
436
  end
406
437
 
@@ -419,8 +450,24 @@ module Mongoid #:nodoc:
419
450
  # @return [ Builder ] A new builder object.
420
451
  #
421
452
  # @since 2.0.0.rc.1
422
- def builder(meta, object)
423
- Builders::Referenced::Many.new(meta, object || [])
453
+ def builder(meta, object, loading = false)
454
+ Builders::Referenced::Many.new(meta, object || [], loading)
455
+ end
456
+
457
+ # Get the standard criteria used for querying this relation.
458
+ #
459
+ # @example Get the criteria.
460
+ # Proxy.criteria(meta, id, Model)
461
+ #
462
+ # @param [ Metadata ] metadata The metadata.
463
+ # @param [ Object ] object The value of the foreign key.
464
+ # @param [ Class ] type The optional type.
465
+ #
466
+ # @return [ Criteria ] The criteria.
467
+ #
468
+ # @since 2.1.0
469
+ def criteria(metadata, object, type = nil)
470
+ metadata.klass.where(metadata.foreign_key => object)
424
471
  end
425
472
 
426
473
  # Returns true if the relation is an embedded one. In this case
@@ -497,6 +544,20 @@ module Mongoid #:nodoc:
497
544
  Builders::NestedAttributes::Many.new(metadata, attributes, options)
498
545
  end
499
546
 
547
+ # Get the path calculator for the supplied document.
548
+ #
549
+ # @example Get the path calculator.
550
+ # Proxy.path(document)
551
+ #
552
+ # @param [ Document ] document The document to calculate on.
553
+ #
554
+ # @return [ Root ] The root atomic path calculator.
555
+ #
556
+ # @since 2.1.0
557
+ def path(document)
558
+ Mongoid::Atomic::Paths::Root.new(document)
559
+ end
560
+
500
561
  # Tells the caller if this relation is one that stores the foreign
501
562
  # key on its own objects.
502
563
  #
@@ -509,6 +570,18 @@ module Mongoid #:nodoc:
509
570
  def stores_foreign_key?
510
571
  false
511
572
  end
573
+
574
+ # Get the valid options allowed with this relation.
575
+ #
576
+ # @example Get the valid options.
577
+ # Relation.valid_options
578
+ #
579
+ # @return [ Array<Symbol> ] The valid options.
580
+ #
581
+ # @since 2.1.0
582
+ def valid_options
583
+ [ :as, :autosave, :dependent, :foreign_key, :order ]
584
+ end
512
585
  end
513
586
  end
514
587
  end