User has_many :users, :through => :friends - how? - ruby-on-rails

This is my code:
class Friend < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
end
class User < ActiveRecord::Base
#...
has_many :friends
has_many :users, :through => :friends
#...
end
When I now start adding users by...
user.users << user2
user.save
Only the user_id of friend is filled, friend_id is null.
Any help?
Yours,
Joern.

Try: Railscasts - Self-Referential Associations. Generally has very good tutorials on all topics listed.

You need to add the :source attribute to your has_many through association.
class User < ActiveRecord::Base
has_many :friends
has_many :users, :source => :friend, :through => :friends
end
Now the following calls will work.
u1.users << u2
u.friends.last
# will print #<Friend id: 1, user_id: 1, friend_id: 4>
Notes:
Rails auto saves the associations.You need to call save only if the user model is new.
You probably should rename the association to something more explicit. E.g: friend_users etc.

I think you need delete the belongs_to :user in your Friend model

Related

Friends of friends in Ruby on Rails

I have a self-referrential association Friendship. A user has many friends through friendships. I want to get a list of friends of friends. What would be the most efficient way to do this? I'm using ActiveRecord. Thanks!
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, through => :friendships, :class_name => "User"
def friends_of_friends
User.joins(:friendships).where(:user_id => friendships.pluck(:friend_id))
end
end
The friendship model would be something like
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => 'User'
end

Rails - How to give user notification messages on certain events

Im having a problem in rails. Im actually solving it but I guess theres a easier way out there.
I got user/membership/group models and user/invitation/event models. Membership joins user and group. Invitation joins user and event.
The membership and invitation model are equal. Group and event do have some equal some different columns. The membership/invitation model both have a boolean column "accepted", meaning the user which is invited to a group/event has to accept this invitation before he is a member/participant.
Now if a user signs in all group and event invitations should appear in a list. In fact I want to add more notifications to the system later on and events aren't even included in mine yet.
My solution is to add a notification model which belongs to user. So every user has many notifications. Additionally, this model is polymorphic and belongs to membership AND invitation.
#user model
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :groups, :through => :memberships
has_many :invitations, :dependent => :destroy
has_many :events, :through => invitations
has_many :notifications
#membership model (equal to invitation model)
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
has_one :notifications, :as => :noticeable
#group model (equal to event model but participants for members and invitation for membership)
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
has_many :members, :through => :memberships, :source => :user,
:conditions => ['memberships.accepted = ?', true]
#notification model
class Notification < ActiveRecord::Base
belongs_to :user
belongs_to :noticeable, :polymorphic => true
Ive added some data to the database and Im testing it in the console
myUser = User.find(6) # will be exchanged with current_user in the actual program
I will run through all notifications with each do... but for the start I test all further actions on one notification
myNotice = myUser.notifications.first
so whether the noticeable_type of myNotices is membership or invitation I will render it as group or event notification
in this case noticeable_type=membership
myGroup = Group.find(Membership.find(myNotice.noticeable_id).group_id)
--> Do you want to join the Group "myGroup.name"? Yes | No
On Yes: Membership.find(myNotice.noticeable_id).accepted = true
On No: Membership.find(myNotice.noticeable_id).destroy
And: myNotice.destroy
Thats the my idea.
Is this the way to solve the problem?
The "each do" which goes through all notifications will be in a view file. Which means "Group.find(Membership.find(myNotice.noticeable_id).group_id)" has to be in the view file or a partial aswell. Isn't that a bit ugly?
I think Ive used a lot of "find" which means many SQL queries. Isn't there a way to reduce them with any "Ruby on Rails"-magic?
Thank you :)
Needed something similar for my app, so updating the answer here in case someone finds it useful.
#user model
has_many :membership_notices, :through => :notifications, :source => :membership, :conditions => {"notifications.noticeable_type" => "membership"}
has_many :event_notices, :through => :notifications, :source => :event ,:conditions => {"notifications.noticeable_type" => "event"}
The notifications are now accessible as
user.membership_notices
user.event_notices
Finally, I found how to solve this! I bumped into this problem again and googled "reverse polymorphic". I found the following post:
https://gist.github.com/runemadsen/1242485
Polymorphic Associations reversed.
It's pretty easy to do polymorphic associations in Rails: A Picture
can belong to either a BlogPost or an Article. But what if you need
the relationship the other way around? A Picture, a Text and a Video
can belong to an Article, and that article can find all media by
calling #article.media
This example shows how to create an ArticleElement join model that handles the polymorphic relationship. To add fields that are common to all polymorphic models, add fields to the join model.
class Article < ActiveRecord::Base
has_many :article_elements
has_many :pictures, :through => :article_elements, :source => :element, :source_type => 'Picture'
has_many :videos, :through => :article_elements, :source => :element, :source_type => 'Video'
end
class Picture < ActiveRecord::Base
has_one :article_element, :as =>:element
has_one :article, :through => :article_elements
end
class Video < ActiveRecord::Base
has_one :article_element, :as =>:element
has_one :article, :through => :article_elements
end
class ArticleElement < ActiveRecord::Base
belongs_to :article
belongs_to :element, :polymorphic => true
end
t = Article.new
t.article_elements # []
p = Picture.new
t.article_elements.create(:element => p)
t.article_elements # [<ArticleElement id: 1, article_id: 1, element_id: 1, element_type: "Picture", created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]
t.pictures # [#<Picture id: 1, created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]

Rails has_many through with condition, build new

