meatherly

Basic web app with Phoenix

Overview

In this blog post I’m going to be building a basic web app with the Phoenix framework. I want to pick up from where the getting started guide for phoenix left you. Let’s create a nested model relationship app. I hope to also show my little neat tricks I’ve learned along the way. Just a heads up, I’m writing this blog like you’ve already gone through the getting started guide for elixir and you’re ready for the next step. So if you’re feeling a little lost please consult the Overview page.

This will be a simple app that allows you to create users and then create cars for that user. Simple right? Let’s go! You can see the code here

Setup

Things you’ll need:

  • Postgres: brew install postgresql. This will be your DB service.
  • Node & NPM: brew install node. You’ll want NPM for all the JS goodies you get with Phoenix. Phoenix uses Brunch.io. It’s the first time I’ve heard of it and it looks really cool!
  • Elixir: http://elixir-lang.org/install.html
  • Phoenix:
    • $ mix local.hex
    • $ mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v0.12.0/phoenix_new-0.12.0.ez

Alright let’s create our simple app.

Create a new Phoenix app

First we’ll create our app with the mix phoenix.new command.

$ mix phoenix.new simple_phoenix_app

When it asks:

  • Install mix dependencies? [Yn]
    • yes.
  • Install brunch.io dependencies? [Yn]
    • yes.

Now you should have all your dependencies to create the app.

That’s going to install all the things you need for your phoenix app. I’ve never heard of Brunch before Phoenix but it’s a really nice template generator. check it out here: (http://brunch.io/)

Okay now we have our new Phoenix project. Let’s run it.


$ cd simple_phoenix_app
$ mix phoenix.server

Now you should be able to open your browser and visit http://localhost:4000

Just like you would see in Rails. It’s a simple welcome page. Yay!

Okay now it’s time for me just to show you the coolest “batteries included” feature about this framework. Open the simple_phoenix_app/web/templates/page/index.html.eex file in your editor. Then make sure you have the editor and browser pointed at http://localhost:4000 and it’s visible. Now at the top of the page add a h1 to the jumbotron div. like:

<div class="jumbotron">
  <h1>O Snap!</h1>
  <h2>Welcome to Phoenix!</h2>
  <p class="lead">Most frameworks make you choose between speed and a productive environment. <a href="http://phoenixframework.org">Phoenix</a> and <a href="http://elixir-lang.org">Elixir</a> give you both.</p>
</div>

Then hit save. OMG!! You catch that?! Auto-reloading baked in!!

Create user scaffolding

Alright let’s create our first model. We’ll use the scaffold like command for Phoenix.

$ mix phoenix.gen.html User users name email

The command above is creating:

  • A User model
  • A users table
  • With the string attributes name and email
  • The controller, view, and templates needed for requests
  • And some tests for you.

Now let’s follow the directions from the output.

Add resources "/users", UserController to the router.ex file.

### simple_phoenix_app/web/router.ex
scope "/", SimplePhoenixApp do
  pipe_through :browser # Use the default browser stack

  get "/", PageController, :index
  resources "/users", UserController
end

Before we migrate the DB we’ll edit the DB connection info, or you can leave it if you have a postgres user with the password set to postgres, but I don’t so I’m going to change mine to my username. I you change this stetting be sure to restart your server or else it won’t pick up the changes.

### simple_phoenix_app/config/dev.exs
...
# Configure your database
config :simple_phoenix_app, SimplePhoenixApp.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "meatherly",
  database: "simple_phoenix_app_dev"

Now let’s create the DB and Migrate.

$ mix ecto.create
$ mix ecto.migrate

Then let’s add a link to our home page that points to the user’s index route.

<!-- simple_phoenix_app/web/templates/page/index.html.eex -->
<div class="jumbotron">
  <h2>Welcome to Phoenix!</h2>
  <p class="lead">Most frameworks make you choose between speed and a productive environment.</p>
  <%= link "Users", to: user_path(@conn, :index) %>
</div>

Now you should be able to click on the users link on the home page. I didn’t have to refresh the page either :)

Boom! We have our basic user index page!

You can go ahead and create a user and all that fun stuff. Try not to let your face melt with the speed. lol.

Create car model

Now lets create our Car model for our users. Well use the model generator because we’re not creating a top level route.

$ mix phoenix.gen.model Car cars name year:integer

We need to edit the migration first to make the referenced foreign key for the users and cars. To do that we’ll need to add the references function to the user_id column.

### simple_phoenix_app/priv/repo/migrations/*_create_car.exs
defmodule SimplePhoenixApp.Repo.Migrations.CreateCar do
  use Ecto.Migration

  def change do
    create table(:cars) do
      add :user_id, references(:users)
      add :name, :string
      add :year, :integer

      timestamps
    end
  end
end

Now let’s migrate our DB to add the latest migration

$ mix ecto.migrate

Adding a relationship between cars and users

We’ll need to edit our car and user model and add the relationships to it

### simple_phoenix_app/web/models/user.ex
###...
schema "users" do
  has_many :cars, SimplePhoenixApp.Car
  field :name, :string
  field :email, :string

  timestamps
end
###...
### simple_phoenix_app/web/models/car.ex
###...
schema "cars" do
  belongs_to :user, SimplePhoenixApp.User
  field :name, :string
  field :year, :integer

  timestamps
end
###...

Adding Car controller, view and templates

Now let’s create our Controller, View, and Templates.

####Controller

### simple_phoenix_app/web/controllers/car_controller.ex
defmodule SimplePhoenixApp.CarController do
  use SimplePhoenixApp.Web, :controller

  alias SimplePhoenixApp.Car

  plug :action
end

####View

### simple_phoenix_app/web/views/car_view.ex
defmodule SimplePhoenixApp.CarView do
  use SimplePhoenixApp.Web, :view

