mongoid 3.0.23 → 3.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 (95) hide show
  1. data/CHANGELOG.md +253 -9
  2. data/LICENSE +1 -1
  3. data/README.md +4 -1
  4. data/lib/config/locales/en.yml +7 -6
  5. data/lib/mongoid.rb +18 -1
  6. data/lib/mongoid/atomic.rb +22 -20
  7. data/lib/mongoid/atomic/paths/embedded.rb +19 -5
  8. data/lib/mongoid/atomic/paths/root.rb +1 -1
  9. data/lib/mongoid/atomic/positionable.rb +73 -0
  10. data/lib/mongoid/attributes.rb +63 -1
  11. data/lib/mongoid/callbacks.rb +58 -4
  12. data/lib/mongoid/components.rb +8 -3
  13. data/lib/mongoid/config.rb +71 -23
  14. data/lib/mongoid/contextual.rb +2 -1
  15. data/lib/mongoid/contextual/aggregable/mongo.rb +27 -63
  16. data/lib/mongoid/contextual/atomic.rb +4 -3
  17. data/lib/mongoid/contextual/find_and_modify.rb +1 -1
  18. data/lib/mongoid/contextual/geo_near.rb +238 -0
  19. data/lib/mongoid/contextual/map_reduce.rb +12 -1
  20. data/lib/mongoid/contextual/memory.rb +36 -31
  21. data/lib/mongoid/contextual/mongo.rb +147 -91
  22. data/lib/mongoid/contextual/queryable.rb +25 -0
  23. data/lib/mongoid/copyable.rb +4 -1
  24. data/lib/mongoid/criteria.rb +23 -275
  25. data/lib/mongoid/criterion/findable.rb +179 -0
  26. data/lib/mongoid/criterion/modifiable.rb +191 -0
  27. data/lib/mongoid/criterion/scoping.rb +11 -6
  28. data/lib/mongoid/document.rb +7 -56
  29. data/lib/mongoid/equality.rb +66 -0
  30. data/lib/mongoid/errors/mongoid_error.rb +7 -3
  31. data/lib/mongoid/extensions/array.rb +13 -1
  32. data/lib/mongoid/extensions/date.rb +9 -2
  33. data/lib/mongoid/extensions/hash.rb +38 -2
  34. data/lib/mongoid/extensions/nil_class.rb +12 -0
  35. data/lib/mongoid/extensions/object.rb +24 -0
  36. data/lib/mongoid/extensions/string.rb +14 -2
  37. data/lib/mongoid/extensions/time.rb +4 -1
  38. data/lib/mongoid/fields.rb +49 -5
  39. data/lib/mongoid/fields/foreign_key.rb +12 -0
  40. data/lib/mongoid/fields/standard.rb +12 -0
  41. data/lib/mongoid/finders.rb +8 -0
  42. data/lib/mongoid/hierarchy.rb +19 -1
  43. data/lib/mongoid/indexes.rb +30 -4
  44. data/lib/mongoid/indexes/validators/options.rb +12 -2
  45. data/lib/mongoid/inspection.rb +2 -1
  46. data/lib/mongoid/matchers/strategies.rb +5 -5
  47. data/lib/mongoid/observer.rb +27 -36
  48. data/lib/mongoid/persistence.rb +42 -17
  49. data/lib/mongoid/persistence/atomic.rb +10 -5
  50. data/lib/mongoid/persistence/atomic/operation.rb +26 -9
  51. data/lib/mongoid/persistence/atomic/unset.rb +1 -1
  52. data/lib/mongoid/persistence/operations/embedded/insert.rb +5 -2
  53. data/lib/mongoid/persistence/operations/embedded/remove.rb +5 -2
  54. data/lib/mongoid/persistence/operations/update.rb +7 -3
  55. data/lib/mongoid/railties/database.rake +12 -19
  56. data/lib/mongoid/relations.rb +2 -0
  57. data/lib/mongoid/relations/accessors.rb +30 -8
  58. data/lib/mongoid/relations/binding.rb +5 -1
  59. data/lib/mongoid/relations/bindings/referenced/in.rb +1 -1
  60. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +3 -3
  61. data/lib/mongoid/relations/counter_cache.rb +107 -0
  62. data/lib/mongoid/relations/embedded/batchable.rb +13 -4
  63. data/lib/mongoid/relations/embedded/many.rb +30 -1
  64. data/lib/mongoid/relations/macros.rb +2 -0
  65. data/lib/mongoid/relations/marshalable.rb +0 -1
  66. data/lib/mongoid/relations/metadata.rb +63 -11
  67. data/lib/mongoid/relations/options.rb +1 -0
  68. data/lib/mongoid/relations/proxy.rb +45 -2
  69. data/lib/mongoid/relations/referenced/in.rb +11 -2
  70. data/lib/mongoid/relations/referenced/many.rb +31 -3
  71. data/lib/mongoid/relations/referenced/many_to_many.rb +31 -3
  72. data/lib/mongoid/relations/referenced/one.rb +1 -1
  73. data/lib/mongoid/relations/targets/enumerable.rb +5 -1
  74. data/lib/mongoid/relations/touchable.rb +35 -6
  75. data/lib/mongoid/reloading.rb +5 -3
  76. data/lib/mongoid/scoping.rb +2 -2
  77. data/lib/mongoid/sessions.rb +57 -7
  78. data/lib/mongoid/sessions/factory.rb +22 -1
  79. data/lib/mongoid/threaded.rb +4 -30
  80. data/lib/mongoid/threaded/lifecycle.rb +12 -12
  81. data/lib/mongoid/timestamps.rb +1 -0
  82. data/lib/mongoid/timestamps/created.rb +2 -0
  83. data/lib/mongoid/timestamps/created/short.rb +19 -0
  84. data/lib/mongoid/timestamps/short.rb +10 -0
  85. data/lib/mongoid/timestamps/updated.rb +2 -0
  86. data/lib/mongoid/timestamps/updated/short.rb +19 -0
  87. data/lib/mongoid/validations.rb +2 -0
  88. data/lib/mongoid/validations/queryable.rb +2 -2
  89. data/lib/mongoid/validations/uniqueness.rb +1 -18
  90. data/lib/mongoid/version.rb +1 -1
  91. data/lib/rails/generators/mongoid/model/model_generator.rb +1 -0
  92. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +3 -0
  93. data/lib/rails/mongoid.rb +53 -29
  94. data/lib/support/ruby_version.rb +26 -0
  95. metadata +18 -7
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Criterion
4
+ module Findable
5
+
6
+ # Execute the criteria or raise an error if no documents found.
7
+ #
8
+ # @example Execute or raise
9
+ # criteria.execute_or_raise(id)
10
+ #
11
+ # @param [ Object ] args The arguments passed.
12
+ #
13
+ # @raise [ Errors::DocumentNotFound ] If nothing returned.
14
+ #
15
+ # @return [ Document, Array<Document> ] The document(s).
16
+ #
17
+ # @since 2.0.0
18
+ def execute_or_raise(ids, multi)
19
+ result = multiple_from_map_or_db(ids)
20
+ check_for_missing_documents!(result, ids)
21
+ multi ? result : result.first
22
+ end
23
+
24
+ # Find the matchind document(s) in the criteria for the provided ids.
25
+ #
26
+ # @example Find by an id.
27
+ # criteria.find(Moped::BSON::ObjectId.new)
28
+ #
29
+ # @example Find by multiple ids.
30
+ # criteria.find([ Moped::BSON::ObjectId.new, Moped::BSON::ObjectId.new ])
31
+ #
32
+ # @param [ Array<Moped::BSON::ObjectId> ] args The ids to search for.
33
+ #
34
+ # @return [ Array<Document>, Document ] The matching document(s).
35
+ #
36
+ # @since 1.0.0
37
+ def find(*args)
38
+ ids = args.__find_args__
39
+ raise_invalid if ids.any?(&:nil?)
40
+ for_ids(ids).execute_or_raise(ids, args.multi_arged?)
41
+ end
42
+
43
+ # Adds a criterion to the +Criteria+ that specifies an id that must be matched.
44
+ #
45
+ # @example Add a single id criteria.
46
+ # criteria.for_ids([ 1 ])
47
+ #
48
+ # @example Add multiple id criteria.
49
+ # criteria.for_ids([ 1, 2 ])
50
+ #
51
+ # @param [ Array ] ids The array of ids.
52
+ #
53
+ # @return [ Criteria ] The cloned criteria.
54
+ def for_ids(ids)
55
+ ids = mongoize_ids(ids)
56
+ if ids.size > 1
57
+ send(id_finder, { _id: { "$in" => ids }})
58
+ else
59
+ send(id_finder, { _id: ids.first })
60
+ end
61
+ end
62
+
63
+ # Get the document from the identity map, and if not found hit the
64
+ # database.
65
+ #
66
+ # @example Get the document from the map or criteria.
67
+ # criteria.from_map_or_db
68
+ #
69
+ # @return [ Document ] The found document.
70
+ #
71
+ # @since 2.2.1
72
+ def from_map_or_db
73
+ id = extract_id
74
+ id = klass.fields["_id"].mongoize(id) if id
75
+ doc = IdentityMap.get(klass, id || selector_with_type_selection)
76
+ return nil if doc == {}
77
+ doc && doc.matches?(selector) ? doc : first
78
+ end
79
+
80
+ # Get the documents from the identity map, and if not found hit the
81
+ # database.
82
+ #
83
+ # @example Get the documents from the map or criteria.
84
+ # criteria.multiple_from_map_or_db(ids)
85
+ #
86
+ # @param [ ids ] The searched ids.
87
+ #
88
+ # @return [ Array<Document> ] The found documents.
89
+ def multiple_from_map_or_db(ids)
90
+ return entries if embedded?
91
+ ids = mongoize_ids(ids)
92
+ result = from_identity_map(ids)
93
+ ids.empty? ? result : result + from_database(ids)
94
+ end
95
+
96
+ private
97
+
98
+ # Get the finder used to generate the id query.
99
+ #
100
+ # @api private
101
+ #
102
+ # @example Get the id finder.
103
+ # criteria.id_finder
104
+ #
105
+ # @return [ Symbol ] The name of the finder method.
106
+ #
107
+ # @since 3.1.0
108
+ def id_finder
109
+ @id_finder ||= extract_id ? :all_of : :where
110
+ end
111
+
112
+ # Get documents from the database only.
113
+ #
114
+ # @api private
115
+ #
116
+ # @example Get documents from the database.
117
+ # criteria.from_database(ids)
118
+ #
119
+ # @param [ Array<Object> ] ids The ids to fetch with.
120
+ #
121
+ # @return [ Array<Document> ] The matching documents.
122
+ #
123
+ # @since 3.0.0
124
+ def from_database(ids)
125
+ (ids.size > 1 ? any_in(id: ids) : where(id: ids.first)).entries
126
+ end
127
+
128
+ # Get documents from the identity map only.
129
+ #
130
+ # @api private
131
+ #
132
+ # @example Get documents from the identity map.
133
+ # criteria.from_identity_map(ids)
134
+ #
135
+ # @param [ Array<Object> ] ids The ids to fetch with.
136
+ #
137
+ # @return [ Array<Document> ] The matching documents.
138
+ #
139
+ # @since 3.0.0
140
+ def from_identity_map(ids)
141
+ result = []
142
+ selection = selector_with_type_selection
143
+ ids.reject! do |id|
144
+ doc = IdentityMap.get(klass, id)
145
+ doc && doc.matches?(selection) ? result.push(doc) : false
146
+ end
147
+ result
148
+ end
149
+
150
+ # Convert all the ids to their proper types.
151
+ #
152
+ # @api private
153
+ #
154
+ # @example Convert the ids.
155
+ # criteria.mongoize_ids(ids)
156
+ #
157
+ # @param [ Array<Object> ] ids The ids to convert.
158
+ #
159
+ # @return [ Array<Object> ] The converted ids.
160
+ #
161
+ # @since 3.0.0
162
+ def mongoize_ids(ids)
163
+ ids.map{ |id| klass.fields["_id"].mongoize(id) }
164
+ end
165
+
166
+ # Convenience method of raising an invalid options error.
167
+ #
168
+ # @example Raise the error.
169
+ # criteria.raise_invalid
170
+ #
171
+ # @raise [ Errors::InvalidOptions ] The error.
172
+ #
173
+ # @since 2.0.0
174
+ def raise_invalid
175
+ raise Errors::InvalidFind.new
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,191 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Criterion
4
+ module Modifiable
5
+
6
+ # Build a document given the selector and return it.
7
+ # Complex criteria, such as $in and $or operations will get ignored.
8
+ #
9
+ # @example build the document.
10
+ # Person.where(:title => "Sir").build
11
+ #
12
+ # @example Build with selectors getting ignored.
13
+ # Person.where(:age.gt => 5).build
14
+ #
15
+ # @return [ Document ] A non-persisted document.
16
+ #
17
+ # @since 2.0.0
18
+ def build(attrs = {}, &block)
19
+ create_document(:new, attrs, &block)
20
+ end
21
+ alias :new :build
22
+
23
+ # Create a document in the database given the selector and return it.
24
+ # Complex criteria, such as $in and $or operations will get ignored.
25
+ #
26
+ # @example Create the document.
27
+ # Person.where(:title => "Sir").create
28
+ #
29
+ # @example Create with selectors getting ignored.
30
+ # Person.where(:age.gt => 5).create
31
+ #
32
+ # @return [ Document ] A newly created document.
33
+ #
34
+ # @since 2.0.0.rc.1
35
+ def create(attrs = {}, &block)
36
+ create_document(:create, attrs, &block)
37
+ end
38
+
39
+ # Create a document in the database given the selector and return it.
40
+ # Complex criteria, such as $in and $or operations will get ignored.
41
+ # If validation fails, an error will be raised.
42
+ #
43
+ # @example Create the document.
44
+ # Person.where(:title => "Sir").create
45
+ #
46
+ # @example Create with selectors getting ignored.
47
+ # Person.where(:age.gt => 5).create
48
+ #
49
+ # @raise [ Errors::Validations ] on a validation error.
50
+ #
51
+ # @return [ Document ] A newly created document.
52
+ #
53
+ # @since 3.0.0
54
+ def create!(attrs = {}, &block)
55
+ create_document(:create!, attrs, &block)
56
+ end
57
+
58
+ # Find the first +Document+ given the conditions, or creates a new document
59
+ # with the conditions that were supplied.
60
+ #
61
+ # @example Find or create the document.
62
+ # Person.find_or_create_by(:attribute => "value")
63
+ #
64
+ # @param [ Hash ] attrs The attributes to check.
65
+ #
66
+ # @return [ Document ] A matching or newly created document.
67
+ def find_or_create_by(attrs = {}, &block)
68
+ find_or(:create, attrs, &block)
69
+ end
70
+
71
+ # Find the first +Document+ given the conditions, or initializes a new document
72
+ # with the conditions that were supplied.
73
+ #
74
+ # @example Find or initialize the document.
75
+ # Person.find_or_initialize_by(:attribute => "value")
76
+ #
77
+ # @param [ Hash ] attrs The attributes to check.
78
+ #
79
+ # @return [ Document ] A matching or newly initialized document.
80
+ def find_or_initialize_by(attrs = {}, &block)
81
+ find_or(:new, attrs, &block)
82
+ end
83
+
84
+ # Find the first +Document+, or creates a new document
85
+ # with the conditions that were supplied plus attributes.
86
+ #
87
+ # @example First or create the document.
88
+ # Person.where(name: "Jon").first_or_create(attribute: "value")
89
+ #
90
+ # @param [ Hash ] attrs The additional attributes to add.
91
+ #
92
+ # @return [ Document ] A matching or newly created document.
93
+ #
94
+ # @since 3.1.0
95
+ def first_or_create(attrs = nil, &block)
96
+ first_or(:create, attrs, &block)
97
+ end
98
+
99
+ # Find the first +Document+, or creates a new document
100
+ # with the conditions that were supplied plus attributes and will
101
+ # raise an error if validation fails.
102
+ #
103
+ # @example First or create the document.
104
+ # Person.where(name: "Jon").first_or_create!(attribute: "value")
105
+ #
106
+ # @param [ Hash ] attrs The additional attributes to add.
107
+ #
108
+ # @return [ Document ] A matching or newly created document.
109
+ #
110
+ # @since 3.1.0
111
+ def first_or_create!(attrs = nil, &block)
112
+ first_or(:create!, attrs, &block)
113
+ end
114
+
115
+ # Find the first +Document+, or initializes a new document
116
+ # with the conditions that were supplied plus attributes.
117
+ #
118
+ # @example First or initialize the document.
119
+ # Person.where(name: "Jon").first_or_initialize(attribute: "value")
120
+ #
121
+ # @param [ Hash ] attrs The additional attributes to add.
122
+ #
123
+ # @return [ Document ] A matching or newly initialized document.
124
+ #
125
+ # @since 3.1.0
126
+ def first_or_initialize(attrs = nil, &block)
127
+ first_or(:new, attrs, &block)
128
+ end
129
+
130
+ private
131
+
132
+ # Create a document given the provided method and attributes from the
133
+ # existing selector.
134
+ #
135
+ # @api private
136
+ #
137
+ # @example Create a new document.
138
+ # criteria.create_document(:new, {})
139
+ #
140
+ # @param [ Symbol ] method Either :new or :create.
141
+ # @param [ Hash ] attrs Additional attributes to use.
142
+ #
143
+ # @return [ Document ] The new or saved document.
144
+ #
145
+ # @since 3.0.0
146
+ def create_document(method, attrs = nil, &block)
147
+ klass.__send__(method,
148
+ selector.reduce(attrs || {}) do |hash, (key, value)|
149
+ unless key.to_s =~ /\$/ || value.is_a?(Hash)
150
+ hash[key] = value
151
+ end
152
+ hash
153
+ end, &block)
154
+ end
155
+
156
+ # Find the first object or create/initialize it.
157
+ #
158
+ # @api private
159
+ #
160
+ # @example Find or perform an action.
161
+ # Person.find_or(:create, :name => "Dev")
162
+ #
163
+ # @param [ Symbol ] method The method to invoke.
164
+ # @param [ Hash ] attrs The attributes to query or set.
165
+ #
166
+ # @return [ Document ] The first or new document.
167
+ def find_or(method, attrs = {}, &block)
168
+ where(attrs).first || send(method, attrs, &block)
169
+ end
170
+
171
+ # Find the first document or create/initialize it.
172
+ #
173
+ # @api private
174
+ #
175
+ # @example First or perform an action.
176
+ # Person.first_or(:create, :name => "Dev")
177
+ #
178
+ # @param [ Symbol ] method The method to invoke.
179
+ # @param [ Hash ] attrs The attributes to query or set.
180
+ #
181
+ # @return [ Document ] The first or new document.
182
+ #
183
+ # @since 3.1.0
184
+ def first_or(method, attrs = nil)
185
+ document = first || create_document(method, attrs)
186
+ yield(document) if block_given?
187
+ document
188
+ end
189
+ end
190
+ end
191
+ end
@@ -31,12 +31,7 @@ module Mongoid
31
31
  # @since 3.0.0
