Rails has_many :through - ruby-on-rails

I have a problem understanding the has_many and through relationship. I tried looking at a few other post here was not really clear.(Not sure if its not really because of what I am trying to achieve..)
I am trying to build and app where users have events in a calendar and each user can exchange events among each other.
I want to be able to retrieve data for each user with an API such as "user.trades" <-- which retrieve all trades that the user has made and the other methods, one "user.requested_trades" and the other "user.pending_trades". The requested_trades and pending_trades works but If I should make a 3 way table relations or this 2 way is good enough. Truth fully I really had to play around with the console to figure out what type of query it executes to be able to achieve the two methods to work. But its still very unclear the :through and :has_many.
class Trade < ActiveRecord::Base
belongs_to :seller,
:class_name => "User",
:foreign_key => "seller_id"
belongs_to :buyer,
:class_name => "User",
:foreign_key => "buyer_id"
end
class User < ActiveRecord::Base
has_many :events, :dependent => :destroy
has_many :requested_trades, -> { where(["trades.status = ?",'requested']).order("trades.created_at DESC") },
:class_name => "Trade",
:foreign_key => "buyer_id"
has_many :pending_trades, -> { where(["trades.status = ?",'pending']).order("trades.created_at DESC") },
:class_name => "Trade",
:foreign_key => "buyer_id"
has_many :sent_messages, -> { where(["messages.sender_deleted = ?", false]).order("messages.created_at DESC")},
:class_name => "Message",
:primary_key => "email",
:foreign_key => "sender_id"
has_many :received_messages, -> { where(["messages.recepient_deleted = ?", false]).order("messages.created_at DESC")},
:class_name => "Message",
:primary_key => "email",
:foreign_key => "recepient_id"

has_many :through is many to many relationship between two models. But this many to many relationship maintained through third model.
Suppose, two models teachers and departments. both are bi-directional(may-to-many relation).
But we can't able to maintain the relationship using only these two models so we need third model. Suppose, teacher_departments.
So as any department relation with teacher we can do using the making entry in 3rd model also vice versa.
class Teacher < ActiveRecord::Base
#It describes that teacher many-to many relationship with teacher_department model and
#also if teacher gets deleted depending entries in teacher_departments also gets deleted.
has_many :teacher_departments, dependent: :destroy
#It describe that teacher having multiple departments though 3rd teacher_departments
#model.
has_many :departments, through: :teacher_departments
end
class Department < ActiveRecord::Base
#It describes that many-to many relationship with teacher_department
#model and also if department gets deleted depending entries in teacher_departments
#also gets deleted.
has_many :teacher_departments, dependent: :destroy
#It describe that department having multiple teachers though 3rd teacher_departments
#model.
has_many :teachers, through: :teacher_departments
end
class TeacherDepartment < ActiveRecord::Base
#It describe that this model are belonging to both teacher and department model.
belongs_to :teacher
belongs_to :department
end
Example.
Teacher.first.departments.create()
It will create new entry in teacher_department table with teacher first id and newly created department id.
So you and access departments of first teacher by
Teacher.first.departments
You also take look at following link of Rails Guide
http://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many

Related

How to associate through a join table that reference the same model?

