Rails 5和ActiveRecord:用于列出资源的可重用过滤器

时间:2019-07-12 16:51:27

标签: ruby-on-rails activerecord ruby-on-rails-5

在我的应用程序中,我有很多页面需要显示人员列表,并允许用户使用表单对其进行过滤。这些页面通常看起来很相似。过滤器共享部分,但仍不相同。

我想知道如何避免对不同的控制器重复几乎相同的代码?我尝试了作用域,但无论如何我仍然需要解析参数并在视图中填充表格。

谢谢!

2 个答案:

答案 0 :(得分:0)

免责声明:https://github.com/dubadub/filtered的作者在这里。

ActiveRecord提供了一种用于关联的merge方法。它与两个查询部分相交,从而可以将查询逻辑分解为多个部分。

基于这个想法,我创建了一个宝石https://github.com/dubadub/filtered

在您的情况下,可能是这样的:

# app/controllers/people_controller.rb
class PeopleController < ApplicationController

  before_action :set_filter

  def index
    @people = People.all.merge(@filter)
  end

  private

  def set_filter
    @filter = PersonFilter.new(filter_params)
  end

  def filter_params
    params.fetch(:filter, {}).permit(:age, :active, :sorting)
  end

end
# app/filters/person_filter.rb
class PersonFilter < ApplicationFilter

  field :age

  field :active do |active|
    -> { joins(:memberships).merge(Membership.where(active: active)) }
  end

  field :sorting do |value|
    order_by, direction = value.values_at("order", "direction")

    case order_by
    when "name"
      -> { order(name: direction) }
    when "age"
      -> { order(age: direction) }
    else
      raise "Incorrect Filter Value"
    end
  end
end
# app/views/people/index.slim

  = form_for(@filter, url: search_path, method: "GET", as: :filter) do |f|
    .fields
      span Age
       = f.select :age, (18..90).map { |a| [ a, a ] }

    .fields
      span Active
        = f.check_box :active

    .fields
      span Sorting

      span Name
        = f.radio_button :sorting, "name asc"
        = f.radio_button :sorting, "name desc"

      span Age
        = f.radio_button :sorting, "age asc"
        = f.radio_button :sorting, "age desc"      

    .actions
      = f.submit "Filter"

希望有帮助!

答案 1 :(得分:0)

您看过查询对象吗?

https://mkdev.me/en/posts/how-to-use-query-objects-to-refactor-rails-sql-queries

它们允许您在许多地方重用代码,您只需传递params.permit(...)即可获取AR输出。

# app/queries/user_query.rb
class UserQuery
  attr_accessor :initial_scope
  def initialize(scoped = User.all)
    @initial_scope = initial_scope
  end

  def call(params) # is what you pass from your controller
    scoped = by_email(@initial_scope, params[:email]
    scoped = by_phone(scoped, params[:phone]
    # ...
    scoped
  end

  def by_email(scoped, email = nil)
    email ? where(email: email) : scoped
  end
  def by_phone(scoped, phone = nil)
    phone ? where(phone: phone) : scoped
  end
end

# users_controller.rb
class UsersController < ApplicationController
  def index
    @users = UserQuery.new(User.all)
      .call(params.permit(:email, :phone))
      .order(id: :desc)
      .limit(100)
  end
end

# some other controller
class RandomController < ApplicationController
  def index
    @users = UserQuery.new(User.where(status: 1))
      .call(params.permit(:email))
      .limit(1)
  end
end

您可能可以重构此示例,以减少为编写更丰富的对象的查询而花费的前期投资,如果您提出了替代方案,请在此处张贴,以便其他人可以学习如何使用查询对象。