Pages Navigation Menu

Tworzenie Aplikacji Internetowych

Zagnieżdżone formularze w Ruby on Rails 4.0

Cały wieczór męczyłem się nad dodaniem zagnieżdżonych atrybutów do mojej uczelnianej aplikacji tworzonej w Rails 4.0. Aplikację udostępnię na GIT do swobodnego wglądu., więc jeśli Cię to interesuje, tu możesz przeczytać o podstawach pacy z git. Jeśli szukasz tutoriala, jak utworzyć i zaimplementować zagnieżdżone formularze w Rails, poleciłbym Ci ten film, ale tego nie zrobię – z tego prostego powodu, że jest on nieaktualny, jak wszystkie informacje na ten temat jakie udało mi się znaleźć. Po kilku godzinach frustracji i zastanawiania się, dlaczego zagnieżdżone formularze nie działają mi w Rails 4 (choć dodawanie zagnieżdżonych atrybutów działa w konsoli) w końcu udało mi się rozwiązać ten problem.

Co ma gniazdo do formularza?Do czego służą zagnieżdżone formularze?

Gniazdo to wygoda. Warto o tym pamiętać, bo ma to przełożenie na programowanie. Zagnieżdżone formularze umożliwiają tworzenie edycji i obiektów powiązanych ze sobą różnymi relacjami. Przykładowo, jeśli masz obiekt „article” pozwiązany relacją has_many z komentarzami, być może chciałbyś mieć możliwość utworzenia artykułu z jednoczesnym dodaniem do niego komentarza. Innym przykładem będzie utworzenie ankiety – ankieta sama w sobie nie jest przydatna, jeśli oprócz atrybutów ją opisujących, jak nazwa czy data, nie składa się z pytań. Dobrze by było móc przy tworzeniu ankiety utworzyć od razu kilka pytań z nią powiązanych, prawda?

Problem, z którym spotkałem się w mojej aplikacji, opisywał jeszcze inną relację. Chodzi o relacje osób z rolami, jakie pełnią. Otóż do mojej aplikacji rails mogą się logować różne osoby – studenci, prowadzący oraz recenzenci. Każda z tych osób ma sporą ilość tych samych atrybutów, jak imię, nazwisko, email czy adres, więc naturalną opcją było wyrzucenie wszystkich danych osobowych do osobnej tabeli i powiązanie ich relacjami. Nie mogłem załatwić tego dodaniem dodatkowego atrybutu do tabeli opisującego rolę, ponieważ każda z tych osób posiadała własne pola dostępne wyłącznie dla niej (np. numer indeksu dla studenta).

Pojawiła sie konieczność utworzenia funkcji: zarejestruj się, jako: student / prowadzący / recenzent. Różne formularze powinny zawierać pola specyficzne dla poszczególnych osób, ale też pola ogólne z tabeli gromadzącej dane osobowe.

Przykład zagnieżdżonego formularza w Ruby on Rails

Tutaj z pomocą przychodzą właśnie zagnieżdżone formularze. Rayan Bytes opisał na swoim blogu jak stworzyć zagnieżdżony formularz w rails 3.2, jednak w 4 generacji RoR wprowadzono kilka zmian, przez co jego tutorial stał się nieaktualny.

Jak utworzyć zagnieżdżone formularze w rails 4.0

Na początku potrzebujesz dwa obiekty z opisanymi relacjami. Przykład, którym sie posłużę, to relacja student <-> osoba.

rails new nested_form
rails g scaffold student index:integer
rails g model person first_name:string last_name:string email:string phone:number student:references
rake db:migrate
class Student < ActiveRecord::Base
  has_one :person
end
class Person < ActiveRecord::Base
  belongs_to :student
end

Użyłem rusztowania do szybkiego utworzenia ścieżek i akcji. Zauważ, że przy tworzeniu osoby utworzyłem wyłącznie jej model, bez tworzenia niepotrzebnych widoków. Osoba będzie obecna w aplikacji wyłącznie jako zagnieżdżony atrybut, jako część innych obiektów, nie będzie występować samodzielnie, nie potrzebujemy więc osobnych widoków.

Kolejnym krokiem jest poinformowanie aplikacji, by akceptowała zagnieżdżone atrybuty

