STI with has_one through not working - ruby-on-rails

My Models:
Content
RelatedList
RelatedGroupList < RelatedList # STI
ContentListing
on Content, I have
has_many :content_listings # all the below relations are joined using this which has content_id and related_list_id columns
has_one :related_group_list, through: :content_listings, source: 'RelatedList'
has_one :related_people_list, through: :content_listings, source: 'RelatedList'
has_one :related_website_list, through: :content_listings, source: 'RelatedList'
Basically, I want to get 'content.related_group_list' which should get the record for related group list.
However, I get this error:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :RelatedList in model ContentListing. Try 'has_many :related_group_list, :through => :content_listings, :source => <name>'. Is it one of :content or :related_list?
I checked my ContentListing model, with this line:
belongs_to :related_list
what am I missing in my ContentListing model?
Edit 1:
after I posted this question, I read some other articles on associations and changed the line
has_one :related_group_list, through: :content_listings, source: 'RelatedList'
to
has_one :related_group_list, through: :content_listings, source: :related_list
It now gives me the following error:
ActiveRecord::HasOneThroughCantAssociateThroughCollection: Cannot have a has_one :through association 'Content#related_group_list' where the :through association 'Content#content_listings' is a collection. Specify a has_one or belongs_to association in the :through option instead.
I want my
has_one :related_group_list, through: :content_listings, source: :related_list
automatically fetch me only those related_list whose type is RelatedGroupList and then join through my ContentListing. Is it possible?

Here you can not set has_one relationship 'related_group_list' You can set has_many.
Because Content has many content_listings and each content_listing has one related_list.
It means each content can have many related_group_list not just one.
So if you want to get content.related_group_lists then you can do
In ContentListing model -:
class ContentListing < ActiveRecord::Base
belongs_to :related_list
belongs_to :related_group_list, class_name: 'RelatedList',
foreign_key: 'related_list_id'
belongs_to :content
end
In Content model -:
class Content < ActiveRecord::Base
has_many :content_listings
has_many :related_lists, through: :content_listings
has_many :related_group_lists, through: :content_listings
end

Related

has_many :through within same model

I have User model in my database. It has role. role can be either patient or doctor. I want to maintain doctor_patient table.
doctor_patient.rb
belongs_to :doctor, class_name: 'User'
belongs_to :patient, class_name: 'User'
a patient can belong to many doctors and a docor can have many patients. I am familier to regular or normal has_many through association but facing issues related to this scenarios where I have role in user model.
user.rb
user
has_many :doctor_patients
has_many :patients, :through => :doctor_patients, :class_name=> "User"
patient
has_many :doctor_patients
has_many :doctors, :through=> :doctor_patients, :class_name=> "User"
In ActiveRecord the assocation metadata (the reflection) is strored in a class attribute as a hash and the name is used as the key. So when you define multiple assocations with the same name you're just overwriting your previous assocation.
The solution is to use unique names for each assocation:
class User < ApplicationController
has_many :doctor_patients_as_doctor,
foreign_key: :doctor_id,
class_name: 'DoctorPatient'
has_many :patients,
through: :doctor_patients_as_doctor
has_many :doctor_patients_as_patient,
foreign_key: :patient_id,
class_name: 'DoctorPatient'
has_many :doctors,
through: :doctor_patients_as_patient
end
Also make sure you pluralize the table correctly and name it doctor_patients.

Rails Association returns join model not actual model

So the join table is setup like this
User model.
Book model.
read_books is associating those two.
want_to_read_books is association those two again for a different reason.
Two join tables.
read_books
belongs_to :user
belongs_to :book
want_to_read_books
belongs_to :user
belongs_to :book
In the user table i have
has_many :books, through: :read_books
has_many :books, through: :want_to_read_books
in the book table I have
has_and_belongs_to_many :read_books
has_and_belongs_to_many :want_to_read_books
To this point it all works fine BUT i am forced to reference them as:
user.read_books.first.book.title
instead of
user.read_books.first.title.
What do I need to add to make it return the model not the association?
I have also tried the following thinking it was what i was missing but resulted in the same thing.
has_many :books, through: :read_books
has_many :read_books
has_many :books, through: :want_to_read_books
has_many :want_to_read_books
If you define several associations with the same name the methods defined by the later will just overwrite the previous association. Instead you want to just name your associations appropriately:
class User
has_many :book_readings # for lack of a better name
has_many :read_books,
through: :book_readings,
source: :book
has_many :wishlist_books
has_many :want_to_read_books,
through: :wishlist_books,
source: :book
end
class BookReading
belongs_to :user
belongs_to :book
end
class WishlistBook
belongs_to :user
belongs_to :book
end

Trying to 'alias' a polymorphic has_many relationship

I have a User model:
class User < ActiveRecord::Base
has_many :tracks, dependent: :destroy
has_many :tracked_locations, through: :tracks, source: :tracking, source_type: 'Location'
and a Track model (think of it as 'following'):
class Track < ActiveRecord::Base
belongs_to :user
belongs_to :tracking, polymorphic: true
end
The idea here is I will have many models to track / follow so I am using polymorphism. For example I have a Location model:
class Location < ActiveRecord::Base
has_many :tracks, :as => :tracking, :dependent => :destroy
has_many :users, through: :tracks
Now in the console Location.first.users works fine along with User.first.tracked_locations.
Now I will be adding another polymorphic relationship along the lines of Flagged. The user can 'flag' another model with a note etc. So if I add has_many :users, through: :flagged to the Location model for example I need to differentiate between tracking users and flagged users.
I tried:
has_many :tracking_users, through: :tracks, source: :tracking, source_type: 'User'
but I get:
NoMethodError: undefined method `evaluators_for' for #<Location:0x007ff29e5409c8>
Can I even do this or am I missing something simple here?
UPDATE
Based on the answer below I figured it out:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
I'm not 100% on this, but you could try:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
Or you could also just create a class method and do it by hand.
def self.tracking_users
user_ids = tracks.collect(&:user_id)
User.where(id: user_ids)
end
edit: Had a brainfart, changed the "source" up there to :user. That tells what table to actually do the lookup in with the other attribute you've provided. of course it wouldn't be in :tracks

Rails 4 has_many :through association with :source

I was following the answer laid out at the link below to set up a many_to_many relationships on my Rails 4 app. (New to rails, here.)
Implement "Add to favorites" in Rails 3 & 4
I have Users and Exercises, and I want users to be able to have Favorite Exercises. I created a join table called FavoriteExercise with user_id and exercise_id as columns. I've got it populating, and it seems to be working fine, but I'm not able to use it to call directly to my favorites. 
Meaning, I want to type:
user.favorite = #list of exercises that have been favorited
I get this error when I try to load that list in my browser:
SQLite3::SQLException: no such column: exercises.favorite_exercise_id:
SELECT "exercises".* FROM "exercises" INNER JOIN "favorite_exercises"
ON "exercises"."favorite_exercise_id" = "favorite_exercises"."id"
WHERE > "favorite_exercises"."user_id" = ?
My models:
class User < ActiveRecord::Base
has_many :workouts
has_many :exercises
has_many :favorite_exercises
has_many :favorites, through: :favorite_exercises, source: :exercises
class Exercise < ActiveRecord::Base
belongs_to :user
has_many :workouts, :through => :exercises_workouts
has_many :favorites
has_many :favorited_by, through: :favorite_exercises, source: :exercises
class FavoriteExercise < ActiveRecord::Base
has_many :exercises
has_many :users
I just tried switching FavoriteExercise to 'belongs_to' instead of 'has_many, because it seems maybe that's the way that should go? but then I get this error:
uninitialized constant User::Exercises
Just trying to figure out how to set up the tables and associations so I can call .favorites on a user and get all their favorites.
If you want the list of exercises of the user and at the same, the list of favorite exercise of the user, then I think your join table should just be users_exercises wherein it will list all the exercises by the users. To list the favorite exercises, just add a boolean field indicating if the exercise is a user favorite and add a :scope to get all the favorite exercises.
So in your migration file:
users_exercises should have user_id, exercise_id, is_favorite
Then in your model:
class User < ActiveRecord::Base
has_many :workouts
has_many :users_exercises
has_many :exercises, through: :users_exercises
scope :favorite_exercises, -> {
joins(:users_exercises).
where("users_exercises.is_favorite = ?", true)
}
class Exercise < ActiveRecord::Base
has_many :workouts, :through => :exercises_workouts
has_many :users_exercises
has_many :users, through: :users_exercises
class UsersExercise < ActiveRecord::Base
belongs_to :exercise
belongs_to :user
You just need to simplify your model logic as follow:
class User < ActiveRecord::Base
has_many :workouts
has_many :exercises
has_many :favorite_exercises
has_many :favorites, through: :favorite_exercises, class_name: "Exercise"
class Exercise < ActiveRecord::Base
belongs_to :user
has_many :workouts, :through => :exercises_workouts
has_many :favorite_exercises
has_many :favorited_by, through: :favorite_exercises, class_name: "User"
class FavoriteExercise < ActiveRecord::Base
belongs_to :favorited_by
belongs_to :favorite
Then you can call user.excercises or excercise.users in your User/Excercise instance.
user.excercises = #list of exercises that have been favorited
Is that the many-to-many relationship you want?

Cannot have a has_one :through association ' where the :through association is a collection

I have the following associations. PropertyOwner is a join model which belongs to a property and polymorphically belongs to an owner, which in the below example is a ForeclosureDefense. Everything works well, until I had the has_one :main_property. The idea is the ForeclosureDefense model can have many properties, but the last property is the main property:
class ForeclosureDefense < ActiveRecord::Base
has_many :property_owners, as: :owner
has_many :properties, through: :property_owners
has_one :main_property, through: :property_owners, source: :property, order: 'created_at desc'
end
class PropertyOwner < ActiveRecord::Base
belongs_to :property
belongs_to :owner, polymorphic: :true
end
class Property < ActiveRecord::Base
has_many :property_owners
has_many :owners, through: :property_owners
has_many :foreclosure_owners, through: :property_owners, source: :owner, source_type: "ForeclosureDefense"
has_many :folder_owners, through: :property_owners, source: :owner, source_type: "Folder"
end
Unfortunately, when I try to use that has_one :main_property association, I get the following error:
ActiveRecord::HasOneThroughCantAssociateThroughCollection: Cannot have a has_one :through association 'ForeclosureDefense#main_property' where the :through association 'ForeclosureDefense#property_owners' is a collection.
What am I doing wrong?
My solution was just to add it as a class-level macro:
def main_property
properties.order('created_at desc').first
end

Resources