4

我正在为我的网页开发扩展搜索功能。

我查看了ransack,但是它缺少一些我需要的功能,使 url-query 字符串很长并且有一些错误(报告)。

因此,我开始实施自己的 hack。

首先我想提出我的想法,然后我想问一下如何解决我的问题,最后是否有其他方法可以改善这个问题。

想法:

模型定义了这样的东西(此外,模型在引擎内部):

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

:name 是定义要使用的查询参数,例如这将是 ?q[name]=something 我知道这不像 ransack 那样完全通用,但是...

:as 是为了建立正确的表单标签。:string 用于 text_field, :integer 用于 number_field 等等。我想进一步扩展它以实现为关联等自动生成集合。

现在块是一个简单的使用范围。在构建复杂查询(例如使用 count() 等)时,我遇到了 ransack 的几个缺点。现在我可以在squeel中指定我自己的优化查询。

我扩展了 ActiveRecord::Base 以设置逻辑(全局逻辑,不在引擎内部。我想在任何地方使用它)。我定义了一个范围:搜索,所以我可以像在 ransack 中一样使用 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

现在的问题:

它主要与类的继承有关。但即使在阅读并尝试3 , 4之后它也不起作用。

请查看范围 :search 中的第二行。

在那里,我调用了我在上面定义的简单 Proc,它只包括“作用域”。这是为了解决 self 返回“ActiveRecord::Base”而不是模型本身(如“Post”或“Comment”)的问题。

这是因为在继承时基类上调用了范围,但是我没有找到任何可以解决此问题的方法。

由于在模型本身(例如 Post)上调用了 search_for,因此返回的范围模型是“正确的”。

有谁知道如何规避这个?

下一个问题是,如何存储“可搜索”范围的列表。我使用了@@variables。但由于它们在每个子类中共享,所以这是不行的。但是,它需要是静态的,因为在没有初始化实例的情况下调用了 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提供了一个助手。这提供了可继承的类属性,但允许子类“更改自己的值并且不会影响父类”。但是,例如使用 []= 突变的哈希会影响父级,因此您可以确保在使用 ruby​​s继承方法进行子类化时制作新副本

因此,您可以像这样在基类上声明和初始化:

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 回答