Why aren't my nested attributes being destroyed along with the parent? - ruby-on-rails

I have a Resume Model that has_many :skills, and my skills model that belongs_to :resume.
I am nesting the skills form inside the resume form and it's creating the record and relationship perfectly. But when I try to destroy the resume, the associated Skill is not being destroyed along with it.
Here are my models:
# Resume.rb
class Resume < ActiveRecord::Base
has_many :skills
belongs_to :user
accepts_nested_attributes_for :skills, allow_destroy: true
end
# Skill.rb
class Skill < ActiveRecord::Base
belongs_to :resume
end
Here's the strong params in the resume_controller.rb
def resume_params
params.require(:resume).permit(:user_id, :title, :summary, :job_title, skills_attributes [:skill_name, :_destroy])
end
As far as I can tell I am passing the _destroy key properly. I noticed some people had _destroy checkboxes in the form. I want the Skills to be deleted when I Destroy the entire resume. Thanks!

All you have specified is that you can destroy skills on a resume like with the checkboxes you mention seeing in some examples. If you want it to destroy all skills associated when a resume is destroyed you have adjust your has_many declaration.
has_many :skills, dependent: :destroy

Add :dependent => :destroy in your Resume model like below:
class Resume < ActiveRecord::Base
has_many :skills, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :skills, allow_destroy: true
end
:dependent Controls what happens to the associated objects when their owner is destroyed:
:destroy causes all the associated objects to also be destroyed
:delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
: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 any associated records
:restrict_with_error causes an error to be added to the owner if there are any associated objects

Related

Model creation failing with has_many through validation

I have 2 models, order and owner, which are joined together by an order_owner model using a has_many through association. Each order must have at least 1 owner associated to it but since each order or owner can have many of the other, a many-to-many relationship is required.
I've set up my order model with validates :owners, presence: true which works great if I attempt to create an order without associating any owners to it. However, the issue I'm encountering is that the validation still fails regardless of associating an owner at the time of creation via nested attributes and I don't know how to fix it.
Models - Rails 5.0.2
##order.rb
has_many :order_owners, inverse_of: :order, dependent: :destroy
has_many :owners, through: :order_owners
accepts_nested_attributes_for :owners, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :order_owners, reject_if: :all_blank, allow_destroy: true
validates :owners, presence: true
##owner.rb
has_many :order_owners, inverse_of: :owner, dependent: :destroy
has_many :orders, through: :order_owners
##order_owner.rb
belongs_to :owner
belongs_to :order
accepts_nested_attributes_for :owner
I've tried using custom validation methods like
validate :must_have_owner
...
def must_have_owner
if owners.empty? or owners.all? { |owner| owner.marked_for_destruction? }
errors.add(:base, 'Order must have at least one owner')
end
end
But the same thing occurs.
The way that I'm associating owners to an order upon creation is via nested attributes and using the Cocoon gem. So a user fills out the order form, then adds the appropriate owners (either via a Owners.all select or adding a new owner to be created), then submits the form. Without the aforementioned validation the whole process works fine but it also allows an order to be created without an owner which defeats the purpose.
Any ideas would be helpful, thanks!

Nested attributes validations fails in parent association

If you want to go straight to the question, just go to the last paragraph.
A Pack has many items included, Item is polymorphic and one of the linked tables is Access (so Access is an Item that can be added to the Pack)
Here you are the models and controller.
class Pack < ActiveRecord::Base
has_many :pack_items, dependent: :destroy
has_many :items_included, through: :pack_items, source: :item
accepts_nested_attributes_for :pack_items, allow_destroy: true
validate :valid_max_value, if: :infinite_item?
end
class Item < ActiveRecord::Base
has_many :pack_items, dependent: :restrict_with_error
has_many :packs, through: :pack_items
end
class Access < ActiveRecord::Base
has_one :item, as: :itemable, dependent: :destroy
has_one :entitlement, as: :entitlementable, dependent: :destroy
accepts_nested_attributes_for :item, allow_destroy: true
accepts_nested_attributes_for :entitlement, allow_destroy: true
validate :valid_max_value, if: :infinite?
private
def infinite?
entitlement.infinite
end
end
class PacksController < BaseController
def update
#pack = Pack.find(params[:id])
if #pack.update(permitted_params)
...
end
end
private
def permitted_params
params.require(:pack).permit(item_attributes: [:id, :name, :max_purchasable],
pack_items_attributes: [:id, :item_id, :amount, :_destroy])
end
end
There is an importan validation in pack "valid_max_value. If a pack has an infinite Access inside, the max_value of the Pack should never be higher than 1.
It works perfectly when I create a pack and I add some Accesses, but the problem is this:
I have a Pack with two Items. An Access that's infinite and a common Access(not infinite). So the Pack's max_value should be 1 because it has an infinite Access inside.
Now I edit that Pack and I delete the infinite Access, so now I can select a higher max_value, 5 in example, because the pack doesn't have an Access with restriction inside.
When I click update there is a rollback because the valid_max_value validation runs before the deletion of the infinite Access, so it says the max_value is invalid because the validation depends on a child field.
In short, my question is: How can I delete the nested items before run the parent validation?
You do not have to actually delete the items before validation, check marked_for_destruction? instead in your validations, so that items that are to be deleted will be ignored

