有没有办法动态地将范围添加到活动记录类?

时间:2015-06-13 20:48:26

标签: ruby scope metaprogramming

我正在尝试动态地将范围添加到活动记录对象。我通过元编程我trying的一些事情是行不通的。以下是我想要实现的目标。

class UtilityClass
   def add_scope_to_class klass, name, lambda
     # should add something like
     # scope :published, -> { where(published: true) }
     code = lambda { filter_clause(value, &lambda) }
     klass.class_exec(&code)
   end
end

class Article < ActiveRecord::Base
end

UtilityClass.add_scope_to_class

Article.published # returns published articles

我尝试过使用纯红宝石对象的几种变体,但我认为用这个上下文提问可能会得到一些不同的想法/答案。

感谢。

更新

到目前为止,我已设法提出以下内容,但它无效

class ActiveSearch
  attr_accessor :collection
  attr_reader :params

  def initialize params={}
    @params = params
  end

  def results
    @collection = Person.where(nil)
    params.compact.each do |key, value|
      @collection = @collection.send("#{key}_filter", value)
    end
    @collection
  end

  def self.filter name, filter_proc
    code = lambda { |filter_name| scope("#{filter_name.to_s}_filter".to_s, filter_proc) }
    Person.class_exec(name, &code)
  end
end

class PersonSearch < ActiveSearch
  filter :first_name, ->(name){ where(first_name: name) }
end

我已经硬编码了几件事。主要的想法就在那里。

2 个答案:

答案 0 :(得分:0)

在玩完之后,我发现解决方案比我原先想象的要简单一些。

class ActiveSearch
  attr_accessor :collection
  attr_reader :params

  def initialize params={}
    @params = params
  end

  def results
    @collection = Person.where(nil)
    params.compact.each do |key, value|
      @collection = @collection.send("#{key}_filter", value)
    end
    @collection
  end

  def self.filter name, filter_proc
    Person.define_singleton_method "#{name.to_s}_filter".to_sym, &filter_proc # does most of the heavy lifting
  end
end

class PersonSearch < ActiveSearch
  filter :first_name, lambda { |name| where(first_name: name) }
  filter :last_name, lambda { |name| where(last_name: name) }
end


PersonSearch.new({first_name: 'ryan', last_name: 'test'}).results

以上将结合所有适当的方法并执行查询。

需要进行一些调整,但它几乎可以满足我的需求。主动搜索类仍然需要被清理并变得通用,但我认为它可以解决这个问题。

答案 1 :(得分:0)

我想你可能只是在重新发明范围。

class Person < ActiveRecord::Base
end

Person.send :scope, :first_name, -> {|name| where(first_name: name) }
Person.send :scope, :last_name,  -> {|name| where(last_name: name)  }

Person.first_name("chris").last_name("heald").first

Person.send :scope, :name, ->{|f, l| first_name(f).last_name(l) }
Person.name("chris", "heald").first