Rails has_many through, issues with foreign key and source - ruby-on-rails

A User has many subscribers and many publishers, both of which are users:
class User < ActiveRecord::Base
has_many :relationships, :foreign_key => "subscriber_id"
has_many :subscribers, :through => :relationships, :source => :subscriber
has_many :inverse_relationships, :class_name => "Relationship", :foreign_key => "publisher_id"
has_many :publishers, :through => :inverse_relationships, :source => :publisher
def subscribe_to(publisher)
self.relationships.create!(publisher_id: publisher.id, subscriber_id: id)
end
end
class Relationship < ActiveRecord::Base
belongs_to :subscriber, :class_name => "User"
belongs_to :publisher, :class_name => "User"
end
A user's publishers is who the user is subscribed to.
However, if I do john.subscribe_to(a_publisher), and then attempt to puts john.publishers, I get back an empty array.
I'm going back and forth randomly changing the foreign key and the source, hoping that it will eventually work, but something is off. What should the sources and foreign keys be here?
Update
Here's what I did to make it work:
class User < ActiveRecord::Base
has_many :relationships, :foreign_key => "publisher_id"
has_many :subscribers, :through => :relationships, :source => :subscriber
has_many :inverse_relationships, :foreign_key => "subscriber_id", :class_name => "Relationship"
has_many :publishers, :through => :inverse_relationships, :source => :publisher
def subscribe_to(publisher)
publisher.relationships.create!(subscriber_id: id)
end
end
First, I switched the foreign keys for both. Second, and this is the part I don't understand, I changed
self.relationships.create!(publisher_id: publisher.id)
to
publisher.relationships.create!(subscriber_id: id)
and it worked. For some reason, it doesn't work the other way around. Can anyone explain why?

Your association seems strange. You should try like this:
has_many :subscribers, :through => :relationships, :class_name => 'User'
And call :
john.subscribers.create(a_publisher)
Nowhere you're saying that subscriber is a User. I think your problem comes from here.
Maybe you can try with: :source => :user

Related

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.

How do I set up a directed many-to-many self-join using :through?

I'm setting up a Rails project for a client, and they want Users (a model) to be able to follow each other (as in Twitter). They also want to be able to keep track of when one user started following another.
Since I need to keep track of the creation date, I figured, a has_many X, :through => Y relation would be the way to go, so the Y will keep track of the date it was created.
I have my Follow model set up:
class Follow < ActiveRecord::Base
attr_accessible :actor_id, :observer_id, :follower, :followee
attr_readonly :actor_id, :observer_id, :follower, :followee
belongs_to :follower, :class_name => 'User', :foreign_key => :observer_id
belongs_to :followee, :class_name => 'User', :foreign_key => :actor_id
validates_presence_of :follower, :followee
validates_uniqueness_of :actor_id, :scope => :observer_id
end
The question is how do I set up the relations in the User model?
Ideally I'd like it to have the following:
:follows would be the associated Follow objects where self is the follower (observer_id)
:followed would be the associated Follow objects where self is the followee (actor_id)
:following would be the associated User objects where self is the follower (observer_id)
:followers would be the associated User objects where self is the followee (actor_id)
I'm not sure how to write the has_many :through parts, though? Should I be using :source => X or foreign_key => X? And which key (actor_id or observer_id) should I put in each?
Edit: I'm currently doing this
has_many :follows, :foreign_key => :observer_id
has_many :followed, :class_name => 'Follow', :foreign_key => :actor_id
has_many :following, :class_name => 'User', :through => :follows, :source => :followee, :uniq => true
has_many :followers, :class_name => 'User', :through => :follows, :source => :follower, :uniq => true
and it's mostly working. All of them except :followers work fine, but user.followers is doing something weird. It seems like it's checking whether user is followING someone, and if they are then user.followers returns an array containing only user; if they're not, it returns an empty array.
Does anyone have any advice?
It looks like this is the correct format:
has_many :follows, :foreign_key => :observer_id
has_many :followed, :class_name => 'Follow', :foreign_key => :actor_id
has_many :following, :class_name => 'User', :through => :follows, :source => :followee, :uniq => true
has_many :followers, :class_name => 'User', :through => :followed, :source => :follower, :uniq => true
For Rails newbies, the :uniq => true is important (as I discovered while doing this) because it keeps has_many X, :through => Y relations from returning duplicates (that is, without it, you might get multiple distinct objects, each with their own object ID, all referring to the same record/row).

