5

Non mi aspetto un modello con NULL come chiave esterna per appartenere a qualcosa!Il record attivo has_many genera sql con chiave esterna IS NULL

Ho la seguente app per rotaie, modellazione di formiche e formicai (ispirata a Jozef).

$ rails -v 
Rails 3.2.8 
$ rails new ant_hill 
$ cd ant_hill 

Creare i modelli di formica e di formica. Una formica può appartenere a una formica e una formica può avere molte formiche.

$ rails generate model AntHill name:string 
$ rails generate model Ant name:string ant_hill_id:integer 
$ vim app/models/ant.rb 
$ cat app/models/ant.rb 
class Ant < ActiveRecord::Base 
    belongs_to :ant_hill 
end 
$ vim app/models/ant_hill.rb 
$ cat app/models/ant_hill.rb 
class AntHill < ActiveRecord::Base 
    has_many :ants 
end 
$ rake db:migrate 
== CreateAntHills: migrating ================================================= 
-- create_table(:ant_hills) 
    -> 0.0013s 
== CreateAntHills: migrated (0.0016s) ======================================== 

== CreateAnts: migrating ===================================================== 
-- create_table(:ants) 
    -> 0.0035s 
== CreateAnts: migrated (0.0037s) ============================================ 

Eseguire il seguente codice in una console.

$ rails c 
Loading development environment (Rails 3.2.8) 

Creare un paio di formiche, persistente, che non appartengono a nessuna formicaio.

1.9.2-p290 :001 > Ant.create! name: "August" 
=> #<Ant id: 1, name: "August", ant_hill_id: nil, created_at: "2012-09-27 12:01:06", updated_at: "2012-09-27 12:01:06"> 
1.9.2-p290 :002 > Ant.create! name: "Bertil" 
=> #<Ant id: 2, name: "Bertil", ant_hill_id: nil, created_at: "2012-09-27 12:01:13", updated_at: "2012-09-27 12:01:13"> 

Ora istanziare un formicaio, ma non salvarlo ancora.

1.9.2-p290 :003 > ant_hill = AntHill.new name: "Storkullen" 
=> #<AntHill id: nil, name: "Storkullen", created_at: nil, updated_at: nil> 

Mi aspetto che questo formicaio non abbia formiche e non lo fa.

Mi aspetto ancora che il formicaio non abbia formiche ma ora ne ha due.

1.9.2-p290 :005 > ant_hill.ants.count 
    (0.1ms) SELECT COUNT(*) FROM "ants" WHERE "ants"."ant_hill_id" IS NULL 
=> 2 

Stesso qui, non dovrebbe mai generare una query contenente "IS NULL" quando si tratta di chiavi esterne. Voglio dire "appartiene a NULL" non può appartenere a nulla, giusto?

1.9.2-p290 :006 > ant_hill.ants.all 
    Ant Load (0.4ms) SELECT "ants".* FROM "ants" WHERE "ants"."ant_hill_id" IS NULL 
=> [#<Ant id: 1, name: "August", ant_hill_id: nil, created_at: "2012-09-27 12:01:06", updated_at: "2012-09-27 12:01:06">, #<Ant id: 2, name: "Bertil", ant_hill_id: nil, created_at: "2012-09-27 12:01:13", updated_at: "2012-09-27 12:01:13">] 

Dopo la persistenza, si comporta come previsto.

1.9.2-p290 :007 > ant_hill.save! 
=> true 
1.9.2-p290 :008 > ant_hill.ants.count 
    (0.4ms) SELECT COUNT(*) FROM "ants" WHERE "ants"."ant_hill_id" = 1 
=> 0 
1.9.2-p290 :009 > ant_hill.ants.all 
    Ant Load (0.4ms) SELECT "ants".* FROM "ants" WHERE "ants"."ant_hill_id" = 1 
=> [] 

Qualche idea? È questo il comportamento previsto?

risposta

1

Anche se sembra controintuitivo, penso che questo comportamento abbia senso visto i vostri esempi. Prendi ad esempio ant_hill.ants.count. Conteggio è un metodo di query ActiveRecord che colpisce il database e in sostanza si chiede a ActiveRecord di fornire tutte le formiche che non appartengono a un formicaio. Rails ti sta semplicemente permettendo di fare qualcosa che non dovresti essere in grado di fare e di non lamentarti. Dovrebbe invece sollevare un'eccezione? Possibilmente.

Se vuoi veramente sapere quante formiche appartengono a questo oggetto ant_hill, dovresti usare la dimensione. Interroga l'oggetto quando non è persistente o quando l'associazione è già stata caricata e interroga il database in caso contrario.

ant_hill.ants.size 

Un modo per aggirare questa stranezza è rendere ant_hill_id un campo obbligatorio convalidando la sua presenza.

TL; DR Evitare di utilizzare l'interfaccia di query di ActiveRecord se l'oggetto genitore non è persistente nel database.

+0

Non riesco a rendere ant_hill_id richiesto in questo caso perché la mia applicazione consente di formiche che non appartengono a nessuna formica. – ludde

+0

utilizzando '.size' dovrebbe funzionare nel tuo caso – PinnyM

+1

Per essere chiari, sto già lavorando a questo problema nella mia app e riconosco che ci sono molti modi per ovviare a questo problema. Non penso che sia il comportamento previsto.Quando qualcosa ha una chiave straniera NULL, ciò significa che non appartiene a nulla. Immagino tu possa sostenere che una collina di formica non salvata non è nulla dal punto di vista del database, ma non proprio. – ludde