【问题标题】:Extending ActiveRecord::Base扩展 ActiveRecord::Base
【发布时间】:2015-11-14 12:11:41
【问题描述】:

我正在尝试向 ActiveRecord 添加一些自定义方法。我想为模型的每个 date 字段添加一个 *_after*_before 范围,以便我可以执行以下操作:

User.created_at_after(DateTime.now - 3.days).created_at_before(DateTime.now)

我已经按照Rails extending ActiveRecord::Base 此处解释的解决方案进行了操作,但是当我执行 rails 控制台并尝试调用这些方法时,我收到了 undefined method 错误。

这是我的代码:

# config/initializers/active_record_date_extension.rb
require "active_record_date_extension"

# lib/active_record_date_extension.rb
module ActiveRecordDateExtension
  extend ActiveSupport::Concern

  included do |base|
    base.columns_hash.each do |column_name,column|
      if ["datetime","date"].include? column.type
        base.define_method("#{column_name}_after") do |date|
          where("#{column_name} > ?", date)
        end
        base.define_method("#{column_name}_before") do |date|
          where("#{column_name} < ?", date)
        end
      end
    end
  end
end
Rails.application.eager_load!

ActiveRecord::Base.descendants.each do |model|
  model.send(:include, ActiveRecordDateExtension)
end

我做错了什么?

【问题讨论】:

  • 您介意发布堆栈跟踪吗?以及ActiveRecordDateExtension.instance_methods的结果?
  • @JeremyRodi rails 控制台运行正常。当我尝试调用时出现undefined method 错误,例如,User.created_at_before(DateTime.now) NoMethodError: undefined method 'created_at_before' for #<0x007fb500970b00>&gt;&gt;

标签: ruby-on-rails ruby ruby-on-rails-4 activerecord metaprogramming


【解决方案1】:

使用 Rails 4.1.9 和 Ruby 2.2.1,我注意到上面的代码存在一些问题。

  1. 您将column.type 与字符串进行比较,Rails 返回该属性的符号。
  2. base.define_method 正在尝试调用私有方法,您可以使用 send 解决此问题

这是调整后的代码

module ActiveRecordDateExtension
  extend ActiveSupport::Concern

  included do |base|
    base.columns_hash.each do |column_name,column|      
      if [:datetime, :date].include? column.type              
        base.class.send(:define_method, "#{column_name}_after") do |date|
          where("#{column_name} > ?", date)
        end
        base.class.send(:define_method, "#{column_name}_before") do |date|
          where("#{column_name} < ?", date)
        end
      end
    end
  end
end

【讨论】:

  • 为什么需要base.class而不是base?使用base.class 存在问题:现在每个类都有方法。有没有办法避免这种情况,只需为 ActiveRecord::Base 后代定义这些方法?
  • 我相信你想要User.created_at_after,对吗?这就是为什么base.class 必须是您定义该方法的位置。如果你只是在base上定义它,它就变成了一个实例方法而不是一个类方法。
【解决方案2】:

感谢之前的回答,我意识到了部分问题。以下是我在研究后得出的所有问题和解决方案:

  1. column.type 是一个符号,我将它与字符串进行比较。
  2. base.define_method 是私有方法
  3. 我必须在singleton_class 中定义方法,而不是在base 类或class 中。
  4. Rails.application.eager_load! 即使在不需要时也会导致急切加载。这不会影响功能,但首先急切加载不应该是这个“扩展”的责任,其次它依赖于 Rails,使得“扩展”只与 Rails 兼容。

考虑到这些问题,我决定使用 ruby​​ 的 method_missing 功能来实现它,并编写了这个 gem (https://github.com/simon0191/date_supercharger)。这是这个问题的相关部分:

module DateSupercharger
  extend ActiveSupport::Concern

  included do
    def self.method_missing(method_sym, *arguments, &block)
      return super unless descends_from_active_record? 
      matcher = Matcher.new(self,method_sym)
      # Inside matcher
      # method_sym.to_s =~ /^(.+)_(before|after)$/

      if matcher.match?
        method_definer = MethodDefiner.new(self) # self will be klass inside Matcher
        method_definer.define(attribute: matcher.attribute, suffix: matcher.suffix)
        # Inside MethodDefiner
        # new_method = "#{attribute}_#{suffix}"
        # operators = { after: ">", before: "<" }
        # klass.singleton_class.class_eval do
        #   define_method(new_method) do |date|
        #     where("#{attribute} #{operators[suffix]} ?", date)
        #   end
        # end
        send(method_sym, *arguments)
      else
        super
      end
    end

    def self.respond_to?(method_sym, include_private = false)
      return super unless descends_from_active_record?
      if Matcher.new(self,method_sym).match?
        true
      else
        super
      end
    end
  end
end
ActiveRecord::Base.send :include, DateSupercharger

【讨论】:

    猜你喜欢
    • 2011-01-20
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    • 2010-10-30
    • 1970-01-01
    • 2012-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多