belongs_to a wymuszenie integralnosci danych

Hej!

Wydawalo mi sie, ze jesli zaloze w tabeli odp. kolumne, a w modelu dam belongs_to by tej kolumny uzywalo to obiekt nie zostanie zapisany z wartoscia ID nieprawidlowa, tj. nie istniejaca w referowanej tabeli.

class User < ActiveRecord::Base

ma jakies pola

end

class Pony < ActiveRecord::Base
belongs_to :user
end

I zalozmy, ze mam tylko 1 rekord w tabeli A

Gdy normalnie zrobie b = Pony.create :user => User.find(1) wszystko zadziala pieknie i b.save sie powiedzie.

Ale myslalem, ze zrobienie b = Pony.create :user_id = 0xff wyrzuci blad bo nie ma w tabeli users takiego ID. Tymczasem b.save zapisze rekord do bazy.
A przeciez belongs_to :user tak naprawde dziala po kolumnie user_id…

Czy jest mozliwosc sprawdzania takich zachowan?

musisz dodać walidator

class Pony < ActiveRecord::Base belongs_to :user validates_presence_of :user end
do tego powinieneś jeszcze wymusić integralność po stronie bazy danych, czyli uczynić user_id kluczem obcym w tabeli users

Migracje same z siebie chyba nie mają czegoś takiego:

create_table blabla do |t|
t.integer :user_id, :null => false, :foreign_key => users.id

lub coś w tym stylu?

Czy validates_presence_of :user nie wymusi tylko tego, aby w momencie zapisu ta zmienna była niepusta (!nil?)?

Jeśli używasz Postgresa to: http://snippets.dzone.com/posts/show/5568

  1. Jeśli nie ustawisz klucza obcego w bazie danych, będzie możliwy goły insert do tabeli ponies z nieistniejącym user_id.
  2. Wszystkie walidacje railsowe są podatne na race conditions. http://barelyenough.org/blog/2007/11/activerecord-race-conditions/

Ogólnie developerzy railsów są strasznymi ignorantami jeśli chodzi o wymuszanie integralności bazy danych co ma odbicie w ActiveRecord. Żeby dodać klucz obcy musisz zawołać czystego SQLa przez #execute albo zainstalować plugina foreign_key_migrations http://github.com/harukizaemon/foreign_key_migrations/tree/master
Polecam to drugie rozwiązanie.
wystarczy, że w migracji zrobisz

 t.integer :user_id, :null => false

…i klucz obcy do tabeli users zostanie dodany automatycznie

Nie jestem pewien, ale validates_presence_of razem z validates_associated powinno pomóc.

http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M002171

validates_presence_of - OK, ale co mu pomoże validates_associated? Sprawdzi tylko czy user jest sam w sobie przechodzi wszystkie walidacje. A zauważ, że w tym momencie klasa User nie ma żadnych walidacji :slight_smile:

Dzięki za podpowiedzi; Tak mi się wydawało, że AR robi takie zgodności trochę “sobie a muzom”. Na razie używam SQlite’a, bo Rubyego trenuję dopiero tydzień :wink:

A jak to wygląda w DataMapperze (vide Merb?), który zdaje się z Railsami 3 ma zagościć?

w 1.2.X by i działał taki plugin, który automatycznie zakładał klucz obcy, ale nie wiem czy on działa w Rails 2.x

Klucze obce podczas dewelopmenti i szczególnie testowania z fixturami mogą być problematyczne ( nie załaduje fixtur bo mu klucza brakuje ).
Dobrym rozwiązaniem jest założenie kluczy ale tylko na bazie produkcyjnej (plus ewentualnie deweloperskiej) ale pominięcie ich w testach z wykorzystaniem fixtur.

Nie wiem jak na mysql, ale w postgresie to nie jest problemem. Nie sprawdzałem dokładnie, ale podejrzewam, że podczas ładowania fixtur wyłączane jest sprawdzanie constraintów.

Nie zgodzę się, że to jest dobry pomysł. Załóżmy, że masz tabele users i groups. Tabela users ma klucz obcy group_id z ustawionym ON DELETE RESTRICT. Jeśli zrobić tak jak mówisz to w testach będziesz mógł usunąć grupę do której należą jacyś userzy, a w production już nie…