Rails: Friendship join table clarification - ruby-on-rails

I'm currently working on my first project and am trying to add "friending" to my app. However, I'm having a little trouble grasping the concept of the friendships model, since attributes that don't seem to exist in my database seem to just work.
I have my model and database columns listed below. I understand that this is a join table, but in the user model where do friends, requested_friends, and pending friends come from if they don't exist in the friendships table? Is it because it is a virtual join table so that I could just add whatever columns I need without having to migrate them?
Also, the tutorial eventually does #user.pending_friends.each even though that column doesn't exist in my users table. I'm pretty confused on this part.
Currently I'm going through this brief tutorial: http://railsforum.com/viewtopic.php?id=16760
Here are my files:
user model
class User < ActiveRecord::Base
...
...
has_many :friendships, :dependent => :destroy
has_many :friends, :through => :friendships, :conditions => "status = 'accepted'"
has_many :requested_friends, :through => :friendships, :source => :friend, :conditions => "status = 'requested'", :order => :created_at
has_many :pending_friends, :through => :friendships, :source => :friend, :conditions => "status = 'pending", :order => :created_at
end
friendship model
class Friendship < ActiveRecord::Base
attr_accessible :friend_id
belongs_to :user
belongs_to :friend, :class_name => "User"
end
schema
create_table "friendships", :force => true do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "users", :force => true do |t|
t.string "name"
t.string "email"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "password_digest"
t.string "remember_token"
end

You probably forgotten to migrate since you don't have status column in your schema. Associations you're referring is bound to friend_id key that exist in friendships table. Last 2 associations is just using foreign key defined for belongs_to :friend. Please have a look here. I guess such associations can work without actual attribute if this is what you meant, you just don't get condition: functionality here and all 3 last associations will return same values.

Related

Unexpected behaviour in rails when defining class_name in habtm

I have two models (Course and Dancer). A course can have many dancers (students) and teachers (also dancers). Teachers can be students of other courses.
I define the tables as follows:
create_table "course_enrollments", :force => true do |t|
t.integer "dancer_id", :null => false
t.integer "course_id", :null => false
t.datetime "attended_on", :null => false
end
create_table "courses", :force => true do |t|
t.string "name", :null => false
t.string "genre"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "courses_teachers", :id => false, :force => true do |t|
t.integer "course_id", :null => false
t.integer "teacher_id", :null => false
end
create_table "dancers", :force => true do |t|
t.string "first_name", :null => false
t.string "last_name", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
And the classes:
class Dancer < ActiveRecord::Base
has_many :course_enrollments
has_many :courses, :through => :course_enrollments
has_many :teachers, :through => :courses
end
class Course < ActiveRecord::Base
has_many :course_enrollments
has_many :dancers, :through => :course_enrollments
has_and_belongs_to_many :teachers, :class_name => 'Dancer'
end
class CourseEnrollment < ActiveRecord::Base
belongs_to :dancer
belongs_to :course
end
Based on the guide (http://guides.rubyonrails.org/association_basics.html), I would expect the teachers attribute of Course to look for the table courses_teachers and use teacher_id as the foreign key. Instead it's looking for courses_dancers and dancer_id, presumably from setting class_name to 'Dancer'. Is this by design or is it a bug? I can get it to work if I instead do:
has_and_belongs_to_many :teachers, :class_name => 'Dancer', :join_table => :courses_teachers
And rename teacher_id to dancer_id in the courses_teachers table
Is there a better approach?
If you look at the api for habtm, there's an option for foreign_key and association_foreign_key. Try the following
has_and_belongs_to_many :teachers, :class_name => 'Dancer', :join_table => :courses_teachers, :foreign_key => :course_id, :association_foreign_key => :teacher_id
I'm not sure if it will work without passing foreign_key option but it probably will so try without it first.

Ruby on Rails Many to Many Relationship with Self

I am trying to save a person's Facebook friends into my database. I want to store the Facebook users in a table and then store their friendships in another table. The friendships would have the integer of the FacebookUser that requested the friendship and the integer of the friend, both of which are foreign keys to the facebook_users table. However I keep getting this message when I try to link the a user's facebook friends with friendships.
Error
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :friend or :friends in model Friendship. Try 'has_many :friends, :through => :friendships, :source
=> <name>'. Is it one of :FacebookUser or :FacebookFriend?
friendship.rb
class Friendship < ActiveRecord::Base
attr_accessible :facebook_user_id, :facebook_friend_id
belongs_to :FacebookUser
belongs_to :FacebookFriend, :class_name => :FacebookUser
end
facebook_user.rb
class FacebookUser < ActiveRecord::Base
attr_accessible :first_name, :gender, :last_name
has_many :friendships, :foreign_key => :facebook_user_id
has_many :friends, :through => :friendships, :source => :FacebookUser
end
Schema
create_table "facebook_users", :force => true do |t|
t.string "first_name"
t.string "last_name"
t.string "gender"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "friendships", :force => true do |t|
t.integer "facebook_user_id"
t.integer "facebook_friend_id"
end
the convention Rails uses is to use associations as defined by the class name and the foreign key. if you've set up your tables like above, you should change your models to the following.
class Friendship < ActiveRecord::Base
attr_accessible :facebook_user_id, :facebook_friend_id
belongs_to :facebook_user # implies a foreign key of facebook_user_id and class of FacebookUser
belongs_to :facebook_friend, class_name: 'FacebookUser' #implies a foreign key of facebook_friend_id
end
class FacebookUser < ActiveRecord::Base
attr_accessible :first_name, :gender, :last_name
has_many :friendships
has_many :friends, :through => :friendships, :source => :facebook_friend
end

Has many through with different foreign key

I have the following :has_many :through relation.
Associations
class Profile < ActiveRecord::Base
has_many :teams
has_many :projects, :class_name => "Project", :through => :teams
has_many :leads, :class_name => "Projects"
class Project < ActiveRecord::Base
has_many :teams
has_many :developers, :class_name => "Profile", :through => :teams
belongs_to :lead, :class_name => "Profile", :foreign_key => "developer_lead"
class Team < ActiveRecord::Base
belongs_to :developer, :class_name => "Profile"
belongs_to :project
When I try to get a Profiles projects the relationship doesn't use the right key in the teams table.
Rails C
1.9.3p194 :001 > Profile.first.projects
Profile Load (0.2ms) SELECT "profiles".* FROM "profiles" LIMIT 1
Project Load (0.2ms) SELECT "projects".* FROM "projects" INNER JOIN "teams" ON "projects"."id" =
"teams"."project_id" WHERE "teams"."profile_id" = 1
It should be using "teams"."developer_id" = 1
I've tried using a :foreign_key => "developer_id" in both the Profile and Project models, but nothing seems to work.
I feel like the changes to the models I've been making aren't taking any effects, after each change I've been restarting the rails console though.
Schema
create_table "profiles", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "projects", :force => true do |t|
t.integer "developer_lead"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "teams", :id => false, :force => true do |t|
t.integer "developer_id"
t.integer "project_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
:foreign_key => "developer_id" belongs with has_many :teams.
Also, it makes your code clearer if you stick to rails conventions and end all foreign_key names with "_id", as in "developer_lead_id".

Self referential has_many through in Rails

I've read lots about self referential classes in Rails, but am still having problems getting them working.
I have a class of Articles and I want them to be able to refer to each other, from a source article to an outcome article - and then be able to find the reverse. So I'm trying to do a has_many through, using another class called Links.
My schema is
create_table "articles", :force => true do |t|
t.string "name"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "links", :force => true do |t|
t.integer "source_id"
t.integer "outcome_id"
t.string "question"
t.datetime "created_at"
t.datetime "updated_at"
end
The models are
class Article < ActiveRecord::Base
has_many :links_as_source, :foreign_key => "source_id", :class_name => "Link"
has_many :sources, :through => :links_as_source
has_many :links_as_outcome, :foreign_key => "outcome_id", :class_name => "Link"
has_many :outcomes, :through => :links_as_outcome
end
and
class Link < ActiveRecord::Base
belongs_to :source, :foreign_key => "source_id", :class_name => "Article"
belongs_to :outcome, :foreign_key => "outcome_id", :class_name => "Article"
end
I can create articles in the console, and I can link articles together, using a.outcomes << b but the link table is only storing the outcome_id, not the source_id.
What am I doing wrong?
I got this to work in the end. I changed the names - I don't know if that mattered. I did read somewhere that source was a silly name to use for something.
So this is what works:
My schema
create_table "article_relationships", :force => true do |t|
t.integer "parent_id"
t.integer "child_id"
...
end
create_table "articles", :force => true do |t|
t.string "name"
...
end
My article model
has_many :parent_child_relationships,
:class_name => "ArticleRelationship",
:foreign_key => :child_id,
:dependent => :destroy
has_many :parents,
:through => :parent_child_relationships,
:source => :parent
has_many :child_parent_relationships,
:class_name => "ArticleRelationship",
:foreign_key => :parent_id,
:dependent => :destroy
has_many :children,
:through => :child_parent_relationships,
:source => :child

has_many :through with :primary_key on join table not working

In my Rails 3 project, I have a user model with a self referential join, through the follow model. I want to use this join table to find activity related to the followed user. I have almost everything set up correctly, except that the query generated by the join is totally ignoring the :primary_key option on the join model.
Here is the relevant schema for the relevant models:
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "first_name"
t.string "last_name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "follows", :force => true do |t|
t.integer "user_id"
t.integer "followed_user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "activities", :force => true do |t|
t.integer "user_id"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
end
Here's the associations in the models
class User < ActiveRecord::Base
has_many :follows
has_many :followed_users, :through => :follows
has_many :followed_activities, :through => :follows
has_many :activities
end
class Follow < ActiveRecord::Base
belongs_to :user
belongs_to :followed_user, :class_name => "User"
has_many :followed_activities, :primary_key => :followed_user, :foreign_key => :user_id, :class_name => "Activity"
end
The following work just fine:
u = User.first
u.follows # returns corresponding records from the follows table
u.followed_users # returns all users that u is following
u.followed_users.first.activities # returns all activity records corresponding to the first person the user is following
Follow.first.activities # same as the previous
However, the following just returns an empty array:
u.followed_activities
Here is the sql that is generated from the last statement:
Activity Load (0.2ms) SELECT `activities`.* FROM `activities` INNER JOIN `follows` ON `activities`.user_id = `follows`.id WHERE ((`follows`.user_id = 1))
The reason it isn't working is because it is trying to join use 'follows'.id as the primary key rather than 'follows'.followed_user.
Is this a bug, or do I have to repeat the :primary_key declaration somewhere on the user model? I can't find any mention anywhere in the Rails api, or anywhere else online.
Rails Version: 3.0.7
I've found it intuitive to daisy chain relationships with the 'nested_has_many_through' gem, http://rubygems.org/gems/nested_has_many_through which will be a standard part of rails 3.1 and could give you another tool to tackle the issue here
It will let you 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!
Justin, you have 2 associations called "followed_activities". sure, they have different context (different models), but I'd like to ask you to try method inside the association block like this:
has_many :followed_users, :through => :follows do
def activities
end
end

Resources