Mam pytanie. Kiedy mam dwa modele i jeden posiada klucz obcy do drugiego oznaczone poprzez belongs_to tak jak poniżej
class Employee
has_many :equipments, dependent: :restrict_with_exception
end
class Equipment < ActiveRecord::Base
belongs_to :equipment_category
belongs_to :employee
validates :description, presence: true
validates :equipment_category_id, presence: true
validates :equipment_category, presence: true
validates :employee, presence: true
end
class EquipmentCategory < ActiveRecord::Base
has_many :equipments, dependent: :restrict_with_exception
end
I teraz interesuje mnie jak zrobić aby Equipment przy zapisie sprawdzał czy id w zmiennej equipment_category_id wskazuje na istniejący wpis. Jak zrobić aby przy usuwaniu elementu z EquipmentCategory nie pozwoliło mi na usunięcie dopóki istnieją wpisy w Equipment wskazujące na usuwany element.
Dodatkowo proponowałbym założenie w bazie ograniczeń klucza obcego (w Rails 4.2 jest to dostępne od ręki, w starszych wersjach warto skorzystać z foreignera).
Czy ja dobrze rozumiem dokumentację, że jeżeli mamy zapis:
has_many :categories, dependent: :destroy
to później można też stosować takie konstrukcje:
before_destroy :equipment_have_categories, prepend: true
def equipment_have_categories
if categories.any?
errors[:base] << "Cannot delete equipment while have categories "
false
end
end
A możesz podać przypadek, gdy klucz obcy jest NULL?
Bo gdy definicja wygląda tylko tak
has_many :categories
bez tego:
dependent: :destroy
to mówi ona:
“W czasie usuwania rekordu tabeli nadrzędnej nie usuwaj wierszy skojarzonych definicją has_many (tabela podrzędna), a tylko wstaw do kolumn z referencją wartość NULL”
Zatem w tabeli powiązanej pozostaną tzw zombie/duchy, czyli wiersze bez odniesienia do tabeli nadrzędnej.
Czyli:
Tabela Ojciec
id name
1 Pan Jacek
2 Pan Krzysiek
3 Pan Robert
Tabela Dziecko
id ojciec_id name
1 1 Ania
2 1 Krysia
3 2 Janek
4 3 Basia
… Po usunięciu Ojca = “Pan Krzysiek” (id =2) będziesz miał
Tabela Dziecko
id ojciec_id name
1 1 Ania
2 1 Krysia
3 NULL Janek
4 3 Basia
ale jeżeli w definicji było:
has_many :categories, dependent: :destroy
to będziesz miał
Tabela Dziecko
id ojciec_id name
1 1 Ania
2 1 Krysia
4 3 Basia
Edit:
Nie chcę za bardzo tutaj rozwijać teorii, ale powinno się wystrzegać sytuacji, gdzie w bazie pozostają te ZOMBIE i to z kilku powodów.
Najważniejsze to takie:
Gdy policzysz wszystkie dzieci (dzieci.count), to wyjdzie Ci, że jest ich 4, ale gdy policzysz dla każdego ojca w bazie ile on ma swoich dzieci, to wyjdzie, że 3. Widzisz niespójność?
Usuwając rekordy z tabeli podrzędnej masz 100% pewność, że wyświetlając później dane na zasadzie @dziecko.ojciec.name, to zawsze będziesz miał jakieś “Pan Jacek”, “Pan Krzysiu” itd.
Jeżeli jednak zrobisz to dla rekordu ZOMBIE, to będziesz miał NIL w kolumnie ojciec_id, co często zwróci Ci jakiś ERROR.
W klasie Equipment employee_id może być NULL lub mieć klucz obcy, i chce sprawdzać czy jeśli nie jest NULL’em (przy tworzeniu/edycji podawana jest wartość) to czy obiekt o wskazanym id istnieje.
if @equipment_categories.equipment_id.nil?
"Nawet nie szukam, bo jest NULL"
else
if Equipment.find_by(id: @equipment_categories.equipment_id)
"szukałem i znalazłem"
else
"szukałem i nie znalazłem"
end
end
…chyba, że nie rozumiem o co pytasz.
No i zapis “wygładź”, bo w Railsach moda jest, by pisać:
"znalazłem" if Equipment.find_by(id: @equipment_categories.equipment_id)
Przeczytaj jeszcze o różnicy między find_by!() a find_by()
Zrozumiałeś dobrze. Tylko skąd pomysł, że używam funkcji find
Chcę aby to model dbał o sytuację z kluczami obcymi tj. kiedy jest NULL nie robił walidacji tylko wsadził w bazę NULL’a a kiedy jest wartość sprawdził czy nadaje się to na klucz obcy i czy istnieje jakiś Employee z takim kluczem.
Winno być
“W czasie usuwania rekordu tabeli nadrzędnej nie usuwaj wierszy skojarzonych definicją has_many (tabela podrzędna). Stosując klauzulę dependent: :nullify wstaw do kolumny z referencją wartość NULL”
przyznam, że nie wiem, dlaczego opuściłem ten fragment.
Dobrze wiedzieć. Jest jakiś dokładny spis co i z czym mogę używać w Modelach RoR’a bez przekopywania się przez całą dokumentację (wszystkie te dependend: i :nullify w jednym krótkim spisie)?