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
@@ -17,7 +17,7 @@ module Mongoid
17
17
  # @since 2.1.0
18
18
  def persist
19
19
  prepare do
20
- document.attributes.delete(field)
20
+ fields.each { |f| document.attributes.delete(f) }
21
21
  execute("$unset")
22
22
  end
23
23
  end
@@ -16,7 +16,9 @@ module Mongoid
16
16
  # false
17
17
  # );
18
18
  class Insert
19
- include Insertion, Operations
19
+ include Insertion
20
+ include Operations
21
+ include Mongoid::Atomic::Positionable
20
22
 
21
23
  # Insert the new document in the database. If the document's parent is a
22
24
  # new record, we will call save on the parent, otherwise we will $push
@@ -32,7 +34,8 @@ module Mongoid
32
34
  if parent.new_record?
33
35
  parent.insert
34
36
  else
35
- collection.find(parent.atomic_selector).update(inserts)
37
+ selector = parent.atomic_selector
38
+ collection.find(selector).update(positionally(selector, inserts))
36
39
  end
37
40
  end
38
41
  end
@@ -14,7 +14,9 @@ module Mongoid
14
14
  # false
15
15
  # );
16
16
  class Remove
17
- include Deletion, Operations
17
+ include Deletion
18
+ include Operations
19
+ include Mongoid::Atomic::Positionable
18
20
 
19
21
  # Remove the document from the database. If the parent is a new record,
20
22
  # it will get removed in Ruby only. If the parent is not a new record
@@ -29,7 +31,8 @@ module Mongoid
29
31
  prepare do |doc|
30
32
  parent.remove_child(doc) if notifying_parent?
31
33
  if parent.persisted?
32
- collection.find(parent.atomic_selector).update(deletes)
34
+ selector = parent.atomic_selector
35
+ collection.find(selector).update(positionally(selector, deletes))
33
36
  end
34
37
  end
35
38
  end
@@ -30,7 +30,9 @@ module Mongoid
30
30
  # );
31
31
  #
32
32
  class Update
33
- include Modification, Operations
33
+ include Modification
34
+ include Operations
35
+ include Mongoid::Atomic::Positionable
34
36
 
35
37
  # Persist the document that is to be updated to the database. This will
36
38
  # only write changed fields via MongoDB's $set modifier operation.
@@ -42,9 +44,11 @@ module Mongoid
42
44
  def persist
43
45
  prepare do
44
46
  unless updates.empty?
45
- collection.find(selector).update(updates)
47
+ collection.find(selector).update(positionally(selector, updates))
46
48
  conflicts.each_pair do |key, value|
47
- collection.find(selector).update({ key => value })
49
+ collection.find(selector).update(
50
+ positionally(selector, { key => value })
51
+ )
48
52
  end
49
53
  end
50
54
  end
@@ -5,6 +5,11 @@ namespace :db do
5
5
  task :drop => "mongoid:drop"
6
6
  end
7
7
 
8
+ unless Rake::Task.task_defined?("db:purge")
9
+ desc "Drop all collections except the system collections"
10
+ task :purge => "mongoid:purge"
11
+ end
12
+
8
13
  unless Rake::Task.task_defined?("db:seed")
9
14
  # if another ORM has defined db:seed, don"t run it twice.
10
15
  desc "Load the seed data from db/seeds.rb"
@@ -61,36 +66,24 @@ namespace :db do
61
66
  end
62
67
 
63
68
  namespace :mongoid do
64
-
65
69
  desc "Create the indexes defined on your mongoid models"
66
70
  task :create_indexes => :environment do
67
- engines_models_paths = Rails.application.railties.engines.map do |engine|
68
- engine.paths["app/models"].expanded
69
- end
70
- root_models_paths = Rails.application.paths["app/models"]
71
- models_paths = engines_models_paths.push(root_models_paths).flatten
72
-
73
- models_paths.each do |path|
74
- ::Rails::Mongoid.create_indexes("#{path}/**/*.rb")
75
- end
71
+ ::Rails::Mongoid.create_indexes
76
72
  end