32
32
  def remove_scoping(other)
33
33
  if other
34
- selector.reject! do |key, value|
35
- other.selector.has_key?(key) && other.selector[key] == value
36
- end
37
- options.reject! do |key, value|
38
- other.options.has_key?(key) && other.options[key] == value
39
- end
34
+ reject_matching(other, :selector, :options)
40
35
  other.inclusions.each do |meta|
41
36
  inclusions.delete_one(meta)
42
37
  end
@@ -148,6 +143,16 @@ module Mongoid
148
143
  end
149
144
  crit
150
145
  end
146
+
147
+ private
148
+
149
+ def reject_matching(other, *methods)
150
+ methods.each do |method|
151
+ send(method).reject! do |key, value|
152
+ other.send(method).has_key?(key) && other.send(method)[key] == value
153
+ end
154
+ end
155
+ end
151
156
  end
152
157
  end
153
158
  end
@@ -10,62 +10,8 @@ module Mongoid
10
10
  attr_accessor :criteria_instance_id
11
11
  attr_reader :new_record
12
12
 
13
- # Default comparison is via the string version of the id.
14
- #
15
- # @example Compare two documents.
16
- # person <=> other_person
17
- #
18
- # @param [ Document ] other The document to compare with.
19
- #
20
- # @return [ Integer ] -1, 0, 1.
21
- #
22
- # @since 1.0.0
23
- def <=>(other)
24
- attributes["_id"].to_s <=> other.attributes["_id"].to_s
25
- end
26
-
27
- # Performs equality checking on the document ids. For more robust
28
- # equality checking please override this method.
29
- #
30
- # @example Compare for equality.
31
- # document == other
32
- #
33
- # @param [ Document, Object ] other The other object to compare with.
34
- #
35
- # @return [ true, false ] True if the ids are equal, false if not.
36
- #
37
- # @since 1.0.0
38
- def ==(other)
39
- self.class == other.class &&
40
- attributes["_id"] == other.attributes["_id"]
41
- end
42
-
43
- # Performs class equality checking.
44
- #
45
- # @example Compare the classes.
46
- # document === other
47
- #
48
- # @param [ Document, Object ] other The other object to compare with.
49
- #
50
- # @return [ true, false ] True if the classes are equal, false if not.
51
- #
52
- # @since 1.0.0
53
- def ===(other)
54
- other.class == Class ? self.class === other : self == other
55
- end
56
-
57
- # Delegates to ==. Used when needing checks in hashes.
58
- #
59
- # @example Perform equality checking.
60
- # document.eql?(other)
61
- #
62
- # @param [ Document, Object ] other The object to check against.
63
- #
64
- # @return [ true, false ] True if equal, false if not.
65
- #
66
- # @since 1.0.0
67
- def eql?(other)
68
- self == (other)
13
+ included do
14
+ Mongoid.register_model(self)
69
15
  end
