Ruby
Article

Phoenix for Railsies: Form Helpers and Models

By Dhaivat Pandya

phoenix

Phoenix is a web framework built for Elixir, an awesome language that’s built on the Erlang Virtual Machine. Coming from a Rails background, Phoenix seems immediately familiar. Although it is missing a few of the niceties that Rails gives us, Phoenix has excellent support for technologies such as WebSockets, which makes it a great candidate for modern web development. Secondly, since it is built on the Erlang Virtual Machine, many benchmarks show that it can scale better than Rails. In any case, it’s a great tool to add to any Rubyist’s belt.

We covered some of the basics of Phoenix in my last article. This time around, we’ll build out the Remynders app with form helpers and models. Let’s jump right in. You can find the Github repository for the code covered in this article and the last one here.

Form HTML

If you dial back to where we left off last time around (check the “article1” branch of the repository), we set up the index view, but wanted to set up a form to create a new reminder. This requires the creation an empty view in web/views/reminder_view.ex:

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

We still need a template to actually show the user the form. Let’s do that by creating the template in templates/reminder/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 to ugly, unstyled input fields, so add some CSS in 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;
}

Now, taking a look at http://127.0.0.1:4000/reminders/new, there’s the form. But, wait a second, we’re not actually populating a model with this form. To do that, use the form_for form helper. Before we do that, it’s time set up our models.

Models

We want a model to represent the concept of a reminder. In Phoenix, Ecto is used to do database-related stuff. Basically, it’s a way to write queries in Elixir through a domain specific language. This is a bit different from the OOP-based Rails approach, but most of the setup is very similar.

In Rails, we’d shoot off a rails generate model command followed by a rake db:migrate. Phoenix is pretty similar:

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

We should get output like the following:

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

There, the model is created. But, we need to make space for it inside our database. Ecto comes with adapters for MySQL and PostgreSQL. By default, Phoenix sets you up with the PostgreSQL adapter. To get it going, we need a couple of commands (edit config/dev.exs as needed for your PostgreSQL setup):

$ mix ecto.create
$ mix ecto.migrate

Phoenix borrows a lot of ideas from Rails and Rails-like frameworks, including migrations. In priv/repo/migrations, you’ll find the migration for the model and database table set up by those two commands.

Form Helpers

Now that we have the model, let’s set up the form to actually use the model. Take a look at the model in web/models/reminder.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

The important method here is changeset. We needn’t worry ourselves too much about how it works at this point, but calling the method basically creates a “changeset” for the model, in case we want to update it. We need to update our controller code (web/controllers/reminder_controller.ex) to allow the template to access the changeset for a new Reminder instance:

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

What is going on here? Basically, we’re setting up a changeset from a new instance of Reminder and passing that to the template. Let’s change the template (web/templates/reminder/new.html.eex) to:

<%= 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 %>

As you can see, the HTML is now using form helpers. Now, we need to add a create action to our controller:

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

Also, in order to be able to refer to the Reminder model as just Reminder rather than Remynders.Reminder, create an alias just as we need for Helpers the last time around. So, stick this just after the defmodule line in the controller:

alias Remynders.Reminder

Checking quickly with a filled out form, it is evident that our code works, but it’s pretty opaque at the moment. Let’s break it down bit by bit.

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

This looks like a pretty innocuous function definition until we look at the second parameter. What the heck is going on?! This is using one of Elixir’s functional-programming-like features: pattern matching. We’re saying that out of the second argument to create, expect to get a dictionary. We want the value associated with the key “reminder” within this dictionary to be placed into the variable reminder_params so it can referenced later in the method.

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

This is constructing a changeset out of the reminder parameters we’ve just been handed. Specifically, it’s an empty Reminder instance with %Reminder{} and a set of values that it’s supposed to have with reminder_params. Then, Reminder.changeset figures out what is needed to change in order to get from the first argument to have the values in the second argument.

case Repo.insert(changeset) do

The first important part to consider is Repo.insert(changeset). Generally, database changes are performed through the Repo module. With the statement, we’re telling Ecto to insert the changeset into the underlying database. Next up, consider the case keyword. It’s a bit like a switch statement (if you have a C/Java background) on the return value of Repo.insert but about a million times more powerful. Essentially, we are pattern matching against the return value.

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

This is a part of the case statement. Here, we’re considering the case where the Reminder is successfully inserted into the database. Specifically, the code in this block should execute if we get a given format of return value (i.e. with an :ok instead of an :error). In order to understand the rest of the code, we need to understand the |> operator. Basically, a |> b(c, d) is a different way of writing b(a, c, d), i.e. the left hand is evaluated then passed as the first argument to the right half. If by some wild chance you’re into Haskell, it’s a bit like the reverse of the $ operator. If by some even wilder chance you’re into F#, then it’s like the pipe operator, more or less.

We are chaining together these method calls using the |> operator because both put_flash and redirect_to require conn as their first argument. This puts in a “flash” notice (just as we would with flash[:notice] in Rails) and then redirects to the root of the application.

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

The second case is much simpler. In case something goes wrong, just pass the changeset back to the page we were just on in order to load the form with the values the user has already supplied. But, how do we tell the user what exactly went wrong?

Wrapping It Up

That, and a number of other things will be topics for the next post. We still have to implement notices and errors and a way to deliver Remynders to the user on a timely basis. We’ll be covering both of those things as well as some other tidbits in the next installment. In writing this article, I’ve realized that Phoenix currently doesn’t have amazing, up-to-date documentation, which is a bit much to expect of a project that’s still up and coming. Hopefully this guide helps fill that void and provides a quick on-boarding process for Railies looking to try out something new.

More:
  • http://careersreport.com Susan Grant

    I need to guide you to fantastic online freelancing opportunity… 3 to 5 h of work /a day… Weekly paycheck… Bonus opportunities…Payscale of $6k to $9k /month… Just several hrs of your free time, a pc, basic knowing of Internet and trusted internet-connection is what is required…Get more information by visiting^ my disqus^profile

  • mohd sadiq

    Awesome Article, Want more elixir and phoenix stuff! Thanks

  • http://www.dikaio.com/ Don Dikaio

    Same here, good stuff Daivat!

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Ruby, once a week, for free.