I have been checking options of belongs_to method and testing following behavior in Rails 3.2.7
As per above link the :dependent option states that
If set to :destroy, the associated object is destroyed when this
object is. If set to :delete, the associated object is deleted without
calling its destroy method.
As I understand the Author should be removed if Post is removed in following case:
class Post < ActiveRecord::Base
belongs_to :author, :dependent => :delete
end
class Author < ActiveRecord::Base
attr_accessible :name
has_one :post
before_destroy :log_author_removal
private
def log_author_removal
logger.error('Author is getting removed')
end
end
In console:
> Post.first
Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 1
=> #<Post id: 5, title: "Post 5", author_id: 3>
> p.delete
SQL (197.7ms) DELETE FROM "posts" WHERE "posts"."id" = 5
=> #<Post id: 5, title: "Post 5", author_id: 3>
> Author.find(3)
Author Load (0.5ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 3]]
=> #<Author id: 3, name: "Author 3">
However calling p.destroy deletes associated author.
Am I misunderstood above quoted statement?
Yes, calling delete generally skips all callbacks that either you or rails set on destroying the record. These include callbacks like before_destroy and also destroying associated records.
Therefore if you call p.delete it will not do anything with the associated records.
When you call p.destroy it will:
Call the before_destroy callback if set.
Delete the object.
If you set :dependent => :delete, it will simply delete the Author object. If you set it to :destroy it will repeat this whole process for the author object (callback & destroying its related records if applicable).
Call the after_destroy callback if set.
From what I understand :
:dependent => :destroy will trigger association.destroy if you call destroy on the object.
:dependent => :delete will trigger association.delete if you call destroy on the object.
In both cases, you have to call destroy on the parent object. The difference lies in th methos that is called on the child object. If you don't want to trigger destroy filters on the child object use :dependent => :delete. If you do want them, use :dependent => :destroy.
By quickly taking a look at the source here : https://github.com/rails/rails/blob/357e288f4470f484ecd500954fd17fba2512c416/activerecord/lib/active_record/associations/builder/belongs_to.rb#L68
We see that calling dependent will just create an after_destroy on the parent model, calling either delete or destroy on the child object. But in both cases, it creates an after_destroy.
belongs_to association support both :delete and :destroy for :dependent.
you can refer below link
http://apidock.com/rails/v4.0.2/ActiveRecord/Associations/ClassMethods/belongs_to
calling delete skips all callbacks like before_destroy and as well won't delete associated records for association object as well.
Example
class Order < ActiveRecord::Base
has_one :project, :dependent => :delete
has_many :resources, :dependent => :delete
end
class Project < ActiveRecord::Base
belongs_to :order, :dependent => :delete
end
In above code,if project has been destroyed then order will as well deleted but resources in order won't delete
but if we use
belongs_to :order, :dependent => :destroy
then resources attached with orders as well deleted on project destroy.
belongs_to :author, :dependent => :delete
Should be:
belongs_to :author, :dependent => :destroy
:destroy and :delete behave differently in ActiveRecord, delete bypasses validations and AR associations, thus associated objects are not being removed.
belongs_to association can't support the :delete for :depedent. It supports only :destroy. Please refer this link http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html.
Related
Since the original question didn't hit the mark, here's a re-written version, which better describes the issue.
I have the following models:
class LineItem < ApplicationRecord
has_many :line_item_options, :dependent => :destroy
has_many :options, through: :line_item_options
end
class LineItemOption < ApplicationRecord
belongs_to :option
belongs_to :line_item
has_many :charges, as: :chargeable, dependent: :destroy
after_create :build_charges
def build_charges
surcharges.each do |surcharge|
self.charges.create!(
surcharge_id: surcharge.id,
name: surcharge.name
)
end
end
end
class Charge < ApplicationRecord
belongs_to :chargeable, polymorphic: true
end
LineItemOption is the join model which joins an Option(not shown) to a LineItem. In some cases the LineItemOption will also have a child Charge model.
In my LineItem form I have the following code:
= line_item.collection_check_boxes :option_ids, group.options, :id, :name_and_price do |option|
.checkbox
= option.check_box(class: "check")
= option.label
When a LineItemOption is created using the collection_check_boxes form helper, the after_create callback fires as anticipated. However when a LineItemOption is destroyed using this same form helper no callback is fired. To test this I've used has_many :charges, as: :chargeable, dependent: :destroy as well as a before_destroy callback. In both cases the callbacks work from the rails console, but not the collection_check_boxes form helper.
Looking at the server log I can see that the destroy method is being called on the LineItemOption which happily runs without also running the appropriate callback
LineItemOption Destroy (0.7ms) DELETE FROM "line_item_options" WHERE "line_item_options"."line_item_id" = $1 AND "line_item_options"."option_id" = $2 [["line_item_id", 12], ["option_id", 1]]
(1.2ms) COMMIT
Redirected to http://localhost:3000/orders/6
Im sitting here scratching my head trying to figure out whats going on, and how to address it. Is this common behavior with the collection_check_boxes helper?
It looks like there's a bug or something in rails since a long time ago where the after_destroy callback is not triggered when you delete records from a has_many :through association https://github.com/rails/rails/issues/27099 (and I guess the dependant: :destroy option depends on that callback).
You'll have to implement some hacky solution like, before assigning the new line_items, do something like line_item.line_item_options.destroy_all or line_item.line_item_options.each(&:destroy) to delete the records by hand triggering the propper callbacks and then updating the record so rails can create the new associations without the buggy destoy behaviour.
You must include:
has_many :line_item_options, :dependent => :destroy
accepts_nested_attributes_for :line_item_options, :allow_destroy => true
You must add _destroy in your params:
def line_item_params
params.require(:line_item).permit(line_item_options_attributes: [ ......., :_destroy])
end
I'm trying to setup a has_many with conditions which works fine for the reading part but not for new entries. I've tested it some weeks ago in a sandbox and it worked but I can't get it work again so maybe I'm just blind or it is just a wrong design :-)
class Task
has_many :task_users
has_many :assignees, :through => :task_users, :source => :user, :conditions => {"task_users.is_assignee" => true}
has_many :participants, :through => :task_users, :source => :user
end
class TaskUser < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class User
has_many :tasks
end
After adding a new assignee to a task like this
Task.first.assignees << User.first
the following SQL is executed
SQL (0.3ms) INSERT INTO `task_users` (`created_at`, `is_assignee`, `task_id`, `updated_at`, `user_id`) VALUES ('2012-11-18 15:52:24', NULL, 2, '2012-11-18 15:52:24', 3)
I thought rails will use my conditions to set these values when I'm add ing new ones. Reading works great but I have no idea why adding new values doesn't work with conditions.
I expect this INSERT
SQL (0.3ms) INSERT INTO `task_users` (`created_at`, `is_assignee`, `task_id`, `updated_at`, `user_id`) VALUES ('2012-11-18 15:52:24', 1, 2, '2012-11-18 15:52:24', 3)
I'm not entirely sure whether you can specify :conditions hash on the join table in a has_many :through association. Someone else correct me if I'm wrong, but the condition has to be directly on the source association, :user in your case.
If this is the case, to work around this you can specify an auxiliary association:
has_many :task_users
has_many :assignee_task_users, :class_name => 'TaskUser', :conditions => {"is_assignee" => true}
has_many :assignees, :through => :assignee_task_users, :source => :user
just going to highlight the documentation:
Specify the conditions that the associated objects must meet in order to be
included as a WHERE SQL fragment, such as price > 5 AND name LIKE 'B%'.
Record creations from the association are scoped if a hash is used.
has_many :posts, :conditions => {:published => true} will create published
posts with #blog.posts.create or #blog.posts.build.
even though you used an hash already, the first parameter is a string, which is unneded to be (the association knows already the table name). rewrite it as :conditions => {:is_assignee => true} and it should work.
Also, the way you are creating users should be rewritten in order for this to work of course. Instead of:
Task.first.assignees << User.first
use:
Task.first.assignees.create
and that should do the trick.
I've got 2 models with a 1 to many association that I try to get a rails cascading delete working on.
I'm fairly new to rails and I've tried everything I could but I still can't get it to work...
Here are my 2 models
class CmsHomeSlide < ActiveRecord::Base
attr_accessible :slide_order, :start_datetime, :end_datetime, :slide_type, :header, :headline, :cta_text, :cta_link, :active
has_many :cms_home_slide_detail, :dependent => :delete_all
validates_presence_of :slide_type, :slide_order
end
class CmsHomeSlideDetail < ActiveRecord::Base
attr_accessible :start_datetime, :end_datetime, :position, :image_url, :link, :cms_home_slide, :active
belongs_to :cms_home_slide
end
And the test script I'm using (as a runner, but dev environment and test environment fail the same way)
CmsHomeSlide.delete_all
CmsHomeSlideDetail.delete_all
slide = Factory(:home_slide)
det1 = Factory(:home_slide_detail, :cms_home_slide => slide, :position => 1)
det2 = Factory(:home_slide_detail, :cms_home_slide => slide, :position => 2)
puts "Slides length #{CmsHomeSlide.all.length}"
puts "Details length #{CmsHomeSlideDetail.all.length}"
slide.delete
puts "Slides length #{CmsHomeSlide.all.length}"
puts "Details length #{CmsHomeSlideDetail.all.length}"
Output is
Slides length 1
Details length 2
Slides length 0
Details length 2
What am I missing?
You need to use :dependent => :delete or :dependent => :destroy
From the guide:
If you set the :dependent option to :destroy, then deleting this
object will call the destroy method on the associated object to delete
that object. If you set the :dependent option to :delete, then
deleting this object will delete the associated object without calling
its destroy method.
http://guides.rubyonrails.org/association_basics.html
Whats wrong with this before_save-callback?
class Order < ActiveRecord::Base
has_many :line_items, :dependent => :destroy, :inverse_of => :order
accepts_nested_attributes_for :line_items
attr_accessible :line_items_attributes
before_save :mark_line_items_for_removal
def mark_line_items_for_removal
line_items.each do |line_item|
line_item.mark_for_destruction if line_item.quantity.to_f <= 0
end
end
end
When one of the line_items are marked for destruction, no line_item will be saved.
However the parent Order object does get saved.
Returning true does not make a difference...
about mark_for_destruction: http://apidock.com/rails/v3.1.0/ActiveRecord/AutosaveAssociation/mark_for_destruction
and why that instead of ":allow_destroy => true"? see here:
http://weblogs.manas.com.ar/spalladino/2010/03/15/deleting-children-with-accepts_nested_attributes_for-in-rails/
I believe you need to set the :autosave => true option for your has_many definition.
As stated, here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
"If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object. If false, never save or destroy the associated objects. By default, only save associated objects that are new records."
I've been using the association_collection method "other_ids" throughout my Rails app with no issues. However whenever I try to access it from within the model defining the association, Rails has no idea what I'm taking about. For example:
class Membership < ActiveRecord::Base
belongs_to :course, :touch => true
belongs_to :person, :touch => true
end
class Day < ActiveRecord::Base
belongs_to :course, :touch => true, :counter_cache => true
has_many :presents, :dependent => :delete_all
has_many :people, :through => :presents
before_destroy :clear_attendance
def clear_attendance
mems = Membership.where(:course_id => course.id, :person_id => person_ids)
mems.update_all(["attendance = attendance - ?", (1 / course.days.size.to_f)])
end
end
In this case, person_ids is always null. I've tried self.person_ids, people.ids, etc. All nothing. I have used day.person_ids elsewhere with no issues, so why can't I use it here?
I am using Ruby 1.9.1 and Rails 3.0.3. Here is the SQL call from my log:
[1m[36mAREL (0.0ms)[0m [1mUPDATE "memberships" SET attendance = attendance - 0.3333333333333333 WHERE ("memberships"."course_id" = 4) AND ("memberships"."person_id" IN (NULL))[0m
edit: added more code to clarify question
What you really want there is:
def a_method
self.people.all
end
But to answer your question, person_ids is the correct method, and it should return an empty array, not nil. I just tried an association like that out in 2.3.10. Maybe you can post some more of your code, rails version, etc.
Thanks for your help - I figured it out myself. The problem was the order of my callbacks. I was trying to call person_ids after the association had been deleted. Changing the order to this solved my issues.
class Day < ActiveRecord::Base
before_destroy :clear_attendance
belongs_to :course, :touch => true, :counter_cache => true
has_many :presents, :dependent => :delete_all
has_many :people, :through => :presents