如何在Ransack搜索中仅筛选关联已选中软件的EmployeeApplication?
问题:Ransack搜索EmployeeApplication时未应用selected作用域导致结果不符合预期
我有一个EmployeeApplication类,员工提交申请时需要选择自己可使用的软件。目前用Ransack实现搜索表单,但按软件搜索EmployeeApplication时,本该仅返回选中该软件的申请记录,实际却返回了全部记录——原因是Ransack查询未应用EmployeeSoftware的selected作用域。
相关代码
EmployeeSoftware模型
class EmployeeSoftware < ApplicationRecord belongs_to :employee_application scope :selected, -> { where(selected: true) } end
EmployeeApplicationsController的index方法
class EmployeeApplicationsController < ApplicationController def index @search = EmployeeApplication.ransack(q) @employee_applications = @search.result.to_a.uniq end end
视图搜索表单代码
= form.select :employee_softwares_id_in, options_from_collection_for_select(employee_software_options_for_select, "id", "name", @search.employee_softwares_id_in)
当前临时解决方案
我已经通过在视图中添加隐藏字段的方式解决了问题,代码如下:
= form.select :employee_softwares_id_in, options_from_collection_for_select(employee_software_options_for_select, "id", "name", @search.employee_softwares_id_in) = form.hidden_field :employee_softwares_selected_eq, value: '1'
更优解决方案
方案1:配置Ransack关联作用域(推荐)
在EmployeeApplication模型中配置Ransack,让关联employee_softwares的搜索自动应用selected作用域,视图无需额外修改:
class EmployeeApplication < ApplicationRecord has_many :employee_softwares # 配置Ransack关联查询时自动使用selected作用域 ransack_association :employee_softwares, scope: :selected end
之后直接使用原来的视图代码即可,Ransack在处理employee_softwares_id_in参数时,会自动只查询selected = true的EmployeeSoftware记录。
方案2:自定义Ransacker
如果需要更灵活的控制,可以在EmployeeApplication模型中自定义一个针对选中软件的Ransacker:
class EmployeeApplication < ApplicationRecord has_many :employee_softwares ransacker :selected_software_ids do subquery = EmployeeSoftware.selected.select(:employee_application_id, :id).arel_table Arel::Nodes::Array.new(subquery.group(subquery[:employee_application_id]).pluck(:id)) end end
然后修改视图中的搜索字段:
= form.select :selected_software_ids_in, options_from_collection_for_select(employee_software_options_for_select, "id", "name", @search.selected_software_ids_in)
方案3:控制器层面合并作用域
如果不想修改模型,可以在控制器的查询中手动合并selected作用域,但注意这种方式会让所有搜索请求都过滤掉未选中软件的申请,适合仅需搜索选中软件的场景:
class EmployeeApplicationsController < ApplicationController def index # 先关联EmployeeSoftware并应用selected作用域,再执行Ransack搜索 @search = EmployeeApplication.joins(:employee_softwares).merge(EmployeeSoftware.selected).ransack(q) # 用distinct代替to_a.uniq,数据库层面去重更高效 @employee_applications = @search.result.distinct end end
临时方案的不足
隐藏字段的方式虽然能解决问题,但存在几个局限:
- 不符合MVC分层原则,把查询逻辑放在了视图层
- 不够直观,后续维护时容易忽略这个隐藏条件
- 无法灵活应对需要查询未选中软件的场景
内容的提问来源于stack exchange,提问作者Eslam Tawfik
相关产品推荐
相关产品推荐

