Więcej na rubyonrails.pl: Start | Pobierz | Wdrożenie | Kod (en) | Screencasty | Dokumentacja | Ekosystem | Forum | IRC

Przewodnik po asocjacjach modułu Active Record

Niniejszy przewodnik obejmuje opis asocjacji (associations) – elementów modułu Active Record. Z pomocą tego przewodnika będziesz w stanie:

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

diagram asocjacji belongs_to

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

diagram asocjacji has_one

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.).

diagram asocjacji has_many

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

diagram asocjacji has_many :through

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

diagram asocjacji has_one :through

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

diagram asocjacji has_and_belongs_to_many

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

diagram asocjacji polimorficznej

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

Lighthouse ticket

  • 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)