So, I have a system where users are able to follow authors (other users).
User Model:
class User < ActiveRecord::Base
has_many :author_following, class_name: 'Following'
has_many :following, through: :author_following, source: :author
has_many :followers, foreign_key: 'author_id', through: :author_following, source: :user
end
Following Model:
class Following < ActiveRecord::Base
belongs_to :user
belongs_to :author, foreign_key: 'author_id', class_name: "User"
end
Issue: I am able to get the list of authors that i am following, but I am able to get the list of my followers.
Given: u is a valid user that is following others and has followers
u.following generates the following SQL:
SELECT "users".* FROM "users" INNER JOIN "followings" ON "users"."id" = "followings"."author_id" WHERE "followings"."user_id" = $1 [["user_id", 1]]
Which is correct..
u.followers generates the following SQL:
SELECT "users".* FROM "users" INNER JOIN "followings" ON "users"."id" = "followings"."user_id" WHERE "followings"."user_id" = $1 [["user_id", 1]]
Which is wrong..
Ideally this SQL would be WHERE "followings"."author_id" = $1
Of course, I figure it your right after posting the question. However if you think there is a more elegant way of doing this, please comment :)
To solve, I changed:
User Model:
class User < ActiveRecord::Base
has_many :author_following, class_name: 'Following'
has_many :following, through: :author_following, source: :author
has_many :author_followers, foreign_key: 'author_id', class_name: 'Following'
has_many :followers, through: :author_followers, source: :user
end
Following Model:
class Following < ActiveRecord::Base
belongs_to :user
belongs_to :author, class_name: "User"
end
Another way is to use has_and_belongs_to_many. No second model needed.
class User < ActiveRecord::Base
has_and_belongs_to_many :followers, class_name: 'User', foreign_key: 'follower_id'
has_and_belongs_to_many :followees, class_name: 'User', foreign_key: 'followee_id'
end
# Migration
create_table :followees_followers do |t|
t.belongs_to :followee
t.belongs_to :follower
end
This is simpler, but the validation part(say verifying somebody is an author) need to be done in User model
#Billy Chan's answer above is close, but you also need to specify the other side of relationship as well with "association_foreign_key", and switch follower_id with followee_id on our side. Also, join table is users_users actually.
class User < ActiveRecord::Base
has_and_belongs_to_many :followers, class_name: 'User',
foreign_key: 'followee_id', association_foreign_key: 'follower_id'
has_and_belongs_to_many :followees, class_name: 'User',
foreign_key: 'follower_id', association_foreign_key: 'followee_id'
end
# Migration
create_table :users_users do |t|
t.belongs_to :followee
t.belongs_to :follower
end
Now User.followers and User.followees work as expected
Related
I am building matching system between users, originally it was has and belongs to many association, but due to some validations and additional data i need for that join table, i need to have model for that one, and change it to has many through.
has_and_belongs_to_many(:likes,
class_name: :User,
join_table: :user_likes,
foreign_key: :user_liker_id,
association_foreign_key: :user_liked_id)
has_and_belongs_to_many(:likeds,
class_name: :User,
join_table: :user_likes,
foreign_key: :user_liked_id,
association_foreign_key: :user_liker_id)
This is old implementation which needs to be converted to has_many through. As you can see, with this one i can retrieve all users which i liked and users which liked me. I have created UserLike model but i don't have any success with making it workable inside user model. What i already tried is following:
user.rb
has_many :user_likes
has_many :likes, through: :user_likes, foreign_key: :user_liked_id
has_many :likeds, through: :user_likes, foreign_key: :user_liker_id
user_like.rb
class UserLike < ApplicationRecord
belongs_to :user
end
This implementation raises following exception:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) "like" or :likes in model UserLike. Try 'has_many :likes, :through => :user_likes, :source => '. Is it one of user or user_likes?
I don't know what i am missing here, any help would be appreciated :)
When creating a join table that joins the same table twice you need two separate associations since the user can be in either foreign key:
class User < ApplicationRecord
has_many :likes_as_liker,
class_name: 'Like',
foreign_key: 'liker_id'
has_many :likes_as_liked,
class_name: 'Like',
foreign_key: 'liked_id'
end
class Like < ApplicationRecord
belongs_to :liker, class_name: 'User'
belongs_to :liked, class_name: 'User'
end
With our two has_many associations done we can start creating indirect associations:
class User < ApplicationRecord
has_many :likes_as_liker,
class_name: 'Like',
foreign_key: 'liker_id'
has_many :likes_as_liked,
class_name: 'Like',
foreign_key: 'liked_id'
has_many :likers,
through: :likes_as_liked
has_many :liked_users,
through: :likes_as_liker,
source: :liked
end
Which works perfectly:
irb(main):002:0> u.likers
User Load (2.8ms) SELECT "users".* FROM "users" INNER JOIN "likes" ON "users"."id" = "likes"."liker_id" WHERE "likes"."liked_id" = $1 LIMIT $2 [["liked_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):003:0> u.liked_users
User Load (1.3ms) SELECT "users".* FROM "users" INNER JOIN "likes" ON "users"."id" = "likes"."liked_id" WHERE "likes"."liker_id" = $1 LIMIT $2 [["liker_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
As per the description mentioned in the post you are trying to find the users which have liked my post and the users whom I have liked.
Below mentioned code does not tell rails that likes and likeds are actually users.
has_many :user_likes
has_many :likes, through: :user_likes, foreign_key: :user_liked_id
has_many :likeds, through: :user_likes, foreign_key: :user_liker_id
Modify it to below
has_many :user_likes
has_many :likes, through: :user_likes, foreign_key: :user_liked_id, source: :liked
has_many :likeds, through: :user_likes, foreign_key: :user_liker_id, source: :liked
Best Approach is specifying class along with class name so that rails know in which class is mapped to this foreign key when specifying customized relation names
When your association name is different than the name used at the :through you have to define the source parameter. If you look at the exception message it explicitly asks you to do it.
Hope it answers the question.
I have the following models and relationships:
A User has many Offers (where he/she is the seller), an Offer has many Purchases, a Purchase has many Accbooks
Models and associations:
class User < ApplicationRecord
has_many :offers, foreign_key: :seller_id
has_many :purchases, foreign_key: :buyer_id
end
class Offer < ApplicationRecord
has_many :purchases
belongs_to :seller, class_name: 'User'
end
class Purchase < ApplicationRecord
belongs_to :offer
belongs_to :buyer, class_name: 'User'
has_one :seller, through: :offer
has_many :accbooks, class_name: 'Admin::Accbook', foreign_key: 'purchase_id'
end
module Admin
class Accbook < ApplicationRecord
belongs_to :purchase
end
end
I want to get all the Accbooks of any given user (as a seller). The equivalent SQL statement would look like this:
SELECT "accbooks".*
FROM "accbooks"
INNER JOIN "purchases" ON "purchases"."id" = "accbooks"."purchase_id"
INNER JOIN "offers" ON "offers"."id" = "purchases"."offer_id"
INNER JOIN "users" ON "users"."id" = "offers"."seller_id"
WHERE "users"."id" = ?
So far I've tried this:
Admin::Accbook.joins( {purchase: :offer} )
Which gives me this SQL as a result:
SELECT "accbooks".*
FROM "accbooks"
INNER JOIN "purchases" ON "purchases"."id" = "accbooks"."purchase_id"
INNER JOIN "offers" ON "offers"."id" = "purchases"."offer_id"
Now I don´t know how to add the join to the User model, and then how to add the Where condition.
Thanks for any insight.
You can joins the relations together and apply where clause on the joined relations:
Admin::Accbook
.joins(purchase: :offer)
.where(offers: { seller_id: 123 })
A thing to know, where uses the DB table's name. joins (and includes, eager_load, etc) uses the relation name. This is why we have:
Admin::Accbook
.joins(purchase: :offer)
# ^^^^^ relation name
.where(offers: { seller_id: 123 })
# ^^^^^^ table name
Try Adding following association in users.rb
has_many :accbooks, through: :purchases
So your problem is user is acting as 2 roles for same accounts. You can try something like below stuff
class User < ApplicationRecord
has_many :offers, foreign_key: :seller_id
has_many :purchases, foreign_key: :buyer_id
has_many :offers_purchases,
through: :offers,
:class_name => 'Purchase',
:foreign_key => 'offer_id',
:source => :purchases
end
I have a relationship model in which two Users can enter into a Trade for the exchange of two Items.
class User < ActiveRecord::Base
has_many :owned_items, class_name: "Item"
has_many :trades_received, class_name: "Trade", through: :owned_items, source: :trades
has_many :trades
has_many :wanted_items, class_name: "Item", through: :trades, source: :item
end
class Item < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: :user_id
has_many :trades, dependent: :destroy
has_many :trade_requesters, through: :trades
has_many :trade_recipients, through: :trades
end
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item", foreign_key: :wanted_item_id
belongs_to :collateral_item, class_name: "Item", foreign_key: :collateral_item_id
end
The migration on my Trades table looks like this:
create_table :trades do |t|
t.belongs_to :trade_requester
t.belongs_to :trade_recipient
t.belongs_to :wanted_item
t.belongs_to :collateral_item
end
The stack trace leads to a helper method I'm using to list all Trade requests. That line says #trades = current_user.trades_received.requested.count, and then on down to the model association on User where has_many :owned_items, class_name: "Item". From my understanding, it looks like the trades_received method, which is called through: :owned_items and source: :trades should be referencing the :wanted_item_id foreign key in the migration. But it is not. It works if I create a migration to add item_id, but a Trade needs two items, and so I've split it up into the two wanted_item and collateral_item associations. How do I set that User association up so that it references the Item that is being requested by another User? Should Items has_many :trades, the way I have it, or should Items belongs_to :trades?
Full error:
PG::UndefinedColumn: ERROR: column trades.item_id does not exist
LINE 1: ...LECT COUNT(*) FROM "trades" INNER JOIN "items" ON "trades"."...
^
: SELECT COUNT(*) FROM "trades" INNER JOIN "items" ON "trades"."item_id" = "items"."id" WHERE "items"."user_id" = $1 AND "trades"."approved" IS NULL
tldr: I need to track a bunch of complex has_many :through associations, I don't think my data model is correct, and need help understanding why. Thank you.
You're setting up two has_many :through relationship between User and Item, with Trade as the join table for both. You got some relationship confused. Here is a setup based on your migration:
class User < ActiveRecord::Base
has_many :received_trades, class_name: "Trade", foreign_key: "trade_recipient"
has_many :requested_trades, class_name: "Trade", foreign_key: "trade_requester"
has_many :collateral_items, through: :received_trades
has_many :wanted_items, through: :requested_trades
end
class Item < ActiveRecord::Base
has_many :collateral_items, class_name: "Trade", foreign_key: "collateral_item"
has_many :wanted_items, class_name: "Trade", foreign_key: "wanted_item"
has_many :trade_requesters, through: :wanted_items
has_many :trade_recipients, through: :collateral_items
end
class Trade < ActiveRecord::Base
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item"
belongs_to :collateral_item, class_name: "Item"
end
##migration
create_table :trades do |t|
t.belongs_to :trade_requester
t.belongs_to :trade_recipient
t.belongs_to :wanted_item
t.belongs_to :collateral_item
end
Some explanation:
Item has_many :collateral_item ## item_id in table collateral_items
Item has_many :collateral_item, class_name: "Trade", foreign_key: "collateral_item"
##collateral_item_id in trades table.
Ok, well. The problem is here:
has_many :trades, dependent: :destroy
And in your Trade model:
belongs_to :wanted_item, ...
belongs_to :collateral_item, ..
Rails cannot handle this automatically.
You need to do one of this steps (depending on what you need in your app):
If you need separate associations:
class User < ActiveRecord::Base
has_many :trades_received, class_name: "Trade", through: :owned_items, source: :wantable_trades
end
class Item < ActiveRecord::Base
has_many :wanted_trades, class_name: 'Trade', inverse_of: :wanted_item, dependent: :destroy
has_many :collateral_trades, class_name: 'Trade', inverse_of: :collateral_item, dependent: :destroy
end
If you need all trades as single association:
Well, you will have a pain in the ass :) In this case you should either select associations manually, or rethink your data model.
Users can follow other Users through FollowingRelationship
I would like to be able to say
User.first.followings and it returns a list of Users
This is not working:
Class User
has_many :following_relationships
has_many :followings, through: :following_relationships, foreign_key: :following_id, source: :user
end
Class FollowingRelationship
attr_accessible :following_id, :follower_id
belongs_to :followings, class_name: "User"
end
User.first.followings gives this in console:
SELECT "users".* FROM "users" INNER JOIN "following_relationships" ON "users"."id" = "following_relationships"."user_id" WHERE "following_relationships"."user_id" = 1
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: following_relationships.user_id: SELECT "users".* FROM "users" INNER JOIN "following_relationships" ON "users"."id" = "following_relationships"."user_id" WHERE "following_relationships"."user_id" = 1
Anyone see what piece I am missing?
I think the problem is that FollowingRelationship model doesn't have any relation with User.
It should have belongs_to :user since you specify source: :user, shouldn't it?
I don't quite understand your models and relations between them but belongs_to :followings looks rather strange.
belongs_to uses singular form since it can't belong to more than one object.
UPDATE 1
To be more clear.
I think you should have
class FollowingRelationship
…
belongs_to :user
belongs_to :follower
end
That means followings_relationships table should have user_id and follower_id columns.
UPDATE 2
After some conversation we have figured it out.
Here is the code you should have
class User < ActiveRecord::Base
has_many :fade_relationships, foreign_key: :faded_id
has_many :fadings, through: :fade_relationships, source: :fading
end
class FadeRelationship < ActiveRecord::Base
attr_accessible :faded_id, :fading_id
belongs_to :fading, class_name: "User"
end
I am making a rails app where a user can post courses and also subscribe to courses.My model files are:
user.rb:
class User < ActiveRecord::Base
has_many :courses, dependent: :destroy
has_many :coursejoins, dependent: :destroy
has_many :learnables, through: :coursejoins, class_name: "Course"
has_many :comments, dependent: :destroy
...
end
course.rb:
class Course < ActiveRecord::Base
belongs_to :user
belongs_to :category
has_many :coursejoins, dependent: :destroy
has_many :students, through: :coursejoins, class_name: "User"
has_many :documents, dependent: :destroy
has_many :comments, dependent: :destroy
...
end
coursejoin.rb:
class Coursejoin < ActiveRecord::Base
belongs_to :learnable, :class_name => "Course"
belongs_to :student, :class_name => "User"
end
20160219171527_create_coursejoins.rb
class CreateCoursejoins < ActiveRecord::Migration
def change
create_table :coursejoins do |t|
t.boolean :accepted
t.timestamps
end
end
end
20160219174937_add_student_id_to_coursejoins.rb
class AddStudentIdToCoursejoins < ActiveRecord::Migration
def change
add_column :coursejoins, :student_id, :integer
end
end
20160219224309_add_learnable_id_to_coursejoins.rb
class AddLearnableIdToCoursejoins < ActiveRecord::Migration
def change
add_column :coursejoins, :learnable_id, :integer
end
end
The coursejoin model is like a relation between a course and a user.
When I try to do #course.students, i get error:
SELECT "users".* FROM "users" INNER JOIN "coursejoins" ON "users"."id" = "coursejoins"."student_id" WHERE "coursejoins"."course_id" = ? [[nil, 1]]
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: coursejoins.course_id: SELECT "users".* FROM "users" INNER JOIN "coursejoins" ON "users"."id" = "coursejoins"."student_id" WHERE "coursejoins"."course_id" = ?
I don't know SQL much so I am not able to decipher the error.
I looked at questions with similar error but none seemed related.
I have been stuck with this for two days.
How do I fix this?
Okay i got this solved and thought i should post the solution for future reference. Basically all that was needed to be done was adding foreign_key: "learnable_id" to "has_many :coursejoins, dependent: :destroy" in course model and foreign_key: "student_id" to has_many :coursejoins, dependent: :destroy to user model.