end

####Templates

simple_phoenix_app/web/templates/index.html.eex

<h2>Listing cars for <%= @user.name %></h2>

<table class="table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Year</th>
    </tr>
  </thead>
  <tbody>
<%= for car <- @cars do %>
    <tr>
      <td><%= car.name %></td>
      <td><%= car.email %></td>
    </tr>
<% end %>
  </tbody>
</table>

<%= link "New car", to: user_car_path(@conn, :new, @user) %>

simple_phoenix_app/web/templates/new.html.eex

<h2>New car for <%= @user.name %></h2>

<%= render "form.html", changeset: @changeset,
                        action: user_car_path(@conn, :create, @user) %>

<%= link "Back to cars", to: user_car_path(@conn, :index, @user) %>

simple_phoenix_app/web/templates/form.html.eex

<%= form_for @changeset, @action, fn f -> %>
  <%= if f.errors != [] do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below:</p>
      <ul>
        <%= for {attr, message} <- f.errors do %>
          <li><%= humanize(attr) %> <%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="form-group">
    <label>Name</label>
    <%= text_input f, :name, class: "form-control" %>
  </div>

  <div class="form-group">
    <label>Year</label>
    <%= number_input f, :year, class: "form-control" %>
  </div>

  <div class="form-group">
    <%= submit "Submit", class: "btn btn-primary" %>
  </div>
<% end %>

And one last thing let’s add our cars route to the router.ex file. Just add the cars route inside a block for the users route.

scope "/", SimplePhoenixApp do
  pipe_through :browser # Use the default browser stack

  get "/", PageController, :index
  resources "/users", UserController do
    resources "/cars", CarController
  end
end

Alright time to edit our CarController to add the needed plugs and actions. Let’s start with a plug to find the user. Let’s add the find_user function at the bottom of the controller. Then add the plug :find_user above the action plug since plugs are executed in the order they are defined.

defmodule SimplePhoenixApp.CarController do
  #...
  plug :find_user
  plug :action
  #...
  defp find_user(conn, _) do
    user = Repo.get(SimplePhoenixApp.User, conn.params["user_id"])
    assign(conn, :user, user)
  end
end

Now let’s add our index action to the controller

def index(conn, _params) do
  user = conn.assigns.user
  cars = Repo.all assoc(user, :cars)

  render conn, cars: cars, user: user
end

You’ll notice we get our user from the conn.assigns map that we made down in the find_user function. You can’t really have instance variables flying around your controllers like you have in Ruby in Elixir. So we have to add assignments to the connection and pass that around.

Next lets add a link to the user’s cars on the user’s show page.

<!-- simple_phoenix_app/web/templates/user/show.html.eex -->
<%= link "Back", to: user_path(@conn, :index) %>
| <%= link "Cars", to: user_car_path(@conn, :index, @user) %>

You can add this link to the bottom of the show page.

Now let’s create a new user on our web app using the site. Navigate to http://localhost:4000/users click on New user link.

Create the new user. Now submit the form and you should see your user. Click on the show button for that user and you should see the Cars link we made on the bottom of the page! YAY!

Add new action for car controller

Now Let’s add the new action to the car controller to render the new car form. And since we’ve made that find_user plug it’s going to be smaller than you think :)

#...
def new(conn, _) do
  changeset = Car.changeset(%Car{})
  render conn, changeset: changeset
end
#...

You might be wondering. Won’t I need the user for the view since we have a @user variable in the view? Well no since we’ve added the user to the connections assignments that means it was sent down with the connection to the view and the view passed it along to the templates.

So pro tip: You can add things to the assigns to DRY up your controllers.

Okay now you can render the new car form from then car index page. YAY!

Add create action for car controller

Now let’s add the create action so we can submit the car form. This one will be a bit longer than the new action.

def create(conn, %{"car" => car_params}) do

  changeset =
    build(conn.assigns.user, :cars)
    |> Car.changeset(car_params)

  if changeset.valid? do
    Repo.insert(changeset)

    conn
      |> put_flash(:info, "Car has been successfully created.")
      |> redirect(to: user_car_path(conn, :index, conn.assigns.user))
  else
    render(conn, "new.html", changeset: changeset)
  end
end

Wow! right? lol. Let’s talk about some of this. First off let’s talk about that random build function just hanging out in there. Well I stumbled into this file simple_phoenix_app/web/web.ex and saw this where all the use SimplePhoenixApp.Web, :view are coming from. It has all the View, Model, Controller, and Router functions defined. In the controller one they import Ecto.Model which that has the build function. It’s very similar to the ActiveRecord build method. You can read more about it here. The rest of the action should look very similar to the UserController create action. So if the changeset isn’t valid we’re just rendering the new page again with the errors. Else we’re sending them back the index page for the cars for the user.

Well what are you waiting for?! Try it out!!

Extra credit

After talking with some guys in the IRC channel I found out that I can DRY up our controller by adding the alias SimplePhoenixApp.User to the simple_phoenix_app/web/web.ex file in the controller section. Let’s do that for kicks!

def controller do
  quote do
    use Phoenix.Controller

    # Alias the data repository and import query/model functions
    alias SimplePhoenixApp.Repo
    import Ecto.Model
    import Ecto.Query, only: [from: 2]

    # Import URL helpers from the router
    import SimplePhoenixApp.Router.Helpers

    alias SimplePhoenixApp.User
  end
end

Now we can take out the long namespaced names for User in the CarController! Yay!

Ending notes

So how bout dem apples! We’ve built a simple Phoenix App I bet you might have a lot questions and I bet I don’t have a lot of answers but if you head over to the IRC room #elixir-lang there are some great guys in there that would love to help. Even the creator of the Framework :)

This project is maintained by meatherly