mongoid 2.0.2 → 2.1.0

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.
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
@@ -0,0 +1,113 @@
1
+ # encoding: utf-8
2
+ module Mongoid # :nodoc:
3
+ module Relations #:nodoc:
4
+
5
+ # This module handles the behaviour for synchronizing foreign keys between
6
+ # both sides of a many to many relations.
7
+ module Synchronization
8
+ extend ActiveSupport::Concern
9
+
10
+ # Is the document able to be synced on the inverse side? This is only if
11
+ # the key has changed and the relation bindings have not been run.
12
+ #
13
+ # @example Are the foreign keys syncable?
14
+ # document.syncable?(metadata)
15
+ #
16
+ # @param [ Metadata ] metadata The relation metadata.
17
+ #
18
+ # @return [ true, false ] If we can sync.
19
+ #
20
+ # @since 2.1.0
21
+ def syncable?(metadata)
22
+ !synced?(metadata.foreign_key) && send(metadata.foreign_key_check)
23
+ end
24
+
25
+ # Get the synced foreign keys.
26
+ #
27
+ # @example Get the synced foreign keys.
28
+ # document.synced
29
+ #
30
+ # @return [ Hash ] The synced foreign keys.
31
+ #
32
+ # @since 2.1.0
33
+ def synced
34
+ @synced ||= {}
35
+ end
36
+
37
+ # Has the document been synced for the foreign key?
38
+ #
39
+ # @todo Change the sync to be key based.
40
+ #
41
+ # @example Has the document been synced?
42
+ # document.synced?
43
+ #
44
+ # @param [ String ] foreign_key The foreign key.
45
+ #
46
+ # @return [ true, false ] If we can sync.
47
+ #
48
+ # @since 2.1.0
49
+ def synced?(foreign_key)
50
+ !!synced[foreign_key]
51
+ end
52
+
53
+ # Update the inverse keys for the relation.
54
+ #
55
+ # @example Update the inverse keys
56
+ # document.update_inverse_keys(metadata)
57
+ #
58
+ # @param [ Metadata ] meta The document metadata.
59
+ #
60
+ # @return [ Object ] The updated values.
61
+ #
62
+ # @since 2.1.0
63
+ def update_inverse_keys(meta)
64
+ old, new = changes[meta.foreign_key]
65
+ meta.criteria(new - old).add_to_set(meta.inverse_foreign_key, id)
66
+ meta.criteria(old - new).pull(meta.inverse_foreign_key, id)
67
+ end
68
+
69
+ module ClassMethods #:nodoc:
70
+
71
+ # Set up the syncing of many to many foreign keys.
72
+ #
73
+ # @example Set up the syncing.
74
+ # Person.synced(metadata)
75
+ #
76
+ # @param [ Metadata ] metadata The relation metadata.
77
+ #
78
+ # @since 2.1.0
79
+ def synced(metadata)
80
+ synced_save(metadata)
81
+ end
82
+
83
+ private
84
+
85
+ # Set up the sync of inverse keys that needs to happen on a save.
86
+ #
87
+ # If the foreign key field has changed and the document is not
88
+ # synced, $addToSet the new ids, $pull the ones no longer in the
89
+ # array from the inverse side.
90
+ #
91
+ # @example Set up the save syncing.
92
+ # Person.synced_save(metadata)
93
+ #
94
+ # @param [ Metadata ] metadata The relation metadata.
95
+ #
96
+ # @return [ Class ] The class getting set up.
97
+ #
98
+ # @since 2.1.0
99
+ def synced_save(metadata)
100
+ tap do
101
+ set_callback(
102
+ :save,
103
+ :after,
104
+ :if => lambda { |doc| doc.syncable?(metadata) }
105
+ ) do |doc|
106
+ doc.update_inverse_keys(metadata)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require "mongoid/relations/targets/enumerable"
@@ -0,0 +1,329 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Relations #:nodoc:
4
+ module Targets #:nodoc:
5
+
6
+ # This class is the wrapper for all relational associations that have a
7
+ # target that can be a criteria or array of loaded documents. This
8
+ # handles both cases or a combination of the two.
9
+ class Enumerable
10
+ include ::Enumerable
11
+
12
+ # The three main instance variables are collections of documents.
13
+ #
14
+ # @attribute [rw] added Documents that have been appended.
15
+ # @attribute [rw] loaded Persisted documents that have been loaded.
16
+ # @attribute [rw] unloaded A criteria representing persisted docs.
17
+ attr_accessor :added, :loaded, :unloaded
18
+
19
+ # Check if the enumerable is equal to the other object.
20
+ #
21
+ # @example Check equality.
22
+ # enumerable == []
23
+ #
24
+ # @param [ Enumerable ] other The other enumerable.
25
+ #
26
+ # @return [ true, false ] If the objects are equal.
27
+ #
28
+ # @since 2.1.0
29
+ def ==(other)
30
+ return false unless other.respond_to?(:entries)
31
+ entries == other.entries
32
+ end
33
+
34
+ # Append a document to the enumerable.
35
+ #
36
+ # @example Append the document.
37
+ # enumerable << document
38
+ #
39
+ # @param [ Document ] document The document to append.
40
+ #
41
+ # @return [ Document ] The document.
42
+ #
43
+ # @since 2.1.0
44
+ def <<(document)
45
+ added << document
46
+ end
47
+ alias :push :<<
48
+
49
+ # Clears out all the documents in this enumerable. If passed a block it
50
+ # will yield to each document that is in memory.
51
+ #
52
+ # @example Clear out the enumerable.
53
+ # enumerable.clear
54
+ #
55
+ # @example Clear out the enumerable with a block.
56
+ # enumerable.clear do |doc|
57
+ # doc.unbind
58
+ # end
59
+ #
60
+ # @return [ Array<Document> ] The cleared out added docs.
61
+ #
62
+ # @since 2.1.0
63
+ def clear
64
+ if block_given?
65
+ in_memory { |doc| yield(doc) }
66
+ end
67
+ loaded.clear and added.clear
68
+ end
69
+
70
+ # Delete the supplied document from the enumerable.
71
+ #
72
+ # @example Delete the document.
73
+ # enumerable.delete(document)
74
+ #
75
+ # @param [ Document ] document The document to delete.
76
+ #
77
+ # @return [ Document ] The deleted document.
78
+ #
79
+ # @since 2.1.0
80
+ def delete(document)
81
+ (loaded.delete(document) || added.delete(document)).tap do |doc|
82
+ unless doc
83
+ if unloaded && unloaded.where(:_id => document.id).exists?
84
+ yield(document) if block_given?
85
+ return document
86
+ end
87
+ end
88
+ yield(doc) if block_given?
89
+ end
90
+ end
91
+
92
+ # Deletes every document in the enumerable for where the block returns
93
+ # true.
94
+ #
95
+ # @note This operation loads all documents from the database.
96
+ #
97
+ # @example Delete all matching documents.
98
+ # enumerable.delete_if do |doc|
99
+ # dod.id == id
100
+ # end
101
+ #
102
+ # @return [ Array<Document> ] The remaining docs.
103
+ #
104
+ # @since 2.1.0
105
+ def delete_if(&block)
106
+ load_all!
107
+ tap do
108
+ loaded.delete_if(&block)
109
+ added.delete_if(&block)
110
+ end
111
+ end
112
+
113
+ # Iterating over this enumerable has to handle a few different
114
+ # scenarios.
115
+ #
116
+ # If the enumerable has its criteria loaded into memory then it yields
117
+ # to all the loaded docs and all the added docs.
118
+ #
119
+ # If the enumerable has not loaded the criteria then it iterates over
120
+ # the cursor while loading the documents and then iterates over the
121
+ # added docs.
122
+ #
123
+ # @example Iterate over the enumerable.
124
+ # enumerable.each do |doc|
125
+ # puts doc
126
+ # end
127
+ #
128
+ # @return [ true ] That the enumerable is now loaded.
129
+ #
130
+ # @since 2.1.0
131
+ def each
132
+ if loaded?
133
+ loaded.each do |doc|
134
+ yield(doc)
135
+ end
136
+ else
137
+ unloaded.each do |doc|
138
+ loaded.push(doc)
139
+ yield(doc)
140
+ end
141
+ end
142
+ added.each do |doc|
143
+ next unless doc.new?
144
+ yield(doc)
145
+ end
146
+ @executed = true
147
+ end
148
+
149
+ # Is the enumerable empty? Will determine if the count is zero based on
150
+ # whether or not it is loaded.
151
+ #
152
+ # @example Is the enumerable empty?
153
+ # enumerable.empty?
154
+ #
155
+ # @return [ true, false ] If the enumerable is empty.
156
+ #
157
+ # @since 2.1.0
158
+ def empty?
159
+ if loaded?
160
+ in_memory.count == 0
161
+ else
162
+ unloaded.count + added.count == 0
163
+ end
164
+ end
165
+
166
+ # Get the first document in the enumerable. Will check the persisted
167
+ # documents first. Does not load the entire enumerable.
168
+ #
169
+ # @example Get the first document.
170
+ # enumerable.first
171
+ #
172
+ # @return [ Document ] The first document found.
173
+ #
174
+ # @since 2.1.0
175
+ def first
176
+ (loaded? ? loaded.first : unloaded.first) || added.first
177
+ end
178
+
179
+ # Initialize the new enumerable either with a criteria or an array.
180
+ #
181
+ # @example Initialize the enumerable with a criteria.
182
+ # Enumberable.new(Post.where(:person_id => id))
183
+ #
184
+ # @example Initialize the enumerable with an array.
185
+ # Enumerable.new([ post ])
186
+ #
187
+ # @param [ Criteria, Array<Document> ] target The wrapped object.
188
+ #
189
+ # @since 2.1.0
190
+ def initialize(target)
191
+ if target.is_a?(Criteria)
192
+ @added, @loaded, @unloaded = [], [], target
193
+ else
194
+ @added, @executed, @loaded = [], true, target
195
+ end
196
+ end
197
+
198
+ # Inspection will just inspect the entries for nice array-style
199
+ # printing.
200
+ #
201
+ # @example Inspect the enumerable.
202
+ # enumerable.inspect
203
+ #
204
+ # @return [ String ] The inspected enum.
205
+ #
206
+ # @since 2.1.0
207
+ def inspect
208
+ entries.inspect
209
+ end
210
+
211
+ # Return all the documents in the enumerable that have been loaded or
212
+ # added.
213
+ #
214
+ # @note When passed a block it yields to each document.
215
+ #
216
+ # @example Get the in memory docs.
217
+ # enumerable.in_memory
218
+ #
219
+ # @return [ Array<Document> ] The in memory docs.
220
+ #
221
+ # @since 2.1.0
222
+ def in_memory
223
+ (loaded + added).tap do |docs|
224
+ docs.each { |doc| yield(doc) } if block_given?
225
+ end
226
+ end
227
+
228
+ # Get the last document in the enumerable. Will check the new
229
+ # documents first. Does not load the entire enumerable.
230
+ #
231
+ # @example Get the last document.
232
+ # enumerable.last
233
+ #
234
+ # @return [ Document ] The last document found.
235
+ #
236
+ # @since 2.1.0
237
+ def last
238
+ added.last || (loaded? ? loaded.last : unloaded.last)
239
+ end
240
+
241
+ # Loads all the documents in the enumerable from the database.
242
+ #
243
+ # @example Load all the documents.
244
+ # enumerable.load_all!
245
+ #
246
+ # @return [ true ] That the enumerable is loaded.
247
+ #
248
+ # @since 2.1.0
249
+ alias :load_all! :entries
250
+
251
+ # Has the enumerable been loaded? This will be true if the criteria has
252
+ # been executed or we manually load the entire thing.
253
+ #
254
+ # @example Is the enumerable loaded?
255
+ # enumerable.loaded?
256
+ #
257
+ # @return [ true, false ] If the enumerable has been loaded.
258
+ #
259
+ # @since 2.1.0
260
+ def loaded?
261
+ !!@executed
262
+ end
263
+
264
+ # Reset the enumerable back to it's persisted state.
265
+ #
266
+ # @example Reset the enumerable.
267
+ # enumerable.reset
268
+ #
269
+ # @return [ false ] Always false.
270
+ #
271
+ # @since 2.1.0
272
+ def reset
273
+ loaded.clear and added.clear
274
+ @executed = false
275
+ end
276
+
277
+ # Does this enumerable respond to the provided method?
278
+ #
279
+ # @example Does the enumerable respond to the method?
280
+ # enumerable.respond_to?(:sum)
281
+ #
282
+ # @param [ String, Symbol ] name The name of the method.
283
+ # @param [ true, false ] include_private Whether to include private
284
+ # methods.
285
+ #
286
+ # @return [ true, false ] Whether the enumerable responds.
287
+ #
288
+ # @since 2.1.0
289
+ def respond_to?(name, include_private = false)
290
+ [].respond_to?(name, include_private) || super
291
+ end
292
+
293
+ # Gets the total size of this enumerable. This is a combination of all
294
+ # the persisted and unpersisted documents.
295
+ #
296
+ # @example Get the size.
297
+ # enumerable.size
298
+ #
299
+ # @return [ Integer ] The size of the enumerable.
300
+ #
301
+ # @since 2.1.0
302
+ def size
303
+ (loaded? ? loaded.count : unloaded.count) + added.count{ |d| d.new? }
304
+ end
305
+ alias :length :size
306
+
307
+ # Return all the unique documents in the enumerable.
308
+ #
309
+ # @note This operation loads all documents from the database.
310
+ #
311
+ # @example Get all the unique documents.
312
+ # enumerable.uniq
313
+ #
314
+ # @return [ Array<Document> ] The unique documents.
315
+ #
316
+ # @since 2.1.0
317
+ def uniq
318
+ entries.uniq
319
+ end
320
+
321
+ private
322
+
323
+ def method_missing(name, *args, &block)
324
+ entries.send(name, *args, &block)
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
@@ -24,7 +24,29 @@ module Mongoid #:nodoc:
24
24
  #
