Основные формы

Введение

Вы уже знакомы с формами, и как пользователь Интернет, и как программист, выполнивший курс Основы веб-разработки. Но как много НА САМОМ ДЕЛЕ вы о них знаете? Может показаться странным, но формы, возможно, наиболее сложная тема в веб-разработке. И не из-за сложного кода, а по той причине, что формы предназначены для выполнения множества разных действий.

До этого момента мы рассматривали модели в Rails как отдельные объекты - модели User и Post. Иногда нам было необходимо их связывать через ассоциации, добавляя например в модель Post метод has_many для модели Comment. Но обычно мы не работали с двумя моделями одновременно.

Представим форму для заказа авиабилета. В такую форму необходимо поместить поля для ввода имени, адреса, номера телефона, email, наименования авиакомпании, номера рейса, даты вылета, номера кредитной карточки, ее секретного кода и срока действия, кучу чекбоксов для прочих атрибутов как например страховки. Мы получаем задействованными множество моделей в одной форме. И она должна подтвердиться с помощью всего лишь одной кнопки. Святые угодники!

Большинство форм не будут такими сложными, но будет полезным представить, как много вы можете с ними сделать (и однажды вы сделаете). Создать простую форму очень легко, и первое, что мы сделаем - это убедимся, что у вас есть понимание того, как создаются формы в HTML и как Rails облегчает их создание, предоставляя помощники (хелперы). Мы рассмотрим, как данные структурируются и отправляются в контроллер, пока вы не освоитесь с этим процессом. В последующем уроке мы закрепим основные понятия и рассмотрим дополнительные возможности форм.

Пункты для размышления

Постарайтесь ответить на предложенные вопросы. После выполнения задания попробуйте ответить на них ещё раз

  • Как посмотреть, что было отправлено формой?
  • Что такое метка CSRF и почему она необходима?
  • Как вы ее создаете в Rails?
  • Почему так важен атрибут name в форме?
  • Как поместить атрибуты в одиночном хэше params?
  • Почему это полезно?
  • Что надо изменить/добавить в контроллере, чтобы получить вложенный хэш params?
  • Какие специальные тэги предоставляет Rails' в #form_tag?
  • В чем различие между #form_tag и #form_for?
  • Как получить доступ к ошибкам при неудачном сохранении объекта модели?
  • Какой метод-помощник автоматически добавляет ошибки валидации в разметку?
  • Как получить доступ к действиям Update или Delete из формы?

Формы в HTML

В первом шаге мы создадим форму на HTML. Помните как это выглядит?

    <form action="/somepath" method="post">
      <input type="text">
      ...
      <!-- other inputs here -->
      ...
      <input type="submit" value="Submit This Form">
    </form>

Существует большой выбор тэгов input, включающих button, checkbox, date, hidden, password, radio и многие другие (смотрите список на w3 schools).

Просмотр отсылаемых формой данных

Если вы хотите увидеть, что ваша форма отправляет в приложение, посмотрите, что выводится в консоли, где запущен сервер командой $ rails server. Даже при отправке простой формы аутентификации пользователя, в логе должны быть подобные строки:

Started POST "/user" for 127.0.0.1 at 2013-11-21 19:10:47 -0800
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jJa87aK1OpXfjojryBk2Db6thv0K3bSZeYTuW8hF4Ns=", "email"=>"foo@bar.com", "commit"=>"Submit Form"}

Примечание: этот лог из формы, которая создана хелпером Rails, мы поясним этот момент ниже

На первой строчке мы видим используемый метод HTTP и маршрут, куда форма отправляет данные. На второй мы видим, какой контроллер и действие будут обрабатывать данные этой формы. В третьей содержится все, что будет помещено в хэш params для использования этим контроллером. О содержимом хэша мы поговорим позже.

Вы очень часто будете анализировать подобные логи при создании форм. Это позволит видеть вам то, что браузер отсылает в ваше приложение, так что вы сразу увидите, если что-то в этом обмене вдруг пойдет не так.

Создание форм в Rails

Первое, что вы увидите, если попробуете создать "ванильную" форму в представлении Rails, это то, что она не будет работать. Либо вы получите ошибку, либо обнуление сессии (в зависимости от версии Rails). Это происходит по причине политики безопасности, а именно из-за кросс-сайтовых запросов - Rails требует подтверждения, что подтверждена именно та форма, которая была сгенерирована. Для этого, Rails генерирует так называемую "csrf-метку", которая выглядит бессмысленным набором символов, но позволяет сопоставить форму с вашей сессией и самим приложением.

Вы видите эту метку в логе выше:

...
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jJa87aK1OpXfjojryBk2Db6thv0K3bSZeYTuW8hF4Ns=", "email"=>"foo@bar.com", "commit"=>"Submit Form"}
...

Так что, если вы хотите создать собственную форму для Rails, то необходимо также предоставить и метку. Для этого, вы можете использовать метод Rails form_authenticity_token, и в проекте это будет выглядеть так:

    <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">

Создание params в формах

Как насчет других полей ввода, которые нам необходимы?

Каждое из них отличается от другого, но тем не менее у них есть и общие признаки. Важным из них является атрибут name, который вы присваиваете тэгу input. Это очень важно для Rails. Имя атрибута говорит Rails о том, как назвать то, что вы введете в поле, для последующего помещения в хэш params. К примеру,

    ...
    <input type="text" name="description">
    ...

создаст хэш params, содержащий ключ description, доступ к которому внутри контроллера осуществляется обычным образом, например params[:description]. Также некоторые поля ввода, как например радиокнопки (type="radio"), используют атрибут name для того, чтобы группироваться на основании этого признака, и при нажатии на одну из них, убирать "нажатие" с другой. Удивительным образом, этот атрибут оказывается очень важным!

В уроке, посвященном контроллерам, мы говорили о вложенности данных. Вы часто будете упаковывать данные в хэш, вместо того чтобы оставлять их на верхнем уровне. Это может быть полезно, так как позволяет использовать #create в одну строку (при занесении параметров в белый список с использованием #require и #permit). В таком случае, в params[:user] содержится хэш со всеми атрибутами пользователя, такими как {first_name: "foo", last_name: "bar", email: "foo@bar.com"}. Как заставить форму отправить параметры таким образом? Легко!

Это возвращает нас к атрибуту name полей ввода нашей формы. Просто используйте квадратные скобки для вставки данных:

    ...
    <input type="text" name="user[first_name]">
    <input type="text" name="user[last_name]">
    <input type="text" name="user[email]">
    ...

Теперь эти поля преобразуются во вложенный хэш с ключом :user. В логе сервера теперь будет так:

Parameters: {"utf8"=>"✓", "authenticity_token"=>"jJa87aK1OpXfjojryBk2Db6thv0K3bSZeYTuW8hF4Ns=", "user"=>{"first_name"=>"foo","last_name"=>"bar","email"=>"foo@bar.com"}, "commit"=>"Submit Form"}

К отдельным параметрам хэша params можно получить доступ как в любом другом вложенном хэше params[:user][:email].

Не забудьте занести параметры в белый список при помощи require и permit, так как теперь это хэш, а не обычная строка. Посмотрите ниже раздел про контроллер, чтобы вспомнить процесс на его стороне.

Данную методику вы получите возможность применить в нашем проекте.

Помощники (хелперы) форм

Rails пытается облегчить вам жизнь насколько это возможно, предоставляя методы-помощники, автоматизирующие некоторые повторяющиеся моменты при создании форм. Это не означает, что вам не надо знать, как создаются формы "старым добрым способом". Скорее наоборот, БОЛЕЕ важно знать эти основы при использовании помощников, так как вам понадобится понять происходящее за кулисами, если что-то сломается.

Создадим форму с использованием помощника form_tag, который принимает блок, представляющий все поля ввода для нее. Он берет на себя работу по созданию скрытого поля для хранения метки CSRF, так что вам не надо о ней беспокоиться. Помощнику необходимо передать аргументы пути для отправки формы - сам путь (по умолчанию это текущая страница) и метод HTTP. Затем идут помощники для создания полей ввода, как например text_field_tag в примере ниже. Все, что вам нужно - это указать названия полей, которые они получат при отправке формы.

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

Создаст такую форму:

    <form accept-charset="UTF-8" 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>

ID полей ввода совпадают с их именем.

Помощники существуют для всех основных тэгов и принимаемые ими опции немного различаются. Посмотрите источники ниже, чтобы ознакомиться с деталями.

Привязка формы к объекту модели: form_for

Никто не желает помнить об URL, на который должна быть отправлена форма, или расписывать в ней кучу методов *_tag, и для этого Rails предоставляет метод form_for. Он похож на form_tag, но предоставляет вам больше возможностей.

Предоставив объект модели для form_for, вы получите отправку формы на URL для этого объекта, например @user создаст нового пользователя, отправив форму на корректный URL. Из урока по контроллерам мы помним, что действие #new обычно создает новый (несохраненный в БД) экземпляр объекта и отправляет его в представление... и наконец, становится понятным, зачем использовать этот объект в формах #form_for!

form_tag принимает блок без аргументов и поля ввода должны идти с отдельным something_tag. form_for принимает блок объекта формы, а затем вы создаете поля формы, основанные на этом объекте. В повседневной жизни для обозначения такого аргумента используется f.

Из учебника Rails:

    # app/controllers/articles_controller.rb
    def new
      @article = Article.new
    end

    #app/views/articles/new.html.erb
    <%= form_for @article do |f| %>
      <%= f.text_field :title %>
      <%= f.text_area :body, size: "60x12" %>
      <%= f.submit "Create" %>
    <% end %>

