Web application in Clojure. Part 2
Hello dear users and visitors of Habra. In the first article, a Web application in Clojure were considered the basic tools and libraries for building web projects in Clojure. Namely, Leiningen, Ring, Compojure, Monger, Buddy and Selmer. Here we will focus on their practical application.
Those who are configured to self-development through understanding of code to bypass the reading of the article — ask in the end of the page for a link to the project on Github.
So let's start in order. To make it more interesting, I decided to choose more or less applied character of the article. Today we will create a simple web application in Clojure, this will govern the notes. I assume that you have already installed Leiningen, Clojure and MongoDB (don't forget to turn it on). Lion's share content directly from the comments in the code, which for your convenience is hidden in spoilers.
For Clojure there's a lot of different editors and IDE, in this article I won't show them the pros and cons, forest even once won't. We all have different preferences, what to use is up to you. I use LightTable which is written in ClojureScript and completely happy with it, it has a large number of modules out of the box it has everything you need to start developing in Clojure and ClojureScript, it has a module ParEdit. You do not have to be configured to connect to a project remotely or locally via the repl. The interaction with the repl in LightTable is very peculiar, in my subjective opinion it is very convenient — you can call functions and see the results in a separate window in live mode (as in Emacs and in all other IDE), or do the same directly in your code, just move the cursor to the first or last bracket of the expression and press cmd + enter (MacOS), after that LightTable will create a connection to the repl and compile this expression, you can enter the name of a compiled function or variable with the line below and view the result directly in code.
First we will create our project: $ lein new compojure notes
Now we have the directory with the harvesting of our application. Let's get to it and open the editor project file.clj. Need to add depends on us libraries:
When you run the project Leiningen will install all these dependencies automatically, all you have to do is specify them in the project.clj and be connected to the Internet. Next, let's create the server part of our application. More logical to put the display function, work with databases, handlers and routes in a separate file, to avoid name conflictsbrain-fucking'a inconvenience, but as you like it.
Let's start with the handler.clj, on its establishment we have cared Lein and it is located in the directory /src/notes (it is all of our Clojure code). This is an important part of our application it contains the variable app, which includes the routes of our application and the underlying middleware (wrapper of requests and responses) for HTTP. Add to namespace the routes of our app, the code is as follows:
Now create the file routes.clj, it will post our routes, when the query methods GET and POST on the specified URI will trigger handler functions, forms, and maps pages. In this part of the application we use Compojure API. Immediately apologize for the huge chunks of code someone's mind, but I added a lot of comments to make it easier to understand the logic of their work:
For POST processing, sometimes GET requests in the routes above, we use the so-called "controllers" (handlers), put them in a separate file. Here, I deliberately omit a full test of the validity of the input data as it is deserves a separate article. The name of the file controllers.clj, the content of it with the following:
A very interesting part of the application, it will help us build a library Monger. Create the db file.clj, it will be stored functions to interact with MongoDB. Of course we can call the functions directly Monger in routes and controllers, but for that we will receive retribution in the fixing, extending, along with a bunch of duplicated code, thus increase the final number of lines of code. Monger is also allows you to query MongoDB via DSL queries (for relational databases has a great library sqlcorma), it is very convenient for complex queries, but in this article I will not describe. Let's add functions to db.clj:
In the file views.clj we will place a function that displays HTML templates and transmitting data. In this case, we will help library Selmer, inspired by the system present the data in a Django template. Selmer also allows you to add filters (functions) to process data within the template, tags, and provides flexible settings yourself. Let us write functions display pages:
Our app is almost ready, it remains to create HTML files to display data. The directory /resources you need to host static files, i.e., even after the application is compiled in .jar or .war file, we will be able to replace the files in it. In public in the next article we will add CSS for the table. In the meantime, create the directory templates where to place the HTML files.
First we will create the base file for all of the templates will have the same basic layout for all pages and content block which will house the layout of the remaining sections. Let's start:
Now compose the pattern with shape annotation. In him, as in all forms of our application, you must add tag {% csrf-field %}, which we created in the view.clj, otherwise when you submit a form we get the error Invalid anti-forgery token. Let's start:
We already have a route, view, and handler for the edit notes, let's create this template with the form:
Next, impose the pattern of viewing the notes in this careful reader tag small strange to see a representation of the data is something else as our filter created in the view.clj. To call filter in Selmer decided to write: {{variable|filter}}. Now the code itself:
And finally our main page, where it will display the list of notes:
Now our application is ready, of course we missed a very important step — testing functions in the repl and view their results, but in the next article, we will discuss it in detail.
Let's start: $ lein ring server
This command like lein run will install all dependencies, launch our app on a basic web server Ring a Jetty on localhost:3000. Notice that before we can compile our application in .jar or .war file or run it via lein run.
the
In the next article we'll add to our web application, the web server immutant, and implement auto-update code on the fly i.e. as you save the file in the editor, our server will automatically update them and we will be able to see the results of code changes after the page is reloaded and not the server as it is now. At the moment, on the fly we can change only HTML files. And so as we add to our HTML Bootstrap classes, so it looks like the layout is not very fun, despite the fact that the essence of the articles was not in a beautiful design, I think it is not necessary to go back to WEB 1.0.
Despite close scrutiny before publishing the article, I will be grateful to you if you see inaccuracies in the description or the mistakes in grammar and let me know in the messages. So as I would like to thank the people who showed interest in my first article, happy to continue telling you about web development in Clojure. I bid you adieu and wish you all the best!
Article based on information from habrahabr.ru
Those who are configured to self-development through understanding of code to bypass the reading of the article — ask in the end of the page for a link to the project on Github.
Introduction
So let's start in order. To make it more interesting, I decided to choose more or less applied character of the article. Today we will create a simple web application in Clojure, this will govern the notes. I assume that you have already installed Leiningen, Clojure and MongoDB (don't forget to turn it on). Lion's share content directly from the comments in the code, which for your convenience is hidden in spoilers.
IDE
For Clojure there's a lot of different editors and IDE, in this article I won't show them the pros and cons, forest even once won't. We all have different preferences, what to use is up to you. I use LightTable which is written in ClojureScript and completely happy with it, it has a large number of modules out of the box it has everything you need to start developing in Clojure and ClojureScript, it has a module ParEdit. You do not have to be configured to connect to a project remotely or locally via the repl. The interaction with the repl in LightTable is very peculiar, in my subjective opinion it is very convenient — you can call functions and see the results in a separate window in live mode (as in Emacs and in all other IDE), or do the same directly in your code, just move the cursor to the first or last bracket of the expression and press cmd + enter (MacOS), after that LightTable will create a connection to the repl and compile this expression, you can enter the name of a compiled function or variable with the line below and view the result directly in code.
Back-end
Project
First we will create our project: $ lein new compojure notes
Now we have the directory with the harvesting of our application. Let's get to it and open the editor project file.clj. Need to add depends on us libraries:
project.clj
(defproject notes "0.1.0-SNAPSHOT"
:description "notes Manager"
:min-lein-version "2.0.0"
:dependencies [; Yes, Clojure itself is also connected
; as a dependency
[org.clojure/clojure "1.6.0"]
; Routes for GET and POST requests
[compojure "1.3.1"]
; Wrapper (middleware) for our
; routes
[ring/ring-defaults "0.1.5"]
; The templating engine
[selmer "0.8.2"]
; Add Monger
[com.novemberain/monger "2.0.1"]
; Date and time
[clojure.joda-time "0.6.0"]]
Because the web server to connect we will be in
; the following article, while entrust this business
; Ring in which is included your web server Jetty
:plugins [[lein-ring "0.8.13"]]
; When the application starts, the Ring will be
; use the variable app contains
; routes and all of the features they contain
:ring {:handler notes.handler/app}
:profiles {:dev
{:dependencies
[[javax.servlet/servlet-api "2.5"]
[ring-mock "0.1.5"]]}})
When you run the project Leiningen will install all these dependencies automatically, all you have to do is specify them in the project.clj and be connected to the Internet. Next, let's create the server part of our application. More logical to put the display function, work with databases, handlers and routes in a separate file, to avoid name conflicts
Handler (main processor)
Let's start with the handler.clj, on its establishment we have cared Lein and it is located in the directory /src/notes (it is all of our Clojure code). This is an important part of our application it contains the variable app, which includes the routes of our application and the underlying middleware (wrapper of requests and responses) for HTTP. Add to namespace the routes of our app, the code is as follows:
handler.clj
(ns notes.handler
(:require
; Routes app
[notes.routes :refer [notes-routes]]
; The default settings of the middleware
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
; Wrap the routes in the middleware
(def app
(wrap-defaults notes-routes site-defaults))
Routes (routes)
Now create the file routes.clj, it will post our routes, when the query methods GET and POST on the specified URI will trigger handler functions, forms, and maps pages. In this part of the application we use Compojure API. Immediately apologize for the huge chunks of code someone's mind, but I added a lot of comments to make it easier to understand the logic of their work:
routes.clj
(ns notes.routes
(:require
; Routes
[compojure.core :refer [defroutes GET POST]]
[compojure.route :as route]
; Supervisors requests
[notes.controllers :as c]
; Displaying pages
[notes.views :as v]
; Functions for interacting with the database
[notes.db :as db]))
; Declare the routes
(defroutes notes-routes
Page view notes
(GET "/note/:id"
[id]
; Get our note its ObjectId
; and pass the data to the display
(let [note (db/get-note-id)]
(v/note note)))
; Checker removal note its ObjectId
(GET "/delete/:id"
[id]
(c/delete id))
; Handler for the edit notes
(POST "/edit/:id"
request
(-> c/edit))
Page edit notes
; in fact, I suppose to use
; ObjectId of a document in the queries
; a bad idea, but as
; example will do.
(GET "/edit/:id"
[id]
; Get our note its ObjectId
; and pass the data to the display
(let [note (db/get-note-id)]
(v/edit note)))
; Handler notes
(POST "/create"
; We can obtain necessary values
in the [title text], but we'll take
; request completely and put
; this job to our handler
request
; This syntactic sugar is similar to
; expression: (create-controller request)
(-> c/create))
Page notes
(GET "/create"
[]
(v/create))
; Main application page
(GET "/"
[]
; Get a list of notes and
; pass it to fn display
(let [notes (db/get-notes)]
(v/index notes)))
; 404 error
(route/not-found "Nothing found"))
Controllers (processing form)
For POST processing, sometimes GET requests in the routes above, we use the so-called "controllers" (handlers), put them in a separate file. Here, I deliberately omit a full test of the validity of the input data as it is deserves a separate article. The name of the file controllers.clj, the content of it with the following:
controllers.clj
(ns notes.controllers
(:require
; Function redirect
[ring.util.response :refer [redirect]]
; Functions for interacting with the database
[notes.db :as db]))
(defn delete
"The Comptroller is deleting notes"
[id]
(do
(db/remove-note id)
(redirect "/")))
(defn edit
The "controller edit notes"
[request]
; Get the data from the form
(let [note-id (get-in request [:form-params "id"])
note {:title (get-in request [:form-params "title"])
:text (get-in request [:form-params "text"])}]
; Verify data
(if (and (not-empty (:title note))
(not-empty (:text note)))
; If everything is OK
; the document to be updated in the database
; move user
; on the main page
(do
(db/update-note note-id note)
(redirect "/"))
; If data is empty then error
"Check your entries")))
(defn create
The "controller."
[request]
; Get the data from the form
; will not propagate variables
; and we need to create a hash-map
; (associative array)
(let [note {:title (get-in request [:form-params "title"])
:text (get-in request [:form-params "text"])}]
; Verify data
(if (and (not-empty (:title note))
(not-empty (:text note)))
; If everything is OK
; add them to the database
; move user
; on the main page
(do
(db/create-note note)
(redirect "/"))
; If data is empty then error
"Check your entries")))
DB (interaction with MongoDB)
A very interesting part of the application, it will help us build a library Monger. Create the db file.clj, it will be stored functions to interact with MongoDB. Of course we can call the functions directly Monger in routes and controllers, but for that we will receive retribution in the fixing, extending, along with a bunch of duplicated code, thus increase the final number of lines of code. Monger is also allows you to query MongoDB via DSL queries (for relational databases has a great library sqlcorma), it is very convenient for complex queries, but in this article I will not describe. Let's add functions to db.clj:
db.clj
(ns notes.db
(:require
; Monger Directly
monger.joda-time ; to add a time and date
[monger.core :as mg]
[monger.collection :as m]
[monger.operators :refer :all]
A time and date
[joda-time :as t])
; Import the methods from the Java libraries
(:import org.bson.types.ObjectId
org.joda.time.DateTimeZone))
In order to avoid mistakes you need to specify the time zone
(DateTimeZone/setDefault DateTimeZone/UTC)
; Create the variable database connections
(defonce db
(let [uri "mongodb://127.0.0.1/notes_db"
{:keys [db]} (mg/connect-via-uri uri)]
db))
; Private function create the date stamp and time
(defn - date-time
"The current date and time"
[]
(t/date-time))
(defn remove-note
"Delete annotation by its ObjectId"
[id]
; Reformat the string into an ObjectId object
(let [id (ObjectId. id)]
(m/remove-by-id db "notes" id)))
(defn update-note
"Update note its ObjectId"
[note id]
; Reformat the string into an ObjectId object
(let [id (ObjectId. id)]
; Here we use the operator $set,
; with it, if there are
; other fields they will not be removed
; will update only those that have
in our hash-map + if it includes
; fields are not in the document they
; they will be added to it.
; So as to update the documents
, ObjectId using
; function update-by-id
; for clarity, I left the update
; by any parameters
(m/update db "notes", {: id id}
- Update in addition to the document
; the date of its creation
{$set (assoc note
:created (date-time))})))
(defn get-note
"To note its ObjectId"
[id]
; If you search for the document by its :_id
; and the value to pass
; it is a string and not ObjectId
we get an error, so
; reformat it in the type ObjectId
(let [id (ObjectId. id)]
; This function will return a hash-map of a found document
(m/find-map-by-id db "notes" id)))
(defn get-notes
"Receive all notes"
[]
; Find-maps returns all documents
; from collection in the form of a hash-map
(m/find-maps db "notes"))
(defn create-note
"New comment in DB"
Our note is accepted from cotroler
; and is of type hash-map-view:
; {:title "title" :text "Content"}
[note]
; Monger can create a ObjectId
; but developers are strongly encouraged
add this field yourself
(let [object-id (ObjectId.)]
; We can also just pass a hash-map
; the functions create document, only
; add the generated ObjectId
; and stamp the time and date of creation
(m/insert db "notes" (assoc note
:_id object-id
:created (date-time)))))
Views (the HTML templates)
In the file views.clj we will place a function that displays HTML templates and transmitting data. In this case, we will help library Selmer, inspired by the system present the data in a Django template. Selmer also allows you to add filters (functions) to process data within the template, tags, and provides flexible settings yourself. Let us write functions display pages:
views.clj
(ns notes.views
(:require
; "Template engine"
[selmer.parser :as parser]
[selmer.filters :as the filters]
A time and date
[joda-time :as t]
; For HTTP headers
[ring.util.response :refer [content-type response]]
; For CSRF protection
[ring.util.anti-forgery :refer [anti-forgery-field]]))
; Selmer will show you where to find our templates
(parser/set-resource-path! (clojure.java.io/resource "templates"))
; To get the date in human-friendly format
(defn format-date-and-time
"Format time and date"
[date]
(let [formatter (t/formatter "yyyy-MM-dd H:m:s" :date-time)]
(when date
(t/formatter print date))))
; Add a filter for use in the template
(filters/add-filter! :format-datetime
(fn [content]
[:safe (format-date-and-time content)]))
; Add a tag with a field for forms it is
automatically created a field of anti-forgery key
(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
(defn render [template &[params]]
"This function will display our html templates
and transmit data"
(-> template
(parser/render-file
; Add to the feedback you get constant
; values which I would like to
; on any page
(assoc params
:title "notes Manager"
:page (str template)))
; This will make HTTP response
response
(content-type "text/html; charset=utf-8")))
(defn note
"Viewing page notes"
[note]
(render "note.html"
; Transmit the data to the template
{:note note}))
(defn edit
"Edit page notes"
[note]
(render "edit.html"
; Transmit the data to the template
{:note note}))
(defn create
"Page annotation"
[]
(render "create.html"))
(defn index
"The main application page. The notes list"
[notes]
(render "index.html"
; Transmit the data to the template
; If notes is empty return false
{:notes (if (not-empty notes)
notes false)}))
Front-end
HTML templates
Our app is almost ready, it remains to create HTML files to display data. The directory /resources you need to host static files, i.e., even after the application is compiled in .jar or .war file, we will be able to replace the files in it. In public in the next article we will add CSS for the table. In the meantime, create the directory templates where to place the HTML files.
First we will create the base file for all of the templates will have the same basic layout for all pages and content block which will house the layout of the remaining sections. Let's start:
base.html
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>{{title}}</title>
</head>
<body>
<ul>
<li>
{% ifequal page "index.html" %}
<strong>notes</strong>
{% else %}
<a href="/">notes</a>
{% endifequal %}
</li>
<li>
{% ifequal page "create.html" %}
<strong>Add comment</strong>
{% else %}
<a href="/create">Add comment</a>
{% endifequal %}
</li>
</ul>
{% block content %}
{% endblock %}
</body>
</html>
Now compose the pattern with shape annotation. In him, as in all forms of our application, you must add tag {% csrf-field %}, which we created in the view.clj, otherwise when you submit a form we get the error Invalid anti-forgery token. Let's start:
create.html
{% extends "base.html" %}
{% block content %}
<h1>new note</h1>
<form action="POST">
{% csrf-field %}
<p>
<label>Title</label><br>
<input type="text" name="title" placeholder="Title">
</p>
<p>
<label>Note</label><br>
<textarea name="text"></textarea>
</p>
<input type="submit" value="Create">
</form>
{% endblock %}
We already have a route, view, and handler for the edit notes, let's create this template with the form:
edit.html
{% extends "base.html" %}
{% block content %}
<h1>Edit comment</h1>
<form method="POST">
{% csrf-field %}
<input type="hidden" name="id" value="{{note._id}}">
<p>
<label>Title</label><br>
<input type="text" name="title" value="{{note.title}}">
</p>
<p>
<label>Note</label><br>
<textarea name="text">{{note.text}}</textarea>
</p>
<input type="submit" value="Save">
</form>
{% endblock %}
Next, impose the pattern of viewing the notes in this careful reader tag small strange to see a representation of the data is something else as our filter created in the view.clj. To call filter in Selmer decided to write: {{variable|filter}}. Now the code itself:
note.html
{% extends "base.html" %}
{% block content %}
<h1>{{note.title}}</h1>
<small>{{note.created|format-datetime}}</small>
<p>{{note.text}}</p>
{% endblock %}
And finally our main page, where it will display the list of notes:
note.html
{% extends "base.html" %}
{% block content %}
<h1>Notes</h1>
{% if notes %}
<ul>
{% for note in notes %}
<li>
<h4><a href="/note/{{note._id}}" > {{note.title}}</a></h4>
<small>{{note.created|format-datetime}}</small>
<hr>
<a href="/edit/{{note._id}}">Edit</a> | <a href="/delete/{{note._id}}">Delete</a>
</li>
{% endfor %}
</ul>
{% else %}
<strong>Notes yet</strong>
{% endif %}
{% endblock %}
Conclusion
Now our application is ready, of course we missed a very important step — testing functions in the repl and view their results, but in the next article, we will discuss it in detail.
Let's start: $ lein ring server
This command like lein run will install all dependencies, launch our app on a basic web server Ring a Jetty on localhost:3000. Notice that before we can compile our application in .jar or .war file or run it via lein run.
Additional links
the
In the next article we'll add to our web application, the web server immutant, and implement auto-update code on the fly i.e. as you save the file in the editor, our server will automatically update them and we will be able to see the results of code changes after the page is reloaded and not the server as it is now. At the moment, on the fly we can change only HTML files. And so as we add to our HTML Bootstrap classes, so it looks like the layout is not very fun, despite the fact that the essence of the articles was not in a beautiful design, I think it is not necessary to go back to WEB 1.0.
Despite close scrutiny before publishing the article, I will be grateful to you if you see inaccuracies in the description or the mistakes in grammar and let me know in the messages. So as I would like to thank the people who showed interest in my first article, happy to continue telling you about web development in Clojure. I bid you adieu and wish you all the best!
Comments
Post a Comment