25
25
  # @return [ Proxy ] The safety proxy.
26
26
  def safely(safety = true)
27
- Proxy.new(self, safety)
27
+ tap { Threaded.safety_options = safety }
28
+ end
29
+
30
+ class << self
31
+
32
+ # Static class method of easily getting the desired safe mode options
33
+ # from anywhere in the framework.
34
+ #
35
+ # @example Get the options with safe mode included.
36
+ # Safety.merge_safety_options({ :safe => false })
37
+ #
38
+ # @param [ Hash ] options The persistence options.
39
+ #
40
+ # @return [ Hash ] The options hash.
41
+ #
42
+ # @since 2.1.0
43
+ def merge_safety_options(options = {})
44
+ options ||= {}
45
+ return options if options[:safe]
46
+ options.merge!(
47
+ { :safe => Threaded.safety_options || Mongoid.persist_in_safe_mode }
48
+ )
49
+ end
28
50
  end
29
51
 
30
52
  module ClassMethods #:nodoc:
@@ -46,161 +68,7 @@ module Mongoid #:nodoc:
46
68
  #
47
69
  # @return [ Proxy ] The safety proxy.
48
70
  def safely(safety = true)
49
- Proxy.new(self, safety)
50
- end
51
- end
52
-
53
- # When this class proxies a document or class, the next persistence
54
- # operation executed on it will query in safe mode.
55
- #
56
- # Operations that took a hash of attributes had to be somewhat duplicated
57
- # here since we do not want to allow a :safe attribute to be included in
58
- # the args. This is because safe could be a common attribute name and we
59
- # don't want the collision between the attribute and determining whether or
60
- # not safe mode is allowed.
61
- class Proxy
62
-
63
- attr_reader :target, :safety_options
64
-
65
- # Create a new +Document+. This will instantiate a new document and
66
- # insert it in a single call. Will always return the document
67
- # whether save passed or not.
68
- #
69
- # @example Safely create a document.
70
- # Person.safely.create(:title => "Mr")
71
- #
72
- # @param [ Hash ] attributes The attributes to create with.
73
- #
74
- # @return [ Document ] The new document.
75
- def create(attributes = {})
76
- target.new(attributes).tap { |doc| doc.insert(:safe => safety_options) }
77
- end
78
-
79
- # Create a new +Document+. This will instantiate a new document and
80
- # insert it in a single call. Will always return the document
81
- # whether save passed or not, and if validation fails an error will be
82
- # raise.
83
- #
84
- # @example Safely create a document.
85
- # Person.safely.create!(:title => "Mr")
86
- #
87
- # @param [ Hash ] attributes The attributes to create with.
88
- #
89
- # @raise [ Errors::Validations ] If validation failed.
90
- #
91
- # @return [ Document ] If validation passed.
92
- def create!(attributes = {})
93
- target.new(attributes).tap do |document|
94
- fail_validate!(document) if document.insert(:safe => safety_options).errors.any?
95
- end
96
- end
97
-
98
- # Delete all documents given the supplied conditions. If no conditions
99
- # are passed, the entire collection will be dropped for performance
100
- # benefits. Does not fire any callbacks.
101
- #
102
- # @example Delete all documents.
103
- # Person.safely.delete_all
104
- #
105
- # @example Conditionally delete all documents.
106
- # Person.safely.delete_all(:conditions => { :title => "Sir" })
107
- #
108
- # @param [ Hash ] conditions The conditions to delete with.
109
- #
110
- # @return [ Integer ] The number of documents deleted.
111
- def delete_all(conditions = {})
112
- Mongoid::Persistence::RemoveAll.new(
113
- target,
114
- { :validate => false, :safe => safety_options },
115
- conditions[:conditions] || {}
116
- ).persist
117
- end
118
-
119
- # destroy all documents given the supplied conditions. If no conditions
120
- # are passed, the entire collection will be dropped for performance
121
- # benefits. Fires the destroy callbacks if conditions were passed.
122
- #
123
- # @example destroy all documents.
124
- # Person.safely.destroy_all
125
- #
126
- # @example Conditionally destroy all documents.
127
- # Person.safely.destroy_all(:conditions => { :title => "Sir" })
128
- #
129
- # @param [ Hash ] conditions The conditions to destroy with.
130
- #
131
- # @return [ Integer ] The number of documents destroyd.
132
- def destroy_all(conditions = {})
133
- documents = target.all(conditions)
134
- documents.count.tap do |count|
135
- documents.each { |doc| doc.destroy(:safe => safety_options) }
136
- end
137
- end
138
-
139
- # Increment the field by the provided value, else if it doesn't exists set
140
- # it to that value.
141
- #
142
- # @example Safely increment a field.
143
- # person.safely.inc(:age, 1)
144
- #
145
- # @param [ Symbol, String ] field The field to increment.
146
- # @param [ Integer ] value The value to increment by.
147
- # @param [ Hash ] options Options to pass through to the driver.
148
- def inc(field, value, options = {})
149
- target.inc(field, value, :safe => safety_options)
150
- end
151
-
152
- # Create the new +Proxy+.
153
- #
154
- # @example Create the proxy.
155
- # Proxy.new(document, :w => 3)
156
- #
157
- # @param [ Document, Class ] target Either the class or the instance.
158
- # @param [ true, Hash ] safety_options The options.
159
- def initialize(target, safety_options)
160
- @target = target
161
- @safety_options = safety_options
162
- end
163
-
164
- # We will use method missing to proxy calls to the target.
165
- #
166
- # @example Save safely.
167
- # person.safely.save
168
- #
169
- # @param [ Array ] *args The arguments to pass on.
170
- def method_missing(*args)
171
- name = args[0]
172
- attributes = args[1] || {}
173
- target.send(name, attributes.merge(:safe => safety_options))
174
- end
175
-
176
- # Update the +Document+ attributes in the datbase.
177
- #
178
- # @example Safely update attributes.
179
- # person.safely.update_attributes(:title => "Sir")
180
- #
181
- # @param [ Hash ] attributes The attributes to update.
182
- #
183
- # @return [ true, false ] Whether the document was saved.
184
- def update_attributes(attributes = {})
185
- target.write_attributes(attributes)
186
- target.update(:safe => safety_options)
187
- end
188
-
189
- # Update the +Document+ attributes in the datbase.
190
- #
191
- # @example Safely update attributes.
192
- # person.safely.update_attributes(:title => "Sir")
193
- #
194
- # @param [ Hash ] attributes The attributes to update.
195
- #
196
- # @raise [ Errors::Validations ] If validation failed.
197
- #
198
- # @return [ true ] If the document was saved.
199
- def update_attributes!(attributes = {})
200
- target.write_attributes(attributes)
201
- update(:safe => safety_options).tap do |result|
202
- target.class.fail_validate!(target) unless result
203
- end
71
+ tap { Threaded.safety_options = safety }
204
72
  end
205
73
  end
206
74
  end