Ransack: Как использовать существующий объем?
Преобразование приложения Rails 2 в Rails 3, мне нужно заменить драгоценный камень searchlogic. Теперь, используя Rails 3.2.8 с gem Ransack, я хочу создать форму поиска, в которой используется существующая область. Пример:
class Post < ActiveRecord::Base
scope :year, lambda { |year|
where("posts.date BETWEEN '#{year}-01-01' AND '#{year}-12-31'")
}
end
Насколько я знаю, это может быть достигнуто путем определения пользовательского ransacker. К сожалению, я не могу найти документацию об этом. Я пробовал это в классе Post
:
ransacker :year,
:formatter => proc {|v|
year(v)
}
Но это не работает:
Post.ransack(:year_eq => 2012).result.to_sql
=> TypeError: Cannot visit ActiveRecord::Relation
Я пробовал некоторые варианты объявления ransacker
, но никто из них не работает. Мне нужна помощь...
ОБНОВЛЕНИЕ:. Я ищу способ использовать все существующие области в Ransack. В MetaSearch, предшественнике Ransack, существует функция, называемая search_methods
для использования областей. Ransack не поддерживает эту из коробки.
Ответы
Ответ 1
ransack поддерживает его из коробки после слияния https://github.com/activerecord-hackery/ransack/pull/390. вы должны объявить метод ransakable_scopes для добавления видимых областей для ransack.
Из руководства
Продолжая работу в предыдущем разделе, поиск по областям требует определения белого списка ransackable_scopes в классе модели. Белый список должен быть массивом символов. По умолчанию все методы класса (например, области) игнорируются. Области будут применяться для сопоставления истинных значений или для заданных значений, если область принимает значение:
class Employee < ActiveRecord::Base
scope :activated, ->(boolean = true) { where(active: boolean) }
scope :salary_gt, ->(amount) { where('salary > ?', amount) }
# Scopes are just syntactical sugar for class methods, which may also be used:
def self.hired_since(date)
where('start_date >= ?', date)
end
private
def self.ransackable_scopes(auth_object = nil)
if auth_object.try(:admin?)
# allow admin users access to all three methods
%i(activated hired_since salary_gt)
else
# allow other users to search on `activated` and `hired_since` only
%i(activated hired_since)
end
end
end
Employee.ransack({ activated: true, hired_since: '2013-01-01' })
Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })
Ответ 2
Ransack позволяет создавать пользовательские предикаты для этого, к сожалению, документация оставляет место для улучшения, но проверка: https://github.com/ernie/ransack/wiki/Custom-Predicates
Также я считаю, что проблема, которую вы пытаетесь решить, зависит от их проблемы. Там идет хорошая дискуссия: https://github.com/ernie/ransack/issues/34
Ответ 3
Я написал драгоценный камень под названием siphon, который поможет вам перевести параметры в области активизации. Сочетание этого с ransack может достичь этого.
Вы можете прочитать полное объяснение здесь. Между тем здесь суть этого
Вид
= form_for @product_search, url: "/admin/products", method: 'GET' do |f|
= f.label "has_orders"
= f.select :has_orders, [true, false], include_blank: true
-#
-# And the ransack part is right here...
-#
= f.fields_for @product_search.q, as: :q do |ransack|
= ransack.select :category_id_eq, Category.grouped_options
```
ok, поэтому теперь params[:product_search]
содержит области действия, а params[:product_search][:q]
- добросовестность. Теперь нам нужно найти способ распространить эти данные на объект формы. Поэтому сначала пусть ProductSearch проглотит его в контроллере:
Контроллер
# products_controller.rb
def index
@product_search = ProductSearch.new(params[:product_search])
@products ||= @product_formobject.result.page(params[:page])
end
Объект формы
# product_search.rb
class ProductSearch
include Virtus.model
include ActiveModel::Model
# These are Product.scopes for the siphon part
attribute :has_orders, Boolean
attribute :sort_by, String
# The q attribute is holding the ransack object
attr_accessor :q
def initialize(params = {})
@params = params || {}
super
@q = Product.search( @params.fetch("q") { Hash.new } )
end
# siphon takes self since its the formobject
def siphoned
Siphon::Base.new(Product.scoped).scope( self )
end
# and here we merge everything
def result
Product.scoped.merge(q.result).merge(siphoned)
end
end