1 Dlaczego Asocjacje?
Dlaczego potrzebne są asocjacje (powiązania, połączenia – przyp. tłum.) między modelami? Ponieważ sprawiają one, że wspólne operacje (czyli operacje na kilku modelach – przyp.tłum.) stają się mniej skomplikowane i łatwe do implementacji w kodzie. Na przykład, pomyśl o prostej aplikacji zawierającej dwa modele – klienta (customer_) oraz zamówień (_order(s)). Każdy klient może składać wiele zamówień. Bez zdefiniowania powiązania deklaracja modelu mogłaby wyglądać tak:
class Customer < ActiveRecord::Base
end
class Order < ActiveRecord::Base
end
Teraz załóżmy, że chcielibyśmy dodać nowe zamówienie złożone przez istniejącego klienta. Należałoby zrobić coś takiego:
@order = Order.create(:order_date => Time.now, :customer_id => @customer.id)
Przypuśćmy, że chcemy teraz usunąć klienta. W takim przypadku powinniśmy zadbać, by wszystkie zamówienia złożone przez niego zostały również usunięte:
@orders = Order.find_by_customer_id(@customer.id)
@orders.each do |order|
order.destroy
end
@customer.destroy
Z asocjacjami modułu Active Record można znacznie usprawnić tego typu operacje (i wiele innych). Poprzez deklarację możemy powiedzieć Railsom, że istnieje relacja między dwoma modelami. Oto poprawione zgodnie z tymi założeniami modele – klienta i zamówienia:
class Customer < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :customer
end
Dzięki tej zmianie stworzenie nowego zamówienia dla konkretnego klienta jest łatwiejsze:
@order = @customer.orders.create(:order_date => Time.now)
Także usuwanie klienta i jego wszystkich zamówień jest o wiele prostsze.
@customer.destroy
Aby dowiedzieć się więcej o różnych rodzajach asocjacji, przeczytaj następny rozdział tego przewodnika. Zapoznasz się tam z kilkoma wskazówkami i trikami przydatnymi w pracy z asocjacjami, a także z kompletnym spisem metod i opcji dla asocjacji w Railsach.
2 Typy asocjacji
W Railsach asocjacja to powiązanie ze sobą modeli wykorzystujących moduł Active Record. Asocjacje są realizowane poprzez makro-połączenia, a więc dzięki dodaniu stosownej funkcji do modelu poprzez jej zadeklarowanie. Na przykład, deklarując, że jeden model należy do (belongs_to) innego, instruujesz Railsy by utrzymywały powiązanie klucza podstawowego z obcym kluczem między instancjami tych dwóch modeli, a dzięki temu otrzymujesz także szereg narzędzi (metod), które możesz wykorzystać w swoim modelu. Railsy obsługują sześć typów asocjacji:
- belongs_to
- has_one
- has_many
- has_many :through
- has_one :through
- has_and_belongs_to_many
W dalszej części przewodnika dowiesz się jak deklarować asocjacje i jak używać różnych z nich. Ale najpierw – krótkie wprowadzenie do przypadków właściwych użyć poszczególnych asocjacji.
2.1 Asocjacja belongs_to
Asocjacja belongs_to ustanawia relację typu jeden-do-jeden z innym modelem w taki sposób, że każda instancja zadeklarowanego modelu “należy do” (belongs to) jednej instancji innego modelu. Na przykład: jeśli twoja aplikacja zawiera modele klientów i ich zamówienia, a każde zamówienie przypisane jest dokładnie do jednego klienta, można zadeklarować model zamówienia w ten sposób:
class Order < ActiveRecord::Base
belongs_to :customer
end
2.2 Asocjacja has_one
Asocjacja has_one również ustanawia relację typu jeden-do-jeden pomiędzy dwoma modelami, jednak posiada inną semantykę (i konsekwencje). Ta asocjacja wskazuje, iż każda instancja jednego modelu posiada jedną instancję drugiego. Na przykład: Każdy dostawca (supplier) w twojej aplikacji ma tylko jedno konto – możesz zadeklarować model dostawcy w taki sposób:
class Supplier < ActiveRecord::Base
has_one :account
end
2.3 Asocjacja has_many
Asocjacja has_many wskazuje relację typu jeden-do-wiele. Będziesz mógł często znaleźć tę asocjację “po drugiej stronie” asocjacji belongs_to. Ta relacja wskazuje, że każda instancja modelu posiada zero i więcej instancji innego modelu. Na przykład, w aplikacji zawierającej klientów i zamówienia, model klienta mógłby zostać zadeklarowany następująco:
class Customer < ActiveRecord::Base
has_many :orders
end
Kiedy deklarujemy asocjację has_many, nazwa wiązanego modelu jest zamieniana na jej liczbę mnogą (oczywiście mowa tu o gramatyce języka angielskiego – przyp. tłum.).
2.4 Asocjacja has_many :through
Asocjacja has_many :through (posiada_wiele :poprzez) często używana jest do stworzenia relacji wiele-do-wiele z innym modelem. Asocjacja wskazuje, że zadeklarowany model może być powiązany z zerem i większą ilością instancji innego modelu poprzez trzeci model (dzięki użyciu through ). Na przykład, wyobraź sobie praktykę lekarską, gdzie pacjenci (patient(s) ) rezerwują wizyty (appointment(s) ) u lekarzy (physician(s) ). Odpowiednio zadeklarowana asocjacja mogłaby wyglądać tak:
class Physician < ActiveRecord::Base
has_many :appointments
has_many :patients, :through => :appointments
end
class Appointment < ActiveRecord::Base
belongs_to :physician
belongs_to :patient
end
class Patient < ActiveRecord::Base
has_many :appointments
has_many :physicians, :through => :appointments
end
Asocjacja has_many :through przydaje się także do tworzenia “skrótów” poprzez zagnieżdżone asocjacje has_many. Na przykład, jeśli dokument ma wiele rozdziałów (sections ), a rozdział wiele paragrafów (paragraph ), możesz czasem chcieć utworzyć prosty zbiór (in. kolekcję – collection ) wszystkich paragrafów w dokumencie. Da się to zrobić w ten sposób:
class Document < ActiveRecord::Base
has_many :sections
has_many :paragraphs, :through => :sections
end
class Section < ActiveRecord::Base
belongs_to :document
has_many :paragraphs
end
class Paragraph < ActiveRecord::Base
belongs_to :section
end
2.5 Asocjacja has_one :through
Asocjacja has_one :through (posiada jeden :poprzez ) ustanawia relację typu jeden-do-jeden z innym modelem. Asocjacja ta wskazuje, że deklaracja jednego modelu może być dopasowana do instancji innego modelu poprzez model trzeci. Na przykład, jeśli każdy dostawca posiada jedno konto (account ), a każde konto jest powiązane (associate ) z jedną historią konta (account history ), wtedy model klienta mógłby wyglądać następująco:
class Supplier < ActiveRecord::Base
has_one :account
has_one :account_history, :through => :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ActiveRecord::Base
belongs_to :account
end
2.6 Asocjacja has_and_belongs_to_many
Asocjacja has_and_belongs_to_many tworzy bezpośrednią relację typu wiele-do-wiele z innym modelem bez ingerencji modelu pośredniego (nie ma potrzeby deklarowania go, jednak zauważyć należy, iż jest wykorzystywna stosowna tabela wiążąca – przyp.tłum.). Na przykład, jeżeli projekt miałby zawierać zbiory (zestawy – assembly(ies) ) i części (part(s) ) oraz każdy zbiór posiadałby wiele części, a każda część mogłaby pojawić się w wielu zbiorach, modele można zadeklarować w ten sposób:
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
2.7 Wybór pomiędzy belongs_to i has_one
Jeśli chcesz ustawić relację typu jeden-do-jeden między dwoma modelami, powinieneś użyć asocjacji belongs_to do jednego i has_one do drugiego. Ale skąd wiedzieć która powinna być w którym modelu?
Rozróżnić to należy sprawdzając, w której tabeli (w którym modelu) ustawiłeś klucz obcy (foreign key) (dla klasy tej tabeli potrzebujesz asocjacji belongs_to), ale powinieneś pomyśleć także nad znaczeniem danych. Relacja has_one mówi, że “jednen ze zbioru” należy do ciebie – tzn. że coś wskazuje z powrotem na ciebie. Na przykład: ma większy sens powiedzenie, że dostawca posiada konto, aniżeli konto posiada dostawcę. To sugeruje nam, że poprawne relacje wyglądają tak:
class Supplier < ActiveRecord::Base
has_one :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
end
Powiązana relacja może wyglądać tak:
class CreateSuppliers < ActiveRecord::Migration
def self.up
create_table :suppliers do |t|
t.string :name
t.timestamps
end
create_table :accounts do |t|
t.integer :supplier_id
t.string :account_number
t.timestamps
end
end
def self.down
drop_table :accounts
drop_table :suppliers
end
end
Użycie t.integer :supplier_id powoduje, że nazwa klucza obcego jest podana wprost, ale jednocześnie samo użycie klucza jest niejawne. W obecnej wersji Railsów można ukryć ten szczegół implementacyjny używając składni t.references :supplier
2.8 Wybór pomiędzy has_many :through i has_and_belongs_to_many
Railsy oferują dwa sposoby deklaracji relacji tyou wiele-do-wiele między modelami. Łatwiejszym wyjściem jest użycie has_and_belongs_to_many, które umożliwia bezpośrednie ustanowienie asocjacji:
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
Druga opcja implementacji relacji tyou wiele-do-wiele to użycie has_many :through. Ustanawia to asocjację pośrednio, poprzed dołączenie dodatkowego modelu (join model):
class Assembly < ActiveRecord::Base
has_many :manifests
has_many :parts, :through => :manifests
end
class Manifest < ActiveRecord::Base
belongs_to :assembly
belongs_to :part
end
class Part < ActiveRecord::Base
has_many :manifests
has_many :assemblies, :through => :manifests
end
Najłatwiejsza zasada polega na tym, że ustawiasz relację has_many :through jeśli chcesz pracować z pośrednim (wiążącym pozostałe dwa) modelem jako niezależnym bytem. Jeśli jednak nie potrzebujesz niczego z nim robić, łatwiejsze będzie wykorzystanie asocjacji has_and_belongs_to_many (choć musisz pamiętać, że należy stworzyć tabelę łączącą)
Należy używać :has_many :through jeśli potrzebujesz opcji walidacji, callback-ów lub dodatkowych atrybutów dla wiążącego modelu.
2.9 Asocjacje polimorficzne
Nieco bardziej zaawansowanym typem asocjacji jest asocjacja polimorficzna. Dzięki polimorficznym asocjacjom model może być powiązany z więcej niż jednym innym modelem, za pomocją pojedynczej asocjacji. Na przykład, chcesz mieć model “zdjęcia” (picture ), który należy do (belongs_to) modelu pracownika (employee ) lub produktu (product ). Oto w jaki sposób mogłoby to zostać zadeklarowane:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
Mógłbyś pomyśleć, że polimofriczna deklaracja belongs_to ustanawia taki interfejs, że żaden inny model nie może go użyć. Z instancji modelu pracownika (Employee) można pobrać zbiór zdjęć: @employee.pictures. Podobnie, możesz pobrać zdjęcia produktów – @product.pictures. Jeśli masz instancję modelu zdjęcia (Picture), możesz “dostać się” do jego rodzica dzięki @picture.imageable. By to zadziałało, potrzebujesz zadeklarować w modelu, który ma mieć polimorficzny interfejs (tu: Picture) dwie rzeczy: klucz obcy (dla id pracownika lub produktu – przyp. tłum.), jak i “typ” wiązanego modelu:
class CreatePictures < ActiveRecord::Migration
def self.up
create_table :pictures do |t|
t.string :name
t.integer :imageable_id
t.string :imageable_type
t.timestamps
end
end
def self.down
drop_table :pictures
end
end
Migracja dla takiego modelu może być uproszczona dzięki użyciu t.references:
class CreatePictures < ActiveRecord::Migration
def self.up
create_table :pictures do |t|
t.string :name
t.references :imageable, :polymorphic => true
t.timestamps
end
end
def self.down
drop_table :pictures
end
end
2.10 Samoodniesienie (wiązanie rekurencyjne)
Przy projektowaniu modeli danych, możesz czasem natknąć się na model, który powinien mieć odniesienie do siebie samego. Na przykład, chcesz przechowywać wszystkich pracowników w jednym modelu bazy danych (w jednej tabeli w bazie), ale też chciałbyś móc prześledzić hierarchie między pracownikami (takie jak manager i jego podwładni). Takie powiązanie może być zadeklarowane za pomocą asocjacji samoodniesienia:
class Employee < ActiveRecord::Base
has_many :subordinates, :class_name => "Employee", :foreign_key => "manager_id"
belongs_to :manager, :class_name => "Employee"
end
Dzięki takiemu ustawieniu możesz “wyciągnąć” @employee.subordinates (podwładni pracownika) i @employee.manager (manager pracownika).
3 Porady, triki i ostrzeżenia
W tym rozdziale znajdziesz kilka rzeczy, które powinieneś wiedzieć, by skutecznie używać modułu Active Record w twoich aplikacjach Rails.
- Kontrolowanie cachingu
- Unikanie kolizji nazw
- Aktualizacje schematów
- Kontrolowanie zakresu widoczności asocjacji
3.1 Kontrolowanie cachingu
Wszystkie metody asocjacji są cache-owane tak, że wynik ostatniego zapytania dostępny jest dla następnych operacji. Zawartość cache jest nawet widoczna z innych metod. Na przykład:
customer.orders # pobiera zamówienia z bazy danych
customer.orders.size # używa kopii zamówień z cache
customer.orders.empty? # używa kopii zamówień z cache
Ale co zrobić, gdy potrzeba uaktualnić cache, bo dane być może zostały zmienione przez inną część aplikacji? Po prostu ustaw true dla wywołania asocjacji:
customer.orders # pobiera zamówienia z bazy danych
customer.orders.size # używa kopii zamówień z cache
customer.orders(true).empty? # odrzuca kopię zamówień będącą w cache i "wraca" do bazy danych
3.2 Unikanie kolizji nazw
Nazwy twoich asocjacji nie mogą być dowolne. Ponieważ tworzenie asocjacji automatycznie dodaje do modelu metodę z taką samą nazwą, złym pomysłem jest nazywanie asocjacji tak samo jak metody instancji ActiveRecord::Base. Metoda asocjacji w takim przypadku przesłoni metodę bazy, a to nie jest pożądane. Przykładem złych nazw asocjacji są attributes albo connection.
3.3 Aktualizacja schematu
Asocjacje są bardzo przydatne, ale nie są magiczne. Jesteś odpowiedzialny za utrzymanie schematu bazy danych w dopasowaniu do swoich asocjacji. W praktyce oznacza to dwie rzeczy, w zależności od tego, którego rodzaju asocjacji użyłeś w swoim modelu. Dla asocjacji belongs_to porzebujesz stworzyć klucze obce, a dla has_and_belongs_to_many – stosowną tabelę wiążącą (w bazie danych).
3.3.1 Tworzenie kluczy obcych dla asocjacji belongs_to
Kiedy deklarujesz asocjację belongs_to, powinieneś stworzyć odpowiednie klucze obce. Na przykład, rozważ taki model:
class Order < ActiveRecord::Base
belongs_to :customer
end
Taka deklaracja powinna być uwzględniona i “wsparta” odpowiednią deklaracją klucza obcego w tabeli zamówień:
class CreateOrders < ActiveRecord::Migration
def self.up
create_table :orders do |t|
t.datetime :order_date
t.string :order_number
t.integer :customer_id
end
end
def self.down
drop_table :orders
end
end
Jeśli tworzysz asocjację po jakimś czasie od stworzenia modelu bazowego, musisz pamiętać o stworzeniu migracji add_column (dodaj kolumnę) by zapewnić modelowi odpowiedni klucz obcy.
3.3.2 Tworzenie tabeli wiążących dla asocjacji has_and_belongs_to_many
Jeśli tworzysz asocjację has_and_belongs_to_many potrzebujesz stworzyć wiążącą tabelę. Dopóki nazwa dołączanej tabeli nie jest wyraźnie określona poprzez użycie opcji :join_table, moduł Active Record utworzy nazwę używając leksykalnego (alfabetycznego) porządku tworzenia nazw. Zatem: połączenie między modelem klienta (customer) a zamówieniem (order) otrzyma domyślną nazwę tabeli “customers_orders” (zamówienia klientów), bo “c” jest w alfabecie przed “o”.
Pierwszeństwo między nazwami modeli jest “obliczane” przy użyciu operatora “<” (mniejszości) dla stringów (łańcuchów znaków). To znaczy, ze jeśli słowa są różnej długości, ale początkowy łańcuch jest jednakowy – wtedy wyżej w tym porządku jest słowo dłuższe. Na przykład, można by oczekiwać, że dla tabeli paper_boxes i papers wygenerowana nazwa wiążącej tabeli będzie brzmiała “papers_paper_boxes”, ze względu na długość łańcucha “paper_boxes”. Jednak w rzeczywistości wygenerowana zostanie nazwa “paper_boxes_papers”.
Niezależnie od nazwy, musisz “ręcznie” wygenerować wiążącą tabelę za pomocą odpowiedniej migracji. Na przykład, rozważ takie asocjacje:
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
Powinny one zostać zaktualizowane za pomocą migracji, która wygeneruje tabelę assemblies_parts. Taka tabela powinna być stworzona bez klucza podstawowego:
class CreateAssemblyPartJoinTable < ActiveRecord::Migration
def self.up
create_table :assemblies_parts, :id => false do |t|
t.integer :assembly_id
t.integer :part_id
end
end
def self.down
drop_table :assemblies_parts
end
end
3.4 Kontrolowanie zakresu widoczności asocjacji
Domyślnie asocjacje “szukają” obiektów tylko w ramach bieżącego zakresu widoczności modułów. To może być ważne, gdy deklarujesz modele wykorzystujące ActiveRecord wewnątrz modułu, na przykład:
module MyApplication
module Business
class Supplier < ActiveRecord::Base
has_one :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
end
end
end
Powyższy kod będzie działał dobrze, bo zarówno klasa dostawcy, jak i klasa konta są zdefiniowane w ramach tego samego zakresu (zakresu widoczności modelu). Ale już poniższy przykład nie będzie działać, bo dostawca i konto są zdefiniowane w różnych zakresach widoczności:
module MyApplication
module Business
class Supplier < ActiveRecord::Base
has_one :account
end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :supplier
end
end
end
By powiązać dwa modele istniejące w ramach różnych zakresów – musisz podać pełną nazwę klasy w deklaracji asocjacji:
module MyApplication
module Business
class Supplier < ActiveRecord::Base
has_one :account, :class_name => "MyApplication::Billing::Account"
end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :supplier, :class_name => "MyApplication::Business::Supplier"
end
end
end
4 Szczegółowy spis asocjacji
Niniejszy rozdział ukaże ci szczegóły każdego typu asocjacji, w tym informacje o metodach, które dzięki nim zyskujesz oraz opcji, których możesz używać dzięki zadeklarowaniu asocjacji.
4.1 Asocjacja belongs_to
Asocjacja belongs_to tworzy relację typu jeden-do-jeden z innym modelem. W języku baz danych, oznacza to że dana klasa zawiera klucz obcy. Jeśli inna klasa zawiera klucz obcy, powinieneś raczej użyć asocjacji has_one
4.1.1 Metody dodane dzięki belongs_to
Kiedy zadeklarujesz asocjację belongs_to, deklarowana klasa automatycznie zyskuje pięć metod odnoszących się do asocjacji:
- association(force_reload = false)
- association=(associate)
- association.nil?
- build_association(attributes = {})
- create_association(attributes = {})
We wszystkich tych metodach association zastępuje się symbolem przekazanym jako pierwszy argument do belongs_to. Na przykład gdy mamy deklarację:
class Order < ActiveRecord::Base
belongs_to :customer
end
Każda instancja wybranego modelu będzie posiadała poniższe metody:
customer
customer=
customer.nil?
build_customer
create_customer
4.1.1.1 association(force_reload = false)
Metoda association zwraca wiązany obiekt, jeśli tylko taki istnieje. Jeśli wiązany obiekt nie został znaleziony, metoda zwraca nil.
@customer = @order.customer
Jeśli wiązany obiekt został już pobrany z bazy danych, zwrócona będzie jego wersja przechowywana w cache. Aby to zmienić (i wymusić czytanie z bazy), ustaw true przy argumencie force_reload
4.1.1.2 association=(associate)
Metoda association= przydziela wiązany obiekt do naszego obiektu. Od środka wygląda to tak, że wydobywa ona z wiązanego obiektu klucz podstawowy i ustawia naszemu obiektowi klucz obcy na tę samą wartość.
@order.customer = @customer
4.1.1.3 association.nil?
metoda association.nil? zwraca wartość true jeśli nie znaleziono wiązanego obiektu.
if @order.customer.nil?
@msg = "No customer found for this order"
end
4.1.1.4 build_association(attributes = {})
Metoda build_association zwraca nowy obiekt wiązanego typu. Ten obiekt zostanie natychmiast utworzony z ustawionych atrybutów, będzie ustawiony odnośnik do obcego klucza tego obiektu, ale wiązany obiekt nie zostanie zapisany.
@customer = @order.build_customer({:customer_number => 123, :customer_name => "John Doe"})
4.1.1.5 create_association(attributes = {})
Metoda create_association zwraca nowy obiekt wiązanego typu. Ten obiekt zostanie natychmiast utworzony z ustawionych atrybutów i będzie ustawiony odnośnik do obcego klucza tego obiektu. Ponadto wiązany obiekt zostanie zapisany (zakładając, że przejdzie pomyślnie przez walidację).
@customer = @order.create_customer({:customer_number => 123, :customer_name => "John Doe"})
4.1.2 Opcje dla belongs_to
W wielu sytuacjach możesz użyć asocjacji belongs_to bez potrzeby jej dostosowywania. Ale pomimo Ralisowego nacisku na górowanie konwencji nad dostosowaniem do własnych potrzeb, możesz zmieniać domyślne ustawienia na kilka sposobów. Ten rozdział przedstawi ci opcje, których możesz używać, kiedy zadeklarujesz asocjację belongs_to. Na przykład asocjacja z kilkoma opcjami może wyglądać tak:
class Order < ActiveRecord::Base
belongs_to :customer, :counter_cache => true, :conditions => "active = 1"
end
Asocjacja belongs_to wspiera następujące opcje
- :autosave
- :class_name
- :conditions
- :counter_cache
- :dependent
- :foreign_key
- :include
- :polymorphic
- :readonly
- :select
- :touch
- :validate
4.1.2.1 :autosave
If you set the :autosave option to true, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.
4.1.2.2 :class_name
Jeśli nazwa innego modelu nie może zostać utworzona z nazwy asocjacji, możesz użyć opcji :class_name, by dostarczyć własną nazwę modelu. Na przykład, jeśli zamówienie należy belongs to do klienta, ale aktualna nazwa zawierająca klientów to Patron, możesz to ustawić w ten sposób:
class Order < ActiveRecord::Base
belongs_to :customer, :class_name => "Patron"
end
4.1.2.3 :conditions
Opcja :conditions pozwala określić warunki, jakie wiązany obiekt musi spełniać (W składni SQL użyłbyś klauzuli WHERE).
class Order < ActiveRecord::Base
belongs_to :customer, :conditions => "active = 1"
end
4.1.2.4 :counter_cache
Opcja :counter_cache może sprawić, że znajdowanie liczby powiązanych obiektów będzie bardziej wydajne (szybsze). Rozważ te modele:
class Order < ActiveRecord::Base
belongs_to :customer
end
class Customer < ActiveRecord::Base
has_many :orders
end
Z powyższymi deklaracjami, znalezienie wartości @customer.orders.size (wielkość [ilość] zamówień klientów) wymaga zapytania do bazy wykorzystującego COUNT(*). Aby uniknąć tego połączenia możesz dodać :counter_cache (“cache dla licznika”) do modelu:
class Order < ActiveRecord::Base
belongs_to :customer, :counter_cache => true
end
class Customer < ActiveRecord::Base
has_many :orders
end
Dzięki tej deklaracji Railsy będą pobierały bieżącą wartość cache i zwrócą ją w odpowiedzi na metodę size.
Pomimo, że opcja :counter_cache jest określona dla modelu zawierającego deklarację belongs_to, bieżąca kolumna musi być dodana do wiązanego modelu. W powyższym przypadku musisz dodać kolumnę o nazwie orders_count do modelu klienta [Customer]. Możesz zamienić domyślną nazwę kolumny, jeśli tylko chcesz:
class Order < ActiveRecord::Base
belongs_to :customer, :counter_cache => :count_of_orders
end
class Customer < ActiveRecord::Base
has_many :orders
end
Kolumny licznika są dodawane do listy atrybutów tylko-do-odczytu wiązanego modelu (poprzez attr_readonly)
4.1.2.5 :dependent
Jeśli ustawisz opcję :dependent na :destroy, wtedy usunięcie tego obiektu wywoła metodę destroy na wiązanym obiekcie, by usunąć ten obiekt. Jeżeli ustawisz opcję :dependent na :delete, wtedy usunięcie tego obiektu spowoduje usunięcie obiektu wiązanego bez wywoływania dla niego metody destroy.
Nie używaj tej opcji w asocjacji belongs_to, która jest połączona z asocjacją has_many w innej klasie. W przeciwnym wypadku w twojej bazie pojawią się rekordy osierocone.
4.1.2.6 :foreign_key
Zgodnie z konwencją, Railsy “domyślają się”, że kolumna używana do przechowywania klucza obcego to nazwa asocjacji z dodanym przyrostkiem _id. Opcja :foreign_key umożliwia ustawienie nazwy klucza obcego bezpośrednio:
class Order < ActiveRecord::Base
belongs_to :customer, :class_name => "Patron", :foreign_key => "patron_id"
end
W żadnym z wypadków Railsy nie będą same tworzyły dla ciebie kolumn klucza obcego. Musisz zdefiniować te klucze w swoich migracjach.
4.1.2.7 :include
Możesz użyć opcji :include do podania drugorzędnych asocjacji, które zostaną załadowane wtedy, gdy zostanie użyta dana asocjacja. Na przykład, rozważmy takie modele:
class LineItem < ActiveRecord::Base
belongs_to :order
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class Customer < ActiveRecord::Base
has_many :orders
end
Jeśli często pobierasz klienta bezpośrednio z pozycji w jego zamówieniu line item_] (@lineitem.order.customer), możesz sprawić, że twój kod będzie wydajniejszy, włączając klienta w asocjacji z pozycją w zamówieniu do zamówienia:
class LineItem < ActiveRecord::Base
belongs_to :order, :include => :customer
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class Customer < ActiveRecord::Base
has_many :orders
end
Nie ma potrzeby stosowania :include do bezpośrednich asocjacji, to znaczy: jeśli masz zdefiniowaną w modelu relację zamówień należących do (belongs to) klientów, wtedy “klient” jest przygotowany do wczytania zawsze, kiedy tylko jest potrzebny.
4.1.2.8 :polymorphic
Ustawienie true dla opcji :polymorphic wskazuje, że asocjacja jest polimorficzna. Asocjacje polimorficzne zostały omówione wcześniej w tym przewodniku.
4.1.2.9 :readonly
Jeśli ustawisz true dla opcji :readonly, asocjowany obiekt będzie tylko do odczytu, gdy wywoływana jest asocjacja.
4.1.2.10 :select
Użyj opcji :select by zastąpić klauzulę SQL SELECT, która służy do wyszukiwania danych w wiązanym obiekcie. Domyślnie Railsy pobierają wszystkie kolumny.
Po ustawieniu opcji :select dla asocjacji belongs_to, powinieneś też ustawić opcję :foreign_key, by mieć pewność, że uzyskasz poprawny wynik zapytania
4.1.2.11 :validate
Kiedy ustawisz na true opcję :validate, wiązane obiekty będą walidowane zawsze, kiedy będziesz je zapisywać. Domyślne ustawienie to false: wiązane obiekty nie są walidowane kiedy je zapisujesz.
4.1.3 Kiedy obiekty są zapisywane?
Przypisywanie obiektu do asocjacji belongs_to nie zapisuje go automatycznie. Nie zapisuje to także wiązanego obiektu.
4.2 Asocjacja has_one
Asocjacja has_one tworzy relację typu jeden-do-jeden pomiędzy modelami. W języku baz danych oznacza to, że inna klasa zawiera klucz obcy. Jeżeli to rozpatrywana aktualnie klasa zawiera klucz obcy, należy użyć asocjacji belongs_to zamiast has_one.
4.2.1 Metody dodane dzięki has_one
Kiedy zadeklarujesz asocjację has_one, deklarowana klasa automatycznie zyskuje pięć metod odnoszących się do asocjacji:
- association(force_reload = false)
- association=(associate)
- association.nil?
- build_association(attributes = {})
- create_association(attributes = {})
We wszystkich tych metodach association zastępuje się symbolem przekazanym jako pierwszy argument do has_one. Na przykład gdy mamy deklarację:
class Supplier < ActiveRecord::Base
has_one :account
end
Każda instancja modelu dostawcy (Supplier) będzie posiadała poniższe metody:
account
account=
account.nil?
build_account
create_account
4.2.1.1 association(force_reload = false)
Metoda association zwraca wiązany obiekt, jeśli tylko taki istnieje. Jeśli wiązany obiekt nie został znaleziony, metoda zwraca nil.
@account = @supplier.account
Jeśli wiązany obiekt został już pobrany z bazy danych, zwrócona będzie jego wersja przechowywana w cache. Aby to zmienić (i wymusić czytanie z bazy), ustaw true przy argumencie force_reload
4.2.1.2 association=(associate)
Metoda association= przydziela wiązanemu obiekt do naszego obiektu. Od środka wygląda to tak, że wydobywa ona z naszego obiektu klucz podstawowy i ustawia wiązanemu obiektowi klucz obcy na tę samą wartość.
@suppler.account = @account
4.2.1.3 association.nil?
Metoda association.nil? zwraca wartość true jeśli nie znaleziono wiązanego obiektu.
if @supplier.account.nil?
@msg = "No account found for this supplier"
end
4.2.1.4 build_association(attributes = {})
Metoda build_association zwraca nowy obiekt wiązanego typu. Ten obiekt zostanie natychmiast utworzony z ustawionych atrybutów, będzie ustawiony odnośnik do jego obcego klucza tego obiektu, ale wiązany obiekt nie zostanie zapisany.
@account = @supplier.build_account({:terms => "Net 30"})
4.2.1.5 create_association(attributes = {})
Metoda create_association zwraca nowy obiekt wiązanego typu, Ten obiekt zostanie natychmiast utworzony z ustawionych atrybutów i będzie ustawiony odnośnik do jego obcego klucza. Ponadto wiązany obiekt zostanie zapisany (zakładając, że przejdzie pomyślnie przez walidację).
@account = @supplier.create_account({:terms => "Net 30"})
4.2.2 Opcje dla has_one
W wielu sytuacjach możesz użyć asocjacji has_one bez potrzeby jej dostosowywania. Ale pomimo Ralisowego nacisku na górowanie konwencji nad dostosowaniem do własnych potrzeb, możesz zmieniać domyślne ustawienia na kilka sposobów. Ten rozdział przedstawi ci opcje, których możesz używać, kiedy zadeklarujesz asocjację has_one. Na przykład asocjacja z kilkoma opcjami może wyglądać tak:
class Supplier < ActiveRecord::Base
has_one :account, :class_name => "Billing", :dependent => :nullify
end
Asocjacja has_one wspiera następujące opcje
- :as
- :autosave
- :class_name
- :conditions
- :dependent
- :foreign_key
- :include
- :order
- :primary_key
- :readonly
- :select
- :source
- :source_type
- :through
- :validate
4.2.2.1 :as
Użycie opcji :as wskazuje, że mamy do czynienia z asocjacją polimorficzną. Polimorficzne asocjacje zostały omówione szczegółowo we wcześniejszej części podręcznika.
4.2.2.2 :autosave
If you set the :autosave option to true, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.
4.2.2.3 :class_name
Jeśli nazwa innego modelu nie może zostać utworzona z nazwy asocjacji, możesz użyć opcji :class_name, by dostarczyć własną nazwę modelu. Na przykład, jeśli dostawca posiada konto, ale aktualna nazwa zawierająca konta to Billing, możesz to ustawić w ten sposób:
class Supplier < ActiveRecord::Base
has_one :account, :class_name => "Billing"
end
4.2.2.4 :conditions
Opcja :conditions pozwala określić warunki, jakie wiązany obiekt musi spełniać (W składni SQL użyłbyś klauzuli WHERE).
class Supplier < ActiveRecord::Base
has_one :account, :conditions => "confirmed = 1"
end
4.2.2.5 :dependent
Jeśli ustawisz opcję :dependent na :destroy, wtedy usunięcie tego obiektu wywoła metodę destroy na wiązanym obiekcie, by usunąć ten obiekt. Jeżeli ustawisz opcję :dependent na :delete, wtedy usunięcie tego obiektu spowoduje usunięcie obiektu wiązanego bez wywoływania dla niego metody destroy. Jeśli ustawisz opcję :dependent na :nullify, usunięcie tego obiektu sprawi, że klucz obcy w wiązanym obiekcie zostanie zastąpiony przez NULL.
4.2.2.6 :foreign_key
Zgodnie z konwencją, Railsy “domyślają się”, że kolumna używana do przechowywania klucza obcego w innym modelu to nazwa pierwszego modelu z przyrostkiem id. Opcja :foreignkey umożliwia ustawienie nazwy klucza obcego bezpośrednio:
class Supplier < ActiveRecord::Base
has_one :account, :foreign_key => "supp_id"
end
W żadnym z wypadków Railsy nie będą same tworzyły dla ciebie kolumn klucza obcego. Musisz zdefiniować te klucze w swoich migracjach.
4.2.2.7 :include
Możesz użyć opcji :include do podania drugorzędnych asocjacji, które zostaną załadowane wtedy, gdy zostanie użyta dana asocjacja. Na przykład, rozważmy takie modele:
class Supplier < ActiveRecord::Base
has_one :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
belongs_to :representative
end
class Representative < ActiveRecord::Base
has_many :accounts
end
Jeśli często pobierasz przedstawicieli bezpośrednio od dostawców (@supplier.account.representative), możesz sprawić, że twój kod będzie wydajniejszy, załączając przedstawicieli w asocjacji poszczególnych dostawców do kont:
class Supplier < ActiveRecord::Base
has_one :account, :include => :representative
end
class Account < ActiveRecord::Base
belongs_to :supplier
belongs_to :representative
end
class Representative < ActiveRecord::Base
has_many :accounts
end
4.2.2.8 :order
Opcja :order ustawia porządek w jakim powiązane obiekty będą “wyciągane” z bazy (w składni SQL użyłbyś klauzuli ORDER BY). Ponieważ has_one pobierać będzie tylko jeden wiązany obiekt, opcja ta nie będzie potrzebna.
4.2.2.9 :primary_key
Zgodnie z konwencją Railsy “domyślają się” że kolumna z kluczem podstawowym w modelu to “id”. Możesz to zmienić i samodzielnie określić klucz podstawowy dzięki opcji :primary_key.
4.2.2.10 :readonly
Jeśli ustawisz true dla opcji :readonly, asocjowany obiekt będzie tylko do odczytu, gdy wywoływana jest asocjacja.
4.2.2.11 :select
Użyj opcji :select by zastąpić klauzuję SQL SELECT, która służy do wyszukiwania danych w wiązanym obiekcie. Domyślnie Railsy pobierają wszystkie kolumny.
4.2.2.12 :source
Opcja source określa źródło asocjacji dla asocjacji typu has_one :through.
4.2.2.13 :source_type
Opcja :source_type określa typ źródła asocjacji dla asocjacji has_one :through; związane jest to z asocjacją polimorficzną.
4.2.2.14 :through
Opcja :through określa wiążący model, poprzez który wykonywane są zapytania. Asocjacje has_one :through są omówione w dalszej części przewodnika.
4.2.2.15 :validate
Kiedy ustawisz na true opcję :validate, wiązane obiekty będą walidowane zawsze, kiedy będziesz je zapisywać. Domyślne ustawienie to false: wiązane obiekty nie są walidowane kiedy je zapisujesz.
4.2.3 How To Know Whether There’s an Associated Object?
To know whether there’s and associated object just check association.nil?:
if @supplier.account.nil?
@msg = "No account found for this supplier"
end
4.2.4 Kiedy obiekty są zapisywane?
Kiedy przypisujesz obiekt do asocjacji has_one, jest on zapisywany automatycznie (by zaktualizować jego klucz obcy). Ponadto każdy obiekt, który został zmieniony jest również automatycznie zapisywany, ponieważ jego klucz obcy także się zmienił.
Jeśli którykolwiek z tych zapisów nie powiedzie się z winy błędu walidacji, wtedy rozkaz przypisania zwróci false, a samo przypisanie jest anulowane.
Jeśli obiekt-rodzic (ten, w którym zadeklarowałeś asocjację has_one) nie został zapisany (to jest, gdy new_record? zwraca wartość true), wtedy obiekt-dziecko nie jest zapisywany.
Jeśli chcesz przypisać obiekt do asocjacji has_one bez zapisywania obiektu, użyj metody association.build.
4.3 Asocjacja has_many
Asocjacja has_many tworzy relację typu jeden-do-wiele z innym modelem. W języku baz danych oznacza to, że inna klasa zawiera klucz obcy, który odnosi się do instancji rozpatrywanej klasy.
4.3.1 Dodawane metody
Kiedy zadeklarujesz asocjację has_many, deklarowana klasa automatycznie zyskuje 13 metod odnoszących się do tej asocjacji:
- collection(force_reload = false)
- collection<<(object, ...)
- collection.delete(object, ...)
- collection=objects
- collection_singular_ids
- collection_singular_ids=ids
- collection.clear
- collection.empty?
- collection.size
- collection.find(...)
- collection.exist?(...)
- collection.build(attributes = {}, ...)
- collection.create(attributes = {})
We wszystkich wymienionych metodach collection zastępuje się symbolem przekazanym jako pierwszy argument do has_many oraz collection_singular zastępowany jest nazwą tegoż symbolu w liczbie pojedynczej (w języku angielskim). Na przykład, biorąc pod uwagę deklarację:
class Customer < ActiveRecord::Base
has_many :orders
end
Każda instancja modelu klienta będzie posiadała poniższe metody:
orders(force_reload = false)
orders<<(object, ...)
orders.delete(object, ...)
orders=objects
order_ids
order_ids=ids
orders.clear
orders.empty?
orders.size
orders.find(...)
orders.exist?(...)
orders.build(attributes = {}, ...)
orders.create(attributes = {})
4.3.1.1 collection(force_reload = false)
Metoda collection zwraca tablicę (array) wszystkich wiązanych obiektów. Jeśli nie istnieją żadne wiązane obiekty, zwraca pustą tablicę.
@orders = @customer.orders
4.3.1.2 collection<<(object, ...)
Metoda collection<< dodaje jeden lub więcej obiektów do zbioru poprzez ustawienie ich kluczy obcych na wartość klucza podstawowego wywoływanego modelu
@customer.orders << @order1
4.3.1.3 collection.delete(object, ...)
Metoda collection.delete usuwa jeden lub więcej obiektów ze zbioru, poprzez ustawienie ich kluczy obcych na NULL.
@customer.orders.delete(@order1)
Obiekty będą dodatkowo niszczone, jeśli są powiązane przez :dependent => :destroy lub usuwane, jeśli są powiązane z :dependent => :delete_all.
4.3.1.4 collection=objects
Metoda collection= usuwa i dodaje odpowiednie obiekty tak, żeby dana kolekcja zawierała tylko podane (wymienione) obiekty.
4.3.1.5 collection_singular_ids
Metoda collection_singular_ids zwraca tablicę identyfikatorów obiektów w zbiorze.
@order_ids = @customer.order_ids
4.3.1.6 collection_singular_ids=ids
Metoda collection_singular_ids= sprawia, że w zbiorze znajdują się tylko te obiekty, których wartość podstawowego klucza równa jest wartościom dostarczonym do metody.
4.3.1.7 collection.clear
Metoda collection.clear usuwa wszystkie elementy zbioru. Niszczy ona też wiązane obiekty jeśli zostały związane poprzez :dependent => :destroy, usuwa te wiązane poprzez :dependent => :delete_all lub (w przeciwnym przypadku do dwóch wymienionych) ustawia klucze obce obiektów na NULL.
4.3.1.8 collection.empty?
Metoda collection.empty? zwraca wartośc true, jeśli zbiór nie posiada żadnych związanych obiektów.
<% if @customer.orders.empty? %>
Nie znaleziono zamówień
<% end %>
4.3.1.9 collection.size
Metoda collection.size zwraca liczbę obiektów w zbiorze.
@order_count = @customer.orders.size
4.3.1.10 collection.find(...)
Metoda collection.find służy do wyszukiwania obiektów w zbiorze. Korzysta ona z tej samej składni i tych samych opcji, co ActiveRecord::Base.find.
@open_orders = @customer.orders.find(:all, :conditions => "open = 1")
4.3.1.11 collection.exist?(...)
Metoda collection.exist? sprawdza, czy obiekt spełniający podane warunki istnieje w danej kolekcji. Metoda ta używa takiej samej składni i tych samych opcji, co ActiveRecord::Base.exists?.
4.3.1.12 collection.build(attributes = {}, ...)
Metoda collection.build zwraca jeden lub więcej nowych obiektów wiązanego typu. Obiekty te będą konstruowane na podstawie podanych atrybutów. Połączenie poprzez ich klucz obcy zostanie stworzone, ale powiązane obiekty nie będą zapisane.
@order = @customer.orders.build({:order_date => Time.now, :order_number => "A12345"})
4.3.1.13 collection.create(attributes = {})
Metoda collection.create zwraca nowy obiekt wiązanego typu. Te obiekty będą konstruowane na podstawie podanych atrybutów. Połączenie poprzez ich klucz obcy zostanie stworzone, a powiązane obiekty zostaną zapisane (zakładając że przejdą przez wszystkie walidacje)
@order = @customer.orders.create({:order_date => Time.now, :order_number => "A12345"})
4.3.2 Opcje dla has_many
W wielu sytuacjach możesz użyć domyślnych ustawień asocjacji has_many bez potrzeby jej dostosowywania. Pamiętaj jednak, że możesz zmieniać domyślne ustawienia według swoich potrzeb. Ten rozdział przedstawi ci opcje, których możesz używać kiedy zadeklarujesz asocjację has_many. Na przykład asocjacja z kilkoma opcjami może wyglądać tak:
class Customer < ActiveRecord::Base
has_many :orders, :dependent => :delete_all, :validate => :false
end
Asocjacja has_many wspiera następujące opcje:
- :as
- :class_name
- :conditions
- :counter_sql
- :dependent
- :extend
- :finder_sql
- :foreign_key
- :group
- :include
- :limit
- :offset
- :order
- :primary_key
- :readonly
- :select
- :source
- :source_type
- :through
- :uniq
- :validate
4.3.2.1 :as
Użycie opcji :as wskazuje, że mamy do czynienia z asocjacją polimorficzną. Polimorficzne asocjacje zostały omówione we wcześniejszej części podręcznika.
4.3.2.2 :class_name
Jeśli nazwa wiązanego modelu nie może zostać utworzona z nazwy asocjacji, możesz użyć opcji :class_name, by dostarczyć własną nazwę modelu. Na przykład jeśli klient ma wiele zamówień, ale aktualna nazwa modelu zawierającego zamówienia to “Transaction”, możesz to ustawić w ten sposób:
class Customer < ActiveRecord::Base
has_many :orders, :class_name => "Transaction"
end
4.3.2.3 :conditions
Opcja :conditions pozwala określić warunki, jakie wiązany obiekt musi spełniać (W składni SQL użyłbyś klauzuli WHERE).
class Customer < ActiveRecord::Base
has_many :confirmed_orders, :class_name => "Order",
:conditions => "confirmed = 1"
end
możesz także określać warunki używając hash-a:
class Customer < ActiveRecord::Base
has_many :confirmed_orders, :class_name => "Order", :conditions => { :confirmed => true }
end
Jeżeli użyjesz opcji :conditions (w notacji z hashem), tworzenie rekordów przez tą asocjację zostanie zautomatyzowane według zawartości hasha. W tym przypadku, używając @customer.confirmed_orders.create albo @customer.confirmed_orders.build, tworzy się zamówienia tam, gdzie kolumna confirmed ma wartość true.
4.3.2.4 :counter_sql
Zwykle Railsy automatycznie tworzą odpowiednie zapytanie SQL do liczenia uczestników asocjacji. Z opcją :counter_sql możesz zdefiniować własne zapytanie sql do policzenia ich.
Jeśli określisz :finder_sql, ale nie stworzysz :counter_sql , wtedy funkcja
licząca sql SELECT COUNT(*) FROM zostanie zastąpiona klauzulą SELECT ... FROM, wykorzystującą deklaracje z :finder_sql.
4.3.2.5 :dependent
Jeśli ustawisz opcję :dependent na :destroy, wtedy usunięcie tego obiektu wywoła metodę :destroy dla obiektów powiązanych, żeby je usunąć. Jeśli ustawisz opcję :dependent na :delete_all, wtedy usunięcie tego obiektu usunie obiekty powiązane bez wywoływania ich metody :destroy. Jeśli ustawisz opcję :dependent na :nullify, wtedy usunięcie tego obiektu ustawi klucz obcy w powiązanych obiektach na NULL.
Ta opcja jest ignorowana, jeśli używasz w asocjacji opcji :through.
4.3.2.6 :extend
Opcja :extend sprawia, że wskazany moduł rozszerza proxy asocjacji. Rozszerzenia asocjacji są opisane dalej w tym podręczniku.
4.3.2.7 :finder_sql
Zwykle Railsy automatycznie tworzą odpowiednie zapytanie SQL do pobrania uczestników asocjacji. Z opcją :finder_sql możesz zdefiniować takie zapytanie samodzielnie. Jeśli pobieranie obiektów wymaga wielu skomplikowanych operacji na wielu tabelach, może to być konieczne.
4.3.2.8 :foreign_key
Zgodnie z konwencją, Railsy “domyślają się”, że kolumna używana do przechowywania klucza obcego w innym modelu to nazwa pierwszego modelu z przyrostkiem _id. Opcja :foreign_key umożliwia ustawienie nazwy klucza obcego bezpośrednio:
class Customer < ActiveRecord::Base
has_many :orders, :foreign_key => "cust_id"
end
W żadnym z wypadków Railsy nie będą same tworzyły dla ciebie kolumn klucza obcego. Musisz zdefiniować teklucze w swoich migracjach.
4.3.2.9 :group
Opcja :group dostarcza nazwę atrybutu według której wyniki mają być pogrupowane. W SQL użyłbyś klauzuli GROUP BY.
class Customer < ActiveRecord::Base
has_many :line_items, :through => :orders, :group => "orders.id"
end
4.3.2.10 :include
Możesz użyć opcji :include do podania drugorzędnych asocjacji, które zostaną załadowane wtedy, gdy zostanie użyta dana asocjacja. Na przykład, rozważmy takie modele:
class Customer < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
end
Jeśli często pobierasz pojedyncze pozycje zamówień bezpośrednio od klientów (@customer.orders.line_items), możesz sprawić, by wój kod był wydajniejszy, załączając pozycje w zamówieniu w asocjacji z zamówieniami do klientów:
class Customer < ActiveRecord::Base
has_many :orders, :include => :line_items
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
end
4.3.2.11 :limit
Opcja :limit pozwala ograniczyć liczbę obiektów, które będą pobierane dzięki asocjacji.
class Customer < ActiveRecord::Base
has_many :recent_orders, :class_name => "Order", :order => "order_date DESC", :limit => 100
end
4.3.2.12 :offset
Opcja :offset pozwala zdefiniować offset dla obiektów pobieranych w asocjacji. Na przykład, jeśli ustawisz :offset => 11, zostanie pominiętych pierwszych 11 rekordów (por. z klauzulą SQL LIMIT 0,1. Offset jest tą pierwszą liczbą – przyp.tłum)
4.3.2.13 :order
Opcja :order ustawia porządek w jakim powiązane obiekty będą “wyciągane” (w składni SQL użyłbyś klauzuli ORDER BY).
class Customer < ActiveRecord::Base
has_many :orders, :order => "date_confirmed DESC"
end
4.3.2.14 :primary_key
Zgodnie z konwencją Railsy “domyślają się” że kolumna z kluczem podstawowym w modelu to “id”. Możesz to zmienić i samodzielnie określić klucz podstawowy dzięki opcji :primary_key
4.3.2.15 :readonly
Jeśli ustawisz true dla opcji :readonly, asocjowany obiekt będzie tylko do odczytu, gdy wywoływana jest asocjacja.
4.3.2.16 :select
Użyj opcji :select by zastąpić klauzulę SQL SELECT, która służy do wyszukiwania danych w wiązanym obiekcie. Domyślnie Railsy pobierają wszystkie kolumny.
Jeśli określisz opcję :select, musisz być pewien, że zawarłeś w nim kolumnę klucza podstawowego (lub klucza obcego) asocjowanego modelu. Jeśli tego nie zrobisz, możesz spowodować błąd Railsów.
4.3.2.17 :source
Opcja :source określa źródło asocjacji dla asocjacji typu has_many :through. Tylko wtedy potrzebujesz korzystać z tej opcji, kiedy nazwa źródła asocjacji nie może być automatycznie wygenerowana (stworzona) z nazwy asocjacji.
4.3.2.18 :source_type
Opcja :source_type określa typ źródła asocjacji dla asocjacji has_one :through; związane jest to z asocjacją polimorficzną.
4.3.2.19 :through
Opcja :through określa model, za pośrednictwem którego wykonywane są zapytania. Asocjacje has_many :through umożliwiają implementację relacji wiele-do-wiele jak to opisano wcześniej w tej instrukcji.
4.3.2.20 :uniq
Ustaw opcję :uniq => true aby usunąć ze zbiorów duplikaty. Ta opcja jest najbardziej użyteczna w połączeniu z opcją :through.
class Person < ActiveRecord::Base
has_many :readings
has_many :posts, :through => :readings
end
person = Person.create(:name => 'john')
post = Post.create(:name => 'a1')
person.posts << post
person.posts << post
person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]
Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]
In the above case there are two readings and person.posts brings out both of them even though these records are pointing to the same post.
Now let’s set :uniq to true:
class Person
has_many :readings
has_many :posts, :through => :readings, :uniq => true
end
person = Person.create(:name => 'honda')
post = Post.create(:name => 'a1')
person.posts << post
person.posts << post
person.posts.inspect # => [#<Post id: 7, name: "a1">]
Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]
In the above case there are still two readings. However person.posts shows only one post because the collection loads only unique records.
4.3.2.21 :validate
Kiedy ustawisz na false opcję :validate, wiązane obiekty nie będą nigdy walidowane, kiedy będziesz zapisywać obiekt. Domyślne ustawienie to true: wiązane obiekty są walidowane kiedy je zapisujesz.
4.3.3 Kiedy obiekty są zapisywane?
Kiedy przypisujesz obiekt do asocjacji has_many, obiekt jest automatycznie zapisywani (żeby zaktualizować jego klucz obcy). Jeśli wiążesz wiele obiektów w jednym zapytaniu, wszystkie zostaną zapisane.
Jeśli którykolwiek z tych zapisów nie powiedzie się z winy błędu walidacji, wtedy rozkaz przypisania zwróci false, a samo przypisanie jest anulowane.
Jeśli obiekt-rodzic (ten, w którym jest deklaracja has_many) nie został zapisany (czyli new_record? zwróci true), wtedy dodane obiekty-dzieci również nie zostaną zapisane. Wszyscy niezapisani uczestnicy asocjacji będą automatycznie zapisani, kiedy rodzic zostanie zapisany.
Jeśli chcesz przypisać obiekt do asocjacji has_many bez jego zapisywania, użyj metody _collection_.build
4.4 Asocjacja has_and_belongs_to_many
Asocjacja has_and_belongs_to_many tworzy relację wiele-do-wiele z innym modelem. W języku baz danych wiąże się się dwie klasy poprzez załączenie pośredniej tabeli, która zawiera klucze obce odnoszące się do każdej z klas.
4.4.1 Dodane metody
Kiedy zadeklarujesz asocjację has_and_belongs_to_many, zyskujesz automatycznie 13 metod powiązanych z tą asocjacją:
- collection(force_reload = false)
- collection<<(object, ...)
- collection.delete(object, ...)
- collection=objects
- collection_singular_ids
- collection_singular_ids=ids
- collection.clear
- collection.empty?
- collection.size
- collection.find(...)
- collection.exist?(...)
- collection.build(attributes = {})
- collection.create(attributes = {})
We wszystkich wymienionych metodach collection zastępuje się symbolem przekazanym jako pierwszy argument do has_many, a także collection_singular zastępuje się nazwą tegoż symbolu w liczbie pojedynczej (w języku angielskim). Na przykład, w takiej deklaracji:
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
Każda instancja modelu part będzie posiadała poniższe metody
assemblies(force_reload = false)
assemblies<<(object, ...)
assemblies.delete(object, ...)
assemblies=objects
assembly_ids
assembly_ids=ids
assemblies.clear
assemblies.empty?
assemblies.size
assemblies.find(...)
assemblies.exist?(...)
assemblies.build(attributes = {}, ...)
assemblies.create(attributes = {})
4.4.1.1 Metody dla dodatkowych kolumn
Jeżeli tabela dołączana do asocjacji has_and_belongs_to_many posiada dodatkowe kolumny poza dwoma kluczami obcymi, kolumny te powinny zostać dodane jako atrybuty do rekordów pobieranych za pomocą tej asocjacji. Rekordy zwrócone z dodatkowymi atrybutami będą zawsze tylko do odczytu, ponieważ Railsy nie potrafią zapisywać zmian dla tych atrybutów
Użycie dodatkowych atrybutów w dołączanej tabeli w asocjacji has_and_belongs_to_many jest dawną tendencją i nie wspiera się jej. Jeśli potrzebujesz tego rodzaju skomplikowanych zachowań w tabeli łączącej dwa modele w relacji wiele-do-wiele, powinieneś użyć asocjacji has_many :through zamiast has_and_belongs_to_many.
4.4.1.2 collection(force_reload = false)
Metoda collection zwraca tablicę [array] wszystkich wiązanych obiektów. Jeśli nie istnieją żadne wiązane obiekty, zwraca pustą tablicę.
@assemblies = @part.assemblies
4.4.1.3 collection<<(object, ...)
Metoda collection<< dodaje jeden lub więcej obiektów do zbioru poprzez dodanie rekordów w tabeli łączącej (pośredniej).
@part.assemblies << @assembly1
Ta metoda jest aliasowana przez collection.concat i collection.push.
4.4.1.4 collection.delete(object, ...)
Metoda collection.delete usuwa jeden lub więcej obiektów ze zbioru, poprzez usunięcie rekordów z tabeli łączącej. Ta metoda nie usuwa żadnych innych obiektów poza tymi z tabeli łączącej.
@part.assemblies.delete(@assembly1)
4.4.1.5 collection=objects
Metoda collection= usuwa i dodaje odpowiednie obiekty tak, żeby dana kolekcja zawierała tylko podane (wymienione) obiekty.
4.4.1.6 collection_singular_ids
Metoda collection_singular_ids zwraca tablicę identyfikatorów obiektów w zbiorze.
@assembly_ids = @part.assembly_ids
4.4.1.7 collection_singular_ids=ids
Metoda collection_singular_ids= sprawia, że w zbiorze znajdują się tylko te obiekty, których wartość podstawowego klucza równa jest wartościom dostarczonym do metody.
4.4.1.8 collection.clear
Metoda collection.clear usuwa wszystkie elementy zbioru poprzez usunięcie wierszy z dołączanej tabeli. Nie niszczy to żadnych wiązanych obiektów.
4.4.1.9 collection.empty?
Metoda collection.empty? zwraca wartośc true, jeśli zbiór nie posiada żadnych związanych obiektów.
<% if @part.assemblies.empty? %>
Ta część nie została użyta w żadnym zbiorze
<% end %>
4.4.1.10 collection.size
Metoda collection.size zwraca liczbę obiektów w zbiorze.
@assembly_count = @part.assemblies.size
4.4.1.11 collection.find(...)
Metoda collection.find służy do wyszukiwania obiektów w zbiorze. Korzysta ona z tej samej składni i tych samych opcji, co ActiveRecord::Base.find. To także daje nam warunek konieczności istnienia obiektu w zbiorze
@new_assemblies = @part.assemblies.find(:all, :conditions => ["created_at > ?", 2.days.ago])
4.4.1.12 collection.exist?(...)
Metoda collection.exist? sprawdza, czy obiekt spełniający podane warunki istnieje w danym zbiorze. Metoda ta używa takiej samej składni i takich samych opcji jak ActiveRecord::Base.exists?.
4.4.1.13 collection.build(attributes = {})
Metoda collection.build zwraca jeden lub więcej nowych obiektów wiązanego typu. Obiekty te będą konstruowane na podstawie podanych atrybutów. Połączenie poprzez ich klucz obcy zostanie stworzone, ale powiązane obiekty nie będą zapisane.
@assembly = @part.assemblies.build({:assembly_name => "Transmission housing"})
4.4.1.14 collection.create(attributes = {})
Metoda collection.create zwraca nowy obiekt wiązanego typu. Ten obiekt będzie konstruowany z ustawionych atrybutów. Powiązanie z pośrednią tabelą będzie stworzone, a wiązany obiekt zostanie zapisany (jeśli przejdzie pomyślnie przez walidację).
@assembly = @part.assemblies.create({:assembly_name => "Transmission housing"})
4.4.2 Opcje dla has_and_belongs_to_many
W wielu sytuacjach możesz użyć asocjacji has_and_belongs_to_many bez potrzeby jej dostosowywania. Ale możesz też zmieniać domyślne ustawienia na kilka sposobów. Ten rozdział przedstawi ci opcje, których możesz używać, kiedy zadeklarujesz asocjację has_and_belongs_to_many. Na przykład asocjacja z kilkoma opcjami może wyglądać tak:
class Parts < ActiveRecord::Base
has_and_belongs_to_many :assemblies, :uniq => true, :read_only => true
end
Asocjacja has_and_belongs_to_many wspiera takie opcje:
- :association_foreign_key
- :class_name
- :conditions
- :counter_sql
- :delete_sql
- :extend
- :finder_sql
- :foreign_key
- :group
- :include
- :insert_sql
- :join_table
- :limit
- :offset
- :order
- :readonly
- :select
- :uniq
- :validate
4.4.2.1 :association_foreign_key
Zgodnie z konwencją Railsy “domyślają się”, że kolumna w tabeli łączącej, używana do przechowywania klucza obcego wskazującego na inny model to nazwa tego modelu z przyrostkiem _id. Opcja :association_foreign_key pozwala Ci ustawić nazwę obcego klucza bezpośrednio.
Opcje :foreign_key oraz :association_foreign_key są przydatne gdy ustawiasz samodniesienie się tabeli (odwołanie rekurencyjne) dla typu relacji wiele-do wiele. Na przykład:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends, :class_name => "User",
:foreign_key => "this_user_id", :association_foreign_key => "other_user_id"
end
4.4.2.2 :class_name
Jeśli nazwa innego modelu nie może zostać utworzona z nazwy asocjacji, możesz użyć opcji :class_name, by dostarczyć nazwę modelu. Na przykład jeśli part (część) ma wiele assemblies (zadań), ale aktualna nazwa zawierająca assemblies to “Gadget”, możesz to ustawić w ten sposób:
class Parts < ActiveRecord::Base
has_and_belongs_to_many :assemblies, :class_name => "Gadget"
end
4.4.2.3 :conditions
Opcja :conditions pozwala określić warunki, jakie wiązany obiekt musi spełniać (W składni SQL użyłbyś klauzuli WHERE).
class Parts < ActiveRecord::Base
has_and_belongs_to_many :assemblies, :conditions => "factory = 'Seattle'"
end
Możesz także określać warunki używając hash-a:
class Parts < ActiveRecord::Base
has_and_belongs_to_many :assemblies, :conditions => { :factory => 'Seattle' }
end
Jeżeli użyjesz opcji :conditions (w notacji z hashem), tworzenie rekordów przez tą asocjację zostanie zautomatyzowane według zawartości hasha. W tym przypadku, używając @parts.assemblies.create lub @parts.assemblies.build, tworzy się zamówienia tam, gdzie kolumna factory ma wartość “Seattle”.
4.4.2.4 :counter_sql
Zwykle Railsy automatycznie tworzą odpowiednie zapytanie SQL do liczenia uczestników asocjacji. Z opcją :counter_sql możesz zdefiniować własne zapytanie sql do policzenia ich.
Jeśli określisz :finder_sql, ale nie stworzysz :counter_sql, wtedy funkcja licząca sql SELECT COUNT(*) FROM zostanie zastąpiona klauzulą SELECT ... FROM, wykorzystującą deklaracje z :finder_sql.
4.4.2.5 :delete_sql
Zwykle Railsy automatycznie generują właściwe zapytanie SQL w celu usunięcia związków pomiędzy związanymi klasami. Dzięki opcji delete_sql możesz zdefiniować własne zapytanie SQL by usunąć je samodzielnie.
4.4.2.6 :extend
Opcja :extend sprawia, że nazwany moduł rozszerza proxy asocjacji. Rozszerzenia asocjacji są opisane dalej w tym podręczniku.
4.4.2.7 :finder_sql
Zwykle Railsy automatycznie tworzą odpowiednie zapytanie SQL do pobrania uczestników asocjacji. Z opcją :finder_sql możesz zdefiniować takie zapytanie samodzielnie. Jeśli pobieranie obiektów wymaga wielu skomplikowanych operacji na wielu tabelach, może to być konieczne.
4.4.2.8 :foreign_key
Zgodnie z konwencją, Railsy “domyślają się”, że kolumna używana do przechowywania klucza obcego w innym modelu to nazwa pierwszego modelu z przyrostkiem _id. Opcja :foreign_key umożliwia ustawienie nazwy klucza obcego bezpośrednio:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends, :class_name => "User",
:foreign_key => "this_user_id", :association_foreign_key => "other_user_id"
end
4.4.2.9 :group
Opcja :group dostarcza nazwę atrybutu według której wyniki mają być pogrupowane, W SQL użyłbyś klauzuli GROUP BY.
class Parts < ActiveRecord::Base
has_and_belongs_to_many :assemblies, :group => "factory"
end
4.4.2.10 :include
Możesz użyć opcji :include do podania drugorzędnych asocjacji, które zostaną załadowane wtedy, gdy zostanie użyta dana asocjacja.
4.4.2.11 :insert_sql
Zwykle Railsy generują automatycznie odpowiednie zapytanie SQL do stworzenia powiązania między asocjowanymi klasami. Jednak z opcją :insert_sql możesz sam określić kompletne zapytanie SQL do tworzenia klas.
4.4.2.12 :join_table
Jeśli domyślna nazwa tabeli łączącej utworzona w oparciu o porządek alfabetyczny nie jest tym, czego oczekujesz, możesz uzyć opcji :join_table by nadpisać domyślne ustawienia
4.4.2.13 :limit
Opcja :limit pozwala ograniczyć liczbę obiektów, które będą pobierane dzięki asocjacji.
class Parts < ActiveRecord::Base
has_and_belongs_to_many :assemblies, :order => "created_at DESC", :limit => 50
end
4.4.2.14 :offset
Opcja :offset pozwala zdefiniować offset dla obiektów pobieranych w asocjacji. Na przykład, jeśli ustawisz :offset => 11, zostanie pominiętych pierwszych 11 rekordów (por. z klauzulą SQL LIMIT 0,1. Offset jest tą pierwszą liczbą – przyp.tłum)
4.4.2.15 :order
Opcja :order ustawia porządek w jakim powiązane obiekty będą “wyciągane” (w składni SQL użyłbyś klauzuli ORDER BY).
class Parts < ActiveRecord::Base
has_and_belongs_to_many :assemblies, :order => "assembly_name ASC"
end
4.4.2.16 :readonly
Jeśli ustawisz true dla opcji :readonly, asocjowane obiekty będą tylko do odczytu, gdy wywoływana jest asocjacja.
4.4.2.17 :select
Użyj opcji :select by zastąpić klauzulę SQL SELECT, która służy do wyszukiwania danych w wiązanym obiekcie. Domyślnie Railsy pobierają wszystkie kolumny
4.4.2.18 :uniq
Ustaw opcję :uniq => true aby usunąć ze zbiorów duplikaty.
4.4.2.19 :validate
Kiedy ustawisz na false opcję :validate, wiązane obiekty nie będą nigdy walidowane, kiedy będziesz zapisywać obiekt. Domyślne ustawienie to true: wiązane obiekty są walidowane kiedy je zapisujesz.
4.4.3 Kiedy obiekty są zapisywane?
Kiedy przypisujesz obiekt do asocjacji has_and_belongs_to_many, obiekt jest automatycznie zapisywany (żeby zaktualizować tabelę wiążącą). Jeśli wiążesz wiele obiektów w jednym zapytanie, wszystkie zostaną zapisane.
Jeśli którykolwiek z tych zapisów nie powiedzie się z winy błędu walidacji, wtedy rozkaz przypisania zwróci false, a samo przypisanie będzie anulowane.
Jeśli obiekt-rodzic (ten, w którym jest deklaracja has_and_belongs_to_many) nie został zapisany (czyli new_record? zwróci true), wtedy dodane obiekty-dzieci również nie zostaną zapisane. Wszyscy niezapisani uczestnicy asocjacji będą automatycznie zapisani, kiedy rodzic zostanie zapisany.
Jeśli chcesz przypisać obiekt do asocjacji has_many bez jego zapisywania, użyj metody collection.build.
4.5 Callbacki asocjacji
Funkcje z callbackiem “zaczepiają się” o cykl życia obiektów Active Record, pozwalając na pracę z tymi obiektami w różnych momentach. Na przykład, możesz użyć callbacka :before_save i sprawić, by coś się stało tuż przed tym, kiedy obiekt zostanie zapisany.
Callbacki w asocjacjach podobne są do zwykłych callbacków, z tym, że (te pierwsze - przyp.tłum) uruchamiane są przez zdarzenia w cyklu życia zbioru. W asocjacjach dostępne są cztery callbacki:
- before_add
- after_add
- before_remove
- after_remove
Callback asocjacji definiuje się dodając opcje do deklaracji asocjacji. Na przykład:
class Customer < ActiveRecord::Base
has_many :orders, :before_add => :check_credit_limit
def check_credit_limit(order)
...
end
end
Railsy przekazują do callbacka obiekt, który ma być dodany lub wymazany.
Możesz użyć stosu [stack] wywołań, które będą traktowane jak pojedynczy callback, przekazując wywołania do w tablicy:
class Customer < ActiveRecord::Base
has_many :orders, :before_add => [:check_credit_limit, :calculate_shipping_charges]
def check_credit_limit(order)
...
end
def calculate_shipping_charges(order)
...
end
end
Jeśli callback before_add rzuci wyjątek, obiekt nie może zostać dodany do zbioru. Podobnie jeśli before_remove rzuci wyjątek, nie można obiektu usunąć ze zbioru.
4.6 Rozszerzenia asocjacji
Nie jesteś ograniczony do funkcjonalności, którą Railsy automatycznie wbudowuje w obiekty proxy asocjacji. Możesz rozszerzać te obiekty używając anonimowych modułów, dodawanie nowych finderów, kreatorów i innych metod. Na przykład:
class Customer < ActiveRecord::Base
has_many :orders do
def find_by_order_prefix(order_number)
find_by_region_id(order_number[0..2])
end
end
end
Jeśli posiadasz rozszerzenia, które powinny zostać udostępnione dla wielu asocjacji, możesz użyć modułu o nazwie extension (rozszerzenie). Na przykład:
module FindRecentExtension
def find_recent
find(:all, :conditions => ["created_at > ?", 5.days.ago])
end
end
class Customer < ActiveRecord::Base
has_many :orders, :extend => FindRecentExtension
end
class Supplier < ActiveRecord::Base
has_many :deliveries, :extend => FindRecentExtension
end
By załączyć więcej niż jeden moduł rozszerzeń w jednej asocjacji, użyj tablicy nazw:
class Customer < ActiveRecord::Base
has_many :orders, :extend => [FindRecentExtension, FindActiveExtension]
end
Rozszerzenia mogą pobierać informacji o asocjacji, dzięki użyciu trzech akcesorów:
- proxy_owner zwraca obiekt, którego asocjacja jest częścią.
- proxy_reflection zwraca “odbicie” obiektu opisywanego przez asocjację.
- proxy_target zwraca powiązany element dla belongs_to lub has_one lub zbiór wiązanych obiektów dla has_many lub has_and_belongs_to_many.
5 Changelog
- February 11, 2009, Initial version of polish translation.
- September 28, 2008: Corrected has_many :through diagram, added polymorphic diagram, some reorganization by Mike Gunderloy . First release version.
- September 22, 2008: Added diagrams, misc. cleanup by Mike Gunderloy (not yet approved for publication)
- September 14, 2008: initial version by Mike Gunderloy (not yet approved for publication)