I've got users and organisations with a join model UsersOrganisation. Users may be admins of Organisations - if so the is_admin boolean is true.
If I set the is_admin boolean by hand in the database, Organisations.admins works as I'd expect.
In the console, I can do Organisation.first.users << User.first and it creates an organisations_users entry as I'd expect.
However if I do Organisation.first.admins << User.last it creates a normal user, not an admin, ie the is_admin boolean on the join table is not set correctly.
Is there a good way of doing this other than creating entries in the join table directly?
class User < ActiveRecord::Base
has_many :organisations_users
has_many :organisations, :through => :organisations_users
end
class Organisation < ActiveRecord::Base
has_many :organisations_users
has_many :users, :through => :organisations_users
has_many :admins, :through => :organisations_users, :class_name => "User",
:source => :user,
:conditions => {:organisations_users => {:is_admin => true}}
end
class OrganisationsUser < ActiveRecord::Base
belongs_to :organisation
belongs_to :user
end
You can always override the << method of the association:
has_many :admins do
def <<(user)
user.is_admin = true
self << user
end
end
(Code has not been checked)
there are some twists with the has_many :through and the << operator. But you could overload it like in #Erez answer.
My approach to this is using scopes (I renamed OrganisationsUsers to Memberships):
class User < ActiveRecord::Base
has_many :memberships
has_many :organisations, :through => :memberships
end
class Organisation < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :class_name => 'User', :source => :user
# response to comment:
def admins
memberships.admin
end
end
class Memberships < ActiveRecord::Base
belongs_to :organisation
belongs_to :user
scope :admin, where(:is_admin => true)
end
Now I create new admins like this:
Organisation.first.memberships.admin.create(:user => User.first)
What I like about the scopes is that you define the "kind of memberships" in the membership class, and the organisation itself doesn't have to care about the kinds of memberships at all.
Update:
Now you can do
Organisation.first.admins.create(:user => User.first)
You can try below code for organization model.
class Organisation < ActiveRecord::Base
has_many :organisations_users
has_many :organisations_admins, :class_name => "OrganisationsUser", :conditions => { :is_admin => true }
has_many :users, :through => :organisations_users
has_many :admins, :through => :organisations_admins, :source => :user
end

has_many through self referential association

I want to (as an example) create a has_many association to all posts by friends of a person, something like has_many :remote_posts to give me something like person > friends > person > posts.
..here is how I would go about it
script/generate model post title:string person_id:integer
script/generate model friendship person_id:integer friend_id:integer
script/generate model person name:string
class Person < ActiveRecord::Base
has_many :posts
has_many :friendships, :foreign_key => 'friend_id'
has_many :people, :through => :friendships
has_many :remote_posts, :class_name => 'Post', :through => :people, :source => :posts
end
class Friendship < ActiveRecord::Base
belongs_to :person
#also has a 'friend_id' to see who the friendship is aimed at
end
class Post < ActiveRecord::Base
belongs_to :person
end
# generate some people and friends
{'frank' => ['bob','phil'], 'bob' => ['phil']}.each {|k,v|
v.each {|f|
Friendship.create(
:person_id => Person.find_or_create_by_name(f).id,
:friend_id => Person.find_or_create_by_name(k).id
)
}
}
# generate some posts
Person.all.each {|p|
p.posts.create({:title => "Post by #{p.name}"})
}
Now,
Person.first.friendships # ..works
Person.first.people # (friends) ..works
Person.first.posts # ..works
Person.first.remote_posts #....
...and I get this error..
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: people.person_id: SELECT "posts".* FROM "posts" INNER JOIN "people" ON "posts".person_id = "people".id WHERE (("people".person_id = 1))
Aside from the foreign key error - seems like the friendships association isn't coming into play at all. I was thinking that this might be because of the :source => :posts, since the posts association would come into it twice.
I could write some finder sql (and that is what I have working at the moment), though I'd sooner do it this way.
Any ideas of how to get this to work?
How about this:
In the FriendShip class, add:
has_many :posts, :through => :person
and in the Person class, change the remote_posts to:
has_many :remote_posts, :class_name => 'Post',
:through => :friendships, :source => :person
How about a nested has_many :through relationship. This seems to work for me:
class Friendship < ActiveRecord::Base
belongs_to :person
belongs_to :friend, :class_name => 'Person'
has_many :posts, :through => :friend, :source => :posts
end
class Person < ActiveRecord::Base
has_many :posts
has_many :friendships, :foreign_key => 'friend_id'
has_many :people, :through => :friendships
has_many :remote_posts, :through => :friendships, :source => :posts
end
Note: this requires this nested_has_many_through plugin. (Note: direct linking to github repos seems to be broken... but that repo is there despite the error message.)

Rails - Two-way "friendship" model (cont'd)

This is a continuation of this question:
Original Question (SO)
The answer to this question involved the following set of models:
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :through => :friendships #...
end
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => 'User', :foreign_key => 'friend_id'
end
<% for friendship in #user.friendships %>
<%= friendship.status %>
<%= friendship.friend.firstname %>
<% end %>
This works fine if say, I have a user and I want to get all the "friendships" for which his or her id is the :user_id FK on the Friendship model. BUT, when I run something like
#user.friendships.friends
I would like it to return all User records for which that User is either the :user or the :friend in the friendship - so, in other words, return all friendships in which that user is involved.
Hopefully the above makes sense. I'm still quite new to rails and hope there is a way to do this elegantly without making just a standard link table or providing custom SQL.
Thank you!
Tom
railscasts episode on this topic
You cannot just use #user.friendships here because it will only give you those friendships where #friendship.user_id == #user.id.
The only thing I can think of right now is just to do
Friendship.find_by_user_id(#user.id).concat Friendship.find_by_friend_id(#user.id)
my solution is a a scope:
# model
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => "User"
belongs_to :recipient, :class_name => "User"
scope :of_user, lambda { |user_id| where("sender_id = ? or recipient_id = ?",
user_id, user_id) }
end
# in controller
#messages = Message.of_user(current_user)
From the railscast link:
# models/user.rb
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user

Resources