私はこの 1 週間、この同じ問題を必死に達成しようとしてきましたが、最終的にそれを管理することができました。悩みの中でこの質問を見つけたので、他の誰かが同様の問題に直面した場合に備えて、ここで答えを共有したいと思いました.
彼の回答で書き直されたように、モデルインスタンスがまだないため、スコープからモデルにインスタンス変数を設定することはできません。スコープは、スコープの結果を反復しようとしたときに評価される 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 でこの解決策を考え出しました。他のバージョンでどのように動作するかわかりません。