mongoid 2.4.0 → 2.4.12

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 (107) hide show
  1. data/CHANGELOG.md +329 -1
  2. data/Rakefile +0 -9
  3. data/lib/config/locales/bg.yml +1 -0
  4. data/lib/config/locales/de.yml +1 -0
  5. data/lib/config/locales/en-GB.yml +1 -0
  6. data/lib/config/locales/en.yml +1 -0
  7. data/lib/config/locales/es.yml +1 -0
  8. data/lib/config/locales/fr.yml +1 -0
  9. data/lib/config/locales/hi.yml +1 -0
  10. data/lib/config/locales/hu.yml +1 -0
  11. data/lib/config/locales/id.yml +1 -0
  12. data/lib/config/locales/it.yml +1 -0
  13. data/lib/config/locales/ja.yml +1 -0
  14. data/lib/config/locales/kr.yml +1 -0
  15. data/lib/config/locales/nl.yml +1 -0
  16. data/lib/config/locales/pl.yml +1 -0
  17. data/lib/config/locales/pt-BR.yml +1 -0
  18. data/lib/config/locales/pt.yml +1 -0
  19. data/lib/config/locales/ro.yml +1 -0
  20. data/lib/config/locales/ru.yml +1 -0
  21. data/lib/config/locales/sv.yml +1 -0
  22. data/lib/config/locales/vi.yml +1 -0
  23. data/lib/config/locales/zh-CN.yml +1 -0
  24. data/lib/mongoid/atomic/modifiers.rb +56 -15
  25. data/lib/mongoid/atomic.rb +14 -8
  26. data/lib/mongoid/attributes.rb +1 -1
  27. data/lib/mongoid/callbacks.rb +3 -3
  28. data/lib/mongoid/collection.rb +5 -1
  29. data/lib/mongoid/collections/retry.rb +2 -1
  30. data/lib/mongoid/components.rb +2 -1
  31. data/lib/mongoid/config/database.rb +4 -7
  32. data/lib/mongoid/config/environment.rb +4 -2
  33. data/lib/mongoid/config/replset_database.rb +3 -5
  34. data/lib/mongoid/config.rb +18 -0
  35. data/lib/mongoid/contexts/enumerable.rb +26 -1
  36. data/lib/mongoid/contexts/mongo.rb +62 -14
  37. data/lib/mongoid/copyable.rb +38 -8
  38. data/lib/mongoid/criteria.rb +1 -0
  39. data/lib/mongoid/criterion/exclusion.rb +3 -1
  40. data/lib/mongoid/criterion/inclusion.rb +2 -2
  41. data/lib/mongoid/criterion/optional.rb +2 -2
  42. data/lib/mongoid/criterion/selector.rb +25 -4
  43. data/lib/mongoid/cursor.rb +4 -2
  44. data/lib/mongoid/dirty.rb +61 -18
  45. data/lib/mongoid/document.rb +6 -2
  46. data/lib/mongoid/extensions/hash/criteria_helpers.rb +3 -1
  47. data/lib/mongoid/extensions/string/inflections.rb +1 -1
  48. data/lib/mongoid/factory.rb +3 -3
  49. data/lib/mongoid/fields/internal/array.rb +4 -9
  50. data/lib/mongoid/fields/internal/big_decimal.rb +7 -1
  51. data/lib/mongoid/fields/internal/date_time.rb +1 -1
  52. data/lib/mongoid/fields/internal/false_class.rb +10 -0
  53. data/lib/mongoid/fields/internal/foreign_keys/array.rb +22 -8
  54. data/lib/mongoid/fields/internal/set.rb +1 -1
  55. data/lib/mongoid/fields/internal/timekeeping.rb +2 -1
  56. data/lib/mongoid/fields/internal/true_class.rb +10 -0
  57. data/lib/mongoid/fields/mappings.rb +14 -13
  58. data/lib/mongoid/fields/serializable.rb +41 -3
  59. data/lib/mongoid/fields.rb +154 -26
  60. data/lib/mongoid/hierarchy.rb +6 -1
  61. data/lib/mongoid/identity_map.rb +15 -0
  62. data/lib/mongoid/logger.rb +7 -1
  63. data/lib/mongoid/matchers/all.rb +8 -1
  64. data/lib/mongoid/matchers/in.rb +8 -1
  65. data/lib/mongoid/matchers/strategies.rb +6 -2
  66. data/lib/mongoid/multi_database.rb +5 -0
  67. data/lib/mongoid/named_scope.rb +17 -1
  68. data/lib/mongoid/nested_attributes.rb +1 -1
  69. data/lib/mongoid/observer.rb +7 -0
  70. data/lib/mongoid/persistence/insertion.rb +2 -1
  71. data/lib/mongoid/persistence/operations/update.rb +0 -9
  72. data/lib/mongoid/persistence.rb +5 -5
  73. data/lib/mongoid/railtie.rb +22 -4
  74. data/lib/mongoid/relations/bindings/referenced/in.rb +31 -11
  75. data/lib/mongoid/relations/bindings/referenced/many.rb +8 -8
  76. data/lib/mongoid/relations/bindings/referenced/one.rb +8 -8
  77. data/lib/mongoid/relations/builders/nested_attributes/many.rb +3 -1
  78. data/lib/mongoid/relations/cascading/delete.rb +29 -2
  79. data/lib/mongoid/relations/cascading/destroy.rb +18 -1
  80. data/lib/mongoid/relations/cascading/nullify.rb +18 -1
  81. data/lib/mongoid/relations/cascading.rb +0 -1
  82. data/lib/mongoid/relations/embedded/atomic.rb +3 -3
  83. data/lib/mongoid/relations/embedded/many.rb +58 -26
  84. data/lib/mongoid/relations/embedded/one.rb +1 -1
  85. data/lib/mongoid/relations/metadata.rb +33 -1
  86. data/lib/mongoid/relations/one.rb +12 -0
  87. data/lib/mongoid/relations/proxy.rb +6 -3
  88. data/lib/mongoid/relations/referenced/many.rb +14 -9
  89. data/lib/mongoid/relations/referenced/many_to_many.rb +3 -4
  90. data/lib/mongoid/relations/targets/enumerable.rb +9 -3
  91. data/lib/mongoid/relations.rb +3 -1
  92. data/lib/mongoid/serialization.rb +18 -6
  93. data/lib/mongoid/state.rb +1 -1
  94. data/lib/mongoid/threaded.rb +26 -0
  95. data/lib/mongoid/timestamps/updated.rb +1 -1
  96. data/lib/mongoid/validations/format.rb +20 -0
  97. data/lib/mongoid/validations/length.rb +20 -0
  98. data/lib/mongoid/validations/localizable.rb +30 -0
  99. data/lib/mongoid/validations/presence.rb +6 -2
  100. data/lib/mongoid/validations/uniqueness.rb +162 -30
  101. data/lib/mongoid/validations.rb +54 -2
  102. data/lib/mongoid/version.rb +1 -1
  103. data/lib/mongoid/versioning.rb +12 -3
  104. data/lib/mongoid.rb +1 -0
  105. data/lib/rails/mongoid.rb +18 -12
  106. metadata +58 -41
  107. data/lib/mongoid/relations/cascading/strategy.rb +0 -26
