mongoid 3.0.23 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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