Le foreign keys


#1

Per gestire per foreign keys so che e’ possibile fare una cosa del
genere
con le migrations.
Tabelle: settori, categorie.
script/generate scaffold sector name:string;
script/generate scaffold category codice:string descrizione:text
esempi:text
sector:references.
Come database uso postgres e lavoro su linux debian sid.
Quando vado a vedere le proprieta’ della colonna sector_id, che sarebbe
la
foreign_key, con lo strumento pgadmin, mi aspetto di vedere un “si” alla
voce “chiave esterna”.
Invece c’e’ un bel “no” secco.
Eppure http://dev.rubyonrails.org/changeset/7973 sembrerebbe che quel
“references” serva proprio a creare le chiavi esterne.
Non e’ cosi’?


#2

Io ho risolto con un plugin molto carino (foreign_key_migrations) però
sono nuovo di questo mondo
magari c’è un metodo migliore, la parola agli esperti :slight_smile:

ciao ciao.


#3

2008/12/16 Salvatore removed_email_address@domain.invalid

Io ho risolto con un plugin molto carino (foreign_key_migrations) però
sono nuovo di questo mondo
magari c’è un metodo migliore, la parola agli esperti :slight_smile:

allora esperti fatevi sentire dai…


#4

A quanto ho capito, ma potrei sbagliarmi, il metodo references crea
nella tabella i campi con i nomi giusti per poi essere usati
dall’accoppiata belongs_to e has_one/many, però non crea la foreign key
nello schema del db.

Mauro wrote:

2008/12/16 Salvatore removed_email_address@domain.invalid

Io ho risolto con un plugin molto carino (foreign_key_migrations) per�
sono nuovo di questo mondo
magari c’� un metodo migliore, la parola agli esperti :slight_smile:

allora esperti fatevi sentire dai…

Non conoscevo quel plugin ma da quel che ho visto nella sua
documentazione pare risolvere il problema molto bene. Finora, proprio
con PostgreSQL, includevo questo modulo nelle migration però adesso
forse cambierò abitudini.


module MigrationHelpers
def foreign_key(from_table, from_column, to_table)
constraint_name = “fk_#{from_table}_#{from_column}”

