扩展ActiveRecord :: Base

时间:2015-08-20 18:01:01

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

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

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

我做错了什么?

2 个答案:

答案 0 :(得分:4)

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

  1. 您正在将DriverManager.getConnection("jdbc:derby:;shutdown=true") 与字符串进行比较,Rails会返回该属性的符号。
  2. column.type正在尝试调用私有方法,您可以使用base.define_method
  3. 来解决这个问题。

    这是经过调整的代码

    send

答案 1 :(得分:2)

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

  1. column.type是一个符号,我将它与一个字符串进行比较。
  2. base.define_method是私有方法
  3. 我必须在singleton_class中定义方法,而不是base类和class
  4. 即使不需要,
  5. Rails.application.eager_load!也会引起急切的负担。这并没有影响功能,但首先,急切的负载不应该是这个&#34;扩展&#34;在第二位,它取决于Rails,使&#34;扩展&#34;只有Rails兼容。
  6. 考虑到这些问题,我决定使用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