77
73
 
78
74
  desc "Remove the indexes defined on your mongoid models without questions!"
79
75
  task :remove_indexes => :environment do
80
- engines_models_paths = Rails.application.railties.engines.map do |engine|
81
- engine.paths["app/models"].expanded
82
- end
83
- root_models_paths = Rails.application.paths["app/models"]
84
- models_paths = engines_models_paths.push(root_models_paths).flatten
85
-
86
- models_paths.each do |path|
87
- ::Rails::Mongoid.remove_indexes("#{path}/**/*.rb")
88
- end
76
+ ::Rails::Mongoid.remove_indexes
89
77
  end
90
78
 
91
79
  desc "Drops the database for the current Rails.env"
92
80
  task :drop => :environment do
93
81
  ::Mongoid::Sessions.default.drop
94
82
  end
83
+
84
+ desc "Drop all collections except the system collections"
85
+ task :purge => :environment do
86
+ ::Mongoid.purge!
87
+ end
95
88
  end
96
89
  end
@@ -4,6 +4,7 @@ require "mongoid/relations/auto_save"
4
4
  require "mongoid/relations/cascading"
5
5
  require "mongoid/relations/constraint"
6
6
  require "mongoid/relations/conversions"
7
+ require "mongoid/relations/counter_cache"
7
8
  require "mongoid/relations/cyclic"
8
9
  require "mongoid/relations/proxy"
9
10
  require "mongoid/relations/bindings"
@@ -43,6 +44,7 @@ module Mongoid
43
44
  include Reflections
44
45
  include Synchronization
45
46
  include Touchable
47
+ include CounterCache
46
48
 
47
49
  attr_accessor :metadata
48
50
 
@@ -113,11 +113,7 @@ module Mongoid
113
113
  else
114
114
  _building do
115
115
  _loading do
116
- # @note In the case where the record is new and we are binding,
117
- # we want to avoid an extra database query when we know it is not
118
- # necessary.
119
- key = (new_record? && _binding?) ? nil : attributes[metadata.key]
120
- __build__(name, key, metadata)
116
+ __build__(name, attributes[metadata.key], metadata)
121
117
  end
122
118
  end
123
119
  end
@@ -128,6 +124,30 @@ module Mongoid
128
124
  end
129
125
  end
130
126
 
127
+ # @todo: Durran: Refactor before release, but this fixes the issue with
128
+ # the extra queries.
129
+ def get_relation_for_set(name, metadata, object)
130
+ variable = "@#{name}"
131
+ value = if instance_variable_defined?(variable)
132
+ instance_variable_get(variable)
133
+ else
134
+ _building do
135
+ _loading do
136
+ if needs_no_database_query?(object, metadata)
137
+ __build__(name, object, metadata)
138
+ else
139
+ __build__(name, attributes[metadata.key], metadata)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def needs_no_database_query?(object, metadata)
147
+ object.is_a?(Document) && !object.embedded? &&
148
+ object.id == attributes[metadata.key]
149
+ end
150
+
131
151
  # Is the current code executing without autobuild functionality?
132
152
  #
133
153
  # @example Is autobuild disabled?
@@ -151,10 +171,10 @@ module Mongoid
151
171
  #
152
172
  # @since 3.0.0
153
173
  def without_autobuild
154
- Threaded.begin("without_autobuild")
174
+ Threaded.begin_execution("without_autobuild")
155
175
  yield
156
176
  ensure
157
- Threaded.exit("without_autobuild")
177
+ Threaded.exit_execution("without_autobuild")
158
178
  end
159
179
 
160
180
  module ClassMethods
@@ -241,8 +261,10 @@ module Mongoid
241
261
  def setter(name, metadata)
