#| src/models/registered-table-mixin.lisp Model Object Class. Instead of writing new code every time for every new Model (Model, View, Component), encapsulate the behavior here. Now using mixins! |# (in-package #:cl-deck-builder2.models.registered-table) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Thanks mariari! https://lispcookbook.github.io/cl-cookbook/clos.html#metaclasses (defclass model-registry () ((registry :allocation :class :accessor model-registry :initform (make-hash-table)))) (defmethod registered-classes ((registry model-registry) &optional package) (if package (gethash (find-package package) (model-registry registry)) ;; If no package was provided return a LIST of all packages tracked by the MODEL-REGISTRY. (apply #'concatenate 'list (alexandria:hash-table-values (model-registry registry))))) (defparameter *registry* (make-instance 'model-registry) "Global model registry instance.") (defun registry (&optional (registry *registry*)) "Get the MODEL-REGISTRY associated with REGISTRY. Default is *REGISTRY*." registry) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Inherit from MITO:DAO-TABLE-CLASS because MITO:DAO-TABLE-MIXIN ;; doesn't have the additional PRIMARY-KEY or TIMESTAMP mechanisms or ;; anything. (defclass registered-table-class (mito:dao-table-class) ()) (defmethod closer-mop:validate-superclass ((class registered-table-class) (superclass mito:dao-table-class)) t) (defmethod initialize-instance :after ((class registered-table-class) &rest initargs &key &allow-other-keys) (declare (ignore initargs)) (pushnew (class-name class) (gethash (symbol-package (class-name class)) (model-registry (registry))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Tables (defmethod create-tables ((registry model-registry) &optional package) "Create tables for CLASS-LIST by wrapping DBD:CREATE-TABLE." (apply #'create-table (registered-classes registry package))) (defmethod drop-tables ((registry model-registry) &optional package) "Drop tables for CLASS-LIST by wrapping DB:DROP-TABLE." (apply #'drop-table (registered-classes registry package))) (defmethod recreate-tables ((registry model-registry) &optional package) (drop-tables registry package) (create-tables registry package)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defmethod migration-expressions ((registry model-registry) &optional package) (with-connection (db) (with-transaction (mapcar #'mito:migration-expressions (registered-classes registry package))))) (defmethod migrate-table ((registry model-registry) &optional package) (with-connection (db) (with-transaction (mapcar #'mito:migrate-table (registered-classes registry package))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun ensure-tables-exist (&optional package) (create-tables (registry) package)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defmethod export-registered-classes ((registry model-registry) &key package (stream *standard-output*)) "Export REGISTERED-CLASSES for REGISTRY to STREAM, which by default, is *STANDARD-OUTPUT*." (with-connection (db) (with-transaction (format stream "~{~a~^;~%~};" (mapcar (lambda (class) (sxql:yield (car (mito:table-definition class)))) (registered-classes registry package)))))) (defun export-registered-classes-to-file (&key (output (relative-pathname "db/schema.sql")) package) (with-open-file (stream output :direction :output :if-exists :supersede :if-does-not-exist :create) (export-registered-classes (registry) :package package :stream stream) (probe-file output)))