@@ -39,9 +39,8 @@ module Mongoid #:nodoc:
39
39
  #
40
40
  # @since 2.3.0
41
41
  def run_callbacks(kind, *args, &block)
42
- run_cascading_callbacks(cascadable_children(kind), kind, *args) do
43
- super(kind, *args, &block)
44
- end
42
+ run_cascading_callbacks(cascadable_children(kind), kind, *args) {}
43
+ super(kind, *args, &block)
45
44
  end
46
45
 
47
46
  private
@@ -106,6 +105,7 @@ module Mongoid #:nodoc:
106
105
  #
107
106
  # @since 2.3.0
108
107
  def cascadable_child?(kind, child)
108
+ return false if kind == :initialize || !child.respond_to?("_#{kind}_callbacks")
109
109
  [ :create, :destroy ].include?(kind) || child.changed? || child.new_record?
110
110
  end
111
111
 
@@ -142,12 +142,16 @@ module Mongoid #:nodoc
142
142
  #
143
143
  # @since 2.0.0
144
144
  def update(selector, document, options = {})
145
- updater = Threaded.update_consumer(klass)
145
+ updater = Threaded.update_consumer(name)
146
146
  if updater
147
147
  updater.consume(selector, document, options)
148
148
  else
149
149
  master(options).update(selector, document, options)
