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

Helpery formularzy w Railsach

Formularze w aplikacjach webowych są istotnym elementem jeśli chodzi o wprowadzanie danych przez użytkownika. Jednakże, znaczniki formularzy mogą stać się kłopotliwe do napisania i zarządzania, ze względu na konieczność kontrolowania ich nazw i ich licznych atrybutów. Railsy radzą sobie z tymi zawiłościami, gdyż są zaopatrzone w helpery widoku służące do generowania kodu formularzy. Ze względu na to, że helpery mogą być użyte na wiele sposobów, programiści powinni znać wszystkie różnice pomiędzy metodami ich zastosowania przed wykorzystaniem w praktyce.

W tym przewodniku dowiesz się:

Ten przewodnik nie jest kompletną dokumentacją dostępnych helperów formularzy i ich argumentów. W celu uzyskania pełnej dokumentacji odwiedź the Rails API documentation .

1 Podstawowe formularze

Najprostszym helperem jest: form_tag.

<% form_tag do %> Zawartość formularza <% end %>

Wywołany bez argumentów tworzy element formularza, którego pole “action” odwołuje się do bieżącej strony, poprzez metodę “post”. (w celu poprawienia czytelności kodu dodano znaki nowej linii):

Przykładowy kod wygenerowany przez form_tag:

<form action="/home/index" method="post"> <div style="margin:0;padding:0"> <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> </div> Zawartość formularza </form>

Jeśli przyjrzysz się dokładnie, zauważysz, że helper wygenerował coś, czego nie określiliśmy: znacznik div , a w nim pole typu “hidden”. Jest to forma zabezpieczenia w Railsach o nazwie cross-site request forgery protection – występująca zawsze w przypadku formularza, którego metodą nie jest “get” (oczywiście pod warunkiem, że zabezpieczenia te są włączone). Możesz dowiedzieć się o tym więcej czytając: Bezpieczeństwo w Ruby on Rails.

W tym przewodniku- w celu uproszczenia kodu -element div z ukrytym polem nie będzie pokazywany.

1.1 Ogólne formularze wyszukiwania

Prawdopodobnie najprostszym formularzem często spotykanym na stronach WWW jest pojedyncze pole tekstowe do wyszukiwania haseł. Jego elementami są:

  1. formularz z metodą “GET”,
  2. etykieta pola formularza,
  3. pole tekstowe, oraz
  4. przycisk wysyłający dane.

W formularzach wyszukiwania zawsze używaj metody “GET”. Pozwoli to użytkownikowi na dodanie do zakładek wyników wyszukiwania i późniejszy powrót do nich. Podsumowując, Railsy zachęcają do korzystania z protokołu HTTP we właściwy sposób.

Do stworzenia tego rodzaju formularza użyj kolejno: form_tag, label_tag, text_field_tag oraz submit_tag.

Podstawowa wyszukiwarka:

<% form_tag(search_path, :method => "get") do %> <%= label_tag(:q, "Search for:") %> <%= text_field_tag(:q) %> <%= submit_tag("Search") %> <% end %>

search_path może być ścieżką określoną w “routes.rb”:

map.search “search”, :controller => “search”

Powyższy kod wygeneruje następujące znaczniki:

<form action="/search" method="get"> <label for="q">Search for:</label> <input id="q" name="q" type="text" /> <input name="commit" type="submit" value="Search" /> </form>

Oprócz text_field_tag i submit_tag dla każdego elementu formularza w HTML’u istnieje podobny helper.

Dla każdego pola formularza, atrybut ID jest generowany z jego nazwy (tak jak w przykładzie “q”). Wartości ID mogą być przydatne podczas korzystania z kaskadowych arkuszy stylów (CSS) i operowaniu na tych polach w JavaScript.

1.2 Tablice asocjacyjne w wywoływaniu helperów

Jak na razie dowiedziałeś się, że form_tag helper przyjmuje 2 argumenty: adres pola “action” i hash z opcjami. Hash definiuje metodę wysyłania danych formularza, a także atrybuty HTML, takie jak np. nazwa klasy.

Tak jak w przypadku helpera link_to, ścieżka nie musi być łańcuchem znaków. Może być na przykład tablicą asocjacyjną parametrów adresu URL, którą Railsy przekonwertują na poprawny URL. Jednak przekazywanie wielu hashy jest złą metodą, na przykład:

form_tag(:controller => "people", :action => "search", :method => "get", :class => "nifty_form") # => <form action="/people/search?method=get&class=nifty_form" method="post">

W przykaładzie próbowano przekazać dwa hashe, lecz interpreter Ruby widzi tylko jeden z nich, toteż Railsy skontruowały URL z niepotrzebnymi argumentami. Poprawnym sposobem na przekazywanie wielu hashy jako argumentów jest ograniczenie pierwszego (lub obu) nawiasami klamrowymi:

form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form") # => <form action="/people/search" method="get" class="nifty_form">

Większość helperów formularzy przyjmuje wiele hashy jako argumenty. Pamiętaj, że jeżeli Twój helper zwróci nieoczekiwany wynik, upewnij się, że ograniczyłeś parametry hashy w prawidłowy sposób.

Nie ograniczaj drugiego hasha, jeśli nie zrobiłeś tego przy tym pierwszym, w przeciwnym wypadku wywołanie Twojej metody zwóci błąd składni: expecting tASSOC.

1.3 Helpery do generowania elementów formularzy

