通过命名范围初始化实例变量

时间:2012-10-08 16:56:48

标签: ruby-on-rails ruby named-scope

是否可以通过命名范围初始化实例变量?我想在初始搜索后记住我的搜索条件。请参阅以下内容:

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

或者,我可以创建一个调用命名范围的类方法,然后遍历结果以在每个上设置实例变量,但在这种情况下我失去了命名的范围可链接性。

3 个答案:

答案 0 :(得分:1)

非常感谢您提供有关如何做的最新文章。 这个猴子补丁仍然可以在Rails 5上使用。

当将范围与“ where()”条件链接时,我刚刚遇到了上下文没有转发的情况。

为此,我向Association的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

答案 1 :(得分:0)

命名范围不会实例化任何单个记录,直到它们被迭代。正如你所说的那样,他们可能会被更多的范围链接。

我看到两种可能的解决方案。一个依赖于数据库来填充具有匹配值的假列。使用查询的特殊:select选项(或rails 3中.select()的特殊参数)应该很简单

另一种是依靠控制器。这更容易实现,但您必须确保ruby中的“匹配”与数据库中的“LIKE”相同。我说的是整理,unicode规范化形式等。最有可能至少有一种情况,它们在两个引擎中表现不同。

答案 2 :(得分:0)

我一直疯狂地试图在过去一周内完成同样的问题,并最终成功地解决了这个问题。由于我在遇到麻烦时发现了这个问题,我想我会在这里分享答案,以防万一其他人发现自己遇到了类似的问题。

作为rewritten stated in his answer,您无法在范围内设置模型的实例变量,因为还没有模型实例。范围仅用于构建将在尝试迭代范围结果时将评估的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变量。 SpawnMethods#merge malarky是为了确保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和Concerses目录。您的申请需要了解这些:

/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提出了这个解决方案;不知道它与其他版本的表现如何。