Статьи

Phoenix for Railsies: помощники и модели форм

Феникс

Phoenix — это веб-фреймворк, созданный для Elixir , потрясающего языка, созданного на виртуальной машине Erlang. Исходя из истории Rails, Феникс кажется сразу знакомым. Хотя в нем отсутствуют некоторые тонкости, которые нам дает Rails, Phoenix имеет отличную поддержку таких технологий, как WebSockets, что делает его отличным кандидатом для современной веб-разработки. Во-вторых, поскольку он построен на виртуальной машине Erlang, многие тесты показывают, что он может масштабироваться лучше, чем Rails. В любом случае, это отличный инструмент для добавления в пояс любого Рубииста.

Мы рассмотрели некоторые основы Феникса в моей последней статье . На этот раз мы создадим приложение Remynders с помощниками и моделями форм. Давайте сразу приступим. Вы можете найти репозиторий Github для кода, описанного в этой статье, и последний здесь .

Форма HTML

Если вы вернетесь к тому месту, где мы остановились в прошлый раз (проверьте ветку «article1» хранилища), мы настроили представление индекса, но хотели настроить форму для создания нового напоминания. Это требует создания пустого представления в web / views / remder_view.ex :

defmodule Remynders.ReminderView do use Remynders.Web, :view end 

Нам все еще нужен шаблон, чтобы фактически показать пользователю форму. Давайте сделаем это, создав шаблон в templates / Remderner / new.html.eex :

 <form> <div class="input_wrapper center_block"> <label>Reminder text</label> <input type="number" value="10"></input> </div> <div class="input_wrapper center_block"> <label>Minutes from now</label> <input type="number" value="10"></input> </div> <div class="input_wrapper center_block"> <label>Your email</label> <input type="email"></input> </div> <div class="input_wrapper center_block"> <input type="submit" value="Remynd me" class="button"></div> </div> </form> 