Has_many through association with dependant: destroy not destroying

I have this model
class XmlImport < ActiveRecord::Base
belongs_to :video
belongs_to :user
has_many :events, through: :event_import_records, dependent: :destroy
has_many :event_import_records, dependent: :destroy
has_attached_file :xml
validates_attachment_content_type :xml, :content_type => ["text/xml"]
end
The :event_import_records entries are being destroyed. But the :events are not.
Is the dependent:destroy on the has_many through association valid?
Is there another way of writing it? If that is not correct
How can I destroy all the events associated to the XmlImport through the event_import_records?
You can find at the Rails API that: "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." I understand that it delete the joins records but not the associated by through.
If I were you, I try:
class EventImportRecord < ActiveRecord::Base
has_many :events, dependent: :destroy
end
If not work I swap the order of the has_many relations on the XmlImport model, because of "Note that :dependent is implemented using Rails' callback system, which works by processing callbacks in order. Therefore, other callbacks declared either before or after the :dependent option can affect what it does." Also find at the same page of the Rails API.

Rails 4.2 dependent: :destroy problems with CollectionProxy

I've used dependent: :destroy on models before with out any problem, but in rails 4.2 I'm stuck. The past uses were mainly for classic has_many belongs_to models. It almost seems that #<ActiveRecord::Associations::CollectionProxy is causing my problems.
Models
class Subject < ActiveRecord::Base
has_many :properties
has_many :values, :through => :properties
has_many :tags, :through => :properties
class Property < ActiveRecord::Base
belongs_to :subject
belongs_to :tag
belongs_to :value
class Value < ActiveRecord::Base
has_one :property
has_one :subject, :through => :property
has_one :tag, :through => :property
class Tag < ActiveRecord::Base
has_many :properties
has_many :subjects, :through => :properties
My goals were to
Deleting a Subject would delete all associated Properties and Values
Deleting a Property would delete the associated Value, leaving Subject intact
or, Deleting a Value would delete the associated Property, leaving Subject intact
I tried adding dependent destroy on the values line in Subject and the property line in Value. It would delete the properties, but not the values. I tried putting it on the values properties line and value line in Property and got the same results - it would not delete the Values.
I then tried before_destroy filter and ran into the same type of problem or a ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR when I tried the the model associations. I then hacked it and got it to work:
# In Subject model
before_destroy :destroy_values
def destroy_values
# relations does not seem to work got the Values using a new query
#values.destroy_all
pids = values.pluck(:id)
Value.where(id:pids).destroy_all
end
# in Value model
before_destroy :destroy_property
def destroy_property
property.destroy
end
Not sure what is going on, read up as much as I could on dependent and tried delete_all, and every other thing I saw with no joy!
Yes, it is a strange model, just playing around and tried to replicate the "Whatit?" Apple II database in rails for grins.
Sometimes, when your stuck going around circles, you just have to ask a question. When you don't get an answer, you've had time to rethink the problem. I guess I didn't try all the options.
I think the problem is my not understanding default strategy of nullify. According to my goals, you can't have a value without a property and vice versa. Trying to destroy using a through association would raise the pg error. The simple solution that I apparently didn't try was to dependent destroy properties in the Subject model and dependent destroy value in the Property model. The warning in the guide not to use dependent destroy on a belongs_to association may have started my circle trip. I'm still not sure I understand the warning. My guess is that when subject.properties is destroyed, the subject_id is set to null before the property.value destroy call is made, avoiding the pg error. My cleaned up models:
class Subject < ActiveRecord::Base
has_many :properties, dependent: :destroy
has_many :values, :through => :properties
has_many :tags, :through => :properties
class Property < ActiveRecord::Base
belongs_to :subject
belongs_to :tag
belongs_to :value, dependent: :destroy
class Value < ActiveRecord::Base
has_one :property
has_one :subject, :through => :property
has_one :tag, :through => :property

How to delete nested objects in Rails3?

How can I delete nested objects in a form? I found out that I need to add :allow_destroy in the parent model at the accepts_nested_attributes_for directive.
Further, I want to restrict the deletion. A nested object only should be deleted, if the parent object is the only one that retains the association.
Example:
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships
end
Explanation: A company can host many internships. Therefore, I do not want to delete the company record as long as there is at least one other internship associated with it.
You could use dependent => :destroy
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships, :dependent => :destroy
end
If you return false in a before_destroy filter, then the destroy action will be blocked. So we can check to see if there are any internships associated to the company, and block it if so. This is done in the company model.
class Company < ActiveRecord::Base
has_many :internships
before_destroy :ensure_no_internships
private
def ensure_no_internships
return false if self.internships.count > 0
end
end

Resources