I am using Mongoid and I have 2 models, Flow and Node with a referenced parent-child relationship.
class Node
belongs_to :flow
end
class Flow
has_many :nodes
end
When I want to remove a node with a flow I do this:
flow.nodes.clear
This destroy the associated nodes. What if I want to remove the association between the node and the flow without destroying the associated nodes? Is there a way of doing that?
You should be able to use flow.nodes.clear as long as you don't have :dependent => :destroy set. From the Rails Guide on Association Basics:
4.3.1.7 collection.clear
The collection.clear method removes every object from the collection.
This destroys the associated objects if they are associated with
:dependent => :destroy, deletes them directly from the database if
:dependent => :delete_all, and otherwise sets their foreign keys to
NULL.
If this isn't working for you, you could try this and it should remove the association:
flow.nodes = nil
EDIT 1
If not, you'll have to create a method to remove the association manually.
flow.nodes.update_all :flow_id => nil
I don't believe there's any in built method for this, but you can do this:
Node.where(:flow_id => flow.id).update_all(:flow_id => nil)
Related
I'm trying to use dependent: :destroy without success.
Lets put a simple example. I create a simple application with the following:
rails g model parent
rails g model child parent:references
Add following lines to parent.rb
has_many :children, dependent: :destroy
I do the following test in rails console (rails c)
p = Parent.create!
c = Child.create!
c.parent = p
c.save
#check association
Child.first == Child.first.parent.children.first
p.delete
#This should return 0
Child.count == 0
And Child.count returns 1.
What I'm missing?
Thanks
4.2.2.4 :dependent
If you set the :dependent option to:
:destroy, when the object is destroyed, #destroy will be called on its associated objects.
:delete, when the object is destroyed, all its associated objects will be deleted directly from the database without calling their #destroy method.
As per your settings, you have to do p.destroy.
The :dependent option can have different values which specify how the deletion is done. For more information, see the documentation for this option on the different specific association types. When no option is given, the behaviour is to do nothing with the associated records when destroying a record.
For has_many, destroy and destroy_all will always call the destroy method of the record(s) being removed so that callbacks are run. However delete and delete_all will either do the deletion according to the strategy specified by the :dependent option, or if no :dependent option is given, then it will follow the default strategy. The default strategy is :nullify (set the foreign keys to nil), except for has_many :through, where the default strategy is delete_all (delete the join records, without running their callbacks).
Calling the delete method on an ActiveRecord derived object will issue a direct DELETE statement do the database, skipping any ActiveRecord callbacks and configurations such as dependent: destroy.
I believe you want the destroy method.
You could also set up a foreign key in the database and set it to cascade on delete, it might make sense, depending on your needs.
I'm using Rails polymorphic associations, that way some models have many cash_histories children, like this:
has_many :cash_histories, as: :cashable
But when I try to delete all cash histories from a parent #resource, like this:
#resource.cash_histories.delete_all
I get the following query:
UPDATE "cash_histories" SET "cashable_id" = NULL WHERE "cash_histories"."cashable_id" = $1 AND "cash_histories"."cashable_type" = $2 [["cashable_id", 1], ["cashable_type", "ServiceOrder"]]
I can't understand this behavior, setting relationship id to null instead of removing, that will result in dead rows in my table. Why is that happening?
I'm using Rails 4.1.
From the Rails API docs for delete_all:
Deletes all the records from the collection. For has_many associations, the deletion is done according to the strategy specified by the :dependent option. Returns an array with the deleted records.
If no :dependent option is given, then it will follow the default strategy. The default strategy is :nullify. This sets the foreign keys to NULL. For, has_many :through, the default strategy is delete_all.
So you you just need to set the :dependent option on your has_many to either :delete_all or :destroy, depending on what behavior you want.
has_many :cash_histories, as: :cashable, dependent: :delete_all
From the Rails API docs for has_many:
Objects will be in addition destroyed if they're associated with dependent: :destroy, and deleted if they're associated with dependent: :delete_all.
its still weird because everywhere else it always describes what happens when the owner is destroyed.
Controls what happens to associated objects when their owner is
destroyed:
:destroy causes the associated objects to also be destroyed.
:delete_all causes the associated objects to be deleted directly from the database (callbacks are not executed).
:nullify causes the foreign keys to be set to NULL (callbacks are not executed).
:restrict_with_exception causes an exception to be raised if there are associated records.
:restrict_with_error causes an error to be added to the owner if there are associated objects.
I've got a record in my Rails app with an after_destroy hook that needs to be aware of why the record gets destroyed. More specifically, if the record is being destroyed in a cascade because its parent says dependent: :destroy, it needs to do things differently than if the record was individually destroyed.
What I tried to do is to see if its parent was destroyed?, only to figure out that dependent: :destroy callbacks are done before the parent is destroyed. Which makes sense because it should be able to fail. (i.e. restrict).
So, how do I do this?
Solution #1
If your model is simple enough and you don't need to invoke any callbacks in the child relation, you can just use dependent: delete_all in the parent.
Solution #2
For more complex scenarios you can use destroyed_by_association, which returns a ActiveRecord::Reflection::HasManyReflection object when it's part of cascade, or nil otherwise:
after_destroy :your_callback
def your_callback
if destroyed_by_association
# this is part of a cascade
else
# isolated deletion
end
end
I just tried this in Rails 4.2 and it works.
Source: https://github.com/rails/rails/issues/12828#issuecomment-28142658
One way to do this is using the before_destroy callback in the parent object to mark all child objects as destroyed through parent destroy. Like this:
class YourClass
before_destroy :mark_children
...
...
def mark_children
[:association1, :association2].each do |association| # Array should include association names that hate :dependent => :destroy option
self.send(association).each do |child|
# mark child object as deleted by parent
end
end
end
end
You can also use ActiveRecord reflections to determine automatically which associations are marked as :dependent => :destroy. Doing this is helpful when you need this function in many classes.
I'm working on a Rails app (Ruby 1.9.2 / Rails 3.0.3) that keeps track of people and their memberships to different teams over time. I'm having trouble coming up with a scalable way to combine duplicate Person objects. By 'combine' I mean to delete all but one of the duplicate Person objects and update all references to point to the remaining copy of that Person. Here's some code:
Models:
Person.rb
class Person < ActiveRecord::Base
has_many :rostered_people, :dependent => :destroy
has_many :rosters, :through => :rostered_people
has_many :crews, :through => :rosters
def crew(year = Time.now.year)
all_rosters = RosteredPerson.find_all_by_person_id(id).collect {|t| t.roster_id}
r = Roster.find_by_id_and_year(all_rosters, year)
r and r.crew
end
end
Crew.rb
class Crew < ActiveRecord::Base
has_many :rosters
has_many :people, :through => :rosters
end
Roster.rb
class Roster < ActiveRecord::Base
has_many :rostered_people, :dependent => :destroy
has_many :people, :through => :rostered_people
belongs_to :crew
end
RosteredPerson.rb
class RosteredPerson < ActiveRecord::Base
belongs_to :roster
belongs_to :person
end
Person objects can be created with just a first and last name, but they have one truly unique field called iqcs_num (think of it like a social security number) which can be optionally stored on either the create or update actions.
So within the create and update actions, I would like to implement a check for duplicate Person objects, delete the duplicates, then update all of the crew and roster references to point to the remaining Person.
Would it be safe to use .update_all on each model? That seems kind of brute force, especially since I will probably add more models in the future that depend on Person and I don't want to have to remember to maintain the find_duplicate function.
Thanks for the help!
The 'scalable' way to deal with this is to make the de-duplication process part of the app's normal function - whenever you save a record, make sure it's not a duplicate. You can do this by adding a callback to the Person model. Perhaps something like this:
before_save :check_for_duplicate
def check_for_duplicate
if iqcs_num
dup = Person.find_by_iqcs_num(self.iqcs_num)
if dup && dup.id != self.id
# move associated objects to existing record
dup.crews = dup.crews + self.crews
# update existing record
dup.update_attributes(:name => self.name, :other_field => self.other_field)
# delete this record
self.destroy
# return false, so that no other callbacks get triggered
return false
end
end
end
You'll want to make sure that you index the table you store Person objects in on the iqcs_num column, so that this lookup stays efficient as the number of records grows - it's going to be performed every time you update a Person record, after all.
I don't know that you can get out of keeping the callback up-to-date - it's entirely likely that different sorts of associated objects will have to be moved differently. On the other hand, it only exists in one place, and it's the same place you'd be adding the associations anyway - in the model.
Finally, to make sure your code is working, you'll probably want to add a validation on the Person model that prevents duplicates from existing. Something like:
validates :iqcs_num, :uniqueness => true, :allow_nil => true
I have a has_many :through model that works perfectly.
has_many :varietals
has_many :grapes, :through => :varietals, :dependent => :destroy
I would like to call another action instead of :destroy. In fact, I don't want to nullify the item OR destroy it, I want to update the record status field from 1 to 0 instead of destroy the record.
How to call a custom method instead of destroy ? I suppose I can do that in the model itself... Thanks.
Where to put this method ? In the master model or in the model where the record will be destroyed ?
EDIT:
I'm sorry but I think I didn't enough explain my problem. My problem is not only to so something after the master model is destroyed. I want to custom the destroy action in the Varietal model itself even if the master record is not destroyed.
Something like:
class Varietal < ActiveRecord::Base
private
def destroy
self.update_attributes(:status => 0)
end
end
Actually this action is not called...
You can use before_destroy to put your custom logic there. E.g.,
before_destroy :reset_status
def reset_status
...
end
Check here for more details.
You just need add a callback on before_destroy or after_destroy and manipulate your associations. By example
after_destroy :do_on_grapes
def do_on_grapes
grapes.map(&:to_do)
end
has_many :dependent is limited to only a few options. According to the documentation:
:dependent If set to :destroy all the associated objects are destroyed
alongside this object by calling their destroy method. If set to
:delete_all all associated objects are deleted without calling their
destroy method. If set to :nullify all associated objects’ foreign
keys are set to NULL without calling their save callbacks. If set to
:restrict this object raises an ActiveRecord::DeleteRestrictionError
exception and cannot be deleted if it has any associated objects.
If using with the :through option, the association on the join model
must be a belongs_to, and the records which get deleted are the join
records, rather than the associated records.
It looks like you would need to alter the destroy method to update the status field.
I believe that good approach to solve your problem is to provide a custom destroy method. There are several responses to questions like these, but you should keep in mind that ActiveRecord and Relationships like:
class Image < ActiveRecord::Base
has_many :comments, dependent: :destroy
use callback mechanisms that trigger destroy chaining to your relations, too. Usually you should preserve this mechanism and add it to your custom implementation. E.g.
def destroy
self.update deleted_at: Time.now
run_callbacks :destroy
end
You can read this post, too:
Triggering dependent: :destroy with overridden destroy-method