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
@@ -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