Browse Source

add Chapter 4 code (database, models, mito and sxql)

master
Craig Oates 2 years ago
parent
commit
dd47e3adda
  1. 1
      rails-to-caveman.asd
  2. 47
      src/config.lisp
  3. 9
      src/db.lisp
  4. 236
      src/model.lisp

1
rails-to-caveman.asd

@ -26,6 +26,7 @@
(:file "web" :depends-on ("view"))
(:file "view" :depends-on ("config"))
(:file "db" :depends-on ("config"))
(:file "model" :depends-on ("db")) ; <--- Chapter 4.
(:file "config"))))
:description ""
:in-order-to ((test-op (test-op "rails-to-caveman-test"))))

47
src/config.lisp

@ -2,14 +2,14 @@
(defpackage rails-to-caveman.config
(:use :cl)
(:import-from :envy
:config-env-var
:config-env-var
:defconfig)
(:export :config
:*application-root*
:*application-root*
:*static-directory*
:*template-directory*
:*template-directory*
:appenv
:developmentp
:developmentp
:productionp))
(in-package :rails-to-caveman.config)
@ -20,16 +20,47 @@
(defparameter *template-directory* (merge-pathnames #P"templates/" *application-root*))
(defconfig :common
`(:databases ((:maindb :sqlite3 :database-name ":memory:"))))
#| CHANGED DATABASE FROM IN-MEMORY(CHAPTER 4)
==============================================
Changed the database name from ':memory:' to 'rails-to-caveman'
in Chapter 4. Be default, Caveman sets the database to
'in-memory'. You need to override this everytime you want to
your a persistent database in your app.
You can name your database whatever you want. I have just went
with 'rails-to-caveman' because the tutorial said so.
|#
#| EXCERPT TAKEN FROM TUTORIAL (CHAPTER 4):
============================================
Initially: I set the database-name to your-app, but later when I
tried to access the database, I got angry with CANTOPEN. So I
changed the name to your_app and it worked fine. Apparently, words
separated by . Dashes Are Capitalized At The Beginning . The Name
Specified In The Code Here Is "Your-App", But The Registered File
Name Is "Your-App" However MITO:CONNECT-TOPLEVEL, If You
Specify "Your-App": Database-Name, The Case conversion of the
implicit reason is not performed and the file "your-app" is searched
for in a case-sensitive manner, resulting in an error. Here, for
safety, the database name is defined in camel case without symbols.
The database file is created in the current directory. If you don't
like this, you can specify the absolute path for:
DATABASE-NAME. *APPLICATION-ROOT* The variable is created DEFCONFIG,
the argument to is :DATABASE-NAME(DATABASE-PATH), and there is
also a "db "directory in the project directory, so I don't wonder if
it will do it well. I think it's unfriendly to have no documentation.
|#
`(:databases ((:maindb :sqlite3 :database-name "rails-to-caveman.db"))))
(defconfig |development|
'())
'())
(defconfig |production|
'())
'())
(defconfig |test|
'())
'())
(defun config (&optional key)
(envy:config #.(package-name *package*) key))

9
src/db.lisp

@ -18,6 +18,13 @@
(defun db (&optional (db :maindb))
(apply #'connect-cached (connection-settings db)))
;; ORIGINAL VERSION BEFORE CHAPTER 4
;; (defmacro with-connection (conn &body body)
;; `(let ((*connection* ,conn))
;; ,@body))
;; REPLACE ABOVE IN CHAPTER 4
;; This replaces 'datafly' with 'mito'.
(defmacro with-connection (conn &body body)
`(let ((*connection* ,conn))
`(let ((mito.connection:*connection* ,conn)) ; <--- This!
,@body))

236
src/model.lisp

@ -0,0 +1,236 @@
;; (in-package :cl-user) ; Not sure if this needs to exist (Chapter 4)
(defpackage :rails-to-caveman.model
(:use #:cl
#:rails-to-caveman.db)
(:export #:seeds
#:ids))
(in-package #:rails-to-caveman.model)
;;; Defines the USER table class for the database.
;;; Will be used by mito (an ORM).
(defclass user ()
((number
:col-type
:integer
:initarg
:number
:reader number-of)
(name :col-type (:varchar 64)
:initarg
:name
:reader name-of)
(full-name :col-type
(or (:varchar 128) :null)
:initarg
:full-name
:reader full-name-of)
(email :col-type
(or :null :text)
:initarg
:email
:accessor email-of)
(birthday :col-type
(or :null :date)
:initarg
:birthday
:reader birthday-of)
(sex :col-type
:integer
:initarg
:sex
:initform 1
:reader sex-of)
#| CHANGED :ACCESSOR VALUE FROM TUTORIAL (CHAPTER 4)
===================================================
The Chapter 4 tutorial has the :accessor value set to
'administratorp'. Unfortunately, this causes initialisation
argument errors when trying to seed the database. To fix this, I
had to remove the 'p' part from that line. At the time of
writing, I do not know if that will have a negative effect on
future tutorials.
I have left a note in the 'seeds' function below highlighting the
change to the :accessor value.
|#
(administrator :col-type
:boolean
:initarg
:administrator
:initform nil
:accessor administrator))
(:metaclass mito:dao-table-class))
(defun seeds()
;; '#(' are ARRAY LITERALS. I keep forgetting this and need to look
;; it up.
(let ((names
#("Taro" "Jiro" "Hana" "John" "Mike" "Sophy" "Bill" "Alex" "Mary" "Tom"))
(fnames ; First Names
#("Hippo" "Darling" "Lopez" "Jerry"))
(gnames ; Given Names
#("Orange" "Fox" "Snake")))
(with-connection (db)
(dotimes (x 10)
(mito:create-dao 'user
:number (+ x 10)
:name (aref names x)
:full-name (format nil "~A ~A"
(aref fnames (rem x 4))
(aref gnames (rem x 3)))
:email (format nil "~A@example.com" (aref names x))
:birthday "1981-12-01"
:sex (nth (rem x 3) '(1 1 2))
;; Removed 'p' from end of :administrator --
;; so the code differs from the code in the
;; tutorial (Chapter 4). I had to change it
;; because it produced errors when trying to
;; seed the database (using 'seeds'
;; function. I have, also, left a note in the
;; 'user' class definition highlighting this.
:administrator (zerop 0))))))
(defun rebuild ()
"Drops the current database table, recreates it and populates it using seeded data."
(with-connection (db)
(mito:recreate-table 'user))
(seeds))
(defun ids ()
"Produces a list of all the Id's in the database. Part of Chapter 4
tutorial and is a port of the 'ids method' in the Ruby on Rails book
this tutorial was translated/ported from."
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mapcar #'mito:object-id
(mito:retrieve-dao 'rails-to-caveman.model::user))))
#| STAND ALONE BITS OF CODE
===========================
The code below is to use in a live coding environment. Think of it as
a persistent scratch pad -- for when you close Emacs but still want
those little snippets of code which do not have a place in the main
code base but are useful for little tests/proof-of-concepts.
When you have loaded the system in SLIME, use c-c c-c to run the code
below.
|#
(with-connection (db) ; Creates a table in the database called 'user'.
(mito:ensure-table-exists 'user))
;; Retrieves the record with the specified Id. Change ':id' to
;; retrieve different entries from the database.
;; NOTE: EVAL. THIS IN SLIME BEFORE USING (DESCRIBE *) FURTHER DOWN
;; THE FILE.
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:find-dao 'rails-to-caveman.model::user :id 3))
;; Retrieves the database entry with the specified name.
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:find-dao 'rails-to-caveman.model::user :name "Taro"))
#| SQLITE DATABASE BOOLEAN TYPES
================================
Use multiple columns to specify with data you want to retrieve.
Because SQLite database does not have a Boolean type,
:administrator : 0 = false
1 = true
:sex 1 = male
2 = female
SQLite type quirk and how the tutorial decided to model the data in
the database.
|#
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:find-dao 'rails-to-caveman.model::user :sex 1 :administrator 1))
;; A common strategy to deal with SQLite Boolean types is to set
;; constants and refer to them instead of passing hard coded 1 & 0.
(defconstant +false+ 0)
(defconstant +true+ 1)
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:retrieve-dao 'rails-to-caveman.model::user :administrator +false+))
(ids) ; Lists out all the Id's (in SLIME) in the 'users' table.
(seeds) ; Populates the database with seeded data (in 'users')
;; Drops the current table, creates a new one and populates it with
;; seeded data -- in the 'users' tables.
(rebuild)
#| SEEING THE CONTENTS OF A DATABASE ENTRY IN SLIME.
====================================================
This is a bit picky. First of all, you need to retrieve a database
entry (in SLIME) before checking the contents. Search for 'NOTE:
EVAL.' for the code. After you have retrieved the entry from the
database, calling (describe *) will produce something like the
following,
[standard-object] Slots with
:INSTANCE allocation:
CREATED-AT = @yyyy-mm-ddThh:mm:ss.ms+tz
UPDATED-AT = @yyyy-mm-ddThh:mm:ss.ms+tz
SYNCED = T
ID = 3
NUMBER = 12
NAME = "Hana"
FULL-NAME = "高橋 花子"
EMAIL = "Hana@example.com"
BIRTHDAY = @yyyy-mm-ddThh:mm:ss.ms+tz
SEX = 2
ADMINISTRATOR = NIL
THIS IS SPECIFIC TO SBCL -- OTHER IMPLEMENTATIONS MIGHT PRODUCE
DIFFERENT OUTPUTS
|#
(describe *)
#| BUILD QUERIES WITH SXQL AND MITO
===================================
If you want to build a complex query, use it in combination
MITO.DAO:SELECT-DAO with sxql .
The one in the previous section MITO:RETRIEVE-DAOis equivalent to the
following code.
COPY THE CODE BELOW INTO SLIME OR WORK THEM INTO A FUNCTION.
|#
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:select-dao 'rails-to-caveman.model::user
(sxql:where '(:= :administrator 0))))
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:select-dao 'rails-to-caveman.model::user
(sxql:where
'(:and (:= :name "Taro")
(:< :number 20)))))
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:select-dao 'rails-to-caveman.model::user
(sxql:where '(:= :sex 2))
(sxql:order-by :number)))
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:select-dao 'rails-to-caveman.model::user
(sxql:where '(:= :sex 2))
(sxql:order-by (:desc :number))))
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:select-dao 'rails-to-caveman.model::user
(sxql:where
`(:or ,@(mapcar (lambda(num)
`(:= :number ,num))
'(15 17 19))))))
(rails-to-caveman.db:with-connection (rails-to-caveman.db:db)
(mito:select-dao 'rails-to-caveman.model::user
(sxql:where
`(:and (:<= 12 :number)
(:<= :number 14))))
Loading…
Cancel
Save