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