我在ActiveAdmin中添加了以下过滤器。
filter :roles, as: :select, collection Model::ROLES, multiple: true
但是当我选择过滤器值来搜索角色时。它给了我以下错误
PG::InvalidTextRepresentation: ERROR: malformed array literal: "teacher"LINE 1: ...ted" = $1 AND roles" IN('teacher
DETAIL: Array value must start with "{" or dimension information. ^
有什么想法吗?我们如何使用AA过滤器搜索/过滤ARRAY字段?我正在使用 Rails 4.2.4, 红宝石2.2.2p95
答案 0 :(得分:0)
我想出了一个略有不同(并受其启发)的解决方案:https://stackoverflow.com/a/45728004/1170086
矿井涉及一些变更(在其他情况下,请防止破坏contains
运算符)。因此,您将基本上创建两个初始化程序文件:
此代码用于Arel,以支持给定表列的@>
运算符(PG中的数组包含运算符)。
# config/initializers/arel.rb
module Arel
class Nodes::ContainsArray < Arel::Nodes::Binary
def operator
:"@>"
end
end
class Visitors::PostgreSQL
private
def visit_Arel_Nodes_ContainsArray(o, collector)
infix_value o, collector, ' @> '
end
end
module Predications
def contains(other)
Nodes::ContainsArray.new self, Nodes.build_quoted(other, self)
end
end
end
另一个文件旨在创建一个新的Ransack谓词,但我还决定支持:array
类型(Ransack本身不支持谓词)。
# config/initializers/ransack.rb
module Ransack
module Nodes
class Value < Node
alias_method :original_cast, :cast
def cast(type)
return Array(value) if type == :array
original_cast(type)
end
end
end
end
Ransack.configure do |config|
config.add_predicate 'contains_array',
arel_predicate: 'contains',
formatter: proc { |v| "{#{v.join(',')}}" },
validator: proc { |v| v.present? },
type: :array
end
并在其他使用它。您所需要做的就是:
User.ransack(roles_contains_array: %i[admin manager])
或作为ActiveAdmin中的过滤器(这是我的情况):
ActiveAdmin.register User do
# ...
filter :roles_contains_array, as: :select, collection: User.roles_for_select
# ...
end
我希望它对我有用。 ;)
答案 1 :(得分:0)
leandroico解决方案效果很好。
但是如果您使用此格式化程序添加谓词
formatter: proc { |v| "{#{v.join(', ')}}" },
(注意逗号后的空格)
然后,您可以在过滤器输入中使用multiple: true
关键字,并按多个值过滤:
filter :roles_contains_array, as: :select, multiple: true, collection: User.roles_for_select
答案 2 :(得分:0)
我使用@leandroico的答案提出了以下wiki类型的方法来实现此目的。
如何为ActiveAdmin创建自定义SQL搜索(使用Arel和Ransack)
在ActiveAdmin中,过滤器在app/admin/model.rb
中声明,例如:
ActiveAdmin.register Model do
filter 'column_name', label: 'column_name', as: :string
end
这将使搜索框在前端可用,并提供可供选择的选项
contains
equals
starts with
ends with
您甚至可以做类似...
filter 'column_name_contains', label: 'column_name', as: :string
...仅在前端提供contains
类型的搜索。
您还可以(在其他地方定义一些自定义方法之后)指定其他非内置的搜索方法,例如:
filter 'column_name_custom_contains', label: 'column_name', as: :string
本文档的其余部分将介绍如何定义此自定义搜索方法custom_contains
在config/initializers/arel.rb
中,定义以下内容:
module Arel
# this example of custom_contains will cast the SQL column as ::text and then do a wildcard-wrapped ILIKE
class Nodes::CustomContains < Arel::Nodes::Binary
def operator
'::text ILIKE'.to_sym
end
end
class Visitors::PostgreSQL
private
def visit_Arel_Nodes_CustomContains(o, collector)
infix_value o, collector, '::text ILIKE '
end
end
module Predications
def custom_contains(column_value)
column_value = self.relation.engine.column_types[self.name.to_s].type_cast_for_database(column_value)
column_value = "%#{self.relation.engine.send(:sanitize_sql_like, column_value)}%" # wrap escaped value with % wildcard
column_value = Nodes.build_quoted(column_value, self)
Nodes::CustomContains.new(self, column_value)
end
end
end
module ActiveRecord::QueryMethods
def custom_contains(predicates)
return none if predicates.length == 0
predicates.map{ |column_name, column_value|
column_value = table.engine.column_types[column_name.to_s].type_cast_for_database(column_value)
column_value = "%#{table.engine.send(:sanitize_sql_like, column_value)}%" # wrap escaped value with % wildcard
column_value = Arel::Nodes.build_quoted(column_value)
where Arel::Nodes::CustomContains.new(table[column_name], column_value)
}.inject(:merge)
end
end
module ActiveRecord::Querying
delegate :custom_contains, :to => :all
end
在config/initializers/ransack.rb
中,定义以下内容:
Ransack.configure do |config|
config.add_predicate(
'custom_contains',
arel_predicate: 'custom_contains',
formatter: proc { |v| v.to_s },
validator: proc { |v| v.present? },
type: :string
)
end
上面完成了几件事:
1)您可以使用delegate
到all
ActiveRecord模型中的custom_contains方法:
puts Model.custom_contains(column_name: 'search for me').to_sql
2)您可以使用Ransack搜索已定义的Arel谓词:
puts Model.ransack(column_name_custom_contains: 'search for me').result.to_sql
但是,为了在ActiveAdmin中执行以下操作...
filter 'column_name_custom_contains', label: 'column_name', as: :string
...我们必须为Model
添加一个范围,以便在column_name_custom_contains
上有一个方法Model
scope_name = "#{column_name}_custom_contains".to_sym
unless Model.methods.include?(scope_name)
Model.scope(
scope_name,
->(value) {
Model.custom_contains({column_name.to_sym => value})
}
)
end
Voila!
答案 3 :(得分:0)
您可以设置一个自定义的 ransacker 方法,首先使用常规 postgres 搜索收集您想要返回的 id,然后根据这些 id 返回结果:
class ListOfItems(private val list: Map<String, Item> = mapOf()) : Parcelable, List<Item> by list.values.toList() {
operator fun get(name: String) = list[name] ?: Item(name)
}
如果您的过滤器是一个选择下拉菜单,那么这应该可以正常工作。如果您有自由格式的文本框,请确保使用“in”谓词:
class User < ApplicationRecord
ransacker :roles,
formatter: proc { |str|
data = where("? = ANY (roles)", str).map(&:id)
data.present? ? data : nil
} do |parent|
parent.table[:id]
end
end