Skip to content

Instantly share code, notes, and snippets.

@the-frey
Last active July 3, 2019 13:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save the-frey/dd31dab1d261d48a9e35d651121c614b to your computer and use it in GitHub Desktop.
Save the-frey/dd31dab1d261d48a9e35d651121c614b to your computer and use it in GitHub Desktop.
The lesson notes for the Clojure and Heroku Workshop

Goal

The goal is threefold:

  • To deploy a working web app to Heroku.
  • To see the REPL-based workflow in action.
  • To build a mini-API, switching out return Content-Type.

Note to instructors: periodically stop and make sure people are following along with a show of hands. Help out where needed.

Getting set up

Pre-requisites: Must have Git and Java 8+ installed.

Install Leiningen

This is easiest using a package manager.

Mac, using Homebrew: brew install leiningen

Ubuntu: apt install leiningen-clojure (other distros are similar)

Windows, using Chocolatey: choco install lein

Alternatively, follow the instructions at https://leiningen.org/

Install Heroku CLI

Mac: brew install heroku/brew/heroku

Ubuntu: sudo snap install heroku --classic

For Windows or other Linux distros, follow the instructions here.

Get the Example app

Clone this Git repository.

Have an editor set up

Emacs: If you're an emacs user, then rejoice! There's a number of different setups you can use. This is probably the best beginner guide, but on the whole if you've got a recent install (25 or 26) then that will do. If you want a slicker experience, then maybe try Spacemacs, although we prefer it in non-Evil mode.

Atom: ProtoREPL for Atom - readme here - this is a great all-rounder with some ace in-line evaluation options, good repl and emacs-style tight integration. If you want to mirror the author of ProtoREPL's setup, then his guide is here.

VS Code: Many people are now beginning to use VS Code for Clojure. There are some decent guides and plugins out there, and with our test group this was the best plugin to use. It seems that before this, another plugin was preferred, but we had issues and it seems we're not the only ones.

Sublime Text: You can do this, but it will be painful. We only edit Clojure in Sublime if we're forced to by natural disaster or lack of laptop.

A very very brief introduction to Clojure

Let's look at the basic syntax you need to read and write Clojure.

As an aside, it might be good to look at an AST with this tool:

def hello ():
    print("Hello, world!")

This is important. If we can modify the AST ourselves, we can extend the language in userspace. This is the power of homoiconicity, and it is leveraged using the macro system.

Let's deploy the example app

  • Navigate to src/clojure_getting_started/web.clj
  • Change the :body key in the return function so it returns a different string
  • Deploy to heroku:
    • $ heroku create
    • $ git push heroku master
    • $ heroku open
    • If the app doesn't appear to be running, you might need to force it to scale up: $ heroku ps:scale web=1

Good work! Any questions so far?

Let's add HTML templating

We're going to use the hiccup library for HTML templating. To do this:

  1. Add hiccup to your project.clj - at the time of writing the current version was [hiccup "1.0.5"]
  2. Restart your REPL
  3. Require [hiccup.core :refer :all] in the file you want to use it
  4. Build a data structure representation of the page you want to return
  5. Change the headers
  6. Check your work in the REPL as you go, e.g. try calling (app {:request-method :get :uri "/"}); for more info on testing, read this
  7. lein ring server to run the server and check locally
  8. Commit your changes
  9. $ git push heroku master to deploy your changes to Heroku

Let's add a number generator

Reading the instructions on the EFF site, how do you think we would go about this?

Loading the CSV

We're going to need to load a CSV, so again:

  1. Add [org.clojure/data.csv "0.1.4"] (current at Jan 2019) to project.clj
  2. Restart your REPL

Make sure the deps are pulled in to the relevant namespace:

(:require [clojure.data.csv :as csv]
          [clojure.java.io :as io])

Make a directory for the new file, data in your working directory.

$ mkdir data

Now download the file into this directory. Call it wordlist.csv.

Then, use the following snippet to load this file so we can look up words:

(def default-file-location "./data/wordlist.csv")

(defn load-wordlist-file [path-with-extension]
  (with-open [reader (io/reader path-with-extension)]
    (doall
     (csv/read-csv reader))))

(defn wordlist-numbered-mapping [file-location]
  (reduce (fn [acc i]
            (assoc acc (Integer/parseInt (first i)) (second i)))
          {}
          (load-wordlist-file file-location)))

NB: you can also obviously do this in a number of ways, including filtering a vector of vectors returned by the csv reader.

NOW: implement a function, using get - docs here - that gets the correct word for a given dice roll. Call it dice-roll->word.

NB to instructors: here are the notes for what the participants need to write in this section.

Implementing the number generator

You might need the following useful functions:

(rand-int 6)
;; there is also an inc function

;; bear in mind this will return 0-5
(take 5 (repeatedly #(rand-int 6)))

;; join a collection of strings
clojure.string/join
  1. Generate 5 random numbers
  2. Look up the corresponding word from this list
  3. Do this five more times, for a total of six words
  4. Return this as a single string
  5. Test it in the REPL

Instructors: stop here and check everybody is okay. Ask the question, is dice-roll->word actually needed? How could we refactor that, and the transforms that follow? If you're feeling confident, or with a crowd that has a good grasp of FP concepts, raise partials and comp.

A number generator API

Cool! We've come a long way already. We'd like to be able to return this in a non-HTML format so that different clients can use it though. How do we do this?

JSON, of course!

Let's add a route that returns a random passphrase in a JSON object.

  1. Add jsonista, the blazing-fast JSON serialiser to your app. This will be similar to the process for adding CSV support.
  2. Add a route, something like "passphrases/create"
  3. Construct an appropriate JSON response
  4. Return it to the client
  5. Test as you go in the REPL and check it locally
  6. Push to heroku, $ git push heroku master
  7. Check your handiwork - $ heroku open the page, and then use cURL to send a request to the server from your terminal!

Phew! Let's stop there.

Some ideas to extend the app

  1. Add unit tests - your router is just a function, so this is pretty easy
  2. Add CSS and styling
  3. Add forms to the front end to call the API
  4. Make it so the app doesn't load and parse the file on every request (memoization)
  5. Why not load the words into a database like SQLite (or another of your choice)?
  6. Add hooks for reloaded-REPL workflow
  7. Add compojure-api functionality
  8. Come up with a sentence to wrap the passwords, e.g. "The panoramic view, as I tasted the nectar of a precut granny smith apple and banana, deserved a handclap."
@the-frey
Copy link
Author

https://tinyurl.com/ll-jan-clj <-- tinyurl to this set of instructions
https://tinyurl.com/ll-jan-code <-- tinyurl to the first exercise

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment