Associations
You can easily use Ransack to search for objects in has_many and belongs_to
associations.
Given these associations…
class Employee < ActiveRecord::Base
belongs_to :supervisor
# has attributes first_name:string and last_name:string
end
class Department < ActiveRecord::Base
has_many :supervisors
# has attribute title:string
end
class Supervisor < ActiveRecord::Base
belongs_to :department
has_many :employees
# has attribute last_name:string
end
… and a controller…
class SupervisorsController < ApplicationController
def index
@q = Supervisor.ransack(params[:q])
@supervisors = @q.result.includes(:department, :employees)
end
end
… you might set up your form like this…
<%= search_form_for @q do |f| %>
<%= f.label :last_name_cont %>
<%= f.search_field :last_name_cont %>
<%= f.label :department_title_cont %>
<%= f.search_field :department_title_cont %>
<%= f.label :employees_first_name_or_employees_last_name_cont %>
<%= f.search_field :employees_first_name_or_employees_last_name_cont %>
<%= f.submit "search" %>
<% end %>
...
<%= content_tag :table do %>
<%= content_tag :th, sort_link(@q, :last_name) %>
<%= content_tag :th, sort_link(@q, :department_title) %>
<%= content_tag :th, sort_link(@q, :employees_last_name) %>
<% end %>
If you have trouble sorting on associations, try using an SQL string with the
pluralized table ('departments.title','employees.last_name') instead of the
symbolized association (:department_title), :employees_last_name).
Searching a relation that already has joins
A search can start from a relation rather than a model, and the relation may
already join the tables the search needs — through joins,
left_outer_joins, an includes that will be eager loaded, or a previous
search. Ransack reuses those joins: the condition is bound to the alias
Active Record gives the existing join, and no second join is added.
Supervisor.joins(:department).where(departments: { title: 'Sales' })
.ransack(department_title_cont: 'Eng').result.to_sql
# ... INNER JOIN "departments" ON ... WHERE "departments"."title" = 'Sales'
# AND "departments"."title" LIKE '%Eng%' ESCAPE '\'
Supervisor.joins(:department, :backup_department) # both belongs_to Department
.ransack(backup_department_title_eq: 'Ops').result.to_sql
# ... INNER JOIN "departments" ON ...
# INNER JOIN "departments" AS "backup_departments_supervisors" ON ...
# WHERE "backup_departments_supervisors"."title" = 'Ops'
This also covers the alias Active Record picks when a where hash is keyed by
the association name (where(department: { ... }) joins departments as
department), and a search chained onto the result of another search.
Before Ransack 6.0 the join tree was built from the model class alone, so a
join already on the relation was joined a second time under a new alias, which
multiplied rows for a has_many, and a condition could land on the wrong one
of two joins to the same table.
The joins Ransack adds itself are LEFT OUTER JOINs, so a search on an
association does not drop records that have nothing associated unless the
condition itself does. To search with inner joins, join the association on the
relation first.