Rails: Model associations - ruby-on-rails

I have two models, one called User and another called Recruiter. What I would like to do is to be able to create a scope that searches users and returns the results so recruiters can see them. But I'm not sure how to go about setting up the association. I made a through association between users and recruiters and created a new join table called recruiter_users but I'm not sure if this is the correct approach.
1) What the best way to make the association between the 2 models
2) how exactly would I display the user results in the recruiters view?
class RecruiterUser < ApplicationRecord
# this is a join model between recruiters and users
belongs_to :recruiter
belongs_to :user
class User < ApplicationRecord
# creates association with recruiters model through the join table recruiter_users
has_many :recruiter_users
has_many :recruiters, through: :recruiter_users
class Recruiter < ApplicationRecord
# creates association with users model through the join table recruiter_users
has_many :recruiter_users
has_many :users, through: :recruiter_users

Again, without having more details about your application, if all you need to do is display the Users associated with a particular Recruiter in a view, it could be as simple as this:
<% #recruiter.users.each do |user| %>
<%= user.whatever_attribute %>
<% end %>

It sounds like you want your average run of the mill many to many association:
class User
has_many :recruitments
has_many :recruiters,
through: :recruitments
end
class Recruiter
has_many :recruitments
has_many :recruited_users,
through: :recruitments,
source: :user
end
class Recruitment
belongs_to :user
belongs_to :recruiter
end
You don't have to name your join models a + b. If there is a more descriptive name of what the relation is use it.
this would let you iterate through the users recruited by a recruiter by:
#recruiter = Recruiter.includes(:recruited_users).find(params[:id])
<% #recruiter.recruited_users.each do |user| %>
# ...
<% end %>

Related

Rails5: belongs_to AND has_and_belongs_to - 2 relations between the same tables

I have the following Rails Model Relation:
class Grade < ApplicationRecord
has_and_belongs_to_many students, join_table: :grade_student_mappings
has_many :students
end
class Student < ApplicationRecord
has_and_belongs_to_many :grads, join_table: :grade_student_mappings
belongs_to :grade
end
Now when I want to access the grade table and the student table using the join_table - rails will fail.
Example: Grade.find_by_id(1).student will always response me the content of the direct relation between Grade and Student. The Relation via the join_table will be ignored.
Only when I uncomment the model line has_many and belongs_to then the indirect relation via the join_table will be considered.
How I can say to RAILS, which relation between Grade and Student I want to use ?
Rails supports two kinds of many-to-many relationships. has_many through and has_and_belongs_to_many. You are mixing the two. You need to pick one and the more flexible one is has_many through. Change your model files to the following:
# app/models/grade_student_mapping.rb
belongs_to :grade
belongs_to :student
# app/models/grade.rb
has_many :grade_student_mappings, dependent: :destroy
# app/models/student.rb
has_many :grade_student_mappings, dependent: :destroy
has_many :grades, through: :grade_student_mappings
Then you can access the student's grades on their show template for example by setting the grades instance variable in the students_controller show action
# app/controllers/students_controller.rb
def show
#grades = #student.grades
end
And display the grades on the student's show page. Something like the below:
# app/views/students/show.html.erb
<% #grades.each do |grade| %>
<%= grade.semester %>
<%= grade.course %>
<%= grade.letter %>
<% end %>

has_many and belongs_to with join table

I have Users and Trucks. I want the ability to say #truck.drivers << #users and #user.truck = #truck.
The solution is simple until I want the relationship to be stored in a join table.
# tables
:users
:id
:truck_drivers
:user_id
:truck_id
:truck
:id
I've gotten it to where I can say #truck.drivers << #user and #user.trucks << #truck, but I would like to limit a user to occupy one truck at a time, for my sanity.
Is it possible? A has_many/belongs_to with a join table? Or should I try a different approach? I'm not using a third model for the join table. It's just a table. Here's what I have so far.
class User < ApplicationRecord
has_and_belongs_to_many :trucks,
join_table: :truck_drivers, # points to the table
class_name: :Truck # looks for the truck model in the table
end
class Truck < ApplicationRecord
belongs_to :company
has_and_belongs_to_many :drivers,
join_table: :truck_drivers,
class_name: :User
end
The reason I need a join table in the first place is because each User can have many Roles: Admin, Manager, Driver, Customer Service, etc. I thought it didn't make sense to add a truck_id to all the users if all the users are not going to be using trucks.
It seems like you ought to be able to do something like:
#user.trucks << #truck unless #user.trucks.any?
Yes this is a standard strategy with rails using the :through keyword.
rails documentation: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Make a model called TruckUser with truck_id and user_id
then edit your classes:
class User < ApplicationRecord
has_many :truck_users
has_many :trucks, through: :truck_users
end
class Truck < ApplicationRecord
belongs_to :company
has_many :truck_users
has_many :drivers, through: :truck_users
end
class TruckUser < ApplicationRecord
belongs_to :truck
belongs_to :user
end

