Инициализируйте переменную экземпляра через именованную область

Можно ли инициализировать переменную экземпляра через именованную область? Я хочу запомнить свои критерии поиска после первоначального поиска. Смотрите следующее:

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; понятия не имею, как он ведет себя с другими версиями.

Другие вопросы по тегам