Инициализируйте переменную экземпляра через именованную область
Можно ли инициализировать переменную экземпляра через именованную область? Я хочу запомнить свои критерии поиска после первоначального поиска. Смотрите следующее:
class Foo < ActiveRecord::Base
attr_accessor :search
named_scope :bar, lambda {|search|
# Do something here to set the @search variable to search
{
:conditions => [
"name LIKE :search OR description LIKE :search",
{:search => "%#{search}%"} ]
}
}
def match
if name.match(@search)
:name
elsif description.match(@search)
:description
end
end
end
В качестве альтернативы, я мог бы создать метод класса, который вызывает именованную область, а затем перебирает результаты, чтобы установить переменную экземпляра для каждого, но в этом случае я теряю цепочку именованной области действия.
2 ответа
Именованные области не создают ни одной отдельной записи, пока они не будут повторены. И они, как вы правильно сказали, могут быть прикованы большим количеством областей.
Я вижу два возможных решения. Каждый полагается на базу данных, чтобы заполнить поддельный столбец с соответствующим значением. Это должно быть просто с помощью специального :select
вариант запроса (или специальный аргумент .select()
в рельсах 3)
Другой заключается в том, чтобы положиться на контроллер. Это гораздо проще реализовать, но вы должны убедиться, что "match" в ruby совпадает с "LIKE" в базе данных. Я говорю о сопоставлении, нормализованных формах Юникода и т. Д. Скорее всего, есть хотя бы один случай, когда они ведут себя по-разному в обоих движках.
Большое спасибо за последний пост с вдохновением о том, как это сделать. Эти патчи обезьяны все еще работают с Rails 5.
Я только что столкнулся с ситуацией, когда контекст не был перенаправлен при связывании области с условиями "where()".
Для этого я добавил еще один патч в Association's CollectionProxy, который, похоже, работает в моей текущей ситуации.
Возможно, это пригодится вам или кому-то еще:
module ActiveRecord
module Associations
class CollectionProxy
alias_method :original_scope, :scope
def scope
return @scope if @scope
original_scope.tap { |new_scope|
new_scope.scope_context = scope_context
}
end
end
end
end
Я отчаянно пытался решить эту проблему на прошлой неделе и наконец справился с ней. Поскольку я нашел этот вопрос среди своих проблем, я решил поделиться этим ответом на тот случай, если кто-то еще столкнется с подобной проблемой.
Как переписано в его ответе, вы не можете установить переменную экземпляра для вашей модели из области видимости, поскольку еще нет экземпляра модели. Область действия предназначена только для построения выражения sql, которое будет оцениваться при попытке выполнить итерацию по результату области действия. Однако существует объект ActiveRecord::Relation. Это вещь, которая содержит детали вашей области. Поместив переменные, которые вы хотите сохранить в этом объекте, они все равно будут доступны позже.
Итак, как получить переменную для объекта Relation? Обезьяна патчинг приходит вам на помощь здесь:
/lib/core_ext/add_scope_context_to_activerecord_relation.rb:
module ActiveRecord
class Relation
attr_writer :scope_context
def scope_context
@scope_context ||= {}
end
end
module SpawnMethods
alias_method :old_merge, :merge
def merge(r)
merged = old_merge(r)
merged.scope_context.deep_merge! r.scope_context
merged
end
end
end
Теперь во всех ваших областях у вас есть переменная scope_context. Malarky слияния SpawnMethods # должен убедиться, что scope_context хранится в цепочке областей действия (например, Foo.search(xxx).sort(xxx).paginate(xxx)
)
/app/concerns/searchable.rb:
require 'active_support/concern'
module Searchable
extend ActiveSupport::Concern
included do
self.scope :search, Proc.new { |params|
s = scoped
s.scope_context[:searchable] = {}
s.scope_context[:searchable][:params] = parse_params params
s.scope_context[:searchable][:params].each do |param|
s = s.where generate_where_expression param
end
s
} do
def search_val col_name
param = scope_context[:searchable][:params].find do |param|
param[:col_name] == field.to_s
end
param.nil? ? '' : param[:val]
end
end
end
module ClassMethods
def parse_params params
# parse and return the params
end
def generate_where_expression param
"#{param[:col_name]} like '%#{param[:val]}%'"
end
end
end
Теперь наш контроллер, модель и вид будут выглядеть следующим образом:
/app/controllers/foo_controller.rb:
class FooController < ApplicationController
def index
@foos = Foo.search(params[:search])
end
end
/app/models/foo.rb:
class Foo < ActiveRecord::Base
include Searchable
attr_accessor :name, :description
end
/app/views/foos/index.html.erb:
<p>You searched for:</p>
<table>
<tr>
<th>Name</th>
<td><%= @foos.search_val :name %>
</tr>
<tr>
<th>Description</th>
<td><%= @foos.search_val :description %>
</tr>
</table>
<p>And you got:</p>
<table>
<% @foos.each do |foo| %>
<tr> xxx </tr>
<% end %>
</table>
Теперь вы, возможно, заметили core_ext и каталоги, упомянутые выше. Ваша заявка должна быть уведомлена об этих:
/config/application.rb:
# Should be fairly obvious where to put this line
config.autoload_paths += Dir[Rails.root.join('app', 'concerns', '{**}')]
/config/initializers/load_extensions.rb:
Dir[File.join(Rails.root, "lib", "core_ext", "*.rb")].each {|l| require l }
Не забудьте перезапустить свой сервер, и все, и вы уходите (при условии, что я ничего не забыл включить).
О, и я придумал это решение с Rails 3.2.13 и Ruby 1.9.3; понятия не имею, как он ведет себя с другими версиями.