What do polymorphic associations solve?

Let's assume I have an application with a "favorites" feature, where users can add a document, note, or comment to his favorites list.
In my mind..
User has_many Favorites
Favorite belongs_to a User
Document belongs_to a Favorite
Note belongs_to a Favorite
Comment belongs_to a Favorite
What's the problem with this type of association and how would polymorphic associations help?
because then your Favorite instance will not know what it is favouriting :)
it knows that it has_one :note, but also has_one :comment, or? but not both surely.
polymorphic association the opposite way helps because it will express that a Favorite object belongs_to :favorited object that is polymorphic cos it can be any class the name of which will be stored in the :favorited_type string db column, so your favorite object will know that it favors a note or document or comment.
with some code
class Note
has_many :favorites, :as => :favorited
has_many :fans, :through => :favorites, :source => :user
end
class Discussion
has_many :favorites, :as => :favorited
has_many :fans, :through => :favorites, :source => :user
end
class Comment
has_many :favorites, :as => :favorited
has_many :fans, :through => :favorites, :source => :user
end
class Favorite
belongs_to :user
belongs_to :favorited, :polymorphic => true # note direction of polymorphy
end
class User
has_many :favorites
has_many :favorite_notes, :through => :favorites, :source => favorited, :source_type => "Note"
has_many :favorite_comments, :through => :favorites, :source => favorited, :source_type => "Comment"
has_many :favorite_discussions, :through => :favorites, :source => favorited, :source_type => "Discussion"
end
(just set up your db correctly) this design is standard for such usecase of favoriting.

Reverse has_many with polymorphism

I have two models: Users and Projects. The idea is that Users can follow both projects AND other users. Naturally, Users and Projects are part of a polymorphic "followable" type. Now, using the user model, I'd like to get three things:
user.followed_users
user.followed_projects
user.followers
The first two work fine; It's the third that I'm having trouble with. This is sort of a reverse lookup where the foreign key becomes the "followable_id" column in the follows table, but no matter how I model it, I can't get the query to run correctly.
User Model
has_many :follows, :dependent => :destroy
has_many :followed_projects, :through => :follows, :source => :followable, :source_type => "Project"
has_many :followed_users, :through => :follows, :source => :followable, :source_type => "User"
has_many :followers, :through => :follows, :as => :followable, :foreign_key => "followable", :source => :user, :class_name => "User"
Follow Model
class Follow < ActiveRecord::Base
belongs_to :followable, :polymorphic => true
belongs_to :user
end
My follows table has:
user_id
followable_id
followable_type
Whenever I run the query I get:
SELECT `users`.* FROM `users` INNER JOIN `follows` ON `users`.`id` = `follows`.`user_id` WHERE `follows`.`user_id` = 7
where it should be "followable_id = 7 AND followable_type = 'User", not "user_id = 7"
Any thoughts?
Figured it out. Took a look at a sample project Michael Hartl made and noticed that the correct way to do this is to specify not only a relationship table (in this case follows) but also a reverse relationship table (which I called reverse follows).
has_many :follows,
:dependent => :destroy
has_many :followed_projects,
:through => :follows,
:source => :followable,
:source_type => "Project"
has_many :followed_users,
:through => :follows,
:source => :followable,
:source_type => "User"
has_many :reverse_follows,
:as => :followable,
:foreign_key => :followable_id,
:class_name => "Follow"
has_many :followers,
:through => :reverse_follows,
:source => :user
Hope this helps some people out down the line!
I think you need to explicitly spell out the foreign key. You have:
:foreign_key => "followable"
you need:
:foreign_key => "followable_id"
full code:
has_many :followers, :through => :follows, :as => :followable, :foreign_key => "followable_id", :source => :user, :class_name => "User"

