2012-06-05 15 views
5

Sto usando Ruby on Rails 3.2.2 e vorrei recuperare/scope gli oggetti associati "specificando"/"filtrando" un valore di attributo su quegli oggetti associati. Cioè, in questo momento sto usando il seguente codice:Come utilizzare i metodi di scope DRY usati in due classi diverse?

class Article < ActiveRecord::Base 
    def self.search_by_title(search) 
    where('articles.title LIKE ?', "%#{search}%") 
    end 
end 

class ArticleAssociation < ActiveRecord::Base 
    def self.search_by_article_title(search) 
    joins(:article).where('articles.title LIKE ?', "%#{search}%") 
    end 
end 

Nel codice sopra la clausola where('articles.title LIKE ?', "%#{search}%") viene ripetuto due volte e così ho pensato che può essere migliorata con il principio DRY: è possibile utilizzare il metodoArticle.search_by_titledirettamente nel metodoArticleAssociation.search_by_article_title?


casi d'uso tipici sono:

  • ArticleAssociation.search_by_article_title("Sample string")
  • Article.search_by_title("Sample string")
+0

Ho affrontato questa stessa situazione in un progetto (4 modelli correlati) e ho creato un modulo per contenere i metodi di ricerca comuni. Ut non è esattamente quello che chiedi ma è una soluzione tipica. – tokland

+0

Il codice condiviso tra le classi di solito finisce in un modulo – apneadiving

+0

in questo modo, i selettori di squeel possono essere usati per questo, ma l'uso di squeel invece di AR è un grande cambiamento. Https://github.com/ernie/squeel#sifters – tokland

risposta

2

se non si cambia la struttura del codice completamente, no.

Si potrebbe fare un po 'di hacking con lambda, ma quello sarebbe più codice allora il codice che stai ASCIUGANDO. Esiste un buon refactoring e un cattivo refactoring. A meno che un pezzo di molto codice complesso o lungo sia utilizzato in 2 o più luoghi, allora puoi preoccuparti del refactoring. Le convenzioni del codice sono importanti, ma per il metodo minuscolo di una sola parola, chiamare cose del genere è uno spreco e probabilmente renderà il tuo codice più criptico.

Però, so che è fastidioso quando le persone non rispondono alla tua domanda, ecco:

class Article < ActiveRecord::Base 
    SEARCH_BY_TITLE=lambda {|obj, search| obj.where('articles.title LIKE ?', "%#{search}%")} 
    def self.search_by_title(search) 
    SEARCH_BY_TITLE.call(self, search) 
    end 
end 

class ArticleAssociation < ActiveRecord::Base 
    def self.search_by_article_title(search) 
    Article::SEARCH_BY_TITLE.call(joins(:article),search) 
    end 
end 

Questo fa solo un lambda come una costante che esegue la where chiamata su un oggetto specificato. Entrambi i metodi avvolgono quel lambda.

Nota: Sebbene ciò possa essere considerato più elegante, diminuirà notevolmente le prestazioni, poiché lambda, chiusure e chiamate extra sono costose in un linguaggio dinamico come Ruby. Ma non penso che sia un problema per te.

+0

@ Linux_iOS.rb.cpp.c.lisp.n - Grazie per la risposta. Tuttavia, ho * un sacco di posti in cui ho bisogno di usare il codice * 'dove ('articles.title LIKE?',"% # {Search}% ")' e, anche se hai detto "per tiny one-method -le cose del genere è uno spreco e probabilmente renderà il tuo codice più criptico ", * è male ripeterlo ovunque nelle mie classi? * – Backo

+1

@Backo: Se usi molto il codice, il lambda è probabilmente un migliore idea. Se lo usi solo due volte, come nell'esempio, stai meglio ripetendo. – Linuxios

1

Secondo la richiesta OP, vi posto il codice che ho scritto per una ricerca 3 moduli utilizzando un modulo:

module Listable 
    extend ActiveSupport::Concern 

    module ClassMethods 
    # Search a listable module search in properties (or related) tables 
    def search_from_properties(string) 
     return where({}) if string.blank? 
     associations = self.reflect_on_all_associations.map(&:name) & 
     [:property, :properties, :supplier, :suppliers, :address] 
     s = "%#{string}%" 
     associations.inject(self, :includes).where(
     ((Address[:base] =~ s) | (Address[:city] =~ s)) | 
     ((Property[:owner] =~ s) | (Property[:cif] =~ s)) | 
     ((Supplier[:cups] =~ s) | (Supplier[:contract] =~ s)) 
    ) 
    end 
    end 
end 

Ora basta includere questo modulo nelle classi correlati:

class Property < ActiveRecord::Base 
    include Listable 
end 

Nota: tutti i modelli hanno associazioni definite per raggiungere gli altri (ecco perché il joins funziona). Inoltre, utilizza this wrapper over AR.