I have 2 models
class Deal < ActiveRecord::Base
has_many :couponizations, dependent: :destroy
has_many :coupon_codes, through: :couponizations, source: :coupon_code, dependent: :destroy
accepts_nested_attributes_for :coupon_codes, allow_destroy: true
end
and
class CouponCode < ActiveRecord::Base
has_one :couponization, dependent: :destroy
has_one :deal, through: :couponization, source: :deal
which are linked by many-to-many relationship
class Couponization < ActiveRecord::Base
belongs_to :coupon_code
belongs_to :deal
end
Despite I specified dependent: :destroy option, when I delete deal, coupon codes are not being deleted. However couponizations are deleted successfully. Is there any way to delete associated nested records on object destroy?
The options dependent: :destroy is ignored when using with the :through (see doc). You have to do it manually, with a after_destroy callback for example.
class Deal
after_destroy :destroy_coupon_codes
private
def destroy_coupon_codes
self.coupon_codes.destroy_all
end
end
I recommend using :after_destroy callback, so if destroying some Deal instance fails for whatever reason you don't end up deleting all of its CouponCodes.
Here's an :after_destroy example that should work:
after_destroy { |record|
CouponCode.destroy(record.coupon_codes.pluck(:id))
}
Make sure to remove dependent: :destroy from has_many :couponizations in the Deals model, because all couponizations will now be destroyed by the has_one :couponization, dependent: :destroy in the CouponCode model.
Related
I have next models:
class Document < ActiveRecord::Base
has_many :sub_roles_documents, dependent: :destroy
has_many :sub_roles, through: :sub_roles_documents,class_name: '::SubRole'
end
class SubRole < ActiveRecord::Base
has_many :sub_roles_documents, dependent: :destroy
has_many :documents, through: :sub_roles_documents, class_name: '::Document'
end
class SubRolesDocument < ActiveRecord::Base
belongs_to :sub_role, counter_cache: :documents_count, touch: true
belongs_to :document, counter_cache: :sub_roles_count
end
And when I delete sub_roles for some documents using nested parameters counter cache sub_roles_count doesn't change, but when I add new sub_roles to documents all work fine.
If I directly remove sub_roles of documents documents.sub_roles.delete(specific_sub_role) - it's work fine too.
What is best way in my case?
I figured out a problem, all wrote in documentation:
This option can be used to configure a custom named :counter_cache. You only need this option when you customized the name of your :counter_cache on the belongs_to association.
In my case I must write next:
class Document < ActiveRecord::Base
has_many :sub_roles_documents, dependent: :destroy, counter_cache: :documents_count
has_many :sub_roles, through: :sub_roles_documents,class_name: '::SubRole'
end
Because I use the customize name for counter cache.
When I delete AdTemplate record I do not want its connected page_view_stats data to be deleted also. But when I delete a Campaign record I do want ad_templates also delete all its page_view_stats records. Maybe dependent: :destroy can accept some :if condition to achieve it?
class Campaign < ActiveRecord::Base
has_many :ad_templates, dependent: :destroy
end
class AdTemplate < ActiveRecord::Base
belongs_to :campaign
has_many :page_view_stats
end
You could use after_destroy callback in Campaign
after_destroy do
# execute some logic
end
I have these models:
class Project < ActiveRecord::Base
has_many :task_links, -> { includes(:task).order("tasks.name") }, dependent: :destroy
has_many :tasks, through: :task_links
class TaskLink < ActiveRecord::Base
belongs_to :project
belongs_to :task
class Task < ActiveRecord::Base
has_many :task_links, dependent: :destroy
has_many :projects, through: :task_links
I want to create an after_create callback that will automatically create all task_links for all active projects and the newly created task. I can do this by looping over the active projects and creating a task_link for each, but I'm wondering if there is a nicer way to do this? Preferably with one big insert command instead of xxx.
I'm not sure why you'd need to use an after_create to do this, because I think a before_create would work quite well. With a before_create, you can save all of your task_links when your new task is saved.
class Task < ActiveRecord::Base
has_many :task_links, dependent: :destroy
has_many :projects, through: :task_links
before_create :create_task_links
def create_task_links
# I'm assuming that the where below satisfies "all active projects"
Project.where(active: true).find_each do |proj|
# build only creates objects but does not save them to your database
self.task_links.build(project: proj)
end
end
end
I have a many-to-many relation between User and "Link".
The join model is called LinkAddress and besides for saving the IDs of the other two models, it has an attribute called address - information it collects at creation.
How can I access the address attribute for a certain link in a request scenario like the following: User.first.links.first.address ?
Models:
class User < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :links, through: :link_addresses
accepts_nested_attributes_for :link_addresses, allow_destroy: true
end
class LinkAddress < ActiveRecord::Base
belongs_to :user
belongs_to :link
end
class Link < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :users, through: :link_addresses
end
You could access it through User since it's a has_many ... :through relation:
User.first.link_addresses.first.address
Or, if you'd like to go through links then:
User.first.links.first.link_addresses.first.address
SQL Aliases
I had this exact question: Rails Scoping For has_many :through To Access Extra Data
Here's the answer I got:
#Images
has_many :image_messages, :class_name => 'ImageMessage'
has_many :images, -> { select("#{Image.table_name}.*, #{ImageMessage.table_name}.caption AS caption") }, :class_name => 'Image', :through => :image_messages, dependent: :destroy
This uses SQL Aliases which I found at this RailsCast (at around 6:40 in). It allows us to call #user.image.caption (even though .caption is in the join model)
Your Code
For your query, I'd use this:
class User < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :links, -> { select("#{Link.table_name}.*, #{LinkAddress.table_name}.address AS address") }, through: :link_addresses
accepts_nested_attributes_for :link_addresses, allow_destroy: true
end
This will allow you to write #user.links.first.address, and gracefully handles an absence of the address record
With a regular has_many, there's the option of :dependent => :destroy to delete the associations when the parent record is deleted. With has_many :through, there might be other parents associated to the child records, so :dependent => :destroy doesn't have any effect.
How do you ensure child records are deleted after they are orphaned from the last HMT association?
The solution I have found seems to be an after_destroy callback, such as this:
class Parent < ActiveRecord::Base
has_many :children, :through => :parentage
after_destroy :destroy_orphaned_children
private
def destroy_orphaned_children
children.each do |child|
child.destroy if child.parents.empty?
end
end
end
On the join model, use "belongs_to :model, dependent: :destroy"
for example, if you want to destroy a patient once their doctor is destroyed, and doctor has_many patients though appointments
Class Appointment
belongs_to :doctor
belongs_to :patient, dependent: :destroy
Class Doctor
has_many :appointments, dependent: :destroy
has_many :patients, through: :appointments
Class Patient
has_many :appointments
has_many :doctors, through: :appointments