Help to build a Model in Rails

class Profile
has_many :projects, :through => "teamss"
has_many :teams, :foreign_key => "member_id"
has_many :own_projects, :class_name => "Project", :foreign_key => :profile_id
has_many :own_teams, :through => :own_projects, :source => :teams
end
class Project
belongs_to :profile, :class_name => "Profile"
has_many :teams
has_many :members, :class_name => "Profile", :through => "teams", :foreign_key => "member_id"
end
class Team
belongs_to :member, :class_name => 'Profile'
belongs_to :project
end
I need to create model Evaluation. What I want to do is generate a link in project#view page for each member of the project, including the owner, in order to make an Evaluation The person will click on the link and evaluate the person related to this link. The owner of the Project will evaluate all the members, and all the members will evaluate the owner.
I have defined model Evaluation as following, but I think I miss something:
class Evaluations < ActiveRecord::Base
belongs_to :evaluated, :class_name => 'Profile', :foreign_key => "evaluated_id"
belongs_to: :profile, :class_name => 'Profile', :foreign_key => "profile_id"
end
Remembering that Evaluation table will have tons of attributes, that's why I'm going not going with has_many_and_belongs_to_many.
How can I create this model in order to do what I want and be able to acess all I need via project#show page?
Thanks!
Edited
Changes done:
class Profile
has_many :evaluations, :dependent => :destroy, :foreign_key => :evaluation_id
has_many :evaluators, :through => :evaluations, :foreign_key => :profile_id
end
class Project
has_many :evaluations,:foreign_key => "project_id"
end
class Evaluations < ActiveRecord::Base
belongs_to :evaluated, :class_name => 'Profile', :foreign_key => "evaluated_id"
belongs_to: :profile, :class_name => 'Profile', :foreign_key => "profile_id"
belongs_to: :project, :class_name => 'Project', :foreign_key => "project_id"
end
You have your team and profile (member) associations a little messed up. A team has many profiles (members), and a profile (member) belongs to a team. Beyond that, you may just be over-complicating things. I'm curious as to why you wouldn't just call the Profile Model Member instead and keep things that much simpler?
That being said, you are doing a pretty complex set of associations. I would highly recommend you read the ActiveRecord Associations doc closely to get a firm understanding of how these things work, especially how primary and foreign keys are automagically determined.
I think below will get you what you want.
class Team
has_and_belongs_to_many :members,
:class_name => "Profile"
belongs_to :project
belongs_to :team_leader,
:class_name => "Profile"
end
class Profile
has_and_belongs_to_many :teams
has_many :projects,
:through => :teams
has_many :owned_projects,
:class_name => "Project",
:foreign_key => "owner_id"
has_many :owned_teams,
:class_name => "Team",
:foreign_key => "team_leader_id"
has_many :evaluations
has_many :evaluations_given,
:class_name => "Evaluation",
:foreign_key => "evaluator_id"
end
class Project
has_many :teams
belongs_to :owner,
:class_name => "Profile"
has_many :members,
:through => :teams
has_many :evaluations
end
class Evaluation
belongs_to :profile
belongs_to :evaluator,
:class_name => "Profile",
:foreign_key => "evaluator_id"
belongs_to :project
end
In support of these model definitions would be the following tables/keys:
evaluation
id
profile_id
evaluator_id
project_id
teams
id
project_id
team_leader_id
profiles
id
project
id
owner_id
profiles_teams
profile_id
team_id
With this, you should be able to access collections/objects that you need to get your links right such as:
project.members
project.owner
project.teams
team.project
profile.projects
profile.owned_projects
profile.teams
team.members
team.leader
project.evaluations
profile.evaluations
evaluation.evaluator
profile.evaluations_given
Note, also, that your nested resources are going to be just as complex, if you try to stay with a RESTFUL framework. Read up!

Resources