242
262
  re_define_method("#{name}=") do |object|
243
263
  without_autobuild do
244
- if metadata.many? || get_relation(name, metadata)
264
+ if metadata.many?
245
265
  set_relation(name, get_relation(name, metadata).substitute(object.substitutable))
266
+ elsif value = get_relation_for_set(name, metadata, object)
267
+ set_relation(name, value.substitute(object.substitutable))
246
268
  else
247
269
  __build__(name, object.substitutable, metadata)
248
270
  end
@@ -190,12 +190,16 @@ module Mongoid
190
190
  # @since 3.0.0
191
191
  def bind_from_relational_parent(doc)
192
192
  check_inverse!(doc)
193
- bind_foreign_key(doc, base.id)
193
+ bind_foreign_key(doc, record_id(base))
194
194
  bind_polymorphic_type(doc, base.class.name)
195
195
  bind_inverse(doc, base)
196
196
  bind_inverse_of_field(doc, metadata.name)
197
197
  end
198
198
 
199
+ def record_id(base)
200
+ base.__send__(metadata.primary_key)
201
+ end
202
+
199
203
  # Ensure that the metadata on the base is correct, for the cases
200
204
  # where we have multiple belongs to definitions and were are setting
201
205
  # different parents in memory in order.
@@ -21,7 +21,7 @@ module Mongoid
21
21
  def bind_one
22
22
  binding do
23
23
  check_inverses!(target)
24
- bind_foreign_key(base, target.id)
24
+ bind_foreign_key(base, record_id(target))
25
25
  bind_polymorphic_inverse_type(base, target.class.name)
26
26
  if inverse = metadata.inverse(target)
27
27
  if set_base_metadata
@@ -20,7 +20,7 @@ module Mongoid
20
20
  binding do
21
21
  inverse_keys = doc.you_must(metadata.inverse_foreign_key)
22
22
  if inverse_keys
23
- inverse_keys.push(base.id)
23
+ inverse_keys.push(record_id(base))
24
24
  doc.reset_relation_criteria(metadata.inverse)
25
25
  end
26
26
  base.synced[metadata.foreign_key] = true
@@ -36,10 +36,10 @@ module Mongoid
36
36
  # @since 2.0.0.rc.1
37
37
  def unbind_one(doc)
38
38
  binding do
39
- base.send(metadata.foreign_key).delete_one(doc.id)
39
+ base.send(metadata.foreign_key).delete_one(record_id(doc))
40
40
  inverse_keys = doc.you_must(metadata.inverse_foreign_key)
41
41
  if inverse_keys
42
- inverse_keys.delete_one(base.id)
42
+ inverse_keys.delete_one(record_id(base))
43
43
  doc.reset_relation_criteria(metadata.inverse)
44
44
  end
45
45
  base.synced[metadata.foreign_key] = true