150
150
  end
151
151
  end
152
+
153
+ def drop(*args)
154
+ @master = nil if master.drop
155
+ end
152
156
  end
153
157
  end
@@ -32,6 +32,7 @@ module Mongoid #:nodoc:
32
32
  retry
33
33
  rescue Mongo::OperationFailure => ex
34
34
  if ex.message =~ /not master/
35
+ Mongoid.reconnect!
35
36
  retries = increase_retry_attempts(retries, ex)
36
37
  retry
37
38
  else
@@ -51,7 +52,7 @@ module Mongoid #:nodoc:
51
52
  end
52
53
 
53
54
  def log_retry(retry_number, ex)
54
- Mongoid.logger.warn "A #{ex.class.name} was raised. Retry attempt ##{retry_number}."
55
+ Mongoid.logger.warn "A #{ex.class.name} was raised. Retry attempt ##{retry_number}." if Mongoid.logger
55
56
  end
56
57
  end
57
58
  end
@@ -22,7 +22,6 @@ module Mongoid #:nodoc
22
22
  include Mongoid::Dirty
23
23
  include Mongoid::Attributes
24
24
  include Mongoid::Collections
25
- include Mongoid::Copyable
26
25
  include Mongoid::DefaultScope
27
26
  include Mongoid::Extras
28
27
  include Mongoid::Fields
@@ -45,6 +44,7 @@ module Mongoid #:nodoc
45
44
  include Mongoid::Timestamps::Timeless
46
45
  include Mongoid::Validations
47
46
  include Mongoid::Callbacks
47
+ include Mongoid::Copyable
48
48
  include Mongoid::MultiDatabase
49
49
 
50
50
  MODULES = [
@@ -66,6 +66,7 @@ module Mongoid #:nodoc
66
66
  Mongoid::NestedAttributes,
67
67
  Mongoid::Persistence,
68
68
  Mongoid::Relations,
69
+ Mongoid::Relations::Proxy,
69
70
  Mongoid::Safety,
70
71
  Mongoid::Serialization,
71
72
  Mongoid::Sharding,
@@ -6,9 +6,6 @@ module Mongoid #:nodoc:
6
6
  # database from options.
7
7
  class Database < Hash
8
8
 
9
- # keys to remove from self to not pass through to Mongo::Connection
10
- PRIVATE_OPTIONS = %w(uri database username password logger)
11
-
12
9
  # Configure the database connections. This will return an array
13
10
  # containing the master and an array of slaves.
14
11
  #
@@ -86,7 +83,7 @@ module Mongoid #:nodoc:
86
83
  #
87
84
  # @since 2.0.0.rc.1
88
85
  def master
89
- Mongo::Connection.from_uri(uri(self), optional).tap do |conn|
86
+ Mongo::Connection.from_uri(uri(self), optional.symbolize_keys).tap do |conn|
90
87
  conn.apply_saved_authentication
91
88
  end
92
89
  end
@@ -102,7 +99,7 @@ module Mongoid #:nodoc:
102
99
  # @since 2.0.0.rc.1
103
100
  def slaves
104
101
  (self["slaves"] || []).map do |options|
105
- Mongo::Connection.from_uri(uri(options), optional(true)).tap do |conn|
102
+ Mongo::Connection.from_uri(uri(options), optional(true).symbolize_keys).tap do |conn|
106
103
  conn.apply_saved_authentication
107
104
  end
108
105
  end
@@ -162,8 +159,8 @@ module Mongoid #:nodoc:
162
159
  :pool_size => pool_size,
163
160
  :logger => logger? ? Mongoid::Logger.new : nil,
164
161
  :slave_ok => slave
165
- }).merge(self).reject { |k,v| PRIVATE_OPTIONS.include? k }.
166
- inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo} # mongo likes symbols
162
+ }).merge(self).reject { |k,v| Config.blacklisted_options.include? k }.
163
+ inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo}
167
164
  end
168
165
 
169
166
  # Get a Mongo compliant URI for the database connection.