Railsy dostarczają serię helperów do generowania elementów formularzy, takich jak pola wielokrotnego wyboru (checkboxes), pola służące do wprowadzenia tekstu (text field), czy pola jednokrotnego wyboru (radio buttons). Helpery te, z nazwą kończącą się _tag jak np.: text_field_tag, check_box_tag, itp., generują tylko pojedynczy <element formularza>. Pierwszym parametrem jest zawsze nazwa pola formularza. W kontrolerze nazwa ta będzie kluczem w hashu params używanym do pobierania wartości wprowadzonej przez użytkownika. Np. jeśli formularz zawiera:

<%= text_field_tag(:query) %>

kod kontrolera będzie używał

params[:query]

do pobrania wartości wprowadzonej przez użytkownika. Nadając nazwy polom, musisz musisz być świadom tego, że Railsy wykorzystują pewne reguły określające, czy wartości znajdują się w górnej warstwie hasha params, wewnątrz tablicy lub zagnieżdżonego hasha, itd. Możesz przeczytać więcej na ten temat w sekcji parameter_names. Po więcej szczegółów sięgnij do API documentation.

1.3.1 Pola wielokrotnego wyboru

Pola wielokrotnego wyboru dają użytkownikowi kilka opcji, które można zaznaczyć i odznaczyć:

<%= check_box_tag(:pet_dog) %> <%= label_tag(:pet_dog, "I own a dog") %> <%= check_box_tag(:pet_cat) %> <%= label_tag(:pet_cat, "I own a cat") %> wynik: <input id="pet_dog" name="pet_dog" type="checkbox" value="1" /> <label for="pet_dog">I own a dog</label> <input id="pet_cat" name="pet_cat" type="checkbox" value="1" /> <label for="pet_cat">I own a cat</label>

Drugim parametrem check_box_tag jest wartość pola formularza (value). To jest wartość jaka zostanie przesłana przez przeglądarkę jeśli pole zostanie zaznaczone (n.p.: wartość znajdująca się w hashu params). Powyższym formularzem możesz sprawdzić wartość params[:pet_dog] i params[:pet_cat] by zobaczyć które zwierzęta posiada użytkownik.

1.3.2 Pola jednokrotnego wyboru

Pola jednokrotnego wyboru, wyglądające podobnie jak pola wielokrotnego wyboru, są kontrolkami określającymi zestaw opcji wykluczających się wznajemnie (n.p.: użytkownik może wybrać tylko jedną opcję):

<%= radio_button_tag(:age, "child") %> <%= label_tag(:age_child, "I am younger than 21") %> <%= radio_button_tag(:age, "adult") %> <%= label_tag(:age_adult, "I'm over 21") %> wynik: <input id="age_child" name="age" type="radio" value="child" /> <label for="age_child">I am younger than 21</label> <input id="age_adult" name="age" type="radio" value="adult" /> <label for="age_adult">I'm over 21</label>

Tak samo jak w check_box_tag drugim parametrem radio_button_tag jest wartość pola formularza. Ponieważ oba pola mają taką samą nazwę (age), użytkownik może wybrać tylko jedno z nich i params[:age] będzie zawierać albo “child”, albo “adult”.

Dla każdego pola wielokrotnego lub jednokrotnego wyboru należy zawsze stosować etykiety. Kojarzą one tekst z daną opcją i zapewniają większy obszar, który można kliknąć.

1.4 Pozostałe helpery tego rodzaju

Pozostałe rodzaje pól formularza warte wymienienia to: obszar tekstowy (text area), pole służące do wprowadzenia hasła (password) i pole ukryte (hidden):

<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %> <%= password_field_tag(:password) %> <%= hidden_field_tag(:parent_id, "5") %> wynik: <textarea id="message" name="message" cols="24" rows="6">Hi, nice site</textarea> <input id="password" name="password" type="password" /> <input id="parent_id" name="parent_id" type="hidden" value="5" />

Pola ukryte nie są pokazywane użytkownikowi, ale przechowują dane. Ich wartości mogą być zmieniane przez JavaScript.

Jeśli używasz pola służącego do wprowadzania hasła (w jakimkolwiek celu), masz możliwość zabezpieczenia tych pól przed zapisaniem ich wartości w pamięci aplikacji poprzez włączenie filter_parameter_logging(:password) w module ApplicationController.

2 Korzystanie z Modeli Obiektowych

2.1 Helpery Modeli Obiektowych

Podstawowym zadaniem formularzy jest edytowanie i tworzenie obiektu. Helpery *_tag mogłyby być do tego użyte, ale dla każdego z użytych tagów należałoby poprawnie wprowadzić nazwę i zestaw domyślnich wartości, co jest dość uciążliwe. Na szczęście Railsy posiadają helpery przeznaczone specjalnie do tworzenia obiektów. Helpery te nie posiadają sufixu _tag, np.: text_field, text_area.

Dla tych helperów pierwszym atrybutem jest nazwa zmiennej instancji, a drugim- nazwa metody (zwykle atrybut) wywoływanej na tym obiekcie. Wprowadzając wartości wejściowe, Railsy automatycznie wygenerują pole z ustawionym id, nazwą i wartością. Jeśli kontroler zdefiniował obiekt @person i nazwa tego obiektu (osoby) to Henryk, wówczas forma:

<%= text_field(:person, :name) %>

wygeneruje kod podobny do:

<input id="person_name" name="person[name]" type="text" value="Henryk"/>

