ucwTutorialGettingFromOnePlaceToAnother

Describe ucwTutorialGettingFromOnePlaceToAnother here _ucw It's early in the morning, so let's get up and let's catch the worm :) Yesterday was our starting day into the wide wide world of ucw. We've outlined the to be implemented example and we even got the first Web page. Now it's time to get further down the line.

So here's what I think is the way to go in ucw. - component (aka place) -> action -> component (maybe another place)

Where do we want to go?

We have been here

(defmethod render((form get-downloader-name))
  (<:h1 "Contact details")
  (<:p "Let us know who you are:")
  (<:table
              (<:tr  
               (<:td  :class "download-query" "Name:") 
               (<:td :class "download-query" (render (name-input-field form)))))
             (render (submit-button form))))

and we wish (for now to get) to

(defcomponent present-downloader-data (simple-window-component) 
  ((downloader :accessor downloader :initarg :downloader)))

Ok, agreed that does not look as if we really get impressive, but we have to learn to walk before we can take-off ;-). Another small thing we like to achieve. We want to get there if the user has given us some data (as written in the description it does not matter for now what he/she gives us, we just want to get something)

With the current sources don't we have any chance to get there, we have to

Let's add a few things to get from 'get-downloader-name' to 'present-downloader-data'. We modify our first render form to:

