在过去的一周里,我一直在疯狂地尝试解决同样的问题,并最终解决了这个问题。由于我在麻烦中发现了这个问题,我想我会在这里分享答案,以防万一其他人发现自己面临类似的问题。
正如他在回答中重写的那样,您无法从范围内为模型设置实例变量,因为还没有模型实例。范围仅用于构建将在您尝试迭代范围结果时评估的 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 提出了这个解决方案;不知道它在其他版本中的表现如何。