BOO для уродливых, неустановленных полей ввода, поэтому добавьте немного CSS в web / static / app.scss :

 textarea:focus, input:focus{ outline: 0; } label { font-weight: bold; font-size: 20px; display: block; margin: 20px; } .input_wrapper { margin-top: 20px; } input[type="number"], input[type="email"], input[type="number"]:active { font-size: 25px; text-align: center; font-weight: bold; border: 5px solid #000; padding: 15px; width: 225px; } input[type="email"] { font-size: 18px; } input[type="submit"] { width: 265px; padding: 15px; border-color:; #cc0000; background-color: #fff; } 

Теперь, посмотрев на http://127.0.0.1:4000/reminders/new , есть форма. Но, подождите секунду, мы не заполняем модель этой формой. Для этого используйте form_for формы form_for . Прежде чем мы это сделаем, пришло время настроить наши модели.

модели

Мы хотим, чтобы модель представляла концепцию напоминания. В Фениксе Ecto используется для работы с базами данных. По сути, это способ написания запросов в Elixir через язык, специфичный для предметной области. Это немного отличается от подхода Rails на основе ООП, но большая часть настроек очень похожа.

В Rails мы снимаем команду rails generate model последующим rake db:migrate . Phoenix очень похож:

 mix phoenix.gen.model Reminder reminders minutes:integer email:string title:string 

Мы должны получить вывод, как показано ниже:

  • creating priv/repo/migrations/20150805211918_create_reminder.exs
  • creating web/models/reminder.ex
  • creating test/models/reminder_test.exs

Там модель создана. Но нам нужно освободить место для этого внутри нашей базы данных. Ecto поставляется с адаптерами для MySQL и PostgreSQL. По умолчанию Phoenix настраивает вас с помощью адаптера PostgreSQL. Для этого нам понадобится пара команд (отредактируйте config / dev.exs, как требуется для вашей установки PostgreSQL):

 $ mix ecto.create $ mix ecto.migrate 

Phoenix заимствует много идей из Rails и подобных Rails фреймворков, включая миграции. В priv / repo / migrations вы найдете миграцию для модели и таблицы базы данных, настроенную этими двумя командами.

Помощники по формам

Теперь, когда у нас есть модель, давайте настроим форму для фактического использования модели. Взгляните на модель в web / models / Remder.ex :

 defmodule Remynders.Reminder do use Remynders.Web, :model schema "reminders" do field :minutes, :integer field :email, :string field :title, :string timestamps end @required_fields ~w(minutes email title) @optional_fields ~w() @doc """ Creates a changeset based on the `model` and `params`. If `params` are nil, an invalid changeset is returned with no validation performed. """ def changeset(model, params \\ :empty) do model |> cast(params, @required_fields, @optional_fields) end end 

Важным методом здесь является changeset . Нам не нужно слишком сильно беспокоиться о том, как он работает в данный момент, но вызов метода в основном создает «набор изменений» для модели на случай, если мы захотим обновить ее. Нам нужно обновить код нашего контроллера ( web / controllers / Remder_controller.ex ), чтобы позволить шаблону получить доступ к набору изменений для нового экземпляра Reminder:

 def new(conn, _params) do changeset = Reymnders.Reminder.changeset(%Reymnders.Reminder{}) render conn, "new.html", changeset: changeset end 

Что здесь происходит? По сути, мы настраиваем набор изменений из нового экземпляра Reminder и передаем его в шаблон. Давайте изменим шаблон ( web / templates / remder / new.html.eex ) на:

 <%= form_for @changeset, reminder_path(@conn, :create), fn f -> %> <div class="input_wrapper center_block"> <label>Reminder text</label> <%= text_input f, :title %> </div> <div class="input_wrapper center_block"> <label>Minutes from now</label> <%= number_input f, :minutes, [{:value, "10"}] %> </div> <div class="input_wrapper center_block"> <label>Your email</label> <%= text_input f, :email %> </div> <div class="input_wrapper center_block"> <%= submit "Remynd me" %> </div> <% end %> 

Как вы можете видеть, HTML теперь использует помощники форм. Теперь нам нужно добавить действие create в наш контроллер:

 def create(conn, %{"reminder" => reminder_params}) do changeset = Reminder.changeset(%Reminder{}, reminder_params) case Repo.insert(changeset) do {:ok, _reminder} -> conn |> put_flash(:info, "Reminder created successfully.") |> redirect(to: "/") {:error, changeset} -> render(conn, "new.html", changeset: changeset) end end 

Кроме того, чтобы иметь возможность ссылаться на модель напоминания как просто на Reminder а не на Remynders.Reminder , создайте псевдоним так же, как нам нужно для Helpers в последний раз. Итак, defmodule это сразу после строки defmodule в контроллере:

 alias Remynders.Reminder 

Быстрая проверка с заполненной формой, очевидно, что наш код работает, но на данный момент он довольно непрозрачен. Давайте разберемся с этим постепенно.

 def create(conn, %{"reminder" => reminder_params}) do 

Это выглядит как довольно безобидное определение функции, пока мы не рассмотрим второй параметр. Что, черт возьми, происходит?! Это использует одну из функциональных возможностей Elixir: сопоставление с образцом. Мы говорим, что из второго аргумента для create ожидаем получить словарь. Мы хотим, чтобы значение, связанное с ключом «напоминание» в этом словаре, было помещено в переменную reminder_params чтобы на него можно было ссылаться позже в методе.

 changeset = Reminder.changeset(%Reminder{}, reminder_params) 

Это создание набора изменений из параметров напоминания, которые мы только что получили. В частности, это пустой экземпляр Reminder с %Reminder{} и набор значений, которые он должен иметь с reminder_params . Затем Reminder.changeset выясняет, что необходимо изменить, чтобы получить из первого аргумента значения во втором аргументе.

 case Repo.insert(changeset) do 

Первая важная часть для рассмотрения — Repo.insert(changeset) . Как правило, изменения базы данных выполняются через модуль Repo . В заявлении мы говорим Ecto вставить набор изменений в базовую базу данных. Далее рассмотрим ключевое слово case . Это немного похоже на оператор switch (если у вас есть фон C / Java) для возвращаемого значения Repo.insert но примерно в миллион раз мощнее. По сути, мы сопоставляем шаблон с возвращаемым значением.

 {:ok, _reminder} -> conn |> put_flash(:info, "Reminder created successfully.") |> redirect(to: "/") 

Это часть описания дела. Здесь мы рассмотрим случай, когда напоминание успешно вставлено в базу данных. В частности, код в этом блоке должен выполняться, если мы получим заданный формат возвращаемого значения (т.е. с :ok вместо :error ). Чтобы понять остальную часть кода, нам нужно понять оператор |> . По сути, a |> b(c, d) — это другой способ записи b(a, c, d) , то есть левая рука вычисляется, а затем передается как первый аргумент в правую половину. Если по какой-то дикой случайности вы попали в Haskell, это немного похоже на обратную операцию оператора $ . Если по какой-то еще более дикой случайности вы попали в F #, то это как оператор трубы, более или менее.

Мы объединяем эти вызовы методов, используя оператор |> потому что и put_flash и redirect_to требуют conn качестве первого аргумента. Это помещает «флэш» уведомление (как мы сделали бы с flash[:notice] в Rails) и затем перенаправляет в корень приложения.

 {:error, changeset} -> render(conn, "new.html", changeset: changeset) 

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

Завершение

Это, и ряд других вещей будут темами для следующего поста. Нам все еще необходимо реализовать уведомления и ошибки, а также способ своевременно доставлять пользователю Remynders. Мы рассмотрим обе эти вещи, а также некоторые другие лакомые кусочки в следующей части. При написании этой статьи я понял, что в настоящее время у Phoenix нет удивительной современной документации, чего можно ожидать от проекта, который еще только начинается. Надеемся, что это руководство поможет заполнить этот пробел и обеспечит быструю настройку для Railies, желающих попробовать что-то новое.