Przesłane wartości wprowadzone przez użytkownika będą przechowywane w params[:person][:name]. Hash params[:person] jest przystosowany do przekazania wartości do Person.new lub, jeśli istnieje instancja @person klasy Person, do @person.update_attributes. Chociaż nazwa atrybutu jest najczęściej spotykanym drugim parametrem, w tych helperach nie jest on konieczny. W przykładzie powyżej, dopóki obiekt-osoba posiada name i metode name= Railsy będą zadowolone.

Trzeba podać nazwę zmiennej instancji, n.p.: :person lub "person", a nie obiekt.

Railsy posiadają również helpery do wyświetlania błędów walidacji dotyczących danego obiektu. Szczegóły znajdziesz w Walidacje i callbacki w module Active Record guide.

2.2 Przypisanie formularza do obiektu

Mimo, że poprawiliśmy komfort pracy, nadal jest daleko do perfekcji. Jeśli Person ma wiele atrybutów do edycji, będziemy powtarzać nazwę edytowanrgo obiektu wiele razy. To co chcemy zrobić, to przypisać formularz do obiektu, czyli dokładnie to do czego służy form_for .

Załużmy, że mamy kontroler do zarządzania artykułami app/controllers/articles_controller.rb:

def new @article = Article.new end

Odpowiedni przykład app/views/articles/new.html.erb wykorzystujący form_for wygląda tak:

<% form_for :article, @article, :url => { :action => "create" }, :html => {:class => "nifty_form"} do |f| %> <%= f.text_field :title %> <%= f.text_area :body, :size => "60x12" %> <%= submit_tag "Create" %> <% end %>

Kilka rzeczy należy tutaj podkreślić:

  1. :article jest nazwą modelu, a @article jest aktualnie edytowanym obiektem.
  2. Istnieje jeden hash z opcjami. Opcje routingu są przekazane w hashu :url, opcje HTML umieszczone są w hashu :html.
  3. Metoda form_for tworzy obiekt form builder (zmienna f).
  4. Metody do tworzenia kontrolek formularza (on) należą do buildera f

W efekcie dostajemy kod HTML:

<form action="/articles/create" method="post" class="nifty_form"> <input id="article_title" name="article[title]" size="30" type="text" /> <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea> <input name="commit" type="submit" value="Create" /> </form>

Nazwa przekazana do form_for kontroluje klucz użyty w params by uzyskać dostęp do wartości formularza. W tym przypadku nazwą jest article tak więc wszystkie pola mają nazwy dziedziczone po formularzu article[attribute_name]. Odpowiednio, w create akcją params[:article] będzie hash z kluczami :title i :body. Możesz przeczytać więcej na ten temat w sekcji parameter_names.

Metody helpera wywoływane na builderze formularzy są identyczne, jak te używane przy modelu obiektowym, z tym wyjątkiem, że nie ma konieczności określenia, który obiekt jest edytowany, gdyż jest to już określone przez buildera formularzy.

Można stworzyć podobne przypisanie bez konieczności tworzenia tagów <form> przy pomocy helpera fields_for. Jest to bardzo przydatne podczas edycji dodatkowych modeli obiektowych tego samego formularza. Na przykład, jeżeli masz model Person z przypisanym modelem ContactDetail, możesz zbudować formularz do tworzenia obu tych modeli:

<% form_for :person, @person, :url => { :action => "create" } do |person_form| %> <%= person_form.text_field :name %> <% fields_for @person.contact_detail do |contact_details_form| %> <%= contact_details_form.text_field :phone_number %> <% end %> <% end %>

który wygeneruje następujący kod:

<form action="/people/create" class="new_person" id="new_person" method="post"> <input id="person_name" name="person[name]" size="30" type="text" /> <input id="contact_detail_phone_number" name="contact_detail[phone_number]" size="30" type="text" /> </form>

Otrzymany w wyniku obiekt fields_for jest builderem formularza takim jak ten otrzymany dzięki form_for (w gruncie rzeczy form_for wywołuje fields_for wewnętrznie).

2.3 Poleganie na Identyfikacji Rekordów

Model Article jest bezpośrednio dostępny dla użytkownika aplikacji, więc — stosując się to zalecanych reguł budowania skryptów w Railsach — powinieneś zadeklarować a resource:

map.resources :articles

Deklarowanie zasobów ma również efekty uboczne. Zobacz artykuł Routing w Railsach , aby znaleźć więcej informacji dotyczących ustawiania i używania zasobów.

Gdy korzystasz z zasobów RESTful, możesz ułatwić wywołania form_for polegając na record identification. W skrócie, wystarczy przesłać instancję modelu i pozostawić Railsom problem znalezienia nazwy obiektu i pozostałych:

## Creating a new article # long-style: form_for(:article, @article, :url => articles_path) # same thing, short-style (record identification gets used): form_for(@article) ## Editing an existing article # long-style: form_for(:article, @article, :url => article_path(@article), :method => "put") # short-style: form_for(@article)

Zauważ, jak wygodne jest to, że niezależnie od tego czy rekord jest nowy czy istniejący, wywoływanie form_for (w przypadku krótkiego stylu) pozostanie takie samo. Identyfikacja rekordu jest na tyle sprytna, że wykonując zapytanie do record.new_record? jest w stanie dojść do tego, czy rekord jest nowy. Ponadto wybiera poprawną ścieżkę do wysłania oraz nazwę na podstawie klasy obiektu.

Railsy również automatycznie ustawią class i id formularza w następujący sposób: formularz tworzący artykuł będzie miał id oraz class new_article. Jeżeli będziesz edytował artykuł z id 23, class zostanie ustawiony na edit_article, a id na edit_article_23. W tym przewodniku atrybuty te będą pomijane.

