0

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

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

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

4

3 回答 3

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
于 2020-05-16T15:17:34.503 回答
0

命名范围不会实例化任何单个记录,直到它们被迭代。正如您正确地说的那样,它们可能与更多范围链接在一起。

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

另一种是依赖控制器。这实现起来要容易得多,但是您必须确保 ruby​​ 中的“匹配”与数据库中的“LIKE”相同。我说的是排序规则、unicode 规范化表单等。很可能至少存在一种情况,它们在两个引擎中的行为不同。

于 2012-10-08T23:25:33.453 回答
0

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

正如他在回答中重写的那样,您无法从范围内为模型设置实例变量,因为还没有模型实例。范围仅用于构建将在您尝试迭代范围结果时评估的 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 和关注目录。您的应用程序需要了解这些:

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

于 2013-11-14T06:35:05.897 回答