Простое извлечение и отображение записей CouchDB в Common LISP
Я делаю свои первые шаги в Common Lisp и благодаря clouchdb http://common-lisp.net/project/clouchdb/
Мне удалось получить некоторые простые данные из couchdb
(invoke-view "hulk" "hulk" )
((:|total_rows| . 2) (:|offset| . 0) (:|rows| ((:|id| . "gjc") (:|key| . "hulk") (:|value|
(:|_id| . "gjc2321o3io13") (:|_rev| . "3-b6433781c65460f2c9b1f1a153953171")
(:NAME . "Dr Bruce Banner") (:|kind| . "users") (:|username| . "hulk") (:|title| . "gamma r adia
tions: what to do ?"))) ((:|id| . "irnmn239223") (:|key| . "ironman") (:|value| (:|_id| . "irnmn2 39223")
(:|_ rev| . "5-2b6cf739d24b1208fe8eca70e37ffdc9") (:|name| . "tony stark") (:|title| .
(:|name| . "tony stark") (:|title| . "why iphone 5 sucks - but i own one \"") (:|kind| . "users") (:|username| . "ironman") (:|text| . "welcome to post number one ......")))))
7>
Я использую SEXML для отображения записей HTML, поэтому мой шаблон отображения HTML выглядит следующим образом
;;static here
(<:h2 "((LISP RETRO BLOG))")
(<:h3 "(( ***** RETRO BUT STILL COOL *****))")
(<:p "( (MADE IN LISP ))")
(<:p "READY.")
(<:img :src "/img/prompt.gif" :alt "cursor"))
;;this is a variable
(<:p "universal time: " mytime)
Теперь я бы создал простой цикл по вышеупомянутым результатам (например, имена пользователей Ironman и Hulk), чтобы отобразить что-то вроде
(<:p "Welcome!" username)
Извините за публикацию такого большого количества кода для того, что в итоге может быть очень простым циклом. Я читал и пробовал примеры ( http://psg.com/~dlamkins/sl/chapter05.html) и другие ресурсы, но я, вероятно, упускаю что-то очень простое и буду признателен за вашу помощь. Обратите внимание, что документы couchdb могут иметь разные поля, так что это не то же самое, что циклически проходить по некоторым записям, где у вас есть схема. Это может быть актуально, например, если документ представляет собой запись в блоге, он может включать или не включать теги, поэтому я могу захотеть показать / создать страницу со всеми данными, доступными в документе (возможно, за исключением _id).
Если что-то не понятно, просто прокомментируйте, и я буду рад отредактировать вопрос.
Заранее спасибо!
2 ответа
Вы будете намного лучше понимать выходные данные, если переформатируете их, чтобы они были более читабельными. Как это:
((:|total_rows| . 2) (:|offset| . 0)
(:|rows|
((:|id| . "gjc") (:|key| . "hulk")
(:|value|
(:|_id| . "gjc2321o3io13")
(:|_rev| . "3-b6433781c65460f2c9b1f1a153953171")
(:NAME . "Dr Bruce Banner")
(:|kind| . "users")
(:|username| . "hulk")
(:|title| . "gamma radiations: what to do ?")))
((:|id| . "irnmn239223") (:|key| . "ironman")
(:|value|
(:|_id| . "irnmn2 39223")
(:|_ rev| . "5-2b6cf739d24b1208fe8eca70e37ffdc9")
(:|name| . "tony stark")
(:|title| .
;; here you repeat name and title, so the previous and next lines are erroneous
(:|name| . "tony stark")
(:|title| . "why iphone 5 sucks - but i own one \"")
(:|kind| . "users")
(:|username| . "ironman")
(:|text| . "welcome to post number one ......")))))
Итак, что вы получили от CouhcDB через clouchdb - это специально структурированный список, который на языке Lisp называется alist. Есть набор функций для работы со списками, наиболее важная из которых ASSOC
,
Результат говорит вам, что у вас есть 2 строки, каждая из которых содержит данные как другой список. Чтобы перебрать их, вы можете использовать следующую функцию:
(defun maprows (fn data)
(mapcar fn (cdr (assoc :|rows| data))))
Теперь вы должны перейти в MAPROWS
функция FN
одного аргумента. Например, если вы хотите просто распечатать значения разумным образом, вы можете передать следующую функцию.
(defun print-row (record)
(dolist (pair (cdr (assoc :|value| record)))
(format t "~A: ~A~%" (car pair) (cdr pair)))
(terpri))
Давайте посмотрим, как это работает:
CL-USER> (maprows 'print-row
'((:|total_rows| . 2) (:|offset| . 0)
(:|rows|
((:|id| . "gjc") (:|key| . "hulk")
(:|value|
(:|_id| . "gjc2321o3io13")
(:|_rev| . "3-b6433781c65460f2c9b1f1a153953171")
(:NAME . "Dr Bruce Banner")
(:|kind| . "users")
(:|username| . "hulk")
(:|title| . "gamma radiations: what to do ?")))
((:|id| . "irnmn239223") (:|key| . "ironman")
(:|value|
(:|_id| . "irnmn2 39223")
(:|_rev| . "5-2b6cf739d24b1208fe8eca70e37ffdc9")
(:|name| . "tony stark")
(:|title| . "why iphone 5 sucks - but i own one \"")
(:|kind| . "users")
(:|username| . "ironman")
(:|text| . "welcome to post number one ......"))))))
_id: gjc2321o3io13
_rev: 3-b6433781c65460f2c9b1f1a153953171
NAME: Dr Bruce Banner
kind: users
username: hulk
title: gamma radiations: what to do ?
_id: irnmn2 39223
_rev: 5-2b6cf739d24b1208fe8eca70e37ffdc9
name: tony stark
title: why iphone 5 sucks - but i own one "
kind: users
username: ironman
text: welcome to post number one ......
(NIL NIL)
Как вы видите, MAPROWS
также собирает результаты применения FN
так же, как и MAPCAR
,
Вот немного другой подход, хотя я не совсем доволен им. Возможно, кто-то исправит меня / предложит лучший способ, но эта общая стратегия известна как сопоставление объектов. Т.е. когда вы извлекаете данные из таблицы в базе данных, вы создаете объект, который более удобен на языке программирования вашей прикладной логики.
Теперь, по иронии судьбы, CouchDB должен был превзойти этот этап - поскольку он кодирует объекты в формат JSON, он будет "родным" для JavaScript, где он обычно используется. Но формат, который вы получили в CL, неудобен для работы / не является хорошим нативным представлением объектов. Теперь, поскольку вы не можете использовать CL в клиентском браузерном приложении, я не вижу смысла в вашей комбинации... Я имею в виду, что вы ничего не выиграете на стороне сервера (скорее всего, вы потеряете производительность в противном случае, но так как это ваш личный блог, это вряд ли будет проблемой).
Итак, ниже приведена попытка наивного сопоставления объектов:
(defclass db-object () ())
(defun slot-list-from-query (query)
(mapcar
#'(lambda (pair)
(let ((name (string-upcase (symbol-name (car pair)))))
(list (intern name)
:accessor
(intern (concatenate 'string name "-OF"))
:initarg (intern name "KEYWORD")))) (eval query)))
(defmacro table-to-class (name describe-table-query)
`(let ((db-class (defclass ,name (db-object)
,(slot-list-from-query describe-table-query))))
(defmethod initialize-instance :after ((object ,name) &key raw-data)
(mapcar
#'(lambda (pair)
(setf (slot-value object
(find-symbol
(string-upcase
(symbol-name (car pair)))))
(cdr pair))) raw-data))
db-class))
(defparameter *raw-data*
'((:|total_rows| . 2) (:|offset| . 0)
(:|rows|
((:|id| . "gjc") (:|key| . "hulk")
(:|value|
(:|_id| . "gjc2321o3io13")
(:|_rev| . "3-b6433781c65460f2c9b1f1a153953171")
(:|name| . "Dr Bruce Banner")
(:|kind| . "users")
(:|username| . "hulk")
(:|title| . "gamma radiations: what to do ?")))
((:|id| . "irnmn239223") (:|key| . "ironman")
(:|value|
(:|_id| . "irnmn2 39223")
(:|_rev| . "5-2b6cf739d24b1208fe8eca70e37ffdc9")
(:|name| . "tony stark")
(:|title| . "why iphone 5 sucks - but i own one \"")
(:|kind| . "users")
(:|username| . "ironman")
(:|text| . "welcome to post number one ......"))))))
(table-to-class example-mapping (car (cdaddr *raw-data*)))
(id-of (make-instance 'example-mapping :id "foo")) ; foo
(id-of (make-instance 'example-mapping :raw-data (car (cdaddr *raw-data*)))) ; gjc
Я взял на себя смелость немного "исправить" ваши данные, чтобы они выглядели согласованно. Кроме того, обычно у вас должно быть описание таблиц до запуска вашего кода, поэтому вся сложность объявления классов во время выполнения сводится к некоторой механической операции, которую вы выполняли бы до развертывания, и не было бы никакой необходимости в eval
в этом макросе. Я опубликовал это так, чтобы сохранить имеющуюся информацию и выглядеть так, как будто она была получена после развертывания кода.
Если вы хотите использовать его, вы удалите eval
и сделать что-то вроде:
(table-to-class example-mapping ((name-1 . value-1) (name-2 . value-2) ...))