I have a model called House and I want to be able to associate houses with each other to show recommendations.
So I would expect that given a house, I should be able to ask: house.recommended_houses. A house could be recommended for more than one house.
I was thinking on having a table that would store this association (I don't know the name yet), so it would have the following columns:
recommended_house_id
recommended_for_house_id
I am failing to understand how would I hook this up with my House model. What would the associations look like, and also what name should I be using for that join model?
This should get you started:
class House < ApplicationRecord
has_and_belongs_to_many :recommendations,
class_name: "House",
foreign_key: "recommended_by_id",
association_foreign_key: "recommendation_id"
end
What you're describing is called a self-referential association.
You can set up a join table (recommendations) and the associated model:
class Recommendation < ActiveRecord::Base
belongs_to :house
belongs_to :recommended_house, :class_name => 'House'
end
and then use has_many, :through relationships within the House model to set up the relationships you're looking for.
class House < ActiveRecord::Base
has_many :recommendations
has_many :recommended_houses, :through => :recommendations
has_many :inverse_recommendations, :class_name => "Recommendation", :foreign_key => "recommended_house_id"
has_many :recommended_by_houses, :through => :inverse_recommendations, :source => :house
end
Now you can use both house.recommended_houses and house.recommended_by_houses.

How to create records in has_many through association in rails 3.2

I have two models (User and Event) with multiple has_many through associations, where my current association is by the below logic:
User can participate in many events through EventGroup
Event has many users through EventGroup
User can like many events through Eventgroup
Event has many user likes through Eventgroup
Model:
class User
has_many :event_groups
has_many :events,:through => :event_groups
has_many :event_likes,:through => :event_groups,:class_name => "Event"
end
class Event
has_many :event_groups
has_many :users,:through => :event_groups
has_many :user_likes,:through => :event_groups,:class_name => "User"
end
class EventGroup
belongs_to :user
belongs_to :event
belongs_to :user_like,:class_name => "User"
belongs_to :event_like,:class_name => "Event"
end
EventGroup columns:
user_id
event_id
user_like_id
event_like_id
After setting up the association I tried to create the association record with the below code:
user = User.first
user.event_likes << Event.first
user.save
Here the record is created with user_id & event_like_id instead of `user_like_id & event_like_id
But I am not able to get the User records by event.user_likes, so I checked my eventgroup record. It has the nil value for user_like_id.
#<EventGroup id: 24, event_id: 1, user_id: 2,event_like_id: 1, user_like_id: nil>
Let me know the proper way to do this.
Using the .where format, you can pass a string like .where("event_groups.user_like_id = ?", 17) to qualify the joined taggings table.
For example:
User.joins(:event_groups).where("event_groups.user_like_id = ?", 17)
I'm not sure, but you may need to define inverse_of in your has_many relations:
class User
#...
has_many :event_likes, :through => :event_groups, :inverse_of => :user_likes, :class_name => "Event"
end
class Event
#...
has_many :user_likes, :through => :event_groups, :inverse_of => :event_likes, :class_name => "User"
end
See the chapter on :through in the doc of has_many
It seems to me you're trying to model two many-to-many relationships (Users-Participate-In-Events and Users-Like-Events) that are distinct concepts in one relationship. I'm assuming here that a User can Participate in an Event without Liking it, and vice-versa. In that case, keep EventGroup modeled to only represent the Participation relationship, and create another EventLikes model to represent the Likes. The columns in the two tables would be the same, but represent the different relationships. After that, adding the Event to either (events, or event_likes) should work
I create similar relationship between same models (User-User) through Relationship model,
my code:
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower

Has Many Through Association Callbacks with multiple associations using the same join table

So this might be really bad form. I'm relatively new to rails. I'm not sure.
I have a project model and I want there to be many owners (who can read and write everything) and many collaborators (who can read and write some stuff).
In my project.rb file I have:
has_many :project_user_relationships, :dependent => :destroy
has_many :collaborators, :through => :project_user_relationships, :source => :user
has_many :project_owners_relationships, :class_name => "ProjectUserRelationship", :foreign_key => "project_id",
:before_add => Proc.new { |p,owner_r| owner_r.owner = true }, :conditions => "`project_user_relationships`.owner = true"
has_many :owners, :through => :project_owners_relationships, :source => :user
So this works reasonably well. If I add a new owner, that user is also a collaborator which is what I want. The issue I'm not sure how to solve is if I add a user that is already collaborator as an owner, I get two entries in the join table. I'd like for it to just amend the record that's already there. How do I do that?
Here's the data model I would suggest for this:
class Project < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
...
end
class Membership < ActiveRecord::Base
belongs_to :project
belongs_to :user
...
end
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :projects, :through => :memberships
...
end
And then the membership table will have the following attributes:
:id
:user_id
:project_id
:is_owner (boolean)
A scope defined on the membership class:
scope :owner, where("is_owner")
And a special method for User instances:
def owned_projects
memberships.owner.includes(:projects).inject([]) {|array, m| array << m.project; array}
end
will allow you to retrieve a user's owned projects with the user.owned_projects call.
And just a call to user.projects to see a user's projects that they either collaborate on or own.
You have better data normalization with this data model, and a simple boolean attribute to define whether or not a user is a project owner.
This data model is used in this project, with the exception that s/Project/Group/, and there's some additional functionality to handle inviting users to the Project.
This doesn't answer your "real question", but I think part of the issue is that a data model where collaborators are owners are stored in the same table is needed to minimize redundancies and the need to manage two separate tables.

Rails has many and belongs to one

I have a User model which has many projects and a Project model which can have many users, but also belongs to a single user (ie the user who created this project). It must belong to a User. It also allows a list of users to be associated with it, think collaboration.
With this in mind, my models look like this:
class User < ActiveRecord::Base
has_many :assigned_projects
has_many :projects, :through => :assigned_projects
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :assigned_projects
has_many :users, :through => :assigned_projects
end
class AssignedProject < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
Now, when I want to create a new project through a User, this is how I would do it:
user = User.create(:name => 'injekt')
user.projects.create(:name => 'project one')
Now, I know that projects is provided through an AssignedProject join model, which is why project.user will return nil. What I'm struggling to get my head around is the best way to assign the project creator (which by the way doesn't need to be user, it could be creator or something else descriptive, as long as it is of type User).
The idea then is to create a method to return projects_created from a User which will select only projects created by this user. Where user.projects will of course return ALL projects a user is associated with.
Assuming this kind of association is fairly common, what's the best way to achieve what I want? Any direction is greatly appreciated.
Add a creator_id column to your projects table for the creator relationship, and then add the associations to the models:
class User < ActiveRecord::Base
has_many :assigned_projects
has_many :projects, :through => :assigned_projects
has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :assigned_projects
has_many :users, :through => :assigned_projects
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
end
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
I wanted to add little improvement to design. We don't actually need intermediate model because it does not contain any extra column other than reference_ids hence HABTM association is best suited over here.
class User < ActiveRecord::Base
has_and_belongs_to_many :projects, :join_table => :assigned_projects
has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end
class Project < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :assigned_projects
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
end

has_many association with non-traditional data model

I'm struggling with a has_many association. I have a diary application. The model players are as follows:
User
UserFriend
UserFoodProfile
I want to be able to get at all the foods that a user's friends have eaten. So, I want to be able to get: current_user.friends.profiles
I've setup the associations properly so far so that I'm able to access current_user.friends, but now I want to be able to get all the friend's entries as well over the last 30 days.
Here are my models
class User < ActiveRecord::Base
cattr_reader :per_page
##per_page = 20
has_many :user_food_profiles
has_many :preferred_profiles
has_many :food_profiles, :through => :user_food_profiles
has_many :weight_entries
has_one :notification
has_many :user_friends
has_many :friendships, :class_name => "UserFriend", :foreign_key => "friend_id"
has_many :friends, :through => :user_friends
class UserFriend < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
class UserFoodProfile < ActiveRecord::Base
belongs_to :user
belongs_to :food_profile
belongs_to :post
The UserFriend model is setup the following way:
id
user_id
friend_id
friend_name
I want to connect to user_food_profiles from friend so that I can get a user's friend's current user_food_profiles as "entries" but everything I've tried hasn't worked. How would I setup this association?
Tried to do:
UserFriend: has_many :user_food_profiles, :as => 'entries'
UserFoodProfile: belongs_to :friend, :foreign_key => 'friend_id'
Any ideas on how to make this work? Tempted to create a custom finder_sql but I'm sure this can work with associations.
Isn't a "friend" just another user that's in the database?
Let your UserFriend be a many_to_many relationship (either with "has_and_belongs_to_many" or "has_many :through"): each user can have several users as friends.
You can then link those user_ids (which could be in the many_to_many table called 'friend_id' if you like) to their foodprofile without a problem, since it is using the same link as user.foodprofile .
This is the line I see being the problem:
class User < ActiveRecord::Base
# <snip/>
has_many :friendships,
:class_name => "UserFriend",
:foreign_key => "friend_id"
I'm assuming that you're using a join table here called user_friend. That would mean that the foreign key there should be "user_id".
Now, unless you're going to store extra metadata in that UserFriend model, it's not required — you can get away with a self-referential has_and_belongs_to_many relationship like so:
has_and_belongs_to_many :friends,
:class_name => "User",
:join_table => "user_friends",
:foreign_key => "user_id",
:association_foreign_key => "friend_id"
Doing this, all you have to do is user.friends.profiles quite easily.
Now, if the relationship is bi-directional it gets a bit more complex, but I feel like this should at least get you started along the way.

Resources