8

Próbuję dodać niestandardowe metody do ActiveRecord. Chcę dodać *_after i *_before zakresów dla każdej date dziedzinie modelu, więc mogę zrobić coś takiego:Rozszerzenie ActiveRecord :: Base

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

Mam następuje rozwiązanie wyjaśnione tutaj Rails extending ActiveRecord::Base ale kiedy uruchomić konsolę szyn i spróbować zadzwoń do metod otrzymuję błąd undefined method.

Oto mój kod:

# 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 

Co robię źle?

+0

Czy pamiętasz opublikowanie śladu stosu? Jak również wynik "ActiveRecordDateExtension.instance_methods"? –

+0

@JeremyRodi Konsola szyn działa normalnie. Pojawia się niezdefiniowany błąd method' 'gdy próbuję się połączyć, na przykład' User.created_at_before (DateTime.now) ' NoMethodError: Metoda niezdefiniowana 'created_at_before' dla # .... Tutaj jest wynikiem dla 'instancji_methods' i' metod'. Ale weź pod uwagę, że próbuję zdefiniować metody klasy. 'ActiveRecordDateExtension.instance_methods => []' 'ActiveRecordDateExtension.methods (false) => []' –

Odpowiedz

1

Dzięki poprzedniej odpowiedzi zdałem sobie sprawę, część problemów. Oto wszystkie problemy i rozwiązania, że ​​przyszedłem po niektórych badań:

  1. column.type jest symbolem, a ja porównując ją z String.
  2. base.define_method to prywatna metoda
  3. musiałem zdefiniować metody w singleton_class w klasie base ani class nie.
  4. Rails.application.eager_load! spowoduje chęć załadowania, nawet jeśli nie jest to wymagane. Nie wpłynęło to na funkcjonalność, ale na pierwszym miejscu nie powinno być odpowiedzialne za to "rozszerzenie", a na drugim miejscu w Railsach, czyniąc "rozszerzenie" kompatybilnym z Railsami.

Uwzględniając te problemy I zdecydowała się na wdrożenie go za pomocą funkcji method_missing Ruby i napisałem ten klejnot (https://github.com/simon0191/date_supercharger). Oto odpowiednia część tego pytania:

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

Korzystanie z Rails 4.1.9 i Ruby 2.2.1, zauważyłem kilka problemów z powyższym kodem.

  1. Porównułeś column.type z ciągami, a Railsy zwracają symbole dla tego atrybutu.
  2. base.define_method próbuje wywołać prywatną metodę, można obejść, że z send

Jest to kod manipulowane

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

Dlaczego potrzebna jest 'base.class' zamiast nazwy' base'? Występuje problem przy użyciu 'base.class': Każda klasa ma teraz metody.Czy istnieje sposób, aby tego uniknąć i po prostu zdefiniować te metody dla potomków ActiveRecord :: Base? –

+0

Wierzę, że chciałeś 'User.created_at_after', prawda? Dlatego właśnie "base.class" musi być tam, gdzie definiujesz metodę. Jeśli po prostu zdefiniujesz to na 'base', staje się metodą instancji zamiast metody klasy. – yez

Powiązane problemy