@@ -8,18 +8,20 @@ module Mongoid #:nodoc
8
8
 
9
9
  # Get the name of the environment that we are running under. This first
10
10
  # looks for Rails, then Sinatra, then a RACK_ENV environment variable,
11
- # and if none of those are found returns "development".
11
+ # and if none of those are found raises an error.
12
12
  #
13
13
  # @example Get the env name.
14
14
  # Environment.env_name
15
15
  #
16
+ # @raise [ Errors::NoEnvironment ] If no environment was set.
17
+ #
16
18
  # @return [ String ] The name of the current environment.
17
19
  #
18
20
  # @since 2.3.0
19
21
  def env_name
20
22
  return Rails.env if defined?(Rails)
21
23
  return Sinatra::Base.environment.to_s if defined?(Sinatra)
22
- ENV["RACK_ENV"] || raise(Errors::NoEnvironment.new)
24
+ ENV["RACK_ENV"] || ENV["MONGOID_ENV"] || raise(Errors::NoEnvironment.new)
23
25
  end
24
26
 
25
27
  # Load the yaml from the provided path and return the settings for the
@@ -17,11 +17,9 @@ module Mongoid #:nodoc:
17
17
  # yes, construction is weird but the driver wants
18
18
  # "A list of host-port pairs ending with a hash containing any options"
19
19
  # mongo likes symbols
20
- options = self.inject({ :logger => Mongoid::Logger.new }) do |memo, (k, v)|
21
- memo[k.to_sym] = v
22
- memo
23
- end
24
- connection = Mongo::ReplSetConnection.new(*(hosts.clone << options))
20
+ options = reject{ |key, value| Config.blacklisted_options.include?(key.to_s) }
21
+ options["logger"] = Mongoid::Logger.new
22
+ connection = Mongo::ReplSetConnection.new(*(hosts.clone << options.symbolize_keys))
25
23
 
26
24
  if authenticating?
27
25
  connection.add_auth(database, username, password)
@@ -23,6 +23,7 @@ module Mongoid #:nodoc
23
23
  option :autocreate_indexes, :default => false
24
24
  option :identity_map_enabled, :default => false
25
25
  option :include_root_in_json, :default => false
26
+ option :include_type_for_serialization, :default => false
26
27
  option :max_retries_on_connection_failure, :default => 0
27
28
  option :parameterize_keys, :default => true
28
29
  option :scope_overwrite_exception, :default => false
@@ -52,6 +53,23 @@ module Mongoid #:nodoc
52
53
  end
53
54
  end
54
55
 
56
+ # keys to remove from self to not pass through to Mongo::Connection
57
+ PRIVATE_OPTIONS =
58
+ %w(uri host hosts port database databases username
59
+ password logger use_activesupport_time_zone)
60
+
61
+ # Get the blacklisted options from passing through to the driver.
62
+ #
63
+ # @example Get the blacklisted options.
64
+ # Config.blacklisted_options
65
+ #
66
+ # @return [ Array<String> ] The blacklist.
67
+ #
68
+ # @since 2.4.7
69
+ def blacklisted_options
70
+ PRIVATE_OPTIONS + settings.keys.map(&:to_s)
71
+ end
72
+
55
73
  # Get any extra databases that have been configured.
56
74
  #
57
75
  # @example Get the extras.
@@ -210,6 +210,18 @@ module Mongoid #:nodoc:
210
210
 
211
211
  protected
212
212
 
213
+ # Get the root class collection name.
214
+ #
215
+ # @example Get the root class collection name.
216
+ # context.collection_name
217
+ #
218
+ # @return [ String ] The name of the collection.
219
+ #
220
+ # @since 2.4.3
221
+ def collection_name
222
+ root ? root.collection_name : nil
223
+ end
224
+
213
225
  # Filters the documents against the criteria's selector
214
226
  #
215
227
  # @example Filter the documents.
@@ -251,10 +263,22 @@ module Mongoid #:nodoc:
251
263
  documents
252
264
  end
253
265
 
266
+ # Get the root document for the enumerable.
267
+ #
268
+ # @example Get the root document.
269
+ # context.root
270
+ #
271
+ # @return [ Document ] The root.
254
272
  def root
