2012-11-16 12 views
6

Ho un rapporto abbastanza comune habtm:Convert ActiveRecord interrogazione HABTM di Arel

Photo has_and_belongs_to_many :tags 
Tag has_and_belongs_to_many :photos 

Nel mio modello Foto Ho un metodo "con tag" che uso per trovare una foto che viene etichettato con un dato set di tag_ids. Questa query deve corrispondere solo alle foto che hanno tutti i tag specificati, ma ignorando la presenza o la mancanza di altri tag. Ecco il mio metodo:

Questo funziona come previsto.

Ora, al fine di integrare questo meglio con alcune altre biblioteche che sto usando, ho bisogno di ri-scrivere questo in Arel. (rendilo un nodo Arel ?, non sai come lo chiami normalmente).

ho avuto modo di sperimentare con questo, ma ad essere onesti non ho mai provato ad usare Arel prima, quindi sono un po 'perso. Ho avuto modo di sperimentare nella console e provato:

t = Photo.arel_table 
q = t.join(:tags).on(t[:tags_id].in(array)) 
Photo.where(q) 

Ma, (1) non credo q è la query giusta, in primo luogo, e (2) crea un Arel::SelectManager, che quando passò a una chiamata dove si alza Cannot visit Arel::SelectManager. Quindi, ovviamente, sto sbagliando.

Aggiornamento: Solo per essere più specifico qui, sto cercando di restituire un nodo Arel, perché sto lavorando con una gemma (ransack) che si aspetta che tu passi i nodi Arel per i metodi di ricerca. Ransack collegherà questo nodo Arel ad altri nel generare query di ricerca complesse.

modo un guru Arel mostrarmi come farlo correttamente?

+0

Questa non è una risposta, ma prova Squeel !!Le impostazioni predefinite di AR sono abbastanza zoppe e mentre AREL è molto più potente, è tristemente documentato e non molto amichevole per gli sviluppatori. La gemma di squeel è abbastanza buona per fare cose come questa! –

risposta

9

È difficile trovare una buona documentazione Arel, ma @Philip C ha messo insieme qualche utile slides, a cui si fa riferimento nella sua risposta a questo question.

Il seguente dovrebbe essere quello che stai cercando:

photos = Arel::Table.new(:photos) 
tags = Arel::Table.new(:tags) 
photo_tags = Arel::Table.new(:photo_tags) 

q = photos[:id].in(
    photos.project(photos[:id]) 
    .join(photo_tags).on(photos[:id].eql(photo_tags[:photo_id])) 
    .join(tags).on(photo_tags[:tag_id].eql(tags[:id])) 
    .where(tags[:id].in(array)) 
    .group(photos.columns) 
    .having(tags[:id].count.eq(array.length)) 
) 

Ciò si traduce in un Arel::Nodes::In istanza che si dovrebbe essere in grado di utilizzare direttamente come in Photo.where(q).


UPDATE:

Dopo aver guardato attraverso la documentazione e alcuni la fonte per ransack, non sembra essere un modo naturale per definire un predicato personalizzato che coinvolge una sottoquery, che è necessaria nella vostra caso (perché i predicati devono rientrare in una clausola where). Un modo per ovviare a questo potrebbe essere quella di sfruttare la :formatter che il predicato utilizza come segue:

Ransack.configure do |config| 
    config.add_predicate 'with_tag_ids', 
        :arel_predicate => 'in', 
        :formatter => proc {|tag_ids| tags_subquery(tag_ids) }, 
        :validator => proc {|v| v.present?}, 
        :compounds => true 
end 

È possibile definire tags_subquery(tag_ids) come un metodo che genera il nodo AREL come sopra ma sostituisce array con tag_ids e chiama .to_sql su di esso prima di restituirlo (il programma di formattazione deve restituire una stringa, non un nodo).

Non ho provato questo, quindi sarò elettrizzato se funziona!

+0

Impressionante: questa query funziona, ma come la si restituisce come nodo Arel in modo che possa essere passata direttamente in una clausola 'where'? Il motivo è che sto cercando di scrivere un metodo di query che può essere utilizzato da una libreria che si basa su Arel, e in particolare il metodo deve restituire un nodo Arel. Vale a dire, incontrerebbe quello che sto cercando se 'Photo.where (q)' ha restituito il risultato corretto. – Andrew

+0

La query in 'q' è abbastanza complessa. Se vuoi forzarlo in una clausola 'where', ciò comporterà una sottoquery. Se ciò va bene per te, un modo per ottenere ciò è di cambiare 'q' nella forma' "foto". "Id" IN (subquery restituisce id di foto) '. Vedi modifica sopra per l'equivalente Arel. – cdesrosiers

+0

Ah! Sembra promettente, testerà e vedrà se funziona. – Andrew