Filterameter 为 Rails 控制器提供声明式过滤器,以减少样板代码并提高可读性。您见过(或写过)此控制器操作多少次?
def index
@films = Films . all
@films = @films . where ( name : params [ :name ] ) if params [ :name ]
@films = @films . joins ( :film_locations ) . merge ( FilmLocations . where ( location_id : params [ :location_id ] ) ) if params [ :location_id ]
@films = @films . directed_by ( params [ :director_id ] ) if params [ :director_id ]
@films = @films . written_by ( params [ :writer_id ] ) if params [ :writer_id ]
@films = @films . acted_by ( params [ :actor_id ] ) if params [ :actor_id ]
end
这是多余的代码,编写和维护起来有点痛苦。更不用说 RuboCop 会对此说什么了。如果您可以声明控制器接受的过滤器不是很好吗?
filter :name , partial : true
filter :location_id , association : :film_locations
filter :director_id , name : :directed_by
filter :writer_id , name : :written_by
filter :actor_id , name : :acted_by
def index
@films = build_query_from_filters
end
使用 Filterameter 使滤波器参数具有声明性,从而简化并加速 Rails 控制器的开发。
这个 gem 需要 Rails 6.1+,并与 ActiveRecord 一起使用。
将此行添加到应用程序的 Gemfile 中:
gem 'filterameter'
然后执行:
$ bundle install
或者自己安装:
$ gem install filterameter
在控制器中包含模块Filterameter::DeclarativeFilters
以提供过滤器 DSL。它可以包含在ApplicationController
中以使所有控制器都可以使用该功能,也可以根据具体情况混合使用。
filter :color
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
filter :brand_name , association : :brand , name : :name
filter :on_sale , association : :price , validates : [ { numericality : { greater_than : 0 } } ,
{ numericality : { less_than : 100 } } ]
没有选项的过滤器可以使用filters
一次性声明:
filters :color ,
:size ,
:name
可以为每个过滤器指定以下选项。
如果参数的名称与属性或范围的名称不同,则使用 name 参数指定属性或范围的名称。例如,如果属性名称为current_status
但过滤器仅作为status
公开,请使用以下命令:
filter :status , name : :current_status
此选项对于嵌套过滤器也很有帮助,以便查询参数可以使用模型名称作为前缀。请参阅association
选项的示例。
如果属性或范围是嵌套的,则可以通过命名关联来引用它。例如,如果 manager_id 属性位于员工的部门记录中,请使用以下内容:
filter :manager_id , association : :department
属性或范围可以嵌套多于一层。使用按顺序指定关联的数组来声明过滤器。例如,某个员工属于某个部门,而某个部门又属于某个业务单位,则可以使用以下命令查询业务单位名称:
filter :business_unit_name , name : :name , association : [ :department , :business_unit ]
如果关联是has_many
则在查询上调用不同的方法。
限制:如果同一个表有多个关联,并且两个关联都可以是查询的一部分,则不能直接使用嵌套过滤器。相反,构建一个消除关联歧义的范围,然后针对该范围构建一个过滤器。
如果应验证过滤器值,请使用validates
选项和 ActiveModel 验证。以下是用于限制大小的包含验证器的示例:
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] } }
inclusion
验证器已被重写,以提供附加选项allow_multiple_values
。当为 true 时,该值可以是一个数组,并且数组中的每个条目都将被验证。当过滤器可以指定一个或多个值时使用此选项。
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
如果过滤器应执行部分搜索(SQL 的LIKE
),请指定部分选项。部分选项接受哈希值来指定搜索行为。以下是可用的选项:
有两种快捷方式: :可以使用true
声明部分选项,它仅使用默认值;或者可以直接使用 match 选项声明partial 选项,例如partial: :from_start
。
filter :description , partial : true
filter :department_name , partial : :from_start
filter :reason , partial : { match : :dynamic , case_sensitive : true }
match
选项定义您要搜索的位置(然后控制通配符出现的位置):
指定范围选项以启用按范围、最小值或最大值进行搜索。 (所有这些都包含在内。搜索最低价格为 10.00 美元的商品将包括所有售价为 10.00 美元的商品。)
以下是可用的选项:
使用范围选项意味着除了属性过滤器之外,还可以指定最小和最大查询参数。参数名称是属性名称加上后缀_min或_max 。
filter :price , range : true
filter :approved_at , range : :min_only
filter :sale_price , range : :max_only
在第一个示例中,查询参数可以包括price 、 price_min和price_max 。
默认情况下,大多数过滤器都是可排序的。要防止属性过滤器可排序,请将该选项设置为 false。
filter :price , sortable : false
以下过滤器不可排序:
对于不带参数的作用域,过滤器应提供一个布尔值来指示是否应调用该作用域。例如,想象一个名为high_priority
的范围,其标准可识别高优先级记录。该范围将由查询参数high_priority=true
调用。
传递high_priority=false
将不会调用作用域。这使得添加带有复选框 UI 的过滤器变得很容易。
接受参数的作用域必须编写为类方法,而不是内联作用域。例如,想象一个名为recent
作用域,它采用截止日期作为参数。看起来可能是这样的:
def self . recent ( as_of_date )
where ( 'created_at > ?' , as_of_date )
end
如上所述,大多数属性过滤器默认情况下都是可排序的。如果没有为属性声明过滤器,则可以使用sort
声明。根据需要使用相同的name
和association
选项。
例如,可以在活动控制器上使用以下声明,以允许按创建的项目对活动进行排序。
sort :project_created_at , name : :created_at , association : :project
不带选项的排序可以使用sorts
一次性声明:
sorts :created_at ,
:updated_at ,
:description
范围可用于排序,但必须使用sort
(或sorts
)声明。例如,如果模型包含名为by_created_at
范围,您可以将以下内容添加到控制器以公开它。
sort :by_created_at
还可以使用name
和association
选项。例如,如果范围位于项目模型上,则也可以使用association
选项在子活动控制器上使用:
sort :by_created_at , association : :project
只有单数关联才对排序有效。集合关联可能返回多个值,从而使排序不确定。
用于排序的范围必须接受单个参数。根据参数,它将传递:asc
或:desc
。
上面的示例范围可能定义如下:
def self . by_created_at ( dir )
order ( created_at : dir )
end
可以使用default_sort
声明默认排序。参数应按名称指定一种或多种已声明的排序或可排序过滤器。默认情况下,顺序为升序。如果需要降序排列,可以将列名符号映射为:desc。
default_sort updated_at : :desc , :description
为了提供一致的结果,始终应用排序。如果没有指定默认值,它将使用主键降序。
有两种方法可以应用过滤器和构建查询,具体取决于需要多少控制和/或可见性:
build_filtered_query
build_query_from_filters
build_filtered_query
在操作回调build_filtered_query
之前添加应构建查询的控制器操作。这可以在ApplicationController
中完成,也可以根据具体情况进行。
使用回调时,变量名称是复数模型名称。例如,照片模型将使用变量@photos
来存储查询。变量名称可以使用filter_query_var_name
显式指定。例如,如果查询存储为@data
,请使用以下命令:
filter_query_var_name :data
此外, filter_model
命令采用可选的第二个参数来指定变量名称。模型和变量名称都可以使用此快捷方式指定。例如,要使用 Picture 模型并将结果存储为@data
,请使用以下命令:
filter_model 'Picture' , :data
在幸福的道路上,WidgetsController 提供 Widgets 服务,并且可以根据大小和颜色进行过滤。控制器可能如下所示:
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
before_action :build_filtered_query , only : :index
filter :size
filter :color
def index
render json : @widgets
end
end
build_query_from_filters
要手动生成查询,您可以直接调用build_query_from_filters
而不是使用回调。
这是 Widgets 控制器,这次是手动构建查询:
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
filter :size
filter :color
def index
@widgets = build_query_from_filters
end
end
此方法可选择采用起始查询。如果有一个活动小部件的控制器应该只返回活动小部件,则可以将以下内容作为起点传递到该方法中:
def index
@widgets = build_query_from_filters ( Widget . where ( active : true ) )
end
起始查询也是提供任何包含以启用预加载的好地方:
def index
@widgets = build_query_from_filters ( Widgets . includes ( :manufacturer ) )
end
请注意,起始查询提供模型,因此不会查找模型,也不需要model_name
声明。
Rails 约定用于确定控制器的模型。例如,PhotosController 针对照片模型构建查询。如果控制器是命名空间的,则将首先在没有命名空间的情况下查找模型,然后在使用命名空间的情况下查找模型。
如果约定未提供正确的模型,则可以使用以下方式显式命名模型:
filter_model 'Picture'
重要提示:如果使用filter_model
声明,它必须位于任何过滤器或排序声明之前。
有以下三个配置选项:
配置选项可以在初始化程序、环境文件或application.rb
中设置。
可以直接设置选项...
Filterameter.configuration.action_on_undeclared_parameters = :log
...或者可以生成配置:
Filterameter . configure do | config |
config . action_on_undeclared_parameters = :log
config . action_on_validation_failuer = :log
config . filter_key = :f
end
当过滤器参数包含任何未定义的键时发生。有效操作为:log
、 :raise
和false
(不执行操作)。默认情况下,开发将记录日志,测试将引发,而生产将不执行任何操作。
当过滤器参数验证失败时发生。有效操作为:log
、 :raise
和false
(不执行操作)。默认情况下,开发将记录日志,测试将引发,而生产将不执行任何操作。
默认情况下,过滤器参数嵌套在:filter
键下。使用此设置可以覆盖该密钥。
如果过滤器参数不嵌套,则将其设置为 false。这样做会将过滤器参数限制为仅那些已声明的参数,这意味着未声明的参数将被忽略(并且 action_on_undeclared_parameters 配置选项不会发挥作用)。
可以针对每个控制器测试声明,以捕获拼写错误、错误定义的范围或任何其他问题。方法declarations_validator
被添加到每个控制器,并且可以添加单个控制器测试来验证该控制器的所有声明。
RSpec 测试可能如下所示:
expect ( WidgetsController . declarations_validator ) . to be_valid
在 Minitest 中它可能看起来像这样:
validator = WidgetsController . declarations_validator
assert_predicate validator , :valid? , -> { validator . errors }
过滤器参数从控制器参数中提取,嵌套在键filter
下(默认情况下;请参阅配置以更改过滤器键)。例如,对大型蓝色小部件的请求可能在 URL 上具有以下查询参数:
?filter[size]=large&filter[color]=blue
在通用搜索表单上, form_with
表单助手采用允许对参数进行分组的选项scope
:
<%= form_with url: "/search", scope: :filter, method: :get do |form| %>
<%= form.label :size, "Size:" %>
<%= form.text_field :size %>
<%= form.label :color, "Color:" %>
<%= form.text_field :color %>
<%= form.submit "Search" %>
<% end %>
排序也嵌套在过滤器键下面:
/widgets?filter[sort]=size
使用数组传递多个排序。参数的顺序是排序的应用顺序。例如,以下首先按大小排序,然后按颜色排序:
/widgets?filter[sort]=size&filter[sort]=color
默认情况下排序是升序,但可以使用可以添加的前缀来控制排序:
+
升序(默认)-
下降例如,以下按大小降序排序:
/widgets?filter[sort]=-size
欢迎反馈、功能请求和提议的更改。请使用问题跟踪器获取反馈和功能请求。要直接提出更改,请分叉存储库并打开拉取请求。密切关注操作以确保测试和 Rubocop 顺利通过。代码气候也被手动用来评估代码线。
要报告错误,请使用问题跟踪器并提供以下信息:
如果您能够通过测试重现该问题,将获得金星奖励。
测试是用 RSpec 编写的,虚拟应用程序使用 Docker 数据库。脚本bin/start_db.sh
启动并准备测试数据库。这是运行测试之前的一次性步骤。
bin/start_db.rb
bundle exec rspec
还可以使用评估在所有 ruby 和 Rails 组合上运行测试。安装也是一次性步骤。
bundle exec appraisal install
bundle exec appraisal rspec
该 gem 根据 MIT 许可证条款作为开源提供。