/ src / modeling / fields.lisp
fields.lisp
  1  (in-package :ami.modeling)
  2  
  3  (defun field-class-from-name (name)
  4    "Generate field class symbol from the name. It searches the current package to see if there is a field with that name
  5  And if not it searches it in the AMI.MODELING package."
  6    (let ((current-package-sym (alexandria:format-symbol *package* "~a-FIELD" name))
  7          (ami-modeling-sym (alexandria:format-symbol (find-package "AMI.MODELING") "~a-FIELD" name)))
  8      (if (find-class current-package-sym nil)
  9          current-package-sym
 10        (if (find-class ami-modeling-sym nil)
 11            ami-modeling-sym
 12          (error "Field ~a doesn't exist, searched in current-package (~s) and in AMI.MODELING" name (package-name *package*))))))
 13  
 14  (defun field-class-symbol-from-name (name)
 15    (alexandria:format-symbol *package* "~a-FIELD" name))
 16  
 17  
 18  ;; Field types define how types are saved to the DB and how they are rendered
 19  ;;
 20  ;; The base class is field-definition, which contains all the information needed
 21  ;; to render a field
 22  (defclass field-definition ()
 23    ((name :type string :initarg :name :accessor fdef-name :initform (error "Field `name' of field-definition left unbound"))
 24     (sym :type symbol :initarg :sym :accessor fdef-sym :initform (error "Field `sym' of field-definition left unbound"))
 25     (args :type (or nil list) :initarg :args :accessor fdef-args :initform nil)
 26     (reader :type function :initarg :reader :accessor fdef-reader)
 27     (writer :type function :initarg :writer :accessor fdef-writer)
 28     (foreign-key :type (or nil symbol) :initarg :foreign-key :accessor fdef-foreign-key)
 29     (readonly :type boolean :initarg :readonly :accessor fdef-readonly :initform nil)
 30     (nullable :type boolean :initarg :nullable :accessor fdef-nullable :initform T)
 31     (primary-key :type boolean :initarg :primary-key :accessor fdef-primary-key :initform nil)
 32     (unique :type boolean :initarg :unique :accessor fdef-unique :initform nil)))
 33  
 34  (defmethod generate-table-create-row ((fdef field-definition))
 35    (format nil "~(~s~) ~a~:[~; PRIMARY KEY~]~:[~; NOT NULL~]~:[~; UNIQUE~]~@[ REFERENCES \"~(~a~)\"(id)~]"
 36            (fdef-name fdef)
 37            (symbol-name (db-type fdef))
 38            (fdef-primary-key fdef)
 39            (and (not (fdef-primary-key fdef)) (not (fdef-nullable fdef)))
 40            (and (not (fdef-primary-key fdef)) (fdef-unique fdef))
 41            (fdef-foreign-key fdef)))
 42  
 43  (defmethod field-db-type ((fdef field-definition))
 44    "Retrieve the `DB' type depending on the field definition properties"
 45    (if (fdef-nullable fdef)
 46        `(or ,(db-type fdef) :null)
 47      (db-type fdef)))
 48  
 49  (defmethod db-type ((obj field-definition))
 50    (error "Needs to implement db-type"))
 51  
 52  (defmethod convert-from-form-data ((obj field-definition) form-data-value)
 53    form-data-value)
 54  
 55  (defmethod validate ((obj field-definition) value)
 56    (declare (ignore value))
 57    t)
 58  
 59  (defmethod render-view ((obj field-definition) &optional value)
 60    (declare (ignore value))
 61    (error "Needs to implement render-view"))
 62  
 63  (defmethod render-form ((obj field-definition) &optional value)
 64    (declare (ignore value))
 65    (error "Needs to implement render-form"))
 66  
 67  ;; Helper macros
 68  
 69  (defmacro define-field-type (name &key (inherit nil))
 70    (let ((class-name (field-class-symbol-from-name name))
 71  	(parent-class-name (when inherit 
 72  			     (field-class-symbol-from-name inherit))))
 73      `(define-simple-class ,class-name (,(or parent-class-name 'field-definition)) ())))
 74  
 75  (defmacro define-field-method (field-type method-name args body)
 76    (let ((class-name (field-class-symbol-from-name field-type))
 77  	(obj (intern (symbol-name 'obj)))
 78  	(args (mapcar (lambda (s) (when (symbolp s) (intern (symbol-name s)))) args)))
 79      `(defmethod ,method-name ((,obj ,class-name) ,@args)
 80         ,body)))
 81  
 82  (defmacro define-db-type (field-type &body body)
 83    `(define-field-method ,field-type db-type () ,@body))
 84  
 85  (defmacro define-render-form (field-type &body body)
 86    `(define-field-method ,field-type render-form (&optional value) (html ,@body)))
 87   
 88  (defmacro define-render-view (field-type &body body)
 89    `(define-field-method ,field-type render-view (&optional value) (html ,@body)))
 90  
 91  (defmacro define-validate (field-type &body body)
 92    `(define-field-method ,field-type validate (value) ,@body))
 93  
 94  
 95  (defun field-type-from-field-def (field-def)
 96    "Get the field type from a field-def list."
 97    (let* ((field-type-def (cadr field-def)))
 98      (if (listp field-type-def)
 99  	(car field-type-def)
100  	field-type-def)))
101  
102  (defun field-args-from-field-def (field-def)
103    (let* ((field-type-def (cadr field-def)))
104      (if (listp field-type-def)
105  	(cdr field-type-def)
106  	nil)))
107  
108  (define-condition field-type-doesnt-exist (error)
109    ((field-def :initarg :field-def :reader field-type-doesnt-exist-field-def)
110     (model-name :initarg :model-name :reader field-type-doesnt-exist-model-name)))
111  
112  (defmethod print-object ((err field-type-doesnt-exist) out)
113    (let ((model-name (field-type-doesnt-exist-model-name err)))
114      (format out "When defining model ~s in package ~s, an attempt to instantiate a field with an unkown type was made: " model-name (package-name (symbol-package model-name)))
115      (print-unreadable-object (err out :type t)
116        (format out "~s" (field-type-doesnt-exist-field-def err)))))
117  
118  (defun make-field-definition-from-field-def (model-name field-def)
119    "Given a field definition from a model. Create an instance of field-definition
120  for that field definition."
121    (let* ((field-name (car field-def))
122  	 (field-type (field-type-from-field-def field-def))
123  	 (field-type-class (field-class-from-name field-type))
124  	 (field-args (field-args-from-field-def field-def))
125  	 (field-properties (cddr field-def))
126           (field-full-name (alexandria:format-symbol *package* "~a-~a" model-name field-name)))
127      (unless field-type-class
128        (error 'unknown-field-type :field-type field-type))
129      (handler-case
130  	(make-instance
131  	 field-type-class
132  	 :name (symbol-name field-name)
133  	 :args field-args
134           :sym field-name
135           ;; :reader (fdefinition field-full-name)
136           ;; :writer (fdefinition `(setf ,field-full-name))
137           :foreign-key (if (eq field-type :belongs-to) (car field-args))
138  	 :nullable (and (not (in :primary-key field-properties))
139                          (not (in :not-null field-properties)))
140  	 :primary-key (in :primary-key field-properties)
141  	 :readonly (in :readonly field-properties)
142  	 :unique (and (not (in :primary-key field-properties))
143                        (in :unique field-properties)))
144        (error ()
145  	(error 'field-type-doesnt-exist
146  	       :field-def field-def
147  	       :model-name model-name)))))
148