Używając STI (single-table inheritance) z modelami, nie możesz polegać na identyfikacji rekordu podklasy, jeśli jego klasa-rodzic jest zdeklarowana tylko jako zasób. W tym przypadku będziesz musiał zdefiniować name, :url, i :method oddzielnie.

2.3.1 Przestrzenie nazw

Jeśli podczas projektowania tras zostały użyte przestrzenie nazw, helper form_for posiada prosty mechanizm pozwalający z nich skorzystać. Jeśli Twoja aplikacja posiada przestrzeń nazw “admin”, poniższy kod:

form_for [:admin, @article]

utworzy formularz, przesyłający dane do kontrolera “articles” w przestrzeni nazw “admin” (przesyła do admin_article_path(@article) w razie uaktualnienia). Jeśli posiadamy kilka poziomów przestrzeni nazw, składnia wygląda podobnie:

form_for [:admin, :management, @article]

Aby dowiedzieć się więcej o routingu w Railsach, przeczytaj Routing w Railsach.

2.4 Jak działają formularze z metodami PUT lub DELETE?

Railsy zachęcają, abyś w swojej aplikacji korzystał z zasobów RESTful, czyli do tworzenia wielu zapytań typu “PUT” i “DELETE” (oprócz “GET” i “POST”). Jednak większość przeglądarek nie obsługuje metod do wysyłania formularzy innych niż “GET” i “POST” .

W Railsach problem ten rozwiązany jest w taki sposób, że inne metody naśladowane są przez POST, a ukryte pole o nazwie "_method", odzwierciedla wybraną metodę:

form_tag(search_path, :method => "put")

kod:

<form action="/search" method="post"> <div style="margin:0;padding:0"> <input name="_method" type="hidden" value="put" /> <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> </div> ...

Podczas analizowania wysłanych danych Railsy uwzględnią dodatkowy parametr _method_ zachowując się tak, jakby dane te zostały przesłane własnie tą metodą o jakiej mówi tenże parametr (“PUT” w powyższym przykładzie).

3 Rozwijane pola wyboru w formularzach

Rozwijane pola wyboru w HTML wymagają znacznej ilości znaczników (po jednym elemencie OPTION dla każdej z opcji formularza do wyboru), dlatego najsensowniejszym wyjściem jest generowanie ich dynamicznie.

Tak mniej więcej może wyglądać kod:

<select name="city_id" id="city_id"> <option value="1">Lisbon</option> <option value="2">Madrid</option> ... <option value="12">Berlin</option> </select>

Mamy tutaj listę miast, których nazwy są przedstawione użytkownikowi. Wewnętrznie aplikacja będzie obsługiwała tylko ich identyfikatory (ID),więc dlatego są one użyte jako atrybut opcji- wartość. Zobaczmy jak Railsy mogę nam w tym pomóc.

3.1 Tagi Select i Option

Najpopularniejszym helperem jest select_tag, który — jak sama nazwa sugeruje — po prostu generuje tag SELECT zawierający opcjonalny łańcuch znaków:

<%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %>

To jest początek, który nie tworzy dynamicznie tagów opcji. Takie tagi można wygenerować z pomocą helpera options_for_select:

<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...]) %> wynik: <option value="1">Lisbon</option> <option value="2">Madrid</option> ...

Pierwszym argumentem options_for_select jest zagnieżdżona tablica, której każdy element ma dwa elementy: tekst opcji (nazwę miasta) i wartość opcji (identyfikator miasta). Wartością opcji jest to, co będzie przesłąne do kontrolera. Często będzie to identyfikator odpowiedniego obiektu w bazie danych, ale wcale tak nie musi być.

Wiedząc powyższe, można połączyć select_tag i options_for_select by otrzymać pożądany, pełen znacznik:

<%= select_tag(:city_id, options_for_select(...)) %>

options_for_select pozwala na wstępne wybranie opcji, przez podanie jej wartości.

<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %> wynik: <option value="1">Lisbon</option> <option value="2" selected="selected">Madrid</option> ...

Za każdym razem, jeśli Railsy zauważą, że wewnętrzna wartość generowanej opcji pasuje do podanej przez nas wartośći, doda do danej opcji atrybut selected.

Drugi argument options_for_select musi być dokładnie taki sam, jak pożądana przez nas wartość wewnętrzna. Szczególnie, jeśli wartością jest liczba całkowita 2 nie można wpisać “2” do options_for_select - trzeba wpisać 2. Bądź świadom tego, że wszystkie wartości pochodzące z hasha params są łańcuchami znaków.

3.2 Rozwijane pola wyboru do operowania na obiektach

W większości przypadków, kontrolki formularza będą powiązane z określonym modelem bazy danych i jak można się było spodziewać, Railsy dostarczają helpery przeznaczone do tego celu. Zgodnie z innymi helperami formularzy, jeśli operujemy na obiektach, opuszczamy sufiks _tag w select_tag:

# controller: @person = Person.new(:city_id => 2)
# view: <%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %>

Zauważ, że trzeci parametr, tablica opcji, jest argumentem tego samego rodzaju, co wprowadzony do options_for_select. Zaletą jest to, że nie musisz się martwić o wstępny wybór właściwego miasta jeśli użytkownik takowy już posiada — Railsy zrobią to za Ciebie czytając atrybut z @person.city_id.

Jeżeli chodzi o pozostałe helpery, gdybyś chciał użyć helpera select w builderze formularzy odnoszącego się do obiektu @person, składnia byłaby następująca

