问题
I have an employee view, where are listed all skills, which are written in the skills table on my db. For every employee, all skills are displayed, just as want it to.
Employees and skills are related to each other as has many :through association.
class Employee < ApplicationRecord
has_many :employeeskillsets, foreign_key: "employee_id"
has_many :skills, through: :employeeskillsets
end
class Skill < ApplicationRecord
has_many :employeeskillsets, foreign_key: "skill_id"
has_many :employees, through: :employeeskillsets
end
class Employeeskillset < ApplicationRecord
belongs_to :employee, foreign_key: 'employee_id'
belongs_to :skill, foreign_key: 'skill_id'
end
All those skills are displayed as buttons, which are toggles to enable/disable that particular skill on that employee (per click a direct insert/delete, no extra commit needed).
<%= link_to apply_skill_employee_path(employee: @employee, skill_id: skill.id), method: :put, remote: :true do %>
<%= skill.name %></div><% end %>
But now, I want to the buttons to be shown colored, when you load the page. If the skill is already enabled, the buttoncolor should be green, else grey. And here begins my problem: My app checks each skill with one separate select statement. I use the following code for this:
<%= link_to apply_skill_employee_path(employee: @employee, skill_id: skill.id), method: :put, remote: :true do %>
<% if @employee.skills.exists?(skill.id) %>
<div class="button skill e-true"><%= skill.name %></div>
<% else %>
<div class="button skill"><%= skill.name %></div>
<% end %>
I have already tried to use includes, but it seems, the exists? checks each skill independently.
Does anybody here have a suggestion, how I could solve this, by using one single select?
Thanks in advance, I hope I have mentioned everything, what is necessary.
Edit 1: I forgot to mention, that i render this through a partial (if that is important to know). And here is the current used @employee var in the employees_controller.
@employee = Employee.find_by(id: params[:id])
回答1:
Try plucking ids, and then check for include? as you don't need to fetch all attributes of skills
<% skills = @employee.skills.pluck(:id) %>
<%= link_to apply_skill_employee_path(employee: @employee, skill_id: skill.id), method: :put, remote: :true do %>
<% if skills.include?(skill.id) %>
<div class="button skill e-true"><%= skill.name %></div>
<% else %>
<div class="button skill"><%= skill.name %></div>
<% end %>
回答2:
Since skill is also an active record model, you can use include? on the employee's skills collection to check if the employee has a particular skill.
@employee.skills.include?(skill)
This way you are free to use includes clause to eagerly load employees' skills.
回答3:
This will not fire the additional query
<% if @employee.skill_ids.exists?(skill.id) %>
Also to avoid n+1 in the following line
apply_skill_employee_path(employee: @employee, skill_id: skill.id)
Make sure you are including skills
@employee = Employee.includes(:skills).where(......)
回答4:
The Rails way is far simpler.
When you use the has_many macro in ActiveRecord it also creates a _ids method that can be used to add or remove relations with an array:
@employee.skills_ids = [1,2,3]
This also works with indirect assocations with the :through option.
You can use this together with the form collection helpers to create select or checkbox tags:
<%= form_for(@employee) do |f| %>
<%= f.label :skill_ids, 'Skills' %>
<%= f.collection_check_boxes(:skills_ids, Skill.all, :id, :name) %>
<% end %>
To avoid an extra query you can do a left outer join in the controller:
def edit
# left_outer_joins is new in Rails 5
# see https://blog.bigbinary.com/2016/03/24/support-for-left-outer-joins-in-rails-5.html
@employee.left_outer_joins(:skills).find(params[:id])
end
You also don't need a silly extra method in your controller for something that should be handled as a normal update. KISS.
来源:https://stackoverflow.com/questions/44280787/rails-tips-for-solving-this-n1