4

Web ページの拡張検索機能に取り組んでいます。

ransackを見ましたが、必要な機能がいくつか欠けていて、url-query 文字列が非常に長くなり、バグがいくつかあります (報告されています)。

したがって、私は自分のハックを実装し始めました。

最初に私のアイデアを紹介したいと思います。その後、私の問題を解決する方法を親切に尋ねたいと思います。最後に、これを改善する他の方法があるかどうか.

アイデア:

モデルは次のように定義します (さらに、モデルはエンジン内にあります)。

module EngineName
  class Post < ActiveRecord::Base
    search_for :name, :as => :string do |b, q|
      b.where{name =~ "%#{q}%"}
    end
  end
end

:name は、使用する query-param を定義することです。たとえば、これは ?q[name]=something になります。これは、ランサックのように完全に一般的ではないことはわかっていますが、まあ...

:as is 正しいフォームタグを構築します。:string は text_field の場合、:integer は number_field の場合などです。さらに拡張して、関連付けなどのコレクションの自動生成を実装したいと考えています。

これで、ブロックは簡単に使用できるスコープになりました。複雑なクエリ (count() など) を構築するときに、ランサックでいくつかの欠点に遭遇します。squeelで独自の最適化されたクエリを指定できるようになりました。

ActiveRecord::Base を拡張してロジックをセットアップしました (エンジン内ではなく、グローバルなものです。どこでも使用したい)。スコープ :search を定義したので、ランサックのように Model.search(param[q]) を使用できます。また、search_for 呼び出しで定義された「検索可能な」キーのリストを保持しようとしました。

class ActiveRecord::Base
@@searchable_attributes = Hash.new({})

def self.search_for(name, *opts, &search_scope)
  return unless search_scope

  @@searchable_attributes[name] = {
    :type => opts[:as],
    :condition => search_scope
  }

  unless @@searchable_attributes.has_key? :nil
    @@searchable_attributes[:nil] = Proc.new { scoped }
  end
end

scope :search, lambda {|q|
  next unless q.kind_of?(Hash)

  base = @@searchable_attributes[:nil].call
  q.each do |key, search|
    next unless base.class.searchable_attributes.has_key?(key)
    base = @@searchable_attributes[key][:condition].call(base, search)
  end
  base
}
end

今問題:

それは主にクラスの継承に関係しています。しかし、 34を読んで試した後でも、うまくいきません。

スコープ :search の 2 行目を見てください。

そこで、「スコープ」のみを含む上で定義した単純な Proc を呼び出しています。これは、「Post」や「Comment」などのモデル自体ではなく、self が「ActiveRecord::Base」を返すという問題を回避するためです。

これは、継承時に Base クラスでスコープが呼び出されるためですが、これを修正するものは何も見つかりませんでした。

モデル自体 (Post など) で search_for が呼び出されるため、そこに返されるスコープ モデルは「正しいもの」です。

これを回避する方法を知っている人はいますか?

次の質問は、「検索可能な」スコープのリストを保存する方法です。@@変数を使用しました。しかし、それらはすべてのサブクラス内で共有されるため、これは不可能です。ただし、インスタンスを初期化せずに search_for を呼び出すため、静的である必要があります (そうではありませんか?)。

最後になりましたが、すべてのスコープで使用するベースモデルを常に指定して、それらを連鎖できるようにするのは、ある種恐ろしいことです。

これを改善する他の可能性はありますか?

4

2 に答える 2

2

わかりました、他の質問からの他のいくつかの回答をまとめて、最終的に自分で手に入れたようです。

モデル:

module EngineName
  class Post < ActiveRecord::Base
    searchable

    search_for :name, :as => :string do |b, q|
     b.where{name =~ "%#{q}%"}
    end
  end
end

現在イニシャライザとしての私の「プラグイン」:

class ActiveRecord::Base
  def self.searchable
    include Searchable
  end
end

module Searchable
  def self.included(base)
    base.class_eval {

      @@searchable_attributes = Hash.new({})

      def self.search_for(name, opts)
        return unless block_given?

        @@searchable_attributes[name] = {
          :type => opts[:as],
          :condition => Proc.new
        }
      end


      # Named scopes
      scope :search, lambda {|q|
        next unless q.kind_of?(Hash)

        base = self.scoped
        q.each do |key, search|
          key = key.to_sym
          next unless @@searchable_attributes.has_key?(key)
          base = @@searchable_attributes[key][:condition].call(base, search)
        end
        base
      }
    }
  end
end

同じ問題に取り組んでいる他の人に役立つことを願っています。

于 2013-02-09T20:13:58.923 に答える
1

Railsはclass_attributeのヘルパーを提供します。これにより、継承可能なクラス属性が提供されますが、サブクラスは「独自の値を変更でき、親クラスには影響しません」。ただし、たとえば[] =を使用して変更されたハッシュは親に影響を与えるため、rubys継承メソッドを使用してサブクラス化するときに新しいコピーが作成されるようにすることができます。

したがって、次のように基本クラスで宣言して初期化できます。

module Searchable
  extend ActiveSupport::Concern

  included do
    class_attribute :searchable_attributes
  end

  module ClassMethods
    def inherited(subclass)
      subclass.searchable_attributes = Hash.new({})
    end

    def search_for(name,opts)
      return unless block_given?

      searchable_attributes[name] = {
        :type => opts[:as],
        :condition => Proc.new
      }
    end
  end
end

ActiveSupport :: Concernを使用して、クラス上で直接ものを定義し、クラスメソッドを混合するためのすっきりとした構文を取得したことに注意してください。次に、これをアクティブなレコードベースに追加するだけです。

ActiveRecord::Base.send(:include, Searchable)

これで、すべてのクラスが独自の属性ハッシュを取得します。

class Post < ActiveRecord::Base
  search_for :name, :as => :string do |b, q|
    b.where{name =~ "%#{q}%"}
  end
end
于 2013-02-10T21:50:41.657 に答える