255
273
  @root ||= documents.first.try(:_root)
256
274
  end
257
275
 
276
+ # Get the root class for the enumerable.
277
+ #
278
+ # @example Get the root class.
279
+ # context.root_class
280
+ #
281
+ # @return [ Class ] The root class.
258
282
  def root_class
259
283
  @root_class ||= root ? root.class : nil
260
284
  end
@@ -279,7 +303,8 @@ module Mongoid #:nodoc:
279
303
  return documents if options[:sort].blank?
280
304
  documents.sort_by do |document|
281
305
  options[:sort].map do |key, direction|
282
- Sort.new(document.read_attribute(key), direction)
306
+ key = key.to_s.gsub(/(.*)\.#{I18n.locale}$/, '\1')
307
+ Sort.new(document.send(key), direction)
283
308
  end
284
309
  end
285
310
  end
@@ -135,6 +135,28 @@ module Mongoid #:nodoc:
135
135
  klass.collection.distinct(field, selector)
136
136
  end
137
137
 
138
+ # Eager load the inclusions for the provided documents.
139
+ #
140
+ # @example Eager load the inclusions.
141
+ # context.eager_load(docs)
142
+ #
143
+ # @param [ Array<Document> ] docs The docs returning from the db.
144
+ #
145
+ # @since 2.4.1
146
+ def eager_load(docs)
147
+ criteria.inclusions.reject! do |metadata|
148
+ unless docs.empty?
149
+ parent_ids = docs.map(&:id)
150
+ if metadata.macro == :referenced_in
151
+ child_ids = load_ids(metadata.foreign_key)
152
+ metadata.eager_load(child_ids)
153
+ else
154
+ metadata.eager_load(parent_ids)
155
+ end
156
+ end
157
+ end
158
+ end
159
+
138
160
  # Execute the context. This will take the selector and options
139
161
  # and pass them on to the Ruby driver's +find()+ method on the collection. The
140
162
  # collection itself will be retrieved from the class provided, and once the
@@ -148,15 +170,7 @@ module Mongoid #:nodoc:
148
170
  collection, options = klass.collection, process_options
149
171
  if criteria.inclusions.any?
150
172
  collection.find(selector, options).entries.tap do |docs|
151
- parent_ids = docs.map(&:id)
152
- criteria.inclusions.reject! do |metadata|
153
- if metadata.macro == :referenced_in
154
- child_ids = load_ids(metadata.foreign_key)
155
- metadata.eager_load(child_ids)
156
- else
157
- metadata.eager_load(parent_ids)
158
- end
159
- end
173
+ eager_load(docs)
160
174
  end
161
175
  else
162
176
  collection.find(selector, options)
@@ -189,7 +203,12 @@ module Mongoid #:nodoc:
189
203
  # @return [ Document ] The first document in the collection.
190
204
  def first
191
205
  attributes = klass.collection.find_one(selector, options_with_default_sorting)
192
- attributes ? Mongoid::Factory.from_db(klass, attributes) : nil
206
+ return nil unless attributes
207
+ selecting do
208
+ Mongoid::Factory.from_db(klass, attributes).tap do |doc|
209
+ eager_load([ doc ]) if criteria.inclusions.any?
210
+ end
211
+ end
193
212
  end
194
213
  alias :one :first
195
214
 
@@ -237,9 +256,11 @@ module Mongoid #:nodoc:
237
256
  # @example Iterate over the results.
238
257
  # context.iterate { |doc| p doc }
239
258
  def iterate(&block)
240
- return caching(&block) if cached?
241
- if block_given?
242
- execute.each { |doc| yield doc }
259
+ selecting do
260
+ return caching(&block) if cached?
261
+ if block_given?
262
+ execute.each { |doc| yield doc }
263
+ end
243
264
  end
244
265
  end
245
266
 
@@ -255,7 +276,12 @@ module Mongoid #:nodoc:
255
276
  opts = options_with_default_sorting
256
277
  opts[:sort] = opts[:sort].map{ |option| [ option[0], option[1].invert ] }.uniq
257
278
  attributes = klass.collection.find_one(selector, opts)
258
- attributes ? Mongoid::Factory.from_db(klass, attributes) : nil
279
+ return nil unless attributes
280
+ selecting do
281
+ Mongoid::Factory.from_db(klass, attributes).tap do |doc|
282
+ eager_load([ doc ]) if criteria.inclusions.any?
283
+ end
284
+ end
259
285
  end
260
286
 
261
287
  # Return the max value for a field.
@@ -434,6 +460,28 @@ module Mongoid #:nodoc:
434
460
  end
435
461
  options.dup
436
462
  end
463
+
464
+ # If we are limiting results, we need to set the field limitations on a
465
+ # thread local to avoid overriding the default values.
466
+ #
467
+ # @example Execute with selection.
468
+ # context.selecting do
469
+ # collection.find
470
+ # end
471
+ #
472
+ # @return [ Object ] The yielded value.
473
+ #
474
+ # @since 2.4.4
475
+ def selecting
476
+ begin
477
+ unless options[:fields].blank?
478
+ Threaded.selection = options[:fields]
479
+ end
480
+ yield
481
+ ensure
482
+ Threaded.selection = nil
483
+ end
484
+ end
437
485
  end
438
486
  end
439
487
  end
@@ -6,11 +6,10 @@ module Mongoid #:nodoc:
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  COPYABLES = [
9
- :@accessed,
10
- :@attributes,
11
- :@metadata,
12
- :@modifications,
13
- :@previous_modifications
9
+ :attributes,
10
+ :metadata,
11
+ :changed_attributes,
12
+ :previous_changes
14
13
  ]
15
14
 
16
15
  protected
@@ -24,18 +23,49 @@ module Mongoid #:nodoc:
24
23
  # @example Clone the document.
25
24
  # document.clone
26
25
  #
26
+ # @param [ Document ] other The document getting cloned.
27
+ #
28
+ # @return [ Document ] The new document.
29
+ def initialize_copy(other)
30
+ __copy__(other)
31
+ end
32
+
33
+ # Clone or dup the current +Document+. This will return all attributes with
34
+ # the exception of the document's id and versions, and will reset all the
35
+ # instance variables.
36
+ #
37
+ # This clone also includes embedded documents.
38
+ #
27
39
  # @example Dup the document.
28
40
  # document.dup
29
41
  #
30
42
  # @param [ Document ] other The document getting cloned.
31
43
  #
32
44
  # @return [ Document ] The new document.
33
- def initialize_copy(other)
45
+ def initialize_dup(other)
46
+ __copy__(other)
47
+ end
48
+
49
+ private
50
+
51
+ # Handle the copy of the object.
52
+ #
53
+ # @api private
54
+ #
55
+ # @example Copy the object.
56
+ # document.__copy__(other)
57
+ #
58
+ # @param [ Document ] other The other document.
59
+ #
60
+ # @return [ Document ] self.
61
+ #
62
+ # @since 3.0.0
63
+ def __copy__(other)
34
64
  other.as_document
35
65
  instance_variables.each { |name| remove_instance_variable(name) }
36
66
  COPYABLES.each do |name|
37
- value = other.instance_variable_get(name)
38
- instance_variable_set(name, value ? value.dup : nil)
67
+ value = other.send(name)
68
+ instance_variable_set(:"@#{name}", value ? value.dup : nil)
39
69
  end
40
70
  attributes.delete("_id")
41
71
  if attributes.delete("versions")
@@ -262,6 +262,7 @@ module Mongoid #:nodoc:
262
262
  scope_options = @options.dup
263
263
  sorting = scope_options.delete(:sort)
264
264
  scope_options[:order_by] = sorting if sorting
265
+ scope_options[:includes] = inclusions.map(&:name) if inclusions.any?
265
266
  { :where => @selector }.merge(scope_options)
266
267
  end
267
268
  alias :to_ary :to_a
@@ -96,7 +96,9 @@ module Mongoid #:nodoc:
96
96
  def without(*args)
97
97
  clone.tap do |crit|
98
98
  if args.any?
99
- crit.options[:fields] = {}
99
+ unless args == [ :id ] || args == [ :_id ]
100
+ crit.options[:fields] = {}
101
+ end
100
102
  args.flatten.each do |f|
101
103
  crit.options[:fields][f] = 0
102
104
  end
@@ -210,7 +210,7 @@ module Mongoid #:nodoc:
210
210
  #
211
211
  # @since 2.2.0
212
212
  def includes(*relations)
213
- relations.each do |name|
213
+ relations.flatten.each do |name|
214
214
  inclusions.push(klass.reflect_on_association(name))
215
215
  end
216
216
  clone
@@ -268,7 +268,7 @@ module Mongoid #:nodoc:
268
268
  if key.mongoid_id?
269
269
  if crit.selector.has_key?("$and")
270
270
  crit.selector["$and"] << { key => value }
271
- else
271
+ elsif crit.selector[key] != value
272
272
  crit.selector["$and"] = [{ key => crit.selector.delete(key) }, { key => value }]
273
273
  end
274
274
  elsif crit.selector[key].respond_to?(:merge) && value.respond_to?(:merge)
@@ -109,7 +109,7 @@ module Mongoid #:nodoc:
109
109
  #
110
110
  # @return [ Criteria ] The cloned criteria.
111
111
  def limit(value = 20)
112
- clone.tap { |crit| crit.options[:limit] = value }
112
+ clone.tap { |crit| crit.options[:limit] = value.to_i }
113
113
  end
114
114
 
115
115
  # Returns the offset option. If a per_page option is in the list then it
@@ -163,7 +163,7 @@ module Mongoid #:nodoc:
163
163
  #
164
164
  # @return [ Criteria ] The cloned criteria.
165
165
  def skip(value = 0)
166
- clone.tap { |crit| crit.options[:skip] = value }
166
+ clone.tap { |crit| crit.options[:skip] = value.to_i }
167
167
  end
168
168
 
169
169
  # Adds a criterion to the +Criteria+ that specifies a type or an Array of
@@ -85,11 +85,32 @@ module Mongoid #:nodoc:
85
85
  # @since 1.0.0
86
86
  def try_to_typecast(key, value)
87
87
  access = key.to_s
88
- if !fields.has_key?(access) && !aliased_fields.has_key?(access)
89
- return value
88
+ if field = fields[key.to_s] || fields[aliased_fields[key.to_s]]
89
+ typecast_value_for(field, value)
90
+ elsif proper_and_or_value?(key, value)
91
+ handle_and_or_value(value)
92
+ else
93
+ value
94
+ end
95
+ end
96
+
97
+ def proper_and_or_value?(key, value)
98
+ ["$and", "$or"].include?(key) &&
99
+ value.is_a?(Array) &&
100
+ value.all?{ |e| e.is_a?(Hash) }
101
+ end
102
+
103
+ def handle_and_or_value(values)
104
+ [].tap do |result|
105
+ result.push(*values.map do |value|
106
+ Hash[value.map do |_key, _value|
107
+ if klass.fields[_key.to_s].try(:localized?)
108
+ _key = "#{_key}.#{::I18n.locale}"
109
+ end
110
+ [_key, try_to_typecast(_key, _value)]
111
+ end]
112
+ end)
90
113
  end
91
- field = fields[access] || fields[aliased_fields[access]]
92
- typecast_value_for(field, value)
93
114
  end
94
115
 
95
116
  # Get the typecast value for the defined field.
@@ -45,8 +45,10 @@ module Mongoid #:nodoc
45
45
  # @example Iterate over the cursor.
46
46
  # cursor.each { |doc| p doc.title }
47
47
  def each
48
- cursor.each do |document|
49
- yield Mongoid::Factory.from_db(klass, document)
48
+ retry_on_connection_failure do
49
+ while document = cursor.next_document
50
+ yield Factory.from_db(klass, document)
51
+ end
50
52
  end
51
53
  end
52
54
 
data/lib/mongoid/dirty.rb CHANGED
@@ -24,7 +24,21 @@ module Mongoid #:nodoc:
24
24
  #
25
25
  # @since 2.4.0
26
26
  def changed?
27
- changed_attributes.any?
27
+ changed_attributes.any? || children_changed?
28
+ end
29
+
30
+ # Have any children (embedded documents) of this document changed?
31
+ #
32
+ # @example Have any children changed?
33
+ # model.children_changed?
34
+ #
35
+ # @return [ true, false ] If any children have changed.
36
+ #
37
+ # @since 2.4.1
38
+ def children_changed?
39
+ _children.any? do |child|
40
+ child.changed?
41
+ end
28
42
  end
29
43
 
30
44
  # Get the attribute changes.
@@ -117,8 +131,14 @@ module Mongoid #:nodoc:
117
131
  field = fields[name]
118
132
  key = embedded? ? "#{atomic_position}.#{name}" : name
119
133
  if field && field.resizable?
120
- pushes, pulls = new - (old || []), (old || []) - new
121
- field.add_atomic_changes(self, key, modifications, pushes, pulls)
134
+ field.add_atomic_changes(
135
+ self,
136
+ name,
137
+ key,
138
+ modifications,
139
+ new,
140
+ old
141
+ )
122
142
  else
123
143
  modifications[key] = new
124
144
  end
@@ -207,36 +227,59 @@ module Mongoid #:nodoc:
207
227
  # Generate all the dirty methods needed for the attribute.
208
228
  #
209
229
  # @example Generate the dirty methods.
210
- # Model.create_dirty_methods("name")
230
+ # Model.create_dirty_methods("name", "name")
211
231
  #
212
- # @param [ String ] name The name of the attribute.
232
+ # @param [ String ] name The name of the field.
233
+ # @param [ String ] name The name of the accessor.
213
234
  #
214
235
  # @return [ Module ] The fields module.
215
236
  #
216
237
  # @since 2.4.0
217
- def create_dirty_methods(name)
238
+ def create_dirty_methods(name, meth)
218
239
  generated_methods.module_eval do
219
- class_eval <<-EOM
220
- def #{name}_change
221
- attribute_change(#{name.inspect})
240
+ if meth =~ /\W/
241
+ define_method("#{meth}_change") do
242
+ attribute_change(name)
222
243
  end
223
244
 
224
- def #{name}_changed?
225
- attribute_changed?(#{name.inspect})
245
+ define_method("#{meth}_changed?") do
246
+ attribute_changed?(name)
226
247
  end
227
248
 
228
- def #{name}_was
229
- attribute_was(#{name.inspect})
249
+ define_method("#{meth}_was") do
250
+ attribute_was(name)
230
251
  end
231
252
 
232
- def #{name}_will_change!
233
- attribute_will_change!(#{name.inspect})
253
+ define_method("#{meth}_will_change!") do
254
+ attribute_will_change!(name)
234
255
  end
235
256
 
236
- def reset_#{name}!
237
- reset_attribute!(#{name.inspect})
257
+ define_method("reset_#{meth}!") do
258
+ reset_attribute!(name)
238
259
  end
239
- EOM
260
+ else
261
+ class_eval <<-EOM
262
+ def #{meth}_change
263
+ attribute_change(#{name.inspect})
264
+ end
265
+
266
+ def #{meth}_changed?
267
+ attribute_changed?(#{name.inspect})
268
+ end
269
+
270
+ def #{meth}_was
271
+ attribute_was(#{name.inspect})
272
+ end
273
+
274
+ def #{meth}_will_change!
275
+ attribute_will_change!(#{name.inspect})
276
+ end
277
+
278
+ def reset_#{meth}!
279
+ reset_attribute!(#{name.inspect})
280
+ end
281
+ EOM
282
+ end
240
283
  end
241
284
  end
242
285
  end
@@ -44,7 +44,11 @@ module Mongoid #:nodoc:
44
44
  #
45
45
  # @return [ true, false ] True if the classes are equal, false if not.
46
46
  def ===(other)
47
- other.is_a?(self.class)
47
+ if other.class == Class
48
+ self.class === other
49
+ else
50
+ id == other.id
51
+ end
48
52
  end
49
53
 
50
54
  # Delegates to ==. Used when needing checks in hashes.
@@ -276,7 +280,7 @@ module Mongoid #:nodoc:
276
280
  #
277
281
  # @since 2.0.0.rc.4
278
282
  def ===(other)
279
- self == (other.is_a?(Class) ? other : other.class)
283
+ other.class == Class ? self <= other : other.is_a?(self)
280
284
  end
281
285
 
282
286
  # Instantiate a new object, only when loaded from the database or when