Kenny Tilton ktilton@nyc.rr.com writes:
I'm trying to find my way into decent web-programming, and are now looking into the following simple? Problem. The proofed way of doing web-programming seems the MVC stuff. Now the model may be a cl-sql class e.g and the view may come from any of the web"frameworks", however there is a need to get the data cleanly to the view and back I wonder if that would be a good? usage for cells.
Yes, it is a perfect fit because of the requirement that the model and view always be in synch. Of course on Windows they have the "refresh view" option so users can go quietly crazy not finding what they know is there, so there /are/ alternatives. :)
I did this with Franz's AllegroStore. What is needed is a bottleneck on DB writes, some place to put a custom hook which will cooperate with a Cell-powered mechanism to allow seamless declarative view code...uh, here is an example: a simple list view of all my cutomers. That will be a stack of text labels/controls, each showing the customer name. If the name changes we want the label to change, and if a customer is added or removed we want the list contents to grow or shrink. For the latter, we want to say:
(aStack ()... :kids (c? (mapcar #'make-list-cell (^ps-table 'customer))))
...where ps-table is a layer of code I wrote to make it seems as if the AllegroStore (persistent CLOS) customer class had a cell called ps-table (maybe a bad name) which amounted to a list of all the instances of that class in the persistent DB.
Well I just started looking into cells, and have yet not the "slighest" idea on it's usage in any environment, so I wrote down a stripped down example (tested with ucw (actual dev tree), araneida, cl-sql, SBCL 0.9.4.24, on a Debian unstable box)
Of course I can not expect anyone having all this things installed, it's just to have "more" concrete example
(in-package :qss.web)
(defparameter *example-app* (make-instance 'cookie-session-application :url-prefix "/ucw/examples/" :tal-generator (make-instance 'yaclml:file-system-generator :cachep t :root-directories (list *ucw-tal-root*)) :www-roots (list *ucw-tal-root*)))
(clsql:def-view-class example-app () ((mid :type integer :db-kind :key :accessor mid :initarg :mid) (mtag :type string :accessor mtag :initarg :mtag) (mdescription :type string :accessor mdescription :initarg :mdescription) (msome-fk :type integer :accessor msome-fk :initarg :msome-fk) (msome-objects :accessor msome-objects :db-kind :join :db-info (:join-class other-class :retrieval :deferred :set nil :foreign-key mexample-id :home-key mid))))
(clsql:def-view-class other-class () ((mexample-id :type integer :accessor mexample-id :initarg :mexample-id) (mval :type string :accessor mval :initarg :mval)))
(defun populate-db () (let ((other (make-instance 'other-class :mexample-id 1 :mval "some text"))) (clsql:update-records-from-instance other) (let ((mobj (make-instance 'example-app :mid 1 :mtag "some tag" :mdescription "this is the description" :msome-fk 1))) (setf (msome-objects mobj) other) (clsql:update-records-from-instance mobj))))
;; ;; (clsql:create-view-from-class 'example-app) ;; (clsql:create-view-from-class 'other-class) ;; (populate-db)
(defcomponent example-view (simple-window-component) ((db-obj :accessor db-obj :initarg :db-obj :initform (car (clsql:select 'example-app :where [= [mid] 1] :flatp t))) ;; view link to the database object
(vtag :accessor vtag :initarg :vtag :component (text-field :size 20 :maxlength 20)) (vdescription :type string :accessor vdescription :initarg :vdescription :component (text-area-field :width 20 :height 10)) (vother :accessor vother :component (text-field :size 20 :maxlength 20))))
(defun setup-db () (qss.db:db-connect :database "test" :user "user" :password "password") ;; open a connectoin to DB (clsql:enable-sql-reader-syntax))
;; (setup-db)
(defcomponent text (simple-window-component) ((view-text :initarg :view-text :reader view-text) (db-text :initarg :db-text :reader db-text)))
(defmethod render-on ((res response) (text text)) (<:p "view data" (<:as-is (view-text text))) (<:p "database data" (<:as-is (db-text text))))
(defmethod populate-view ((view example-view)) (let ((db-obj (db-obj view))) (setf (ucw::client-value (vtag view)) (mtag db-obj) (ucw::client-value (vdescription view)) (mdescription db-obj) (ucw::client-value (vother view)) (mval (msome-objects db-obj))) (values)))
(defmethod render-on ((res response) (view example-view)) ;; this is not perfect but for the example it should be enough (populate-view view) ;; (inspect view) (<ucw:form :action (save-and-show-data view) (<:table (<:tr (<:td "tag") (<:td "Description") (<:td "Other text")) (<:tr (<:td (render-on res (vtag view))) (<:td (render-on res (vdescription view))) (<:td (render-on res (vother view))))) (<:p (<:input :type "submit" :value "Accept"))))
(defmethod update-model-from-view ((view example-view)) (let* ((db-obj (db-obj view)) ;; I know with-accessor would be fine... (other (msome-objects db-obj))) (setf (mtag db-obj) (read-client-value (vtag view)) (mdescription db-obj) (read-client-value (vdescription view)) (mval other) (read-client-value (vother view))) (clsql:update-records-from-instance db-obj) (values)))
(defaction save-and-show-data ((view example-view)) (update-model-from-view view)
(call 'text :view-text (format nil "tag = ~a, descripton = ~a, other-text = ~a~%" (read-client-value (vtag view)) (read-client-value (vdescription view)) (read-client-value (vother view))) :db-text (format nil "tag = ~a, descripton = ~a, other-text = ~a~%" (mtag (db-obj view)) (mdescription (db-obj view)) (mval (msome-objects (db-obj view))))))
(defentry-point "index.ucw" (:application *example-app*) () (call 'example-view))
I guess one can see the "duplication" of code here I have my model data in the database declard with clsql-def-view class, there is one join slot which links to other-obj. clsql arranges it such way that if you query the Database that it will remember the link between the both tables. So if the join slot has a number then you can simple reach into the other table without the need for a new query you just say (other-class example-app-obj) and got the object constructed from other_class with example-id = 1
The view does not show all the elements of the table because they are "supposed" to be uninterested. Of course I'm aware that I could abstract away the assignment of data, but for that I was thinking to use cells. However I do not have any clue on how to hook it into this application.
I guess one can see how I think UCW should be used you have a chain view-action-view-action ..... And you can see that I used the view to contain a link to the object this is probably not the "right" approach but it seems to be ok for showing the idea nevertheless.
Probaly one should use e.g methods specialiced on a view and a model (which would make the controller, in MVC terms) but the main problem is the manual need to get the data from the database to the view and back. If I forget to update either the view or the database, the whole stuff will break down, and what I especially dislike is the duplication of code, there *must* be a better way.
You'll see also that not validation checks are done, they must be considered also but for now I just drop them.
So my "concrete" question is on how to hook in cells to avoid the manual assignments and e.g hook in the validation tests (wouldn't that be cells task?)
As I understand cells is there to establich links between objects to express relationships between different slots in the same or different classes. Now in this example there is a very strong link in both directions (view -> db and db -> view), the rules are very simple if all will go well but what happens if the link is somehow broken?
Regards Friedrich