execute %{alter table #{from_table}
          add constraint #{constraint_name}
          foreign key (#{from_column})
          references #{to_table}(id)}

end

def remove_foreign_key(from_table, from_column)
constraint_name = “fk_#{from_table}_#{from_column}”

execute %{alter table #{from_table}
          drop constraint #{constraint_name}}

end
end

Paolo


#5

Mauro wrote:

create_table :line_items do |t|
  t.integer :product_id,  :null => false, :options => "CONSTRAINT

fk_line_item_products REFERENCES products(id)"

Anche questa è una possibilità , ma il libro prosegue avvertendo che
“Rails migrations doesn’t provide a database-independent way to specify
these foreign key constraints, so we had to resort to adding native DDL
clauses”. Quel codice in particolare è per SQLlite3.

Quando ritorna sull’esempio un centinaio di pagine dopo infatti crea
anche lui un modulo MigrationHelper che definisce un metodo foreign_key.
Il codice è totalmente diverso da quello che ho presentato io ma l’idea
è la stessa.

L’ideale sarebbe avere un plugin con driver per i vari db, esattamente
come fa ActiveRecord. Anzi, l’ideale sarebbe che
ActiveRecord::ConnectionAdapters::TableDefinition finalmente supportasse
nativamente le foreign key. Cito Agile Web D. with Rails:

“Many Rails developers don’t bother specifying database-level
constraints such as foreign keys, relying instead on the application
code to make sure that everything knits together correctly. That’s
probably why Rails migrations don’t let you specify constraints.
However, when it comes to database integrity, many (including Dave and
Sam) think an ounce of extra checking can save pounds of latenight
production system debugging.”

Una cosa infatti è certa: prima o poi l’applicazione sbaglierà e lascerÃ
dati inconsistenti nel database. Sta al db difendersi tramite
l’impostazione degli opportuni constraint.

Paolo


#6

2008/12/17 Paolo M. removed_email_address@domain.invalid

Mauro wrote:

create_table :line_items do |t|
  t.integer :product_id,  :null => false, :options => "CONSTRAINT

fk_line_item_products REFERENCES products(id)"

Anche questa è una possibilità, ma il libro prosegue avvertendo che
“Rails migrations doesn’t provide a database-independent way to specify
these foreign key constraints, so we had to resort to adding native DDL
clauses”. Quel codice in particolare è per SQLlite3.

Esatto, infatti non funziona su postgres.
Usero’ allora lo stesso metodo ma col codice per postgres.


#7

2008/12/16 Paolo M. removed_email_address@domain.invalid

magari c’� un metodo migliore, la parola agli esperti :slight_smile:
module MigrationHelpers
constraint_name = “fk_#{from_table}_#{from_column}”

execute %{alter table #{from_table}
drop constraint #{constraint_name}}
end
end

Nel manuale Agile development with rails, nell’applicazione di esempio,
per
creare le foreign keys fa una cosa del genere:

class CreateLineItems < ActiveRecord::Migration
def self.up
create_table :line_items do |t|
t.integer :product_id, :null => false, :options => “CONSTRAINT
fk_line_item_products REFERENCES products(id)”
t.integer :order_id, :null => false, :options => “CONSTRAINT
fk_line_item_orders REFERENCES orders(id)”
t.integer :quantity, :null => false
t.decimal :total_price, :null => false, :precision => 8, :scale
=>
2

  t.timestamps
end

end

def self.down
drop_table :line_items
end
end


#8

Mauro wrote:

2008/12/16 Paolo M. removed_email_address@domain.invalid

magari c’� un metodo migliore, la parola agli esperti :slight_smile:
module MigrationHelpers
constraint_name = “fk_#{from_table}_#{from_column}”

execute %{alter table #{from_table}
drop constraint #{constraint_name}}
end
end

Solo per creare delle foreign key non c’e’ necessita’ di installarsi un
plugin, questo modulo basta e avanza, posso copiarmelo pari pari? :slight_smile:

Usalo pure, più si riusa il codice e meglio è. Se fai dei miglioramenti
però postali così ne approfittiamo tutti :slight_smile:

Hai preso lo spunto dal manuale agile development?

Non credo. Ho dei vaghi ricordi a proposito di un post su un blog. Da lì
ho preso del codice che poi ho modificato un po’ (forse era per mysql?
forse mancava la remove?)

Ho visto il codice in quel manuale, aggiunge anche dei trigger.
Forse pero’ manca del codice, ne riporto uno stralcio:

module MigrationHelpers

def foreign_key(from_table, from_column, to_table)
constraint_name = “#{from_table}_#{to_table}_fkey”
execute %{
CREATE TRIGGER #{constraint_name}_insert
BEFORE INSERT ON #{from_table}
FOR EACH ROW BEGIN
SELECT
RAISE(ABORT, “constraint violation: #{constraint_name}” )
WHERE
(SELECT id FROM #{to_table} WHERE id = NEW.#{from_column}) IS
NULL;
END;
}

ci sono altri due trigger, uno per l’update e uno per il delete, non
vedo pero’ dove crea le references.

Credo che faccia proprio a meno delle reference e gestisca tutto con i
trigger. Dovrebbe avere circa lo stesso effetto ma mi chiedo se sia una
buona soluzione: se oltre ai trigger ci sono anche le foreign key forse
una ragione ci sarà … Ci vorrebbe però un esperto DBA per disquisire
di queste cose sapendo veramente quel che si dice. Ce n’è qualcuno che
ci legge?

Paolo


#9

2008/12/18 Paolo M. removed_email_address@domain.invalid:

Credo che faccia proprio a meno delle reference e gestisca tutto con i
trigger. Dovrebbe avere circa lo stesso effetto ma mi chiedo se sia una
buona soluzione: se oltre ai trigger ci sono anche le foreign key forse
una ragione ci sarà… Ci vorrebbe però un esperto DBA per disquisire
di queste cose sapendo veramente quel che si dice. Ce n’è qualcuno che
ci legge?

non sono un dba, ma provo ad aggiungere qualcosa alla discussione:

Idealmente i trigger sono da preferire (per lo scopo in oggetto)
perche’ permettono di definire le regole di consistenza dei dati in
maniera “dichiarativa”, ovvero come proprieta’ del modello dei dati.
Insomma sono una soluzione piu’ “pulita” al problema di mantenere la
consistenza della base dati, e la manutenzione e’ piu’ semplici
rispetto ai trigger (e.g. non c’e’ bisogno di tenere allineate tre
diverse procedure per delete, insert ed update)

Ma in realta’ dipende dal database che si usa perche’ trigger e
constraints sono implementati in maniera diversa sui diversi database.
Addirittura in MySQL non tutti gli storage engine mettono a
disposizione le foreign key: le tabelle MyISAM, pur accettando la
definizione di una FK senza errori poi NON la usano :slight_smile:

per esempio:

CREATE TABLE parent_innodb (id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
CREATE TABLE child_innodb (id INT, parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id) REFERENCES parent_innodb(id)
) ENGINE=INNODB;

insert into parent_innodb values (1);
insert into child_innodb values (1, 1);
commit;
select * from child_innodb;
insert into child_innodb values (1, 2);
select * from child_innodb;
delete from parent_innodb where id=1;

CREATE TABLE parent_myisam (id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM;
CREATE TABLE child_myisam (id INT, parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id) REFERENCES parent_myisam(id)
) ENGINE=MyISAM;

insert into parent_myisam values (1);
insert into child_myisam values (1, 1);
commit;
select * from child_myisam;
insert into child_myisam values (1, 2);
select * from child_myisam;
delete from parent_myisam where id=1;

In quei casi usare un trigger puo’ essere l’unico modo (a meno di
cambiare il db).

un paio di link (su MySQL o Oracle)

http://stackoverflow.com/questions/229765/triggers-that-cause-inserts-to-fail-possible

ciao!
Luca


#10

2008/12/16 Paolo M. removed_email_address@domain.invalid

magari c’� un metodo migliore, la parola agli esperti :slight_smile:
module MigrationHelpers
constraint_name = “fk_#{from_table}_#{from_column}”

execute %{alter table #{from_table}
drop constraint #{constraint_name}}
end
end

Solo per creare delle foreign key non c’e’ necessita’ di installarsi un
plugin, questo modulo basta e avanza, posso copiarmelo pari pari? :slight_smile:
Hai preso lo spunto dal manuale agile development?
Ho visto il codice in quel manuale, aggiunge anche dei trigger.
Forse pero’ manca del codice, ne riporto uno stralcio:

module MigrationHelpers

def foreign_key(from_table, from_column, to_table)
constraint_name = “#{from_table}_#{to_table}_fkey”
execute %{
CREATE TRIGGER #{constraint_name}_insert
BEFORE INSERT ON #{from_table}
FOR EACH ROW BEGIN
SELECT
RAISE(ABORT, “constraint violation: #{constraint_name}” )
WHERE
(SELECT id FROM #{to_table} WHERE id = NEW.#{from_column}) IS
NULL;
END;
}

ci sono altri due trigger, uno per l’update e uno per il delete, non
vedo
pero’ dove crea le references.