2015-05-12 8 views
5

Dire se definisco un metaclasse che migliora gli slot standard con uno slot di validatore, quando passo il :validator (clavier:valid-email "The email is invalid") come opzione, invece di memorizzare il risultato dell'espressione, che è un funzionale, memorizza l'espressione stessa. Mi manca un passaggio quando estendi gli slot standard? Come faccio a garantire che l'espressione venga valutata prima di essere archiviata? Sto usando SBCL 1.2.11 btw. Ecco il codice in questioneLe opzioni di slot personalizzate non applicano alcuna riduzione al suo argomento

(unless (find-package 'clavier) 
    (ql:quickload :clavier)) 
(unless (find-package 'c2mop) 
    (ql:quickload :c2mop)) 
(defpackage #:clos2web/validation 
    (:use #:cl) 
    (:import-from #:c2mop 
       #:standard-class 
       #:standard-direct-slot-definition 
       #:standard-effective-slot-definition 
       #:validate-superclass 
       #:direct-slot-definition-class 
       #:effective-slot-definition-class 
       #:compute-effective-slot-definition 
       #:slot-value-using-class)) 

(in-package #:clos2web/validation) 

(defun true (value) 
    "Always return true." 
    (declare (ignore value)) 
    t) 

(defclass validation-class (standard-class) 
() 
    (:documentation "Meta-class for objects whose slots know how to validate 
    their values.")) 

(defmethod validate-superclass 
    ((class validation-class) (super standard-class)) 
    t) 

(defmethod validate-superclass 
    ((class standard-class) (super validation-class)) 
    t) 

(defclass validation-slot (c2mop:standard-slot-definition) 
    ((validator :initarg :validator :accessor validator :initform #'true 
       :documentation "The function to determine if the value is 
    valid. It takes as a parameter the value."))) 

(defclass validation-direct-slot (validation-slot 
            standard-direct-slot-definition) 
()) 

(defclass validation-effective-slot (validation-slot 
            standard-effective-slot-definition) 
()) 

(defmethod direct-slot-definition-class ((class validation-class) &rest initargs) 
    (declare (ignore initargs)) 
    (find-class 'validation-direct-slot)) 

(defmethod effective-slot-definition-class ((class validation-class) &rest initargs) 
    (declare (ignore initargs)) 
    (find-class 'validation-effective-slot)) 

(defmethod compute-effective-slot-definition 
    ((class validation-class) slot-name direct-slot-definitions) 
    (let ((effective-slot-definition (call-next-method))) 
    (setf (validator effective-slot-definition) 
      (some #'validator direct-slot-definitions)) 
    effective-slot-definition)) 

(defmethod (setf slot-value-using-class) :before 
    (new (class validation-class) object (slot validation-effective-slot)) 
    (when (slot-boundp slot 'validator) 
    (multiple-value-bind (validp msg) 
     (funcall (validator slot) new) 
     (unless validp 
     (error msg))))) 

;; Example usage 

(defclass user() 
    ((name :initarg :name) 
    (email :initarg :email :validator (clavier:valid-email "The email is invalid") :accessor email)) 
    (:metaclass validation-class)) 

(let ((pepe (make-instance 'user :name "Pepe" :email "[email protected]"))) 
    (setf (email pepe) "FU!")) ;; should throw 

Il codice non riesce quando si effettua un'istanza come (CLAVIER: VALIDO-MAIL "L'e-mail non è valido") non è una funcallable.

(CLAVIER:VALID-EMAIL 
    "The email is invalid") fell through ETYPECASE expression. 
Wanted one of (FUNCTION SYMBOL). 
    [Condition of type SB-KERNEL:CASE-FAILURE] 
+0

'Defclass' non valuta le cose, quindi il modulo viene memorizzato come opzione di slot. Sto riflettendo sulla migliore soluzione. – Svante

+0

in sbcl le canonicalize-defclass-slots si occupano dell'elaborazione degli slot e hanno accesso all'env della definizione, avete qualche indicazione su come renderlo suscettibile di ridurre le opzioni non standard utilizzando l'ambiente? – PuercoPop

risposta

5

Come nel commento precedente, defclass non valuta gli argomenti (è una macro). Mentre il solito consiglio è di evitare la valutazione, penso che la valutazione in questa circostanza potrebbe essere esattamente ciò che desideri. Mentre di solito dovresti unire il modulo direttamente in qualche corpo macro, con defclass penso che la risposta sia quella di valutare la forma nell'inizializzazione dello slot e memorizzare la valutazione (se non è stata ancora valutata).

Ciò probabilmente verificarsi in:

(defmethod initialize-instance :after ((obj validation-slot) 
             &key &allow-other-keys) 
    #| ... |#) 

Opzionalmente si può anche memorizzare il :validation-message e :validation-fn come due argomenti separati quindi chiamare:

(multiple-value-bind (validp msg) 
    (funcall (funcall (validator-fn slot) 
         (validator-message slot)) 
      new) 
    (unless validp 
    (error msg))) 

Un'altra alternativa potrebbe essere quella di memorizzare la valutazione del formare e passare alla macro:

(defvar *email-validator* (CLAVIER:VALID-EMAIL "The email is invalid")) 
(defun email-validator (val) 
    (funcall *email-validator* val)) 

Quindi passare email-validator a defclass.

Inoltre, è possibile suggerire che le funzioni di convalida segnalino le condizioni di tipo slot-validation-error invece delle condizioni di tipo error. Quindi la tua condizione potrebbe contenere riferimenti al validatore che ha fallito, il valore, lo slot e l'istanza. Questo potrebbe darti un controllo molto migliore rispetto all'errore grezzo. È anche possibile aggiungere alcuni riavvii (abort per saltare l'impostazione dello slot, valore d'uso per fornire un valore diverso).

A seconda della configurazione, potrebbe anche avere più senso che la funzione di convalida segnali direttamente, invece di restituire più valori che vengono quindi convertiti in segnali.

+0

Grazie per il puntatore su errore di convalida slot. Beh immagino che sia eval per ora. Probabilmente dovrei esaminare come l'initform tiene traccia dell'ambiente al momento della definizione per vedere un'altra alternativa. – PuercoPop

Problemi correlati