(eval-when (:compile-toplevel :load-toplevel :execute)
(defun keywordize (symbol) (intern (string symbol) "KEYWORD")))
(defun this-year () (nth-value 5 (decode-universal-time (get-universal-time))))
(defgeneric slots-for-database (object)
(:method-combination append))
(defgeneric serialize (object))
(defclass entity ()
())
(defmethod slots-for-database append ((entity entity))
'())
(defmethod serialize ((object entity))
(cons (class-name (class-of object))
(mapcar (lambda (slot) (slot-value object slot))
(slots-for-database object))))
(defmethod print-object ((object entity) stream)
(print-unreadable-object (object stream :type t :identity t)
(format stream "~S" (mapcar (function list)
(slots-for-database object)
(rest (serialize object)))))
object)
(defun deserialize (class-and-fields)
(let ((object (make-instance (first class-and-fields))))
(loop
:for slot :in (slots-for-database object)
:for value :in (rest class-and-fields)
:do (setf (slot-value object slot) value))
object))
(defmacro define-entity (name (&rest slots-for-database) (&rest other-slots))
`(progn
(defclass ,name (entity)
,(mapcar (lambda (field)
`(,field :initarg ,(keywordize field) :accessor ,field :initform ""))
(append slots-for-database
other-slots)))
(defmethod slots-for-database append ((object ,name))
',slots-for-database)))
(define-entity person
(email gender lname fname dob address zip city)
(age))
(defmethod (setf dob) :after (new-dob (person person))
(setf (age person) (- (this-year) (parse-integer new-dob :junk-allowed t))))
(setf *print-circle* t)
(print (multiple-value-list
(let ((foo (make-instance 'person
:email "foo@example.com"
:gender "male"
:lname "foo" :fname "bar"
:dob "1970"
:address "Main Street, 42"
:zip "92134"
:city "Los Angeles")))
(values foo
(deserialize (serialize foo))))
))