I'm using Mongoid version 2.8.1. I've noticed that the destroy
method does not work when the model has a has_and_belongs_to_many
relationship.
For example, I have two models
class Article
include Mongoid::Document
has_and_belongs_to_many :subjects
...
end
and
class Subject
include Mongoid::Document
has_and_belongs_to_many :articles
...
end
Now I want to delete an article document. So I tried
a = Article.find('someid1234')
this returns a valid object, then I do
>> a.destroy
=> true
>> a.errors.any?
=> false
>> a.errors.count
=> 0
But when I do
a.reload
I still get the object back!
If I use
a.delete
instead, it would work, but delete
doesn't run callbacks, and I want to run callbacks.
I have nailed this down to the has_and_belongs_to_many
relationship. Because of this relationship, destroy
invokes a callback method.
Article._destroy_callbacks
=> [#<ActiveSupport::Callbacks::Callback:0x007fc4a0e71258 @klass=Shortlist, @kind=:after, @chain=[...], @per_key={:if=>[], :unless=>[]}, @options={:if=>[], :unless=>[]}, @raw_filter=#<Proc:0x007fc4a0e714d8@/opt/boxen/rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/mongoid-2.4.12/lib/mongoid/relations/synchronization.rb:160>, @filter="_callback_after_31(self)", @compiled_options="true", @callback_id=32>]
The method in question in /opt/boxen/rbenv/versions/1.9.3-p392/lib/ruby/gems/1.9.1/gems/mongoid-2.4.12/lib/mongoid/relations/synchronization.rb:160
is
def synced_destroy(metadata)
tap do
set_callback(
:destroy,
:after
) do |doc|
doc.remove_inverse_keys(metadata)
end
end
end
After I call a.destroy
, a's id is removed from its subjects' article_ids array fields. But article a
is not destroyed. So it seems the callback executes correctly, but the object is not destroyed afterwards.
I debugged this problem by looking at mongoid's source code. The destroy
method looks like
def destroy(options = {})
self.flagged_for_destroy = true
run_callbacks(:destroy) do
remove(options) # THIS IS NOT EXECUTED!
end.tap do
self.flagged_for_destroy = false
end
end
def remove(options = {})
Operations.remove(self, options).persist
end
alias :delete :remove
The comment is mine. This seems to be a bug with Mongoid, that destroy
only executes the callbacks, and does not destroy the object itself.
However, when there's no callback methods for destroy (for example, on a model without
the has_and_belongs_to_many
relationship), the object is destroyed correctly. Strange
Has anyone has experienced the same problem and if there's any workaround.
Thanks.