class Student < ActiveRecord::Base
  has_one :person
  accepts_nested_attributes_for :person
end

Teraz pozostaje nam utworzyć formularz, który umożliwi automatyczne generowanie osoby z powiązanym z nią studentem. Do tego celu railsy posiadają form helper o nazwie fields_for . Należy dodać sekcję pól dla atrybutu :person i utworzyć wszysktkie potrzebne pola wewnątrz tego bloku.

<%= form_for(@student) do |f| %>
  <div>
    <%= f.label :index %><br>
    <%= f.number_field :index %>
  </div>
  <%= f.fields_for :person do |builder|%>
    <div>
      <%= builder.label :first_name %><br>
      <%= builder.text_field :first_name %>
    </div>
    <div>
      <%= builder.label :last_name %><br>
      <%= builder.text_field :last_name %>
    </div>
  <% end %>
  <div>
    <%= f.submit %>
  </div>
<% end %>

Teraz już mamy prawie wszystko, jednak po odświeżeniu naszej witryny okazuje się, że dodane przez nas pola się nie pokazują – w źródle strony nie są wogóle widoczne, czyli nie zostały nawet utworzone. Dzieje się tak dlatego, że relacja studenta z osobą jest przez asocjację has_one.

Formularz bez zagnieżdżonych pól

Osoba należy do studenta, więc w swojej tabeli posiada index student_id umożliwiający identyfikację studenta. Gdybyśmy więc zrobili relacje odwrotnie, aplikacja wiedziałaby, że model :person posiada atrybut :student, powyższe pola pokazałyby się więc od razu.

Odwołanie się jednak w drugą stronę nie zadziała, ponieważ student nie przechowuje indeksu osoby w swojej tabeli. Należy więc najpierw poinformować aplikację, że student posiada dane osobowe. W tym celu należy użyć buildera – metody tworzącej modele powiązane relacją has_one lub has_many z innymi. W akcji new oraz edit kontrolera obiektu student dodaj instrukcję @student.build_person

# GET /admin/students/new
def new
  @student = Student.new
  @student.build_person
end
# GET /admin/students/1/edit
def edit
  @student.build_person
end

Teraz po odświeżeniu strony zobaczysz, że dodane przez nas pola zostały utworzone. Więcej o generowaniu atrybutów i asocjacjach w rails możesz przeczytać tutaj. I na tym kończą się tutoriale opisujące zagnieżdżone formularze dla rails 3.2 . Niestety w Rails 4.0 powyższe kroki nie wystarczą, by całość zadziałała.

Jeśli spróbujesz dodać teraz jakiegoś studenta, utworzy się on pomyślnie, jednak dane osobowe nie zostaną do niego przypisane – ba, nawet nie zostaną utworzone. Natomiast kiedy spróbujesz wykonać powyższe kroki w konsoli, wszystko pójdzie gładko i zadziała.

Rozwiązanie tego problemu jest bardzo proste. Domyślnie atrybuty obiektów są prywatne – niedostępne poza samym modelem i jego wewnętrzymi funkcjami. Prawa dostępu do rails 3 precyzowało się właśnie w modelu za pomocą słów kluczowych attr_accessible oraz attr_protected. W rails 4 te informacje przewędrowały do kontrolera i tam należy zarządzać dostępem do danych obiektów. Pliki wygenerowane przez nasz scaffold utworzyły w kontrolerze takie linie:

private
# Use callbacks to share common setup or constraints between actions.
def set_student
  @student = Student.find(params[:id])
end

# Never trust parameters from the scary internet, only allow the white list through.
def student_params
  params.require(:student).permit(:index)
end

W akcji create jest zaś linia odpowiadająca za utworzenie nowego studenta z wykorzystaniem wartości zwracanej przez metodę student_params . Oto, co się dzieje. Podczas generowania formularza przeglądarka wysyła do aplikacji rządanie by wywołać w kontrolerze metodę new. W metodzie new tworzymy nowego studenta z pustymi polami oraz za pomocą buildera tworzymy dane osobowe ze wszystkimi polami pustymi, oprócz pola student_id. To pole jest powiązane z naszym studentem. Następnie nasz formularz się wyświetla, uzupełniamy go o dane i klikamy przycisk zatwierdzający naszą operację.