# select on a form builder <%= f.select(:city_id, ...) %>

Jeśli używasz select (albo podobny helper taki jak collection_select, select_tag) by ustawić asocjacje z belongs_to musisz przekazać nazwę klucza obcego (w przykładzie powyżej: city_id), a nie nazwę samej asocjacji. Jeżeli zdefiniujesz city zamiast city_id moduł Active Record zwróci błąd:

 ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) 
w czasie przekazywania hasha params do Person.new lub update_attributes. Helpery tylko edytują atrybuty. Powinieneś być świadom potencjalnych następstw w zakresie bezpieczeństwa poprzez udostępnianie użytkownikom możliwości bezpośredniej edycji obcych kluczy. Możesz również rozważyć użycie attr_protected i attr_accessible. Aby dowiedzieć się więcej na ten temat zajrzyj do Bezpieczeństwo w Ruby on Rails.

3.3 Tagi opcji jako kolekcje dowolnych obiektów

Generowanie tagów opcji z options_for_select wymaga stworzenia tablicy zawierającej tekst i wartość dla każdej opcji. Ale co, jeśli miałbyś model City (przypuśćmy, że modułu Active Record) i chcialbyś wygenerowac tagi opcji z kolekcji takich obiektów? Pierwszym rozwiazaniem byłoby stworzenie zagnieżdzonej tablicy poprzez wyliczenie iteracyjne tych obiektów:

<% cities_array = City.all.map { |city| [city.name, city.id] } %> <%= options_for_select(cities_array) %>

To poprawne rozwiazanie, ale Railsy oferują nam znacznie prostsze: options_from_collection_for_select. Ten helper wymaga kolekcji dowolnych obiektów i dwóch dodatkowych argumentów: nazwy metody czytajacej opcje wartość oraz tekst, odpowiednio:

<%= options_from_collection_for_select(City.all, :id, :name) %>

Jak sugeruje sama nazwa, ten kod generuje tylko tagi opcji. Aby stworzyć działajace pole wyboru, trzeba połaczyć go z select_tag, dokładnie tak, jak zrobiłbyś to z options_for_select. Gdy pracujesz z obiektem, tak jak select łączy select_tag i options_for_select, tak collection_select łaczy select_tag z options_from_collection_for_select.

<%= collection_select(:person, :city_id, City.all, :id, :name) %>

W skrócie, options_from_collection_for_select to dla collection_select to samo, co options_for_select dla select.

W options_for_select wprowadzamy nazwę jako pierwszą, a id jako drugie, natomiast w options_from_collection_for_select pierwszy argument to wartość, a drugi to metoda tekstowa.

3.4 Strefa czasowa i wybór kraju

Aby wykorzystać wsparcie stref czasowych w Railsach, musisz zadać pytanie użytkownikowi w jakiej strefie się znajduję. Potrzebujemy więc wygenerować listę opcji z wcześniej zdefiniowanych obiektów TimeZone, używając collection_select, ale prościej będzie użyć helpera time_zone_select, który już je opakowuje:

<%= time_zone_select(:person, :time_zone) %>

Istnieje także helper time_zone_option_select, który w sposób mniej automatyczny (zatem z większą możliwością dostosowania) dokona tego samego. Przeczytaj dokumentacje API aby dowiedzieć się jakich argumentów można użyć dla tych dwóch metod.

Railsy posiadały helpera country_select do wybierania kraju, ale został on wyodrębniony do wtyczki country_select plugin. Korzystając z niego należy pamiętać że wyłączenie, bądź włączenie niektórych nazw “do” lub “z” listy może być sporne (jest to powód wyodrębnienia tej funkcjonalności z Railsów).

4 Używanie helperów formularzy daty i czasu

Helpery daty i czasu różnia się od innych helperów dwoma ważnymi aspektami:

  1. Daty i godziny nie są wyrażane poprzez pojedynczy element formularza. Zamiast tego otrzymujemy kilka pól, po jednym dla każdego komponentu (rok, miesiąc, dzień, itd.). Dlatego, nie istnieje pojedyncza wartość w tablicy asocjacyjnej params z datą i czasem.

#Inne helpery używają sufiksu _tag do wskazania, czy helper operuje na jakimś obiekcie, czy też nie (helper niezależny). Jeśli chodzi o datę i czas, select_date, select_time oraz select_datetime są helperami niezależnymi, a date_select, time_select i datetime_select są równoważnymi helperami operującymi na obiektach.

Obie grupy helperów tworzą zestawy pól wyboru dla różnych komponentów (rok, miesiąc, dzień, itd.)

4.1 Helpery niezależne (Barebones Helpers)

Rodzina helperów typu select_* jako pierwszy argument przyjmuje instancję Date, Time lub DateTime, która użyta jest jako obecnie wybrana wartość. Jeśli pominiesz ten parametr, zostanie użyta bieżąca data. Na przykład:

<%= select_date Date.today, :prefix => :start_date %>

zwróci (pomijając pola opcji):

<select id="start_date_year" name="start_date[year]"> ... </select> <select id="start_date_month" name="start_date[month]"> ... </select> <select id="start_date_day" name="start_date[day]"> ... </select>

Powyższe pola stworzą hash params[:start_date] zawierający klucze :year, :month, :day. By dostać aktualny czas lub datę, będziesz musiał wyciągnąć te dane i przekazać je do odpowiedniego konstruktora, na przykład:

Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i)