(defmethod render((form get-downloader-name))
  (<:h1 "Contact details")
  (<:p "Let us know who you are:")
  (

You see the 'magic' takes place in '(present-downloader-data form)'. We make it happen the way we have do, we need to define an action with that name and with the proper parameter. For the time being we use what Bruce A. Tate calls the magic servlet, the action will get the data take action on the model and direct us to the next location.

For now we omit all error handling, or validation of data.
!!! WARNING !!! don't do that in production releases

(defun db-con ()
  "This has to be placed outside an action. Please try yourself
to find out why."
  (db-connect)
  (clsql:enable-sql-reader-syntax))

(defun make-downloader (&key name) "Helper to create an object of type 'DOWNLOADERS', this has been taken of in some code here to get automated. Maybe a nice extension to explain one day" (db-con) (let ((id (car (clsql:query "select nextval('downloaders_seq')" :flatp t)))) (make-instance 'downloaders :id id :name name :downloads 1)))

(defun fetch-downloader (name) "This has to be outside of an action also. But it's also good style." (car (clsql:select 'downloaders :where [= [name] name] :flatp t)))

(defaction present-downloader-data ((form get-downloader-name)) (let* ((view-value (value (name-input-field form))) downloader) (db-con) (setf downloader (fetch-downloader view-value)) (if downloader (incf (downloads downloader)) (setf downloader (make-downloader :name view-value))) (clsql:update-records-from-instance downloader) (answer downloader)))

Now that is quite a mouthful of code. I will try to explain why how it has get how it is ;-). It will explain, why ucw is was and still isn't easy to understand for me and how "incremental" development or let's call it "kreeping to some workable solutions" works for me.

Why db-con outside an action?

(defun db-con ()
  "This has to be placed outside an action. Please try yourself
to find out why."
  (db-connect)
  (clsql:enable-sql-reader-syntax))

I ask you politely to expand this function in the defaction and get surprised yourself. I spent almost 2 hours on this problem, till I remembered some mail I got from Marco some half year or so ago. The point is you can not have (among others) an eval-when within the body of an defaction. The reason for that seems that this continuation stuff does not implement it. I suggest you try to get to find-walker-handler in walk.lisp from the arnesi sources by 'M-.' (slime).

You see there well hidden, if you have no idea.

  "Simple function which tells us what handler should deal
  with FORM. Signals an error if we don't have a handler for
  FORM."
  (if (atom form)
      (gethash '+atom-marker+ *walker-handlers*)
      (aif (gethash (car form) *walker-handlers*)
	   it
	   (case (car form)
	     ((block declare flet function go if labels let let*
		     macrolet progn quote return-from setq symbol-macrolet
		     tagbody unwind-protect catch multiple-value-call
		     multiple-value-prog1 throw load-time-value the
		     eval-when locally progv)

Yes you can find yourself spending that much time on hunting down problems in ucw. That is one of the things which may make you struggle also.

Ok we solved that now next station

Creation of downloader helper

source:

(defun make-downloader (&key name)
  "Helper to create an object of type 'DOWNLOADERS', this has 
been taken of in some code here to get automated. Maybe a nice
extension to explain one day"
  (db-con)
  (let ((id (car (clsql:query "select nextval('downloaders_seq')"
                              :flatp t))))
    (make-instance 'downloaders
                   :id id
                   :name name
                   :downloads 1)))

I had to implement this, because not all the code I have available here is available to you also. We could have gotten away without an 'id' field, but it's almost always a bad idea to not include it. It's really a handy thing to identify things, why should we make our life harder then needed?

What you see above is PostgreSQL specific, because it for having key autoincremented you use a sequence in Postgresql. This was not generated by the lisp data model. But on a command line like this:

create sequence downloader_seq;

in the 'psql' command line utitlity.

Of course that means for you if you use e.g Mysql you have to implement you own creation function. But you see it's encapsulated and that is usually a good thing (tm).

next station in make-downloader

(car (clsql:query "select nextval('downloaders_seq')"
                              :flatp t))))

There may be an easier way of achieving this but this works as follows. The clsql:query function is used to send backend specific stuff to the Database backend, 'id' has to be unique, and so we have to keep track of which numbers are in use and find a free one, this is be done by 'nextval' on a sequence. Now if you send a query to a Database you get back a list of lists. With 'flatp t' we take away one level of list, and because we know that there is just one element in the list we 'car' it.

The next lines are simple calling make-instance on an object nothing fancy.

Getting objects our of the Database

(defun fetch-downloader (name)
  "This has to be outside of an action also. But it's 
also good style."
  (car (clsql:select 'downloaders
                :where [= [name] name]
                :flatp t)))

You can use clsql in diverse ways you can treat the results as "flat" data, then you just have to handle lists or you can use the results as structured in this case you handle objects. Now what happens here we run a query on the 'downloaders object, clsql "knows" that we are talking about the table "downloaders". we then run a comparison on one slot, aka column name (here name) with the given parameter, because we do get back a list of results we flatten that out and take the 'car' of that list.

!!! Warning if it happens that there are more then one entry with one name, we always just fetch one element, which one is unspecified !!!

What is the action now?

All the above code were just helpers, the actual action takes place in:

(defaction present-downloader-data ((form get-downloader-name))
  (let* ((view-value (value (name-input-field form)))
         downloader)
    (db-con)
    (setf downloader (fetch-downloader view-value))
    (if downloader
        (incf (downloads downloader))
        (setf downloader (make-downloader :name view-value)))
    (clsql:update-records-from-instance downloader)
    (answer downloader)))

Now this is not how you should structure your solution really. But it's a chance to show what I think is one great merit of Common Lisp: Get something running and then make it "pretty".

Do you still remember the slots in the component 'get-downloader-name'. Here we access the value (without any validation!) with '(value (name-input-field form))'

You don't feel this is remarkable? Well I do to some extent. We have left the "string" ground and are working with structured data. What you see is simply CLOS programming. If you wouldn't know what this tutorial is about you would not have any idea that we are doing Web programming.

However this code is still bad for the following reasons:

IMHO the right way is the action redirects the work to be carried out in the proper place. I don't think the proper place for modifying the data is in the controller, that should be done in the model backend, the action should just steer the logic of this particular application.

We would get another advantage, insofar as we could test our model separately. So IMHO the right thing to do is. Writing the model, test it, then think about the views you want to grant, and then decide about how to get there. Feel free however to correct this software in that aspect.

I think you understand easily what's going on however. We try to fetch an object with name 'name', if we find it we increment the download count of that customer if we do not find it, we create the person and after that we save it.

Here's ucw again

But now **** TADA **** the ucw part

(answer downloader)

You remember what we displayed to the visitor. Now we got his/her input and now we proceed, we did it in a very unspectacular way. We return some object with 'answer', this can be save e.g in the next component to get us further.

The good thing is you can give back anything with answer, the bad thing: you had better not forget to use answer. If you do, you can go for another round of bug hunting.

But how do I get from one part to the other

Well action will you get there and you can get from call to call. However for the example I wrote the following:

(defentry-point "index.ucw" (:application *downloader-application*) ()
  ;; (setf *inspect-components* t)
  (call 'present-downloader-data :downloader (call 'get-downloader-name)))

Now here's the connection, it is done via the downloader slot in 'present-downloader-data', this code was brought to me by Marco some half year or so ago. I hope you can see why I like ucw despite all my "desparate tries". For me this code has beauty. I'll try to explain the flow through.

The first thing which happened is (call 'get-downloader-name), as you remember this was the first component we defined and for which we wrote a 'render' method. Now the first form is rendered via 'render', because it contains a

So there's one thing left to do (for now)

(defmethod render ((component present-downloader-data))
  (<:p "downloader name: "
       (<:as-html (name (downloader component))))
  (<:p "downloader num of downloads: "
       (<:as-html (downloads (downloader component)))))

This renders now the downloader part of the present-downloader-data class. Again you can see no "model logic" takes place here it's just giving feedback to the user.

Conclusion

Within our two day journey we achieved:

Agreed it lacks a lot of things, but it shows how things can be done. Feel free to provide a nicer solution. Play with it you can get the sources from UCW Tutorial 2006-03-15

Next: ucwTutorialGettingABitFurther ; Previous: gettingUCWIntoPlay


This page is linked from: ucwTutorialWritingYourOwnValidator  

CLiki pages can be edited by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively