【问题标题】:Initialize instance variable through named scope通过命名范围初始化实例变量
【发布时间】:2012-10-08 16:56:48
【问题描述】:

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

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

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

【问题讨论】:

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


    【解决方案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
    

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        【解决方案3】:

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

        作为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 恶意是为了确保 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 中提出了这个解决方案;不知道它与其他版本的行为如何。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-11-05
          • 1970-01-01
          • 1970-01-01
          • 2017-04-05
          • 1970-01-01
          • 2011-03-18
          • 2010-11-26
          • 1970-01-01
          相关资源
          最近更新 更多