@@ -0,0 +1,107 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Relations
4
+ module CounterCache
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+
9
+ # Reset the given counter using the .count() query from the
10
+ # db. This method is usuful in case that a counter got
11
+ # corrupted, or a new counter was added to the collection.
12
+ #
13
+ # @example Reset the given counter cache
14
+ # Post.reset_counters('50e0edd97c71c17ea9000001', :comments)
15
+ #
16
+ # @param [ String ] The id of the object that will be reset.
17
+ # @param [ Symbol, Array ] One or more counter caches to reset
18
+ #
19
+ # @since 3.1.0
20
+ def reset_counters(id, *counters)
21
+ object = find(id)
22
+ counters.each do |name|
23
+ meta = reflect_on_association(name)
24
+ inverse = meta.klass.reflect_on_association(meta.inverse)
25
+ counter_name = inverse.counter_cache_column_name
26
+ object.update_attribute(counter_name, object.send(name).count)
27
+ end
28
+ end
29
+
30
+ # Update the given counters by the value factor. It uses the
31
+ # atomic $inc command.
32
+ #
33
+ # @example Add 5 to comments counter and remove 2 from likes
34
+ # counter.
35
+ # Post.update_counters('50e0edd97c71c17ea9000001',
36
+ # :comments_count => 5, :likes_count => -2)
37
+ #
38
+ # @param [ String ] The id of the object to update.
39
+ # @param [ Hash ] Key = counter_cahe and Value = factor.
40
+ #
41
+ # @since 3.1.0
42
+ def update_counters(id, counters)
43
+ counters.map do |key, value|
44
+ where(:_id => id).inc(key, value)
45
+ end
46
+ end
47
+
48
+ # Increment the counter name from the entries that match the
49
+ # id by one. This method is used on associations callbacks
50
+ # when counter_cache is enable
51
+ #
52
+ # @example Increment comments counter
53
+ # Post.increment_counter(:comments_count, '50e0edd97c71c17ea9000001')
54
+ #
55
+ # @param [ Symbol ] Counter cache name
56
+ # @param [ String ] The id of the object that will be reset.
57
+ #
58
+ # @since 3.1.0
59
+ def increment_counter(counter_name, id)
60
+ update_counters(id, counter_name.to_sym => 1)
61
+ end
62
+
63
+ # Decrement the counter name from the entries that match the
64
+ # id by one. This method is used on associations callbacks
65
+ # when counter_cache is enable
66
+ #
67
+ # @example Decrement comments counter
68
+ # Post.decrement_counter(:comments_count, '50e0edd97c71c17ea9000001')
69
+ #
70
+ # @param [ Symbol ] Counter cache name
71
+ # @param [ String ] The id of the object that will be reset.
72
+ #
73
+ # @since 3.1.0
74
+ def decrement_counter(counter_name, id)
75
+ update_counters(id, counter_name.to_sym => -1)
76
+ end
77
+
78
+ private
79
+
80
+ # Add the callbacks responsible for update the counter cache field
81
+ #
82
+ # @api private
83
+ #
84
+ # @example Add the touchable.
85
+ # Person.add_counter_callbacks(meta)
86
+ #
87
+ # @param [ Metadata ] metadata The metadata for the relation.
88
+ #
89
+ # @since 3.1.0
90
+ def add_counter_cache_callbacks(meta)
91
+ name = meta.name
92
+ cache_column = meta.counter_cache_column_name.to_sym
93
+
94
+ after_create do
95
+ record = __send__(name)
96
+ record.class.increment_counter(cache_column, record.id) if record.try(:persisted?)
97
+ end
98
+
99
+ before_destroy do
100
+ record = __send__(name)
101
+ record.class.decrement_counter(cache_column, record.id) if record.try(:persisted?)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -6,6 +6,7 @@ module Mongoid
6
6
  # Contains behaviour for executing operations in batch on embedded
7
7
  # documents.
8
8
  module Batchable
9
+ include Mongoid::Atomic::Positionable
9
10
 
10
11
  # Insert new documents as a batch push ($pushAll). This ensures that
11
12
  # all callbacks are run at the appropriate time and only 1 request is
@@ -36,7 +37,9 @@ module Mongoid
36
37
  def batch_clear(docs)
37
38
  pre_process_batch_remove(docs, :delete)
38
39
  unless docs.empty?
39
- collection.find(selector).update("$unset" => { path => true })
40
+ collection.find(selector).update(
41
+ positionally(selector, "$unset" => { path => true })
42
+ )
40
43
  post_process_batch_remove(docs, :delete)
41
44
  end
42
45
  _unscoped.clear
@@ -53,8 +56,10 @@ module Mongoid
53
56
  # @since 3.0.0
54
57
  def batch_remove(docs, method = :delete)
55
58
  removals = pre_process_batch_remove(docs, method)
