2012-12-28 5 views
12

Sto usando Rails 3.2.0Perché i risultati dell'associazione modello Rails non sono in natura ActiveRecord :: Relations?

Diciamo che ho:

class Comment < ActiveRecord::Base 
    has_many :articles 
end 

c1 = Comment.last 

poi

c1.articles.class 
# => Array 

c1.articles.where('id NOT IN (999999)').class 
# => ActiveRecord::Relation  

Perché è il risultato di un'associazione non un tipo di ActiveRecord::Relation ?

E 'chiaramente/era ad un certo punto:

c1.articles.to_orig 
# undefined method `to_orig' for #<ActiveRecord::Relation:0x007fd820cc80a8> 

c1.articles.class 
# => Array 

alcune valutazioni agiscono su un oggetto ActiveRecord :: Relation, ma ispezionando la classe dà un tipo diverso.


In particolare, questo si rompe creazione di query pigri-caricato quando si utilizza merge di concat query multiple.

+0

Quale versione di Rails? –

+0

@AndrewMarshall 3.2.0 –

+1

Se ricordo bene il metodo di classe ti sta mentendo - è delegare alla destinazione, che è un array –

risposta

15

Si tratta di un ActiveRecord::Relation, ma Rails è intenzionalmente mentendo a voi. Si può vedere questo già nelle chiamate di metodo, e continuano a vederlo chiamando ancestors, che comprende un gran numero di classi ActiveRecord:

c1.articles.ancestors.select { |c| c.to_s =~ /ActiveRecord/ }.size #=> 35 

che dimostra che è molto non un Array.

Questo accade perché quello che stai ricevendo indietro quando si chiama c1.articles è un ActiveRecord::Associations::CollectionProxy *, che undefines class (insieme a molti altri metodi). Ciò significa che class viene delegato tramite its method_missing, che è sends it to target. Come possiamo vedere, la classe di target qui è, infatti, Array:

c1.articles.target.class #=> Array 

Questo è dove c1.articles.class viene da. Tuttavia, è è un ActiveRecord::Relation.

* possiamo verificare che è davvero un ActiveRecord::Associations::CollectionProxy chiamando il metodo class originale di Ruby on l'oggetto in questione: Object.instance_method(:class).bind(c1.articles).call. Questo è un bel trucco per verificare che l'oggetto non stia cercando di fingere di essere di una classe diversa.

+0

Trucchetto Neat, non ci avevo pensato prima di –

+0

@AndrewMarshall - Mi piacerebbe molto prendi le tue opinioni su questa situazione: http://stackoverflow.com/questions/16927437/why-does-my-activerecord-scope-with-merge-return-an-array –

+3

Trovare questa domanda/risposta ha ripristinato la mia sanità mentale dopo aver combattuto con una collezioneProxy negli abiti di Array tutta la settimana. :) – woodardj

2

Perché quando si definisce l'associazione, si colloca nel modello:

def #{name}(*args) 
    association(:#{name}).reader(*args) 
end 

.reader() ritorna AssociationProxy, che rimuove il metodo .class e delegati metodi sconosciuti @Target attraverso . method_missing.