70
16
 
71
17
  # Freezes the internal attributes of the document.
@@ -143,12 +89,15 @@ module Mongoid
143
89
  _building do
144
90
  @new_record = true
145
91
  @attributes ||= {}
92
+ @attributes_before_type_cast ||= {}
146
93
  options ||= {}
147
94
  apply_pre_processed_defaults
148
95
  process_attributes(attrs, options[:as] || :default, !options[:without_protection]) do
149
96
  yield(self) if block_given?
150
97
  end
151
98
  apply_post_processed_defaults
99
+ # @todo: #2586: Need to have access to parent document in these
100
+ # callbacks.
152
101
  run_callbacks(:initialize) unless _initialize_callbacks.empty?
153
102
  end
154
103
  end
@@ -336,9 +285,11 @@ module Mongoid
336
285
  doc = allocate
337
286
  doc.criteria_instance_id = criteria_instance_id
338
287
  doc.instance_variable_set(:@attributes, attributes)
288
+ doc.instance_variable_set(:@attributes_before_type_cast, {})
339
289
  doc.apply_defaults
340
290
  IdentityMap.set(doc) unless _loading_revision?
341
291
  yield(doc) if block_given?
292
+ doc.run_callbacks(:find) unless doc._find_callbacks.empty?
342
293
  doc.run_callbacks(:initialize) unless doc._initialize_callbacks.empty?
343
294
  doc
344
295
  end