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.
Related
In my project there are many models with has_many association and dependant: :destroy flag. Also, each model have other belong_to associations with the dependant: :destroy flag. This models are nested between each other so when a destroy is executed on the top one, Rails triggers cascading destroy on children models.
Apart from that, models have callbacks that execute before_destroy.
The following represents what I described above:
class Model1Example < ActiveRecord::Base
has_many :model2_examples, :dependent => :destroy
belongs_to :other1_example, :dependent => :destroy
belongs_to :other2_example, :dependent => :destroy
end
class Model2Example < ActiveRecord::Base
belongs_to :model1_example
has_many :model3_examples, :dependent => :destroy
belongs_to :other3_example, :dependent => :destroy
belongs_to :other4_example, :dependent => :destroy
before_destroy :update_something_on_model1
before_destroy :check_some_inconsistence
end
class Model3Example < ActiveRecord::Base
belongs_to :model2_example
belongs_to :other5_example, :dependent => :destroy
belongs_to :other6_example, :dependent => :destroy
before_destroy :update_something_on_model2
before_destroy :check_some_inconsistence
end
Given that on average Model2Example holds about 100+ instances of Model3Example when the Model1Example destroy is triggered many SQL queries are triggered (10k+) because deletion is record by record and also all rules are executed for every instance...and this takes a lot more than what a user could wait for such a simple action.
I could fix this performance issue by using dependant: :delete_all on the has_many associations instead, because I don't really care that all this rules are executed when I trigger Model1Example destroy.
But the problem is that when I execute (from elsewhere in the app) a Model2Example destroy is in my interest that all rules are executed (specially Model3Example rules for each instance), and the previous mentioned approach brakes this.
Is there a "Rails way" to achieve a performance improvement for this case? Or should I just use SQL for Model1Example deletion?
Also, if I have to use this approach and I wanted to check some basic stuff before destroying Model1Example, where is the best place to do this validation? controller?
I have been using this nested attributes with has_many :through
class Check < ActiveRecord::Base
has_many :checks_tags, dependent: :destroy
has_many :tags, through: :checks_tags
attr_accessible :tags_attributes, :checks_tags_attributes
accepts_nested_attributes_for :tags, :checks_tags
end
class Tag < ActiveRecord::Base
has_many :checks_tags, dependent: :destroy
has_many :checks, through: :checks_tags
end
class CheckTag < ActiveRecord::Base
belongs_to :check
belongs_to :tag
end
so here the issue is when i create with this hash
"tags_attributes"=>[{"id"=>"", "name"=>"test12", "company_id"=>"1"}, {"id"=>"", "name"=>"test12", "company_id"=>"1"}]
actually here have two tags with same name, so its creating Tag twice and after putting twice on CheckTag, so is there any way to avoid this creation as twice in Tag?
If you want it forbidden in the database, you could create a unique index on the combination of the two columns on the check_tag table. If you want to handle it in rails, you can do it with a before_save callback on the Check model (if that's the only way you create these), but that may make you vulnerable to a race condition.
See this answer:
Index on multiple columns in RoR
I have two AR models and a third has_many :through join model like this:
class User < ActiveRecord::Base
has_many :ratings
has_many :movies, through: :ratings
end
class Movie < ActiveRecord::Base
has_many :ratings
has_many :users, through: :ratings
end
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :movie
after_destroy do
puts 'destroyed'
end
end
Occasionally, a user will want to drop a movie directly (without directly destroying the rating). However, when I do:
# puts user.movie_ids
# => [1,2,3]
user.movie_ids = [1, 2]
the rating's after_destroy callback isn't called, although the join record is deleted appropriately. If I modify my user model like this:
class User < ActiveRecord::Base
has_many :ratings
has_many :movies,
through: :ratings,
before_remove: proc { |u, m| Rating.where(movie: m, user: u).destroy_all }
end
Everything works fine, but this is really ugly, and Rails then tries to delete the join model a second time.
How can I use a dependent: :destroy strategy for this association, rather than dependent: :delete?
Answering my own question, since this was difficult to Google, and the answer is super counter-intuitive (although I don't know what the ideal interface would be).
First, the situation is described thoroughly here: https://github.com/rails/rails/issues/7618. However, the specific answer is buried about halfway down the page, and the issue was closed (even though it is still an issue in current Rails versions).
You can specify dependent: :destroy for these types of join model destructions, by adding the option to the has_many :through command, like this:
class User < ActiveRecord::Base
has_many :ratings
has_many :movies,
through: :ratings,
dependent: :destroy
end
This is counter-intuitive because in normal cases, dependent: :destroy will destroy that specific association's object(s).
For example, if we had has_many :ratings, dependent: :destroy here, all of a user's ratings would be destroyed when that user was destroyed.
We certainly don't want to destroy the specific movie objects here, because they may be in use by other users/ratings. However, Rails magically knows that we want to destroy the join record, not the association record, in this case.
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
In a rails 4 application with a has_many belongs_to association, how should bi-directional :dependent => :destroy functionality be implemented?
Using Contact and Organisation as an example. The desired behaviour is as follows:
Destroying an organisation destroys the organisation and all of the associated contacts.
Destroying a contact associated to an organisation with multiple contacts destroys only the contact.
Destroying the last contact associated to an organisation destroys the contact and the associated organisation.
Setting :dependent => :destroy on the has_many satisfies conditions 1 and 2, but not 3.
class Organisation < ActiveRecord::Base
has_many :contacts, :dependent => :destroy
end
class Contact < ActiveRecord::Base
belongs_to :organisation
end
Setting :dependent => :destroy on the belongs_to satisfies conditions 1 and 3, but not 2.
class Organisation < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :organisation, :dependent => :destroy
end
What's the cleanest way to achieve this behaviour?
I've found a solution that achieves the desired behaviour without setting :dependent => :destroy:
class Organisation < ActiveRecord::Base
has_many :contacts
def destroy
self.contacts.delete_all if self.contacts.any?
super
end
class Contact < ActiveRecord::Base
belongs_to :organisation
after_destroy :cascade_destroy
private
def cascade_destroy
self.organisation.destroy if self.organisation.contacts.empty?
end
end
Rather than overriding the Organisation#destroy I had tried setting :dependent => :destroy on the has_many :contacts association, however I believe this created an infinite loop, as it caused the app to throw a 'stack level too deep' error.
Calling self.contacts.delete_all seems to avoid that issue, although I don't know why.