Opcja :prefix jest kluczem do uzyskania hasha z elementami daty z hasha params. W tym przypadku było ustawione na start_date (domyślnie date).

4.2 Helpery modelu obiektowego

select_date nie działa najlepiej z formularzami, które aktualizują lub zmieniają obiekty modułu Activer Record, jako że obiekty takie wymagają, aby każdy element tablicy asocjacyjnej params odpowiadał jednemu atrybutowi. Helpery obiektów dla dat i czasów wysyłają parametry ze specjalnymi nazwami, kiedy obiekt modułu Active Record widzi takie parametry, wie że muszą one zostać połączone z innymi parametrami i przekazane do konstruktora przypisanego do odpowiedniego typu kolumny. Na przykład:

<%= date_select :person, :birth_date %>

generuje następujący wynik (dla przejrzystości pominięto właściwe wartości opcji)

<select id="person_birth_date_1i" name="person[birth_date(1i)]"> ... </select> <select id="person_birth_date_2i" name="person[birth_date(2i)]"> ... </select> <select id="person_birth_date_3i" name="person[birth_date(3i)]"> ... </select>

co daje hasha params wyglądającego tak:

{:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}}

Kiedy zostaje to przekazane do Person.new (albo do update_attributes), obiekt modułu Active Record automatycznie rozpoznaje, że wszystkie te parametry powinny być użyte do konstrukcji atrybutu birth_date i używa dołączonej informacji, aby rozpoznać w jakiej kolejności ma przekazać te parametry do funkcji takich jak Date.civil.

4.3 Wspólne opcje

Obie rodziny helperów używają tych samych bazowych zestawów funkcji do generowania pojedynczego tagu select, zatem obie przyjmują w większości te same opcje. Dla przykładu, Railsy domyślnie wygenerują opcje lat dla przedziału od pięciu lat wstecz, do pięciu lat wprzód od aktualnej daty. Ustawienie opcji :start_year i :end_year pozwala na dowolne manipulowanie owym przedziałem. Dokładną listę wszystkich opcji można znaleźć w dokumentacji.

Przyjęło się, że kiedy operujemy na obiektach, powinniśmy używać date_select, a w innych przypadkach takich jak formularz wyszukiwania, filtrujący wyniki po dacie select_date.

W wielu przypadkach wbudowane wybieranie dat działa niezgrabnie, ponieważ wcale nie pomaga użytkownikowi zorientować się w relacji między datą a dniem tygodnia.

4.4 Komponenty indywidualne

Czasami potrzeba jedynie wyświetlić pojedynczy składnik daty, jak rok czy miesiąc. Railsy dostarczają do tego serię helperów- jeden dla każdego składnika:select_year, select_month, select_day, select_hour, select_minute, select_second. Tu brak niespodzianek, nazwy helperów tłumaczą się same. Domyślnie będą one generować pole formularza nazwane tak, jak składnik daty (na przykład “year” dla select_year, “month” dla select_month, itd.), ale można to zmienić opcją :field_name. Opcja :prefix działa w analogiczny sposób jak w select_date i select_time i ma tą samą domyślną wartość.

Pierwszy parametr określa jaka wartość powinna być wybrana. Może nim być instancja Date, Time, lub DateTime- w tym wypadku istotny komponent zostanie wydobyty lub może nim być wartość liczbowa. Na przykład:

<%= select_year(2009) %> <%= select_year(Time.now) %>

wyprodukuje taki sam wynik, jeżeli rok bieżący to 2009, a wartość wybrana przez użytkownika może być wyszukana przez params[:date][:year].

5 Ładowanie plików

Popularnym zadaniem jest wgrywanie pliku, czy to zdjęcia, czy pliku CSV zawierającego dane do przetwarzania. Najważniejsze to pamiętać, że kodowanie formularza MUSI być ustawione na “multipart/form-data”. Jeżeli o tym zapomnisz, plik nie zostanie wgrany. Aby ustawić kodowanie formularza dopisz :multi_part => true jako opcję HTML. Oznacza to, że w przypadku form_tag należy to dopisać w hashu drugich opcji, a w przypadku form_for wewnątrz hasha :html.

Oba poniższe formularze wgrywają plik.

<% form_tag({:action => :upload}, :multipart => true) do %> <%= file_field_tag 'picture' %> <% end %> <% form_for @person, :html => {:multipart => true} do |f| %> <%= f.file_field :picture %> <% end %>

Railsy oferują typową parę helperów: niezależny file_field_tag i zorientowany na obiekt file_field. Jedyne czym różnią się helpery wgrywania pliku od pozostałych helperów to to, że nie można im ustawić domyślnej wartości pliku wejściowego tak, by nie miała znaczenia. Jak możnaby oczekiwać, w pierwszym przypadku wgrywany plik znajduje się w params[:picture], a w drugim w params[:person][:picture].

5.1 Co zostaje uploadowane

Obiekt w hashu params jest instancją podklasy IO. W zależności do wielkości załadowanego pliku może to być StringIO lub instancja File z tymczasowym plikiem. W obu przypadkach będzie mieć atrybuty: original_filename zawierający nazwę pliku z dysku użytkownika oraz content_type zawierający typ MIME załadowanego pliku. Powyższy fragment kodu zapisuje załadowany plik w #{Rails.root}/public/uploads pod tą samą nazwą, jak oryginalny plik (pod warunkiem, że korzystamy z formuarza z poprzedniego przykładu).

def upload uploaded_io = params[:person][:picture] File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |file| file.write(uploaded_io.read) end end

