Coin-toss.org / Simple Phoenix application - Part 5

Summary

The purpose of this post is to document the creation process of the Coin-toss.org website using the Phoenix Web application framework and the Elixir programming languages. Part 4 steps include:

  • Fixing up the templates
  • Deploying the application to Heroku

Technology used:

  • Elixir 1.2
  • Phoenix 1.1.1
  • Postgres 9.4

Fixing up the templates

This section is less related to programming and more related to adding the right HTML and CSS to make the application look passable. They only truly unique feature in this code is the form_for function that is used to call a form for a changeset that will automatically show errors. With that in mind let us get started by removing any extraneous HTML and turning our web\templates\layout\app.html.slim from what it currently is to this bare bones version:

doctype html
html lang="en"
  head
    meta charset="utf-8"
    meta content="IE=edge" http-equiv="X-UA-Compatible"
    meta content="width=device-width, initial-scale=1" name="viewport"
    meta content="" name="description"
    meta content="" name="author"
    title Coin Toss
    link rel="stylesheet" href="#{static_path(@conn, "/css/app.css")}"
  body
    .container
      main role="main"
        = render @view_module, @view_template, assigns
    script src="#{static_path(@conn, "/js/app.js")}"

This will give us a blank canvas to work from. If we boot up the app with mix phoenix.server and visit localhost:4000 we can see that we have blank page being rendered from TossController.new/1 as that is our entry function as defined by the router.ex file. To make the application functional all we need to do now is add a form_for tag to web\templates\toss\new.html.slim with the correct fields and a button:

= form_for @changeset, toss_path(@conn, :create), fn f ->
  = if @changeset.action do
    .alert.alert-danger
      p Oops, something went wrong! Please check the errors below.
  .form-group
    = label f, :heads, class: "control-label"
    = text_input f, :heads, class: "form-control", required: "required"
    = error_tag f, :heads
  .form-group
    = label f, :tails, class: "control-label"
    = text_input f, :tails, class: "form-control", required: "required"
    = error_tag f, :tails
  .form-group
    = submit "Submit", class: "btn btn-primary"

If you remember, there is a case Repo.insert clause in our create/2 function which will automatically return a changeset if there is an error saving the model. This gets set back out to the new.html.slim template and the error is rendered inside the form using the error_tag function.

Next we can add our outcome to the show.html.slim template:

h2= if @toss.result == 0, do: "Heads!", else: "Tails!"
p or
h2= if @toss.result == 0, do: @toss.heads, else: @toss.tails
p
  = link "Share", to: toss_path(@conn, :show, @toss)
  |  | 
  = link "Start again", to: toss_path(@conn, :new)
hr

= if (length(@similar) > 1) do
  h4 Similiar tosses
  table.table
    thead
      tr
        th Heads
        th Tails
        th Outcome
        th Time
    = for toss <- @similar do
      tr
        td= toss.heads
        td= toss.tails
        td= if toss.result == 0, do: toss.heads, else: toss.tails
        td= toss.inserted_at

In this example we are using a number of techniques: The inline if statements will return the correct result text based on the result attribute. This could also be moved to a separate function in the toss_view.ex file. We also use two link helper to create URLs to share this particular result and to start a new toss. Lastly we check if there are more than 1 results in our @similar list, and render them individually using the for function.

We could spend a bit more time on the UI, but this about as functional as it needs to be at this point.

Of course we should check if we broke anything mix test:

$ mix test
................................

Finished in 0.3 seconds (0.2s on load, 0.1s on tests)
32 tests, 0 failures

Deploying the application to Heroku

Now that the application is finished all we need to do is host it. My preferred hosting site at this point is Heroku. I have already created my application in the Heroku UI and now I have to add various build packs as specified in the guide: http://www.phoenixframework.org/docs/heroku

$ heroku git:remote -a coin-toss
set git remote heroku to https://git.heroku.com/coin-toss.git

$ heroku buildpacks:add https://github.com/HashNuke/heroku-buildpack-elixir.git
Buildpack added. Next release on coin-toss will use https://github.com/HashNuke/heroku-buildpack-elixir.git.
Run git push heroku master to create a new release using this buildpack.

$ heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git
Buildpack added. Next release on coin-toss will use:
  1. https://github.com/HashNuke/heroku-buildpack-elixir.git
  2. https://github.com/gjaldon/heroku-buildpack-phoenix-static.git
Run git push heroku master to create a new release using these buildpacks.

Next we need to make a change to config/prod.exs to accomodate the way Heroku passes the database credentials to the application:

use Mix.Config

config :coin_toss, CoinToss.Endpoint,
http: [port: {:system, "PORT"}],
url: [scheme: "http", host: "coin-toss.org", port: 80],
cache_static_manifest: "priv/static/manifest.json",
secret_key_base: System.get_env("SECRET_KEY_BASE")

config :logger, level: :info

# Configure your database
config :coin_toss, CoinToss.Repo,
  adapter: Ecto.Adapters.Postgres,
  url: System.get_env("DATABASE_URL"),
  pool_size: 20

We also need to create a new secret key base (don’t worry this is not the key base I used):

$ mix phoenix.gen.secret
xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53

$ heroku config:set SECRET_KEY_BASE="xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53"
Setting config vars and restarting mysterious-meadow-6277... done, v3
SECRET_KEY_BASE: xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53

At last all we need to do is commit the changes and $ git push heroku master to deploy the application. Last of all we need to run the migrations:

$ heroku run mix ecto.migrate

Conclusion

This wraps up the simple Coin-toss.org application. We went from conception to deployment in what I hope was not a too painful process. It should illustrate how easy it is to write applications in Elixi/Phoenix and what a joy it can be at the same time.