Получаем такой HTML:

    <form accept-charset="UTF-8" action="/articles/create" method="post">
      <input id="article_title" name="article[title]" type="text" />
      <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea>
      <input name="commit" type="submit" value="Create" />
    </form>

Заметим, что этот помощник упаковывает атрибуты объекта Article (как явно намекают нам на это квадратные скобки в атрибуте name).

Когда вы передаете в form_for объект модели, Rails проверяет, существует ли этот объект в БД. Если он там отсутствует, то Rails отправит форму в действие #create. Если же объект имеется в БД, то понятно, что речь идет о редактировании, и форма будет отправлена в действие #update. Корректный URL в данном случае генерируется автоматически при создании формы. Магия!

Формы и валидации

Что произойдет, если форма будет отправлена, но введенные в нее данные не пройдут валидацию? К примеру, пароль слишком короткий? Конечно, в этом случае такие ошибки сначала должен перехватывать Javascript.. но об этом мы поговорим позже. Надеемся, что в любом таком случае ваш контроллер отобразит форму повторно.

Вероятно вы захотите отобразить такие ошибки для оповещения пользователя. Вспомним, что Rails пытается провести валидацию объекта, и в случае ошибки присоединяет набор полей к объекту errors. Для получения этих ошибок используйте your_object_name.errors. Здесь есть пара удобных помощников, с помощью которых вы можете аккуратно отобразить ошибки в браузере - #count и #full_messages. Рассмотрим код:

    <% if @post.errors.any? %>
      <div id="error_explanation">
        <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>

        <ul>
        <% @post.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
        </ul>
      </div>
    <% end %>

Этот код проинформирует пользователя о количестве ошибок, а также выдаст сообщение по каждой из них.

Добавим к этому, что хелперы форм Rails автоматически отображают сообщения об ошибках! Если форма включает модель объекта, такую как form_for @article в примере выше, Rails проверит наличие ошибок, и если они есть, поле с ошибкой будет помещено в специальный <div> с классом field_with_errors, который вы можете выделить произвольным образом с помощью CSS.

PATCH и DELETE в формах

На самом деле формы не предназначены для удаления объектов, так как браузеры поддерживают только запросы GET и POST. Rails обходит это ограничение, добавляя в форму скрытое поле "method". Это позволяет Rails понимать, что на самом деле вы хотите выполнить либо запрос PATCH (известный как PUT), либо DELETE (исходя из того, что было указано). Выглядеть это может так - `<input name="method" type="hidden" value="patch">`.

Это реализуется в Rails добавлением опции :method в форму form_for или form_tag, например:

    form_tag(search_path, method: "patch")

Напомним про работу на стороне контроллера

Просто для напоминания, приведем пример очень простого контроллера с действиями #new и #create.

    # app/controllers/users_controller.rb
    ...
    def new
      @user = User.new
    end

    def create
      @user = User.new(user_params)
      if @user.save
        redirect_to @user
      else
        render :new
      end
    end

    def user_params
      params.require(:user).permit(:first_name, :last_name, :other_stuff)
    end
    ...

Мы привели этот код, чтобы напомнить вам о жизненном цикле формы. Предположим, пользователь выбирает действие #new, переходя по адресу http://www.yourapp.com/users/new. Это действие создает новый объект в памяти (пока не сохраняя его в БД) и рендерит представление new.html.erb. Представление использует form_for для @user.

После того как форма отправлена, действие #create создаст новый объект User, причем при этом мы можем быть уверены в корректности занесенных в БД параметров. Вспомните, что наш метод #user_params вернет хэш params[:user], что позволит методу User.new создать экземпляр с уже указанными атрибутами. Если экземпляр сохраняется в БД, значит атрибуты корректны, и мы переходим к отображению страницы show.html.erb.

Если же @user не может быть сохранен, например из-за цифр в first_name, мы возвращаемся на рендеринг new.html.erb, в этот раз используя переменную экземпляра @user с присоединенными к ней ошибками. Форма должна обработать эти ошибки, указав пользователю где он ошибся.

Ваши задания

  1. Прочтите о хелперах форм в Rails, с раздела 1 до 3.2.
  2. Пробегитесь по разделам с 3.3 до 7 для общего обзора имеющихся возможностей. Когда-нибудь они вам понадобятся, и теперь вы знаете где их найти.
  3. Прочтите разделы 7.1 и 7.2, где содержится официальное пояснение о создании параметров из атрибута name.
  4. Просмотрите раздел 8 о валидациях в Rails.

Заключение

На данный момент у вас должно сложиться твердое понимание о работе форм в общем, а также хорошее представление о том, как Rails помогает вам в их создании. В наших проектах вам предстоит создать множество форм, так что не волнуйтесь если еще не освоили материал. В процессе работы многое станет понятным.

Дополнительные ресурсы

Этот раздел содержит полезные ссылки на дополнительные материалы. Это не обязательно, так что расценивайте их как нечто полезное, если вы хотите поглубже погрузиться в тему

Поделиться уроком: