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.
Pre-requisites: Must have Git and Java 8+ installed.
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/
Mac: brew install heroku/brew/heroku
Ubuntu: sudo snap install heroku --classic
For Windows or other Linux distros, follow the instructions here.
Clone this Git repository.
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.
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.
- 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?
We're going to use the hiccup library for HTML templating. To do this:
- Add hiccup to your
project.clj
- at the time of writing the current version was[hiccup "1.0.5"]
- Restart your REPL
- Require
[hiccup.core :refer :all]
in the file you want to use it - Build a data structure representation of the page you want to return
- Change the headers
- 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 lein ring server
to run the server and check locally- Commit your changes
$ git push heroku master
to deploy your changes to Heroku
Reading the instructions on the EFF site, how do you think we would go about this?
We're going to need to load a CSV, so again:
- Add
[org.clojure/data.csv "0.1.4"]
(current at Jan 2019) toproject.clj
- 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.
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
- Generate 5 random numbers
- Look up the corresponding word from this list
- Do this five more times, for a total of six words
- Return this as a single string
- 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
.
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.
- Add jsonista, the blazing-fast JSON serialiser to your app. This will be similar to the process for adding CSV support.
- Add a route, something like
"passphrases/create"
- Construct an appropriate JSON response
- Return it to the client
- Test as you go in the REPL and check it locally
- Push to heroku,
$ git push heroku master
- Check your handiwork -
$ heroku open
the page, and then usecURL
to send a request to the server from your terminal!
Phew! Let's stop there.
- Add unit tests - your router is just a function, so this is pretty easy
- Add CSS and styling
- Add forms to the front end to call the API
- Make it so the app doesn't load and parse the file on every request (memoization)
- Why not load the words into a database like SQLite (or another of your choice)?
- Add hooks for reloaded-REPL workflow
- Add
compojure-api
functionality - 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."
https://tinyurl.com/ll-jan-clj <-- tinyurl to this set of instructions
https://tinyurl.com/ll-jan-code <-- tinyurl to the first exercise