Trouble on deleting "associated" model instances - ruby-on-rails

I am using Ruby on Rails 3.1.0 and I would like to know why database records in the following example are not deleted on destroying an "associated" model instance.
In my application I have these classes:
class User < ActiveRecord::Base
has_one :account,
:autosave => true,
:dependent => :destroy
has_many :article_relationships,
:class_name => 'ArticleUserRelationship',
:foreign_key => 'user_id',
:autosave => true,
:dependent => :destroy
has_many :articles,
:through => :article_relationships,
:source => :article,
:dependent => :destroy
...
end
class ArticleRelationship < ActiveRecord::Base
belongs_to :user
...
end
class Account < ActiveRecord::Base
belongs_to :user,
:autosave => true,
:dependent => :destroy
end
If I run #article.user.destroy or #article.user.account.destroy methods (note: article is kind of Article) those will delete from the database the user, the account and article relationship records but will not delete users related articles, even if I state has_many :articles ... :dependent => :destroy.
Anyway I read the official RoR guide about the "has_many Association Reference" and at the 4.3.2.6 :dependent chapter it says:
This option (:dependent) is ignored when you use the :through option on the association.
So, what could/should I make in order to delete all user articles? That is, what method could/should I run? How?

Related

after_destroy not called for linked table

I have these models
class User < ActiveRecord::Base
has_many :user_functions, :dependent => :destroy
has_many :functions, :through => :user_functions
accepts_nested_attributes_for :functions, allow_destroy: true
Model of the linked table:
class UserFunction < ActiveRecord::Base
belongs_to :user, inverse_of: :user_functions
belongs_to :function, inverse_of: :user_functions
after_destroy :unplan_items
after_create :plan_items
and of course the model of function but this is like user...
Now when I do the following in my tests:
#user.functions = [#functions]
#user.save
expect(#user.planned_items.count).to eq(1)
#user.functions = []
#user.save
I notice the callback after_destroy isn't called. Why is this and how can I avoid this. There are certain steps that need to be done every time a UserFunction is destroyed...
I believe this has to do with: https://github.com/rails/rails/issues/7618 (I'm using rails 4.2.5 though). The after_create is working perfect though...
Currently rails uses :delete_all as default strategy of has_many_through. It only calls :destroy_all when we explicitly specify dependent: :destroy on the association.
The docs mention advice to use has_many :through if you need callbacks:
See the suggestion here: http://guides.rubyonrails.org/association_basics.html
You should use has_many :through if you need validations, callbacks,
or extra attributes on the join model.
So there currently is an inconsistency between after_create which does do the callback and after_destroy.
This is mentioned in these two issues posted on GitHub:
https://github.com/rails/rails/issues/7618
https://github.com/rails/rails/issues/27099
The fix for now is to explicitly put :dependent => :destroy on the :through part. This will make sure the callback are used.
class User < ActiveRecord::Base
has_many :user_functions
has_many :functions, :through => :user_functions, :dependent => :destroy
accepts_nested_attributes_for :functions, allow_destroy: true
For anyone reading this 2021+
Change This
has_many :object_tags, :as => :taggable, :dependent => :destroy
has_many :tags, :through => :object_tags
To This
has_many :object_tags
has_many :tags, :through => :object_tags, :dependent => :destroy

How to call automatically method in object that needs access to unsaved associations in Rails

I have a class called Treatment with these associations:
class Treatment < ActiveRecord::Base
has_many :threat_treatments, :dependent => :destroy
has_many :threats, :through => :threat_treatments
has_many :subcategory_treatments, :dependent => :destroy
has_many :subcategories, :through => :subcategory_treatments
has_many :treatments_risks, :dependent => :destroy
has_many :risks, :through => :treatments_risks
has_many :risks_from_threats, :through => :threats, :source => :risks
has_many :risks_from_subcategories, :through => :subcategories, :source => :risks
def associate_with_existing_risks
self.risks = self.risks_from_threats & self.risks_from_subcategories
end
end
I need to execute the method associate_with_existing_risks() automatically when a treatment is created. If I use before_save callback, the two associations are still unsaved and unaccessible. If I use after_save, cause I need to save again the treatment, I will create an infinite loop.
I cannot use inverse_of because the associations are through associations.
How and when can I execute the method associate_with_existing_risks to update the risks association when a treatment is created?

Rails: Association was not found; perhaps you misspelled it?

I have a User model with a has_many :through relationship to the Publication model. The Publication model in turn has a has_many :through relationship to Author:
class User < ActiveRecord::Base
has_many :library_publications, :dependent => :destroy, :class_name => "Library::Publication"
has_many :publications, :through => :library_publications
end
class Library::Publication < ActiveRecord::Base
belongs_to :publication
belongs_to :user
end
class Publication < PublicationBase
has_many :library_publications, :dependent => :destroy, :class_name => "Library::Publication"
has_many :users, :through => :library_publications
has_many :publication_contributions, :dependent => :destroy, :class_name => "Publication::Contribution"
has_many :authors, :through => :publication_contributions
end
class Author < AuthorBase
has_many :publication_contributions, :dependent => :destroy, :class_name => "Publication::Contribution"
has_many :publications, :through => :publication_contributions
end
class Publication::Contribution < Publication::ContributionBase
belongs_to :publication, :class_name => "Publication"
belongs_to :author, :class_name => "Author"
end
As far as I can tell, all the associations are written correctly. However, when I try to eagerload authors from a user:
#user.library_publications.includes(:publication => [:authors])
I get this error:
Association named 'authors' was not found; perhaps you misspelled it?
What might be the cause of this?
After experimenting a little, I discovered that all of publication's associations were broken. This led to me to looking for larger problems, and eventually I discovered that this issue was caused by one of the join-table being namespaced, Library::Publication. When I de-namespaced it, publication's associations began working again.
I'm not sure why this happened, though. If anyone has an explanation, please share.

In rails 2.3, how can I retrieve a collection of objects from a second-order has_many association?

I have a Partner model that has_and_belongs_to_many Projects, while each Project has_many Sites. I want to retrieve all sites for a given partner (and am not interested in the projects in between at the moment).
I have accomplished what I need through a named_scope on the Site model, and a project.sites instance method that wraps a call to the Site named scope, as follows:
class Partner < ActiveRecord::Base
has_and_belongs_to_many :projects
def sites
Site.for_partner_id(self.id)
end
end
class Project < ActiveRecord::Base
has_many :sites
end
class Site < ActiveRecord::Base
belongs_to :project
named_scope :for_partner_id, lambda {|partner_id|
{ :include=>{:project=>:partners},
:conditions=>"partners.id = #{partner_id}"
}
}
end
Now, given a partner instance, I can call partner.sites and get back a collection of all sites associated with the partner. This is precisely the behavior I want, but I'm wondering if there's another way to do this using only activerecord associations, without the named scope?
I had a similar deep nesting query/collection problem here (I had to threaten to repeat data before anyone would answer my 4 questions, clever):
Is it appropriate to repeat data in models to satisfy using law of demeter in collections?
The trick is this gem http://rubygems.org/gems/nested_has_many_through which can do something like this:
class Author < User
has_many :posts
has_many :categories, :through => :posts, :uniq => true
has_many :similar_posts, :through => :categories, :source => :posts
has_many :similar_authors, :through => :similar_posts, :source => :author, :uniq => true
has_many :posts_of_similar_authors, :through => :similar_authors, :source => :posts, :uniq => true
has_many :commenters, :through => :posts, :uniq => true
end
class Post < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :comments
has_many :commenters, :through => :comments, :source => :user, :uniq => true
end
This has super-simplified my queries and collections. I hope you find an answer to your problem, it's a tough one!

Trying to design a controller to display courses (Rails)

Below are the models that are relevant to my problem. I am attempting to design a way to display CourseEnrollments along with their steps to a given patient. Here is what I have come up with so far.
INDEX ACTION - /course_enrollments/ --> Display courses user is enrolled in as well as the most recent course overview. This can redirect to most recent course.
SHOW ACTION - /course_enrollments/:id --> Display courses user is enrolled in as well as the most recent course overview
The part I am struggling to figure out is how to display an individual step for a course. Should this be done in the course_steps controller (which is nested inside the courses resource)?
class Course < ActiveRecord::Base
belongs_to :course_category
belongs_to :client
belongs_to :user_created, :foreign_key => :user_created_by, :class_name => "User"
belongs_to :user_updated, :foreign_key => :user_last_updated_by, :class_name => "User"
has_many :course_steps, :dependent => :destroy
has_many :steps, :through => :course_steps
has_many :course_requests, :dependent => :destroy
has_many :course_enrollments, :dependent => :destroy
has_many :patients, :through =>:course_enrollments
end
class CourseStep < ActiveRecord::Base
belongs_to :step
belongs_to :course
validates_uniqueness_of :step_id, :scope => :course_id
end
class Step < ActiveRecord::Base
belongs_to :step_type
belongs_to :client
has_one :step_quiz, :dependent => :destroy
has_one :step_survey, :dependent => :destroy
has_one :step_text, :dependent => :destroy
has_one :step_download, :dependent => :destroy
has_one :step_video, :dependent => :destroy
has_one :step_presentation, :dependent => :destroy
has_many :course_steps, :dependent => :destroy
has_many :courses, :through => :course_steps
end
class CourseEnrollment < ActiveRecord::Base
belongs_to :patient
belongs_to :course
end
class Patient < ActiveRecord::Base
belongs_to :user, :dependent => :destroy
has_many :enrollments, :dependent => :destroy
has_many :clients, :through => :enrollments
has_many :course_requests, :dependent => :destroy
has_many :course_enrollments, :dependent => :destroy
has_many :courses, :through => :course_enrollments
end
The usual approach is to nest these and have a compound sort of path, but how you route these things is often dependent on the level of context involved. For instance, is the display of a course driven by a user to the degree that the path should have the user in it, or is it a case of simply personalizing the course page?
Typically you see things like this:
resources :courses do |course|
course.resources :enrollments
course.resources :steps
end
There's usually a correlation between your has_many relationships and declaring an equivalent resources but not always.
Have a look at the generated routes using rake routes to see what the parameters will be called when passed to your controller, as well as what the expected controller name will be. You can customize the name of the controller by passing in a :controller option to the route.
Typically the last record in the path is passed in as :id whereas the prior ones are named, such as :course_id. This is a somewhat annoying inconsistency, so do be careful to check you're loading using the correct parameters.
If you need to display an individual step for a course, you definitely need to use the course_steps controller, logic being, each CourseStep object is a combo of one-course and one-step.

Resources