[cells-devel] cells in Web applications
Friedrich Dominicus
frido at q-software-solutions.de
Wed Sep 7 07:33:58 UTC 2005
Kenny Tilton <ktilton at 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
More information about the cells-devel
mailing list