8

Я пытаюсь добавить некоторые специальные методы в ActiveRecord. Я хочу добавить *_after и *_before областей для каждого date поля модели, так что я могу сделать что-то вроде этого:Расширение ActiveRecord :: Base

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

Я следовал решение объясняется здесь Rails extending ActiveRecord::Base, но когда я исполню консоль рельсы и попытаться вызовите методы, я получаю ошибку 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 

Что я делаю неправильно?

+0

Не могли бы вы отследить трассировку стека? Также как результат «ActiveRecordDateExtension.instance_methods»? –

+0

@JeremyRodi Консоль рельсов работает нормально. Я получаю 'неопределенные ошибку method', когда я пытаюсь вызвать, например,' User.created_at_before (DateTime.now) ' NoMethodError: неопределенный метод 'created_at_before' для # <Класс: 0x007fb500970b00> .... Здесь это вывод для 'instance_methods' и' методов'. Но учтите, что я пытаюсь определить методы класса. 'ActiveRecordDateExtension.instance_methods => []' 'ActiveRecordDateExtension.methods (false) => []' –

ответ

1

Благодаря предыдущему ответу я понял, что часть проблем. Вот все проблемы и решение, которое я пришел после некоторых исследований:

  1. column.type является символом, и я сравнивал его с String.
  2. base.define_method является частным методом
  3. я должен был определить методы в singleton_class, а не в base классе, ни class.
  4. Rails.application.eager_load! вызовет высокую нагрузку, даже если это не требуется. Это не повлияло на функциональность, но на первом месте нетерпимая нагрузка не должна нести ответственность за это «расширение», а во-вторых, это зависит от Rails, что делает совместимым только «расширение» Rails.

Принимая во внимание эти проблемы, я решил реализовать его, используя method_missing функциональность рубина, и я написал этот драгоценный камень (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 
3

Используя 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 
+0

Зачем нужен 'base.class' вместо простого' base'? Проблема с использованием 'base.class': у каждого класса теперь есть методы.Есть ли способ избежать этого и просто определить эти методы для ActiveRecord :: Base потомков? –

+0

Я считаю, что вы хотели 'User.created_at_after', правильно? Вот почему 'base.class' должен быть там, где вы определяете метод. Если вы просто определяете его на 'base', он становится методом экземпляра вместо метода класса. – yez

Смежные вопросы