2013-08-09 1 views
34

Sto lavorando con has_many abbastanza semplice: situazione in cui posso fare in modo che i parametri class_name/foreign_key funzionino in una direzione ma non nell'altra. Forse puoi aiutarmi. (p.s.so sto usando Rails 4 se questo fa un diff):has_many: through con class_name e foreign_key

inglese: un utente gestisce molti elenchi tramite ListingManager, e un elenco è gestito da molti utenti tramite ListingManager. direttore Listing ha alcuni campi di dati, non germane a questa domanda, così li ho modificato nel codice qui sotto

Qui è la parte semplice che funziona:

class User < ActiveRecord::Base 
    has_many :listing_managers 
    has_many :listings, through: :listing_managers 
end 

class Listing < ActiveRecord::Base 
    has_many :listing_managers 
    has_many :managers, through: :listing_managers, class_name: "User", foreign_key: "manager_id" 
end 

class ListingManager < ActiveRecord::Base 
    belongs_to :listing 
    belongs_to :manager, class_name:"User" 

    attr_accessible :listing_id, :manager_id 
end 

come si può intuire da sopra gli sguardi tabella ListingManager come:

create_table "listing_managers", force: true do |t| 
    t.integer "listing_id" 
    t.integer "manager_id" 
end 

quindi l'unica non semplice ecco che utilizza ListingManager manager_id anziché user_id

Ad ogni modo, quanto sopra funziona. Posso chiamare lo user.listings per ottenere gli elenchi associati all'utente e posso chiamare lo listing.managers per ottenere i gestori associati all'elenco.

Tuttavia (e qui è la domanda), ho deciso che non era terribilmente significativo da dire user.listings dal momento che un utente può anche "proprio", piuttosto che "gestire" in lista, quello che volevo era user.managed_listings così ho ottimizzato user.rb cambiare has_many: annunci, attraverso:: listing_managers a has_many: managed_listings, attraverso:: listing_managers, nome_classe: "Annuncio", foreign_key: "listing_id"

Questa è un'analogia esatta al codice in listing.rb sopra, così ho pensato che questo dovrebbe funzionare subito. Invece il mio test RSpec di questa barfs dicendo essere ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :managed_listing or :managed_listings in model ListingManager. Try 'has_many :managed_listings, :through => :listing_managers, :source => <name>'. Is it one of :listing or :manager?

il test:

it "manages many managed_listings" do 
    user = FactoryGirl.build(:user) 
    l1 = FactoryGirl.build(:listing) 
    l2 = FactoryGirl.build(:listing)  
    user.managed_listings << l1 
    user.managed_listings << l2 
    expect(@user.managed_listings.size).to eq 2 
end 

Ora, io sono convinto io non so nulla. Sì, immagino di poter fare un alias, ma mi preoccupo che la stessa tecnica utilizzata in listing.rb non funzioni nel user.rb. Puoi aiutarmi a spiegare?

AGGIORNAMENTO: Ho aggiornato il codice per riflettere i suggerimenti di @gregates, ma sto ancora incontrando un problema: ho scritto un test aggiuntivo che fallisce (e confermato da "hand" -tesing nella console di Rails). Quando si scrive un test come questo:

it "manages many managed_listings" do 
    l1 = FactoryGirl.create(:listing) 
    @user = User.last 
    ListingManager.destroy_all 
    @before_count = ListingManager.count 
    expect( @before_count).to eq 0 
    lm = FactoryGirl.create(:listing_manager, manager_id: @user.id, listing_id: l1.id) 


    expect(@user.managed_listings.count).to eq 1 
end 

Quanto sopra non riesce. Rails genera l'errore PG::UndefinedColumn: ERROR: column listing_managers.user_id does not exist (Dovrebbe cercare 'listing_managers.manager_id'). Quindi penso che ci sia ancora un errore sul lato utente dell'associazione. In user.rb 's has_many :managed_listings, through: :listing_managers, source: :listing, come fa l'Utente a sapere di usare manager_id per ottenere il suo elenco (s)?

risposta

41

Il problema qui è che in

has_many :managers, through: :listing_managers 

ActiveRecord può dedurre che il nome dell'associazione sul modello join (: listing_managers) perché ha lo stesso nome come ilhas_many :through associazione si sta definendo .Cioè, sia gli elenchi che i listing_mangers hanno molti gestori .

Ma questo non è il caso nella tua altra associazione. Lì, un listing_manager has_many :listings, ma un utente has_many :managed_listings. Quindi ActiveRecord non è in grado di dedurre il nome dell'associazione su ListingManager che dovrebbe utilizzare.

Questo è l'opzione :source (vedere http://guides.rubyonrails.org/association_basics.html#has-many-association-reference). Quindi la dichiarazione corretta sarebbe:

has_many :managed_listings, through: :listing_managers, source: :listing

(ps in realtà non sono necessari i :foreign_key o :class_name opzioni dall'altro has_many :through devi usare quelli per definire diretti associazioni, e quindi tutto ciò che. è necessario puntare all'associazione corretta sul modello :through.)

+0

interessante ... e ho provato 'source:" Listing "' ma non ha funzionato. Quindi il simbolo è l'unico modo per impostarlo. – user2669055

+0

Segnalo come risposta (e grazie per il veloce!) Anche se sono ancora un po 'confuso sul problema della fonte: - Ho trascorso molto tempo con la Guida che hai citato, ma la documentazione per la fonte è molto MOLTO leggera. – user2669055

+0

Il punto chiave credo sia in p.s. Ho aggiunto. Per un'associazione * attraverso * un modello di join, devi solo dirgli il nome dell'associazione a cui stai partecipando (se non può essere dedotto). Il resto delle opzioni non ha importanza. Quelli sono per le associazioni * dirette *. E questo è tutto ciò che fa 'source', è specificare l'associazione da utilizzare sul modello di join per un'associazione 'through'. – gregates

4

So che questa è una vecchia domanda, ma ho passato un po 'di tempo a correre negli stessi errori e alla fine ho capito. Questo è quello che ho fatto:

class User < ActiveRecord::Base 
    has_many :listing_managers 
    has_many :managed_listings, through: :listing_managers, source: :listing 
end 

class Listing < ActiveRecord::Base 
    has_many :listing_managers 
    has_many :managers, through: :listing_managers, source: :user 
end 

class ListingManager < ActiveRecord::Base 
    belongs_to :listing 
    belongs_to :user 
end 

Questo è ciò che la ListingManager unirsi tavolo assomiglia:

create_table :listing_managers do |t| 
    t.integer :listing_id 
    t.integer :user_id 
end 

Spero che questo aiuti i ricercatori futuri.