To działanie znów wysyła rządanie do aplikacji, tym razem o wykonanie metody create, co też się dzieje. A tam w pierwszej linii tworzymy nowego studenta z parametrami zwracanymi przez metodę student_params. Zauważ jednak, że do tej metody przekazujemy tylko jeden parametr :index. Metoda permit bowiem precyzuje, które atrybuty będą dostępne w kontrolerze, wszystkie w niej niewymienione ustawiając jako prywatne.

Po kliknięciu na przycisk create tworzony jest więc nowy obiekt student wyłącznie z parametrem :index, a więc bez poprzednio wygenerowanej osoby i ten obiekt jest zapisywany do bazy danych.

By podejrzeć w jaki sposób zanieżdżone formularze przesyłają dane do modelu, możesz w akcji create wpisać

puts params[:student]

Wyświetli Ci to hash w konsoli, w oknie w którym odpaliłeś serwer. Niestety dość ciężko jest to zauważyć, bo okno to wyświetla wszystkie logi serwera, dlatego warto powtórzyć tę instrukcję w pętli kilka razy by łatwiej to znaleźć. U mnie będzie to wyglądać tak.

Hash zwracany przez zagnieżdżone formularze

Hash zwracany przez zagnieżdżony formularz, zawierający parametry przekazywane do kontrolera

Jak widać, formularz zwraca oprócz numeru indeksu dodatkowy hash, którego nigdzie nie definiowaliśmy. Railsy generują to automatycznie na podstawie struktury formularza – odpowiada za to sekcja fields_for :person.

Wydawałoby się, że usunięcie metody permit z kontrolera umożliwi nam korzystanie z wszystkich atrybutów, ale jest to błędne myślenie – domyślna konfiguracja owszem udostępnia wszystkie atrybuty, ale wyłącznie główne. Atrybuty zagnieżdżone, tj. wewnątrz :person_attributes zostaną pominięte. Jeśli usunęlibyśmy z kontrolera metodę permit(attributes), a zamiast niej wpisali po prostu permit!, bez żadnych atrybutów ale za to z wykrzyknikiem, wszystkie parametry zostałyby przekazane do naszej metody create i utworzony zostałby student ze wszystkimi polami z formularza, także zagnieżdżonymi. Niemniej jest to złe rozwiązanie, ponieważ aplikacja zyskałaby dostęp do takich pól, jak created_at albo id, czego z oczywistych względów należy unikać.

Rozwiązanie problemu jest więc następujące. Dodaj do metody permit atrybut person_attributes razem z wszystkimi polami, które zawiera.

def student_params
  params.require(:student).permit(:index, person_attributes: [:first_name, :last_name])
end

W ten sposób po kliknięciu przycisku zatwierdzającego nasz formularz do funkcji create zostaną przekazane atrybuty zagnieżdżone, umożliwiające utworzenie powiązanego ze studentem obiektu.

Mam nadzieję, że ten wpis pozwoli Ci na zrozumienie działania zagnieżdżonych formularzy w Ruby on Rails oraz zaoszczędzenie kilku godzin bezproduktywnego przeglądania stron internetowych i zastanawiania się nad tym problemem. Tak, mogłem odpowiedź na twój problem przekazać Ci w kilku liniach tekstu, jednak głównym problemem w tego sytuacjach jest brak zrozumienia mechanizmów działania tego z czym pracujemy. Jeśli zrozumiesz jak działa Ruby on Rails, będziesz świetnym programistą nawet bez dokładnej znajomości składni.

Jeśli artykuł Ci się podobał lub w jakis sposób pomógł, kliknij plusa albo poleć znajomym. Nie bój się też zostawić komentarza w przypadku znalezienia błędu, niejasności, czy lepszego rozwiązania ;).

Pozdrowienia
Sebastian Wilgosz

Dodaj Komentarz

Komentarze: 2

  1. Kolejna lekcja dająca dużo do przemyślenia! Zastanawiam się, czy zagnieżdżone formularze są wykorzystywane przy standardowym tworzeniu aplikacji. Czy to jest często stosowane w praktyce?
    Tomasz ostatnio napisał : Lista Mocnych Profili by Tomasz Bołoz – już w sprzedaży!My Profile

Dodaj Komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *


*

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Ostatnie wpisy

Loading Facebook Comments ...