56
- if !docs.empty?
57
- collection.find(selector).update("$pullAll" => { path => removals })
59
+ if !docs.empty? && !_assigning?
60
+ collection.find(selector).update(
61
+ positionally(selector, "$pullAll" => { path => removals })
62
+ )
58
63
  post_process_batch_remove(docs, method)
59
64
  end
60
65
  Threaded.clear_options!
@@ -126,7 +131,9 @@ module Mongoid
126
131
  self.inserts_valid = true
127
132
  inserts = pre_process_batch_insert(docs)
128
133
  if insertable?
129
- collection.find(selector).update(operation => { path => inserts })
134
+ collection.find(selector).update(
135
+ positionally(selector, operation => { path => inserts })
136
+ )
130
137
  post_process_batch_insert(docs)
131
138
  end
132
139
  inserts
@@ -289,6 +296,7 @@ module Mongoid
289
296
  def pre_process_batch_remove(docs, method)
290
297
  docs.map do |doc|
291
298
  self.path = doc.atomic_path unless path
299
+ execute_callback :before_remove, doc
292
300
  if !_assigning? && !metadata.versioned?
293
301
  doc.cascade!
294
302
  doc.run_before_callbacks(:destroy) if method == :destroy
@@ -296,6 +304,7 @@ module Mongoid
296
304
  target.delete_one(doc)
297
305
  _unscoped.delete_one(doc)
298
306
  unbind_one(doc)
307
+ execute_callback :after_remove, doc
299
308
  doc.as_document
300
309
  end
301
310
  end
@@ -130,6 +130,7 @@ module Mongoid
130
130
  #
131
131
  # @since 2.0.0.rc.1
132
132
  def delete(document)
133
+ execute_callback :before_remove, document
133
134
  doc = target.delete_one(document)
134
135
  if doc && !_binding?
135
136
  _unscoped.delete_one(doc) unless doc.paranoid?
@@ -145,6 +146,7 @@ module Mongoid
145
146
  end
146
147
  end
147
148
  reindex
149
+ execute_callback :after_remove, document
148
150
  doc
149
151
  end
150
152
 
@@ -175,6 +177,28 @@ module Mongoid
175
177
  remove_all(conditions, :delete)
176
178
  end
177
179
 
180
+ # Delete all the documents for which the provided block returns true.
181
+ #
182
+ # @example Delete the matching documents.
183
+ # person.addresses.delete_if do |doc|
184
+ # doc.state = "GA"
185
+ # end
186
+ #
187
+ # @return [ Many, Enumerator ] The relation or an enumerator if no
188
+ # block was provided.
189
+ #
190
+ # @since 3.1.0
191
+ def delete_if
192
+ if block_given?
193
+ target.each do |doc|
194
+ delete(doc) if yield(doc)
195
+ end
196
+ self
197
+ else
198
+ super
199
+ end
200
+ end
201
+
178
202
  # Destroy all the documents in the association whilst running callbacks.
179
203
  #
180
204
  # @example Destroy all documents from the relation.
@@ -318,10 +342,12 @@ module Mongoid
318
342
  #
319
343
  # @since 2.0.0.rc.1
320
344
  def append(document)
345
+ execute_callback :before_add, document
321
346
  target.push(*scope([document]))
322
347
  _unscoped.push(document)
323
348
  integrate(document)
324
349
  document._index = _unscoped.size - 1
350
+ execute_callback :after_add, document
325
351
  end
326
352
 
327
353
  # Instantiate the binding associated with this relation.
@@ -608,7 +634,10 @@ module Mongoid
608
634
  #
609
635
  # @since 2.1.0
610
636
  def valid_options
611
- [ :as, :cascade_callbacks, :cyclic, :order, :versioned, :store_as ]
637
+ [
638
+ :as, :cascade_callbacks, :cyclic, :order, :versioned, :store_as,
639
+ :before_add, :after_add, :before_remove, :after_remove
640
+ ]
612
641
  end
613
642
 
614
643
  # Get the default validation setting for the relation. Determines if