8

Ich versuche ActiveRecord einige benutzerdefinierte Methoden hinzuzufügen. Ich möchte ein *_after und *_before Bereiche für jedes date Feld eines Modells hinzufügen, damit ich etwas tun kann:Erweitern ActiveRecord :: Base

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

Ich habe die Lösung gefolgt hier Rails extending ActiveRecord::Base erklärt, aber wenn ich führen die Schienen Konsole und versuchen, Rufen Sie die Methoden Ich bekomme einen undefined method Fehler.

Hier ist mein Code:

# 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 

Was mache ich falsch?

+0

Haben Sie etwas dagegen, den Stack-Trace zu buchen? Sowie das Ergebnis von 'ActiveRecordDateExtension.instance_methods'? –

+0

@ JeremyRodi Die Schienenkonsole läuft normal. Ich erhalte einen 'undefined method' Fehler, wenn ich zu nennen versuchen, zum Beispiel' User.created_at_before (DateTime.now) ' NoMethodError: nicht definierte Methode 'created_at_before' für # .... Hier ist die Ausgabe für 'instance_methods' und' methods'. Bedenken Sie jedoch, dass ich Klassenmethoden definieren möchte. 'ActiveRecordDateExtension.instance_methods => []' 'ActiveRecordDateExtension.methods (false) => []' –

Antwort

1

Dank der vorherigen Antwort, die ich Teil der Probleme realisiert. Hier sind alle Probleme und die Lösung, die ich nach einigen Recherchen kam:

  1. column.type ein Symbol ist, und ich war es mit einem String zu vergleichen.
  2. base.define_method ist eine private Methode
  3. ich die Methoden in den singleton_class, nicht in der base Klasse noch die class zu definieren hatte.
  4. Rails.application.eager_load! verursacht eifrige Last, auch wenn es nicht erforderlich ist. Dies hatte keinen Einfluss auf die Funktionalität, aber an erster Stelle sollte die Eager-Ladung nicht für diese "Erweiterung" verantwortlich sein und an zweiter Stelle kommt es auf Rails an, die "Extension" nur mit Rails kompatibel macht.

Berücksichtigung dieser Probleme, die ich beschlossen, es zu implementieren mit der method_missing Funktionalität von Rubin und ich schrieb dieses Juwel (https://github.com/simon0191/date_supercharger). Hier ist der relevante Teil für diese Frage:

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

Mit Rails 4.1.9 und Ruby 2.2.1 bemerkte ich ein paar Probleme mit dem obigen Code.

  1. Sie vergleichen column.type mit Zeichenfolgen, und Rails gibt Symbole für dieses Attribut zurück.
  2. base.define_method versucht, eine private Methode zu nennen, können Sie umgehen, dass mit send

diesem Zusammenhang die gezwickt Code

ist
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

Warum wird 'base.class' statt nur' base' benötigt? Es gibt ein Problem mit 'base.class': Jede Klasse hat jetzt die Methoden.Gibt es eine Möglichkeit, das zu vermeiden, und diese Methoden nur für ActiveRecord :: Base-Nachkommen zu definieren? –

+0

Ich glaube, du wolltest 'User.created_at_after', richtig? Deshalb muss die 'base.class' dort sein, wo Sie die Methode definieren. Wenn Sie es einfach auf "base" definieren, wird es eine Instanzmethode anstelle einer Klassenmethode. – yez

Verwandte Themen