Implement add Recipe to favorites in Rails and find if current user has 'favorite_set?' a recipe

This question is related to that shows a has_many :through association. I have a similar association as shown in the answer. I will cite for reference.
$ rails g model FavoriteRecipe recipe_id:integer user_id:integer
# Join model connecting user and favorites
class FavoriteRecipe < ActiveRecord::Base
belongs_to :recipe
belongs_to :user
end
---
class User < ActiveRecord::Base
has_many :recipes
# Favorite recipes of user
has_many :favorite_recipes # just the 'relationships'
has_many :favorites, through: :favorite_recipes, source: :recipe # the actual recipes a user favorites
end
class Recipe < ActiveRecord::Base
belongs_to :user
# Favorited by users
has_many :favorite_recipes # just the 'relationships'
has_many :favorited_by, through: :favorite_recipes, source: :user # the actual users favoriting a recipe
end
I am curious to know if this solution would work with a many-to-many relationship with joining model e.g. FavoriteRecipe that has a boolean attribute favorite_set. When iterating through the Recipes I would like to find out if the current user has 'favorited' this/these Recipes. Something like current_user.recipe.favorite_set? but of course going through the FavoriteRecipe joining model. If true display 'FAVORITE SET' otherwise give an option to favorite this recipe. My implementation is a feed that displays all "Recipes" and shows "FAVORITE SET" or gives the option to "favorite" one or multiple Recipes.
Thanks in advance.
To solve this problem I created a helper module in app/helpers
module ReminderHelper
#Check if user has set reminder for a specific event
def event_reminder_set? user, event
EventReminder.where(user: user, event: event).any?
end
end
I included the module in app/controllers/application_controller.rb
include ReminderHelper
In my view I use it as follows
<% unless event_reminder_set?(current_or_guest_user, event) %>
REMIND ME
<% else %>
REMINDER SET
<% end %>
This should work with other models associations with a many-to-many relationship with joining model e.g. FavoriteRecipe

Can a has and belongs to many association not call a query on the associated object

I have two models Activities and Users
class Activity < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :activities
end
I can load all the activities which are associated with users by calling
User.all.includes(:activities)
But this actually pulls up all my activity objects from the database, which can cause performance problems as the table is quite large. My requirement is only to get the activity_ids associated
I can directly get that by executing the plain sql on the join table
select activity_id from activities_users where user_id in (...)
My question is:
Can I do get the above functionality in a rails friendly way
From has_and_belongs_to_many you could do things like #User.find(1).activity_ids
In terms of including them for a collection to avoid an n+1 query I don't think you can do it any way other than the way you've currently got it.
If you make it a has_many_through it'll have a model:
class Activity < ActiveRecord::Base
has_many :user_activities
has_many :users, through: :user_activities
end
class User < ActiveRecord::Base
has_many :user_activities
has_many :activities, through: :user_activities
end
class UserActivity < ActiveRecord::Base
belongs_to :user
belongs_to :activity
end
Then you can do #users = User.includes(:user_activities).all so when you loop over them and do something like it should avoid the n+1 query because you've already loaded them in.
#users.each do |user|
user.activity_ids
end

Rails model design

I want to create a model similar to reddit where users can upvote or downvote a link or article. I am having trouble wrapping my head around this: how do I made my models so that a user can like vote up or down a link only once and be able to change their mine (switch to a downvote) but never be able to vote multiple times no matter how much time has passed/logging out does not matter
has_many :through
You'd create something like this:
#app/models/post.rb
Class Post < ActiveRecord::Base
has_many :votes do
def user(user)
find_by user_id: user.id
end
end
has_many :voters, through: votes, class_name: "User", foreign_key: "user_id"
end
#app/models/vote.rb
Class Vote < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :votes
has_many :posts, through: :votes
end
Standard has_many :through relationship - giving you the ability to use a join model to associate two or more other models together:
--
User
This would allow you to call the following:
#app/views/posts/show.html.erb
<% if #post.votes.user(current_user).present? %>
<% link_path = #votes.votes.user(current_user).value == "up" ? vote_down_path : vote_up_path %>
<%= link_to "Vote", link_path %>
<% else %>
# empty vote link
<% end %>
The tricky bit is to associate a single vote with a single user - hence why I included an ActiveRecord Association Extension for your votes association
You can use relationships to capture this...
An Article has many votes
A Vote belongs to a User
A Vote has one article
An article has many voters (users) through Votes but they must be unique. (validation rule)

Resources