(blog ‘lucindo)

um dia eu aprendo a programar

Arquivo de Setembro de 2007

postmodern-utils-0.0.1

Fiz um pacote que é um conjunto de funções e macros para facilitar o uso do Postmodern.

Download: http://www.lucindo.com.br/lisp/postmodern-utils.tar.gz

É possível instalar com ASDF-Install:

* (require :asdf-install)
* (asdf-install:install "http://lucindo.com.br/lisp/postmodern-utils.tar.gz")

Com ele é possível fazer o seguinte:

(eval-when (:execute)
  (setf postmodern-utils:*db-name* “db-name”
        postmodern-utils:*db-user* “username”
        postmodern-utils:*db-pass* “password”
        postmodern-utils:*db-host* “server”))

Depois disso você pode usar a macro with-pooled-connection para todo código que precise acessar o banco, como por exemplo:

(defmacro update-dao-attribute (type id accessor-fn new-value)
  (let ((dao (gensym)))
    `(with-pooled-connection
         (with-transaction ()
           (let ((,dao (get-dao type id)))
             (progn
               (setf (,accessor-fn ,dao) ,new-value)
               (save-dao ,dao)))))))

Nesse pacote ainda temos a função create-table-if-not-exists, a macro with-new-connection. A função que motivou o desenvolvimento dessa lib foi select-daos. Ela funciona como a select-dao do Postmodern, só que aceita alguns parâmetros (keyword) a mais:

(with-new-connection
 (select-daos 'entity :test '(:> attr1 100))

Mais exemplos:

(select-daos 'entity)
(select-daos 'entity :limit 20)
(select-daos 'entity :limit 20 :offset 40)
(select-daos 'entity :order-by 'attr2)
(select-daos 'entity :order-by '(:desc attr2))
(select-daos 'entity
	     :test '(:> attr1 100)
	     :order-by '(:desc attr2)
	     :limit 100
	     :offset 200)
Sem comentários »

Ajax simples

Warning: Isso deve ser muito manjado, eu que nunca tinha visto. Estou escrevendo só para não esquecer

Imagine o seguinte caso de uma aplicação web: o usuário executa uma ação e o servidor apenas precisa ser notificado, todos os dados necessários para uma eventual alteração da tela já estão presentes. É o caso por exemplo de marcar um item (star-it), votar em um sistema de ratings, adicionar uma tag a um item, e por aí vai.

Podemos fazer da seguinte maneira: um link que o usuário vai clicar para executar uma ação:

<a id=’a_theid’ onclick=“return ajax(this)” href=“/action?user=x&a=y”>

Nesse link, no campo id colocamos uma codificação simples de um eventual objeto a ser alterado, nesse caso usamos a_theid, onde deve um existir um objeto na página com o id theid que poderá ser alterado. A URL que fará a notificação da execução no server esta no href. Dessa forma podemos implementar a função JavaScript ajax assim:

function ajax(node) {
    var v = node.id.split(/_/); // a_theid
    var item = v[1]; // theid

    // Executa a alteração de tela
    document.getElementById(item).innerHTML = …

    // notifica o servidor
    var action = new Image();
    action.src = node.href;

    return false;
}

O browser carrega imagens de forma assíncrona, então essa função não bloca, a alteração na tela é feita na hora e o server é notificado. A vantagem é que isso funciona em qualquer browser, mesmo alguns bem velhos.

Bom, a desvantagem é que você não sabe se o request falhou. Mas isso pode acontecer em poucos casos como:

  • O servidor está fora do ar: assim o usuário não conseguirá continuar navegando no sistema por muito tempo sem perceber isso, e o número de usuários que acessou o sistema antes dele cair e tem como executar umas ações que vão falhar (sem ele saber) é reduzido. E nessa caso o que você ia fazer? Cuspir um alert na cara do usuário com “Ooops, We did it again!“?
  • Existe um problema de rede entre a máquina do usuário e o servidor: isso recai no problema acima, tem o mesmo efeito.
  • O servidor está se comportando de maneira inesperada, gerando erros: aí amigo, o AJAX é o menor dos seus problemas.

Bom, eu vi isso no código do YCombinator News (Hacker News). Dá uma olhada lá para ver isso funcionando. Já o Digg e o Reddit usam Prototype. Alias, o Reddit poderia usar isso, pois eles não passam uma função de callback no caso de erro do Ajax.Request. Já o Digg passa uma função com um alert “ERROR: …”

3 comentários »

Common Lisp e PostgreSQL

Brincando um pouco com Postmodern:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :postmodern))

(defpackage :pg-test
  (:use :cl :postmodern))

(in-package :pg-test)

(defparameter *db-name* “lucindo”)
(defparameter *db-user* “lucindo”)
(defparameter *db-pass* “”)
(defparameter *db-host* “localhost”)

;; global connection
(eval-when (:execute)
  (connect-toplevel *db-name* *db-user* *db-pass* *db-host*))

;; defines a table (and a CLOS class)
(deftable user ()
  ((id :type integer
       :initarg :id
       :accessor id)
   (name :type (or string db-null)
         :initarg :name
         :initform :null
         :accessor name)
   (points :type (numeric 10 3)
           :initarg :points
           :accessor points))
  (:indices id points)
  (:class-name user))

;; creates the table if not exists
(defun setup ()
  (when (not (table-exists-p :user))
    (create-table ‘user)))

(defun create-update-user (id name &optional (points 0.0))
  (save-dao (make-instance ‘user :id id :name name :points points)))

(defun get-user-by-id (id)
  (get-dao ‘user id))

Se você precisar escrever SQL pode usar o mapeamento para expressões Lisp, como:

(:select ‘relname
         :from ‘pg-catalog.pg-class
         :inner-join ‘pg-catalog.pg-namespace
         :on (:= ‘relnamespace ‘pg-namespace.oid)
         :where (:and (:= ‘relkind “r”)
                      (:not-in ‘nspname (:set “pg_catalog” “pg_toast”))
                      (:pg-catalog.pg-table-is-visible ‘pg-class.oid)))

A lib é bem completa, com connection pools, prepared statements, … e 100% Common Lisp (você não precisa ter nada do PostgreSQL instalado na máquina cliente).

Update: deftable não tem suporte a foreign keys :(

Sem comentários »

Common Lisp e AJAX

E não é que funciona! :D Usei:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :hunchentoot)
  (require :cl-who)
  (require :ht-ajax))

(defpackage :ajax-test
  (:use :cl :hunchentoot :cl-who)
  (:export #:start-web #:stop-web))

(in-package :ajax-test)

;; local directory with lokris.js
(defparameter */static-local-dir* “/Users/lucindo/Documents/Lisp/tmp/”)
;; can be any url
(defparameter *ajax-handler-url* “/ajax”)
;; using lokris (very small)
(defparameter *ajax-processor* (ht-ajax:make-ajax-processor
                                :type :lokris
                                :server-uri *ajax-handler-url*
                                :js-file-uris “/static/lokris.js”))

;; hunchentoot handlers setup
(eval-when (:compile-toplevel :load-toplevel :execute)
  (setf *dispatch-table*
        (list ‘dispatch-easy-handlers
              (create-folder-dispatcher-and-handler “/static/”
                                                    */static-local-dir*
                                                    “text/plain”)
              (create-prefix-dispatcher *ajax-handler-url*
                                        (ht-ajax:get-handler
                                         *ajax-processor*))
              (create-prefix-dispatcher “/js” ‘java-script)
              (create-prefix-dispatcher “/” ‘main-page))))

(defparameter *web-server* nil)

(defun start-web (&optional (port 4242))
  (setf *web-server* (start-server :port port)))

(defun stop-web ()
  (stop-server *web-server*))

;; eval and print a lisp expression
(defun testfunc (command)
  (prin1-to-string (eval (read-from-string command nil))))
(ht-ajax:export-func *ajax-processor* ‘testfunc :method :post)

(defun java-script ()
  “function command_clicked(txt) {
    var command = document.getElementById(’command’).value;
    ajax_testfunc_set_element(’result’, command); }”)

(defun main-page ()
  (with-html-output-to-string (*standard-output* nil :prologue t)
    (:html
     (:head
      (:script :type “text/javascript” :src “/js”)
      (:title “AJAX test”))
      (fmt “~a” (ht-ajax:generate-prologue *ajax-processor*))
     (:body
      (:h1 “AJAX test”)
      (:table :width “50%”
              (:tr
               (:td :colspan “2″
                    (:span :id “result”
                           (:i “no results yet”))))
              (:tr
               (:td :width “70%”
                    (:input :type “text”
                            :size “70″
                            :name “command”
                            :id “command”))
               (:td
                (:input :type “button”
                        :value “Eval”
                        :onclick “javascript:command_clicked();”))))))))

Source: ajax-test.lisp (nele mudei para usar Prototype. O legal é o que o HT-AJAX suporta vários processadores ajax: Lokris, Prototype, Dojo, Yahoo! [YUI]).

Se você também não gosta de HTML junto com código pode usar HTML-TEMPLATE. Se você não gosta mesmo de HTML pode usar o Weblocks em que o design fica em CSS e o resto é codigo Lisp (o framework gera todo o HTML e JavaScript para AJAX).

Quase tudo tendo como base Ediware.

2 comentários »