Suffragist

this repo takes the documentation from the Rails-Girls guides to implement it in crystal-lang

Guide

Project initialize

crystal init app suffragist
cd suffragist

Install Kemal

Add to shard.yml

dependencies:
  kemal:
    github: kemalcr/kemal

Create your first Kemal app

In the file src/suffragist.cr

require "kemal"

get "/" do
  "Hello, voter!"
end

Kemal.run

Run your app

in the project directory, run crystal src/suffragist.cr. Wait for the message [development] Kemal is ready to lead at http://0.0.0.0:3000 and visit localhost:3000. You should see a "Hello, voter!" page. Hit Ctrl+C in the terminal to shut down the server.

Add the index view

To keep everything in order let's make a directory for our views (and name it views).

Put this code into an index.ecr file in the views directory:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Suffragist</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <p>What's for dinner?</p>

      <form action="cast" method="post">
        <div class="mb-3">
          <%- Choices.each do |id, text| -%>
            <div class="form-check">
              <input type="radio" name="vote" value="<%= id %>" class="form-check-input" id="vote_<%= id %>" />
              <label class="form-check-label" for="vote_<%= id %>">
                <%= text %>
              </label>
            </div>
          <%- end -%>
        </div>

        <button type="submit" class="btn btn-primary">Cast this vote!</button>
      </form>
    </div>
  </body>
</html>

And into suffragist.cr:

require "kemal"
require "ecr"

Choices = {
  "HAM" => "Hamburger",
  "PIZ" => "Pizza",
  "CUR" => "Curry",
  "NOO" => "Noodles",
}

get "/" do
  render "src/views/index.ecr
end

Kemal.run

Run crystal src/suffragist.cr, check your results and shut down the server with Ctrl+C.

Templates

Adjust the index.ecr file in the views directory and add the <h1>…</h1> line:

<body>
  <div class="container">
    <h1><%= @title %></h1>
    <p>What's for dinner?</p>

Change the get action:

get "/" do
  title = "Welcome to the Suffragist!"
  render "src/views/index.ecr"
end

Add the ability to POST results

Put this into src/suffragist.cr:

post "/cast" do |env|
  title = "Thanks for casting your vote!"
  vote  = env.params.body["vote"].as(UInt32)
  render "src/views/cast.ecr"
end

Create a new file in the views directory, cast.ecr, and put there some HTML with embedded Crystal code:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Suffragist</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <h1><%= title %></h1>
      <p>You cast: <%= Choices[vote] %></p>
      <p><a href="/results">See the results!</a></p>
    </div>
  </body>
</html>

Factor out a common layout

Create a layout.ecr file in the views directory. Put the following in there:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Suffragist</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <h1><%= title %></h1>
      <%= content  %>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
  </body>
</html>

in src/suffragist.cr, add a macro to call this layout

macro my_renderer(filename)
  render "src/views/#{ {{filename}} }.ecr", "src/views/layout.ecr"
end

and now, you can use your new renderer in all actions :

require "kemal"
require "ecr"

Choices = {
  "HAM" => "Hamburger",
  "PIZ" => "Pizza",
  "CUR" => "Curry",
  "NOO" => "Noodles",
}

macro my_renderer(filename)
  render "src/views/#{ {{filename}} }.ecr", "src/views/layout.ecr"
end

get "/" do
  title = "Welcome to the Suffragist!"
  my_renderer "index"
end

post "/cast" do |env|
  title = "Thanks for casting your vote!"
  vote  = env.params.body["vote"].as(String)
  my_renderer "cast"
end

Kemal.run

Remove the above part from the other two templates (index.ecr and cast.ecr in the views directory).

Add the results route and the results view

Paste the following code into src/suffragist.cr:

get "/results" do
  title = "Results"
  votes = { "HAM" => 7, "PIZ" => 5, "CUR" => 3 }
  my_renderer "results"
end

Create a new file in the views directory, called results.ecr.

  <table class="table table-hover table-striped">
    <%- Choices.each do |id, text| -%> 
      <tr>
        <th><%= text %></th>
        <%- if votes.has_key? id -%> 
          <td><%= votes[id] %></td>
          <td><%= "#" * (votes[id]) %></td>
        <%- else -%>
          <td>0</td>
          <td></td>
        <%- end -%> 
      </tr>
    <% end %>
  </table>
  <p><a href="/">Cast more votes!</a></p>

Run src/suffragist.cr, check your results and shut down the server with Ctrl+C.

Contributors