问题
I have a recipe model which has_many ingredients and each ingredient belongs to an item. In my advanced search form, I would like a user to select multiple ingredients and let Ransack find a recipe that contains all the ingredients selected by the user.
I tried the following searchfield:
= f.collection_select(:ingredients_item_id_in, Item.all, :id, :name, {}, {multiple: true})
But logically, this results in all recipes being shown that contain any of the selected ingredients.
changing :ingredients_item_id_in
to :ingredients_item_id_in_all
results in an incorrect Query since one record cannot contain multiple values of item_id.
Any ideas on creating this search parameter within Ransack, or should I create a subquery for this?
Upon request, my controller method for search:
def search
@q = Recipe.ransack(params[:q])
@recipes = @q.result(distinct: true).include_related_models.published
end
回答1:
I recently came across similar task for my project (Rails 4.2.4 / Ruby 2.3.1).
Estate has many comforts. I need to get all estates, which include all selected comforts.
Here is how I resolved it using Ransack
In my case I have has_many :through
relation.
estate.rb
class Estate < ActiveRecord::Base
has_many :estate_comforts
has_many :comforts, through: :estate_comforts
end
comfort.rb
class Comfort < ActiveRecord::Base
has_many :estate_comforts
has_many :estates, through: :estate_comforts
end
estate_comfort.rb
class EstateComfort < ActiveRecord::Base
belongs_to :estate
belongs_to :comfort
end
For complex queries you'll need to do search via post
. For that you have to edit routes like this. And add search
action to estates_controlle.rb
. For more info read Ransack wiki
routes.rb
...
resources :estates
collection do
match 'search' => 'estates#search', via: %i[get post], as: :search
end
end
estates_controller.rb
class EstatesController < ApplicationController
...
def index
@q = Estate.ransack(params[:q])
@estates =
if params[:q]&.has_key?(:estate_comforts_comfort_id_eq_any)
# Store checked comforts
session[:estate_comforts_comfort_id_eq_any] = params[:q][:estate_comforts_comfort_id_eq_any]
comforts_count = params[:q][:estate_comforts_comfort_id_eq_any].count
ids = @q.result.includes(:estate_comforts).group_by(&:id).select { |_, v| v.count == comforts_count}.keys
Estate.where(id: ids)
else
@q.result(distinct: true)
end
end
def search
index
render :index
end
end
And finally the template portion...
estates/index.haml
= search_form_for @q, url: search_estates_path, html: { method: :post } do |f|
# here goes the form inputs
# Polulate checkboxes with previously checked comforts
- Comfort.find_each do |comfort|
# Was checked previously?
- checked = comfort.id.to_s.in?(session[:estate_comforts_comfort_id_eq_any].to_a)
%div
%input{ name: 'q[estate_comforts_comfort_id_eq_any][]',
type: "checkbox",
id: "checkbox#{comfort.id}",
value: comfort.id,
checked: checked }
%label{for: "checkbox#{comfort.id}"}= comfort.name
Will generate following html
<form class="estate_search" id="estate_search" action="/estates/search" accept-charset="UTF-8" method="post">
<div>
<input checked="" id="checkbox1" name="q[estate_comforts_comfort_id_eq_any][]" type="checkbox" value="1">
<label for="checkbox1">Comfort Name 1</label>
</div>
<div>
<input id="checkbox2" name="q[estate_comforts_comfort_id_eq_any][]" type="checkbox" value="2">
<label for="checkbox2">Comfort Name 2</label>
</div>
</form>
来源:https://stackoverflow.com/questions/33767461/ransack-find-record-that-has-all-related-records