Gdy plik został już załadowany, mamy wiele możliwości począwszy od tego gdzie przechowywać plik (dysk, Amazon S3, itd.), jak skojarzyć plik z modelami do zmiany rozmiaru obrazów, aż po generowanie miniatur. Tego rodzaju operacje nie są przedmiotem tego poradnika, jednak jest kilka pluginów stworzonych do ich wspomagania. Dwa najbardziej znane to Attachment-Fu i Paperclip.

Jeżeli użytkownik nie wybrał pliku to odpowiednie parametry będa pustymi ciągami znaków.

5.2 Praca z Ajax’em

W przeciwieństwie do pozostałych formularzy, tworzenie formularza wgrywającego pliki asynchronicznie nie jest tak proste jak zamiana “form_for” z “remote_form_for”. W formularzu ajaksowym w przeglądarce serializację przeprowadza JavaScript i dopóki JS nie może odczytać plików z Twojego dysku, plik nie może być wgrany. Najpopularniejszą metodą obejścia tego problemu jest używanie niewidzialnej pływającej ramki (iframe) która zawiera cel (target) na który działa formularz.

6 Przystosowanie builderów formularzy

Jak już wcześniej wspomniano, obiekt uzyskany przez form_for i fields_for jest instancją FormBuilder (lub jej podklasy). Buildery formularzy zawierają serię wyświetlanych elementów formularza dla pojedynczego obiektu. Dopóki możesz napisać helpery dla swojego formularza w zwykły sposób, możesz również stworzyć podklasę w FormBuilder i dodać tam helpery. Na przykład:

<% form_for @person do |f| %> <%= text_field_with_label f, :first_name %> <% end %>

może być zastąpione

<% form_for @person, :builder => LabellingFormBuilder do |f| %> <%= f.text_field :first_name %> <% end %>

poprzez określenie klasy LabellingFormBuilder podobnie jak w poniższym:

class LabellingFormBuilder < ActionView::Helpers::FormBuilder def text_field(attribute, options={}) label(attribute) + super end end

Jeśli będziesz często z tego korzystać, możesz określić helper labeled_form_for , który automatycznie zastosuje opcję :builder => LabellingFormBuilder.

Zastosowany builder formularza określa również, co się dzieje, kiedy zrobisz:

<%= render :partial => f %>

Jeśli f jest instancją FormBuilder, wtedy form zostanie wykonane częściowo, ustawiając częściowy obiekt do buildera formularza.Jeśli builder formularza jest klasy LabellingFormBuilder, wtedy zamiast tego będzie częściowo wykonane labelling_form.

7 Zrozumienie konwencji nazewnictwa parametrów

Jak można było zauważyć w poprzednich sekcjach, wartości z formularzy mogą występować na najwyższym poziomie tablicy asocjacyjnej params lub być zagnieżdżonymi w innej. Przykładowo w standardowej operacji create dla modelu Person, params[:model] zazwyczaj będzie tablicą asosjacyjna wszystkich utworzonych atrybutów danej osoby. Hash params może też zawierać zwykłe tablice, tablice hashów i tak dalej.

Zasadniczo formularze HTML nie rozpoznają struktury danych. Tworzą pary nazw i wartości, które są ze sobą połączone. Hashe oraz zwykłe tablice, które pojawiają się w aplikacjach są wynikiem nazewnictwa parametrów przyjętego w Railsach.

Może się okazać, że szybciej wykonasz przykłady podane w tej sekcji używając konsoli i bezpośrednio wywołasz parsera parametru Railsów, na przykład:

 ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789" # => {"name"=>"fred", "phone"=>"0123456789"} 

7.1 Podstawowe struktury

Dwiema podstawowymi strukturami są tablice i tablice asocjacyjne. Hashe odzwierciedlają składnię wykorzystaną do udostępnienia wartości w params. Na przykład jeśli formularz zawiera:

<input id="person_name" name="person[name]" type="text" value="Henryk"/>

hash params będzie się składał z

{'person' => {'name' => 'Henryk'}}

i params["name"] odczyta przesłaną wartość w kontrolerze.

Hashe mogą być zagnieżdżone na tylu poziomach, na ilu potrzebujemy, na przykład

<input id="person_address_city" name="person[address][city]" type="text" value="New York"/>

wynikiem w hashu params będzie

{'person' => {'address' => {'city' => 'New York'}}}

Normalnie Railsy ignorują zduplikowane nazwy parametrów. Jeśli nazwa zawiera zbiór pusty pomiędzy nawiasami kwadratowymi [], wtedy to one będą zgromadzone w tablicy. Jeśli chcesz, żeby można było wprowadzić wiele numerów telefonicznych, możesz umieścić następujący kod w formularzu:

<input name="person[phone_number][]" type="text"/> <input name="person[phone_number][]" type="text"/> <input name="person[phone_number][]" type="text"/>

Da to wynik w params[:person][:phone_number] będący tablicą.

7.2 Łączenie obu typów tabilc

Możemy mieszać i łączyć ze sobą oba rodzaje tablic. Przykładowo, jeden z elementów tablicy asocjacyjnej może być tablicą jak w poprzednim przykładzie. Możemy również stworzyć tablicę tablic asocjacyjnych. Na przykład dzięki formularzowi można utowrzyć dowolną ilość adresów poprzez powtórzenie pewnego fragmentu

<input name="addresses[][line1]" type="text"/> <input name="addresses[][line2]" type="text"/> <input name="addresses[][city]" type="text"/>

Wynikiem tego jest params[:addresses] będący tablicą tablic asosjacyjnych z kluczami line1, line2 oraz city. Railsy same decydują o rozpoczęciu gromadzenia wartości w nowej tablicy asosjacyjnej w momencie napotkania wprowadznej danej o nazwie istniejącej w obecnej tablicy asosjacyjnej.

Istnieje jednak pewne ograniczenie, według którego tablica asosjacyjna może być zagnieżdzona samowolnie tylko na jednym poziomie “arrayness”. Tablice zazwyczaj mogą być zastąpione tablicami asosjacyjnymi, na przykłąd zamiast tworzyć tablicę obiektów, możemy wywołać tablicę asosjacyjną obiektów oznaczonych poprzez id, index tablicy lub inny parametr.

Według specyfikacji HTML niezaznaczone checkboxy nie wysyłają żadnej wartości. Jednak często wygodniej byłoby, gdyby przesyłały jakąś wartość. Helper check_box symuluje takie zachowanie poprzez tworzenie dodatkowego, ukrytego pola o tej samej nazwie. Jeżeli checkbox jest odznaczony, wówczas zostanie wysłana tylko wartość pola ukrytego, w przeciwnym wypadku pole ukryte zostanie nadpisane przez checkbox. Gdy zamierzasz wysłać tablicę, podwojone wysyłanie będzie dla Railsów niezrozumiałe, gdyż podwojone nazwy pól świadczą o kolejnym elemencie w tablicy. Zaleca się używanie check_box_tag lub hashy zamiast tablic.

7.3 Korzystanie z helperów formularzy

Poprzednie sekcje nie do końca wykorzystywały helpery formularzy. Pomimo, że można stworzyć nazwy wejściowe własnoręcznie i przekazać je bezpośrednio do helpetów takich jak text_field_tag, Railsy zapewniają również wsparcie na wyższym poziomie. Dwa narzędzia oddane do Twojej dyspozycji to parametr nazwy do form_for i fields_for oraz opcja index, którą pobiera helper.

Istnieje możliwość wygenerowania formlarza z zestawem pól do edycji dla każdego z adresów danej osoby. Na przykład:

<% form_for @person do |person_form| %> <%= person_form.text_field :name %> <% for address in @person.addresses %> <% person_form.fields_for address, :index => address do |address_form|%> <%= address_form.text_field :city %> <% end %> <% end %> <% end %>

Zakładając, że osoba ma dwa adresy o numerach id 23 i 45, otrzymamy na wyjściu kod, który będzie wyglądał mniej więcej tak:

<form action="/people/1" class="edit_person" id="edit_person_1" method="post"> <input id="person_name" name="person[name]" size="30" type="text" /> <input id="person_address_23_city" name="person[address][23][city]" size="30" type="text" /> <input id="person_address_45_city" name="person[address][45][city]" size="30" type="text" /> </form>

Co da nam w rezultacie tablicę asocjacyjną params wyglądającą tak:

{'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}}

Railsy wiedzą, że wszystkie dane wejściowe powinny być częścią tablicy asocjacyjnej przypisanej danej osobie, ponieważ wywołałeś fields_for w pierwszym builderze formularzy. Określając opcję :index przekazujesz Railsom informację, że zamiast nazywać dane wejściowe person[address][city], powinny pomiędzy adres i miasto wstawić indeks opatrzony w nawiasy kwadratowe []. Jeśli przekażesz obiekt modułu Active Record jak my to zrobiliśmy, Railsy wywołają na nim to_param, które w domyśle zwróci id bazy danych. Okazuje się, że jest to przydatne, gdyż łatwo wtedy zlokalizować, który rekord z adresem powinien być zmodyfikowany. Możesz przekazać również liczby znaczące co innego, łańcuchy znaków, a nawet nil (co w efekcie utworzy parametr tablicy).

Aby stworzyć bardziej skomplikowane zagnieżdżenia, możesz jawnie określić pierwszą część nazwy wejściowej (person[address] w poprzenim przykładzie), np:

<% fields_for 'person[address][primary]', address, :index => address do |address_form| %> <%= address_form.text_field :city %> <% end %>

co da wynik:

<input id="person_address_primary_1_city" name="person[address][primary][1][city]" size="30" type="text" value="bologna" />

Jako główna zasadę przyjmijmy, że nazwa wejściowa jest złożeniem nazw podanych do fields_for/form_for, wartości indeksu oraz nazwy atrybutu. Możesz również przekazać opcję :index bezpośrednio do helpera takiego jak np. text_field, ale lepiej będzie określić to przy tworzeniu formularza, niż robiąc to ręcznie (mniejsza powtarzalność czynności).

Dla uproszczenie możesz dodać [] do nazwy i pominąć opcję :index. To to samo co zapis :index => address, więc

<% fields_for 'person[address][primary][]', address do |address_form| %> <%= address_form.text_field :city %> <% end %>

daje w wyniku dokładnie taki sam rezultat jak w poprzednim przykładzie.

8 Budowanie złożonych formularzy

Wiele aplikacji nie sluży już do edycji pojedynczego obiektu. Na przykład, kiedy tworzymy obiekt Person, chcemy rownież pozwolić w tym samym formularzu na przypisanie wielu adresów (dom, praca, itd.). Później, kiedy edytujemy tę osobę – użytkownik powinien móc dodawać, usuwać lub poprawiać adresy. Railsy nie mają standardowego sposobu na zrobienie tego od początku do końca, ale jest wiele rozwiazań. Zawierają je:

9 Changelog

Lighthouse ticket

10 Autorzy