How to pass the variable name as a string to a function? (ClojureScript) - metadata

Description of the situation
I want to make a form template for an element, but they must be dynamically created. The meta data involved in the Component should use the variable-name passed as it's meta-data.
Code
For example,
In the view,
(ns my.app
(:require [my.app.templating :as template])
(defn view-component [nOperacaoExtrato]
[:<>
(template/temp-form nOperacaoExtrato)])
The templating function,
(ns my.app.templating)
(defn temp-form
"Template input"
[dado]
#_(js/console.log (str "meta-data: " (meta #'dado)))
(let [nome-var (:name (meta #'dado))]
[:div.col
[:label
{:for (str "form1_" nome-var)}
"Natureza do Dispendio"]
[:p
{:class "form-control",
:id (str "form1_" nome-var)
:name (str "form1" nome-var)}
dado]]))
The result should be, something like this (because the variable passed is nOperacaoExtrato):
[:div.col
[:label
{:for "form1_re-fin-n-operacao-extrato-prop"}
"Nº da Operação no Extrato"]
[:p
{:class "form-control",
:id "form1_re-fin-n-operacao-extrato-prop",
:name "form1_re-fin-n-operacao-extrato-prop"}
(h/preencher-str nOperacaoExtrato)]]
The issue:
Both of these return null.
(meta #'data)
(meta data)
``

You should probably convert this function to a macro. Let me show you fellow Portuguese speaker:
src/cljs/user.cljc
(ns cljs.user)
(defn temp-form-fn
"Template input"
[dado nome-var]
[:div.col
[:label
{:for (str "form1_" nome-var)}
"Natureza do Dispendio"]
[:p
{:class "form-control",
:id (str "form1_" nome-var)
:name (str "form1" nome-var)}
dado]])
#?
(:clj
(defmacro temp-form
[dado]
`(temp-form-fn ~dado ~(name dado))))
Then in the repl:
cljs.user> (require '[cljs.user :refer-macros [temp-form]])
nil
cljs.user> (let [nOperacaoExtrato 1234]
(temp-form nOperacaoExtrato))
[:div.col
[:label {:for "form1_nOperacaoExtrato"} "Natureza do Dispendio"]
[:p
{:class "form-control",
:id "form1_nOperacaoExtrato",
:name "form1nOperacaoExtrato"}
1234]]

Related

Why isn't my macro functioning in the same way as handwritten code?

So I have 2 router functions for learning to work with clack in common lisp. One is written without macros, and the other is written with macros that expand to match the first function, but only the non-macro version is working. The code below has (:use :trivia) from (ql:quickload 'trivia).
This one is written without any macros, and works:
(defun router (env)
(match env
((guard (property :path-info path)
(equalp "/" path))
(home env))
((guard (property :path-info path)
(equalp "/live/clicked" path))
(live-clicked env))
((property :path-info path)
`(404 nil (,(format nil "404 page not found"))))))
I decided I didn't like those guard clauses taking up so much space in the function definition, so I rewrote the function:
(defun router (env)
(match env
(route "/" (home env))
(route "/live/clicked" (live-clicked env))
((property :path-info path)
`(404 nil (,(format nil "404 page not found"))))))
route is defined as so:
(defmacro route (valid-path &body body)
(let ((path (gensym)))
`((guard (property :path-info ,path)
(equalp ,valid-path ,path))
,#body)))
With this new router function and macro, the function is always short-circuiting on the first clause. When macroexpanding the 2 (route ...) clauses, I receive this output, matching the function I wrote:
* `(defun router (env)
(match env
,(macroexpand '(route "/" (home env)))
,(macroexpand '(route "/live/clicked" (live-clicked env)))
((property :path-info path)
`(404 nil (,(format nil "404")))))))
(DEFUN ROUTER (ENV)
(MATCH ENV
((GUARD (PROPERTY :PATH-INFO #:G120) (EQUALP "/" #:G120)) (HOME ENV))
((GUARD (PROPERTY :PATH-INFO #:G121) (EQUALP "/live/clicked" #:G121)) (LIVE-CLICKED ENV))
((PROPERTY :PATH-INFO PATH) `(404 NIL (,(FORMAT NIL "404")))))
(home env) and (live-clicked env) are functions that return something similar to (backquote (200 nil (,(*form generating html*)))). env is the state of the web request, but in this instance it only needs to be (list :path-info "/live/clicked")
First of all, macroexpand should be
used for interactive debugging rather than in production code.
Its use inside defun (almost) never makes sense.
Second, to understand your second router, you should macroexpand its body
rather than the defun, i.e.,
(macroexpand '(match env
(route "/" (home env))
(route "/live/clicked" (live-clicked env))
((property :path-info path)
`(404 nil (,(format nil "404"))))))
You will see that route is not expanded because it is not in a "function position".
What you need to expand your route before match is expanded, i.e., you probably want a
wrapper around match (I removed gensym because you indicated in the comments that it is really
not necessary):
(defmacro my-match (what &body clauses)
(labels ((handle-clause (clause)
(if (eq (car clause) 'route)
(apply #'route (rest clause))
clause))
(route (valid-path &rest body)
`((guard (property :path-info path)
(equalp ,valid-path path))
,#body)))
`(match ,what ,#(mapcar #'handle-clause clauses))))
and then
(defun router (env)
(my-match env
(route "/" (home env))
(route "/live/clicked" (live-clicked env))
((property :path-info path)
`(404 nil (,(format nil "404 page not found"))))))
Testing:
(macroexpand '(my-match env
(route "/" (home env))
(route "/live/clicked" (live-clicked env))
((property :path-info path)
`(404 nil (,(format nil "404"))))))
==>
(MATCH ENV ((GUARD (PROPERTY :PATH-INFO PATH) (EQUALP "/" PATH)) (HOME ENV))
((GUARD (PROPERTY :PATH-INFO PATH) (EQUALP "/live/clicked" PATH))
(LIVE-CLICKED ENV))
((PROPERTY :PATH-INFO PATH) `(404 NIL (,(FORMAT NIL "404")))))
T
The pattern matcher you are using, Trivia, has a defpattern macro. That's what you must use to define a macro against the pattern language itself, rather than defmacro.

Generating inline javascript with cl-who, parenscript and hunchentoot

I'm trying to generate inline javascript, but I have to put the parenscript code inside (:script) and (str) tags using cl-who. ps, ps*, ps-inline and ps-inline* don't seem to make much difference to the generated js.
Is the usual way to write a macro to avoid code duplication, or is there a better way?
Here's my program:
(in-package #:ps-test)
(defmacro standard-page ((&key title) &body body)
`(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
(:html
:lang "en"
(:head
(:meta :http-equiv "Content-Type"
:content "text/html;charset=utf-8")
(:title ,title)
(:link :type "text/css"
:rel "stylesheet"
:href "/style.css"))
(:body
,#body))))
(defun main ()
(with-html-output (*standard-output* nil :indent t :prologue nil)
(standard-page (:title "Parenscript test")
(:div (str "Hello worldzors"))
(:script :type "text/javascript"
(str (ps (alert "Hello world as well")))))))
(define-easy-handler (docroot :uri "/") ()
(main))
(defun start-ps-test ()
(setf (html-mode) :html5)
(setf *js-string-delimiter* #\")
(start (make-instance 'hunchentoot:easy-acceptor :port 8080)))
(defun stop-ps-test ()
(stop *server*))
(defvar *server* (start-ps-test))
Macros are fine in this use case.
The trick is that macros are expanded in a specific order. Say
you define a js macro: when macroexpansion encounters
with-html-output, the inner call to your macros (js (alert "Ho Ho Ho")) looks like a function call, and is left as-is in the generated
code. If your js macro then expands into (:script ...), then the system will complain that :script is an unknown function (assuming you
didn't actually name a function like that). You should emit an
enclosing (who:htm ...) expression to interpret the code using
CL-WHO's code walker.
(defmacro js (code)
`(who:htm
(:script :type "text/javascript" (who:str (ps:ps ,code)))))
This only works in the context of an enclosing with-html-output.
For inline Javascript, you don't want to have a <script> tag around it,
and you can generally simply use ps-inline:
(who:with-html-output (*standard-output*)
(:a :href (ps:ps-inline (void 0))
"A link where the usual HREF behavior is canceled."))
;; prints:
;;
;; <a href='javascript:void(0)'>A link where the usual HREF behavior is canceled.</a>
But feel free to use a macro if you often do the same thing:
(defmacro link (&body body)
`(who:htm (:a :href #.(ps:ps-inline (void 0)) ,#body)))
(who:with-html-output (*standard-output*) (link "Link"))
;; prints:
;;
;; <a href='javascript:void(0)'>Link</a>

clojure.data.xml and parsing jdbc xml object

I'm attempting to read xml (actual xml type) from a postgres database in clojure, however i'm not sure how to cast the org.postgresql.jdbc4.Jdbc4SQLXML object to something clojure.data.xml can make sense of.
When running (xml/parse ...) on this object i'm getting the following error:
IllegalArgumentException No matching method found: createXMLStreamReader for class com.sun.xml.internal.stream.XMLInputFactoryImpl clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:80)
code below:
(ns clj-xml-dbms.core-test
(:require [clojure.test :refer :all]
[clojure.java.jdbc :as j]
[clojure.data.xml :as xml]
[clj-xml-dbms.core :refer :all])
(:use [clojure.pprint ] ))
(def db-spec {
:classname "org.postgres.Driver"
:subprotocol "postgres"
:subname "//localhost:5432/mydb"
:user "me"
:password "secret"
})
;; get the results of a query
(def results
(let [
sql "select row_id, xml_col from stg.some_table limit 1 "
db-connection (j/get-connection db-spec )
statement (j/prepare-statement db-connection sql)
query-results (j/query db-connection [statement]) ]
(first query-results)))
(pprint results)
;; {:row_id 18627,
;; :xml_col #object[org.postgresql.jdbc4.Jdbc4SQLXML 0x6897f635 "org.postgresql.jdbc4.Jdbc4SQLXML#6897f635"]}
;; obviously clojure.data.xml/parse isn't going to work on that:
(xml/parse (:xml_col results))
;; IllegalArgumentException No matching method found: createXMLStreamReader for class com.sun.xml.internal.stream.XMLInputFactoryImpl clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:80)
I guess i could cast the xml type in the db to a string, but there has to be something cleaner that that i would think. Any tips appreciated, thanks.

ring redirect after login

(ns ...
(:require [ring.util.response :refer [ response redirect]))
My original code be-all-like
(-> (response "You are now logged in! communist party time!")
(assoc :session new-session)
(assoc :headers {"Content-Type" "text/html"}))
Which worked well, but the user still has to navigate elsewhere manually.
Trying to use http://ring-clojure.github.io/ring/ring.util.response.html#var-redirect
(-> (redirect requri)
(assoc :session new-session)
(assoc :headers {"Content-Type" "text/html"}))
doesn't do anything (aside from returning a blank page).
How can I achieve a redirect to a known uri using ring?
response is return a body.
you code is (response requri),but the param of the funtion reponse is html body,not a uri,you can use the this function
like this
(ns foo
(:require [ring.util.response :as response]))
(def requi "/")
(-> (response/redirect requri)
(assoc :session new-session)
(assoc :headers {"Content-Type" "text/html"}))
ps: if you are writing a web site.the lib-noir is a good way to control the session and other.
ipaomian has the answer.
Wanted to share a nice redirect hack:
(ns foo
(:require [ring.util.response :as response]))
(defn redirect
"Like ring.util.response/redirect but also accepts key value pairs
to assoc to response."
[url & kvs]
(let [resp (response/redirect url)]
(if kvs (apply assoc resp kvs) resp)))
(redirect "/" :session new-session :headers {"Content-Type" "text/html"})
ipaomian is right however mine worked by removing the headers. This is my code:
(:require
[ring.util.response :refer [redirect]])
(defn set-user! [id {session :session}]
(-> (redirect "/home")
(assoc :session (assoc session :user id))))

How do I drop or create a database from clojure.java.jdbc?

I would like to create/drop a database from clojure.java.jdbc. This fails:
(require '[clojure.java.jdbc :as sql])
(def db
{:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "//localhost/postgres"
:user "postgres"})
(defn drop-database [name]
(sql/do-commands (str "drop database " name)))
(sql/with-connection db
(drop-database "db_name"))
because do-commands starts a transaction, and apparently you can't drop or create databases inside a transaction. Any ideas?
Thanks!
Take the source for do-commands (here) and remove the call to transaction:
(defn drop-database [name]
(sql/with-connection db
(with-open [s (.createStatement (sql/connection))]
(.addBatch s (str "drop database " name))
(seq (.executeBatch s)))))
The transactionless execution functionality was rolled into db-do-commands.
Now this slightly simpler version is working:
(jdbc/db-do-commands postgres-db false "CREATE DATABASE foo")
If you don't specify false as the second argument, it won't work as it will attempt to start a transaction.
With newer clojure versions, the suggested approach no longer works. I was successful with this function:
(defn exec-db-command [db command]
(jdbc/with-db-connection [conn db]
(with-open [s (.createStatement (:connection conn))]
(.executeUpdate s command))))
(exec-db-command db "create database foo")
This is the only solution that worked for me
(def datasource-options {:auto-commit true
:read-only false
:connection-timeout 30000
:validation-timeout 5000
:idle-timeout 600000
:max-lifetime 1800000
:minimum-idle 10
;; :maximum-pool-size 10
:pool-name "db-pool"
:adapter (:database-adapter env)
:username (:database-username env)
:password (:database-password env)
:database-name (:database-name env)
:server-name (:database-host env)
:port-number (:database-port env)
:register-mbeans false})
(defonce datasource
(delay (make-datasource datasource-options)))
(defn db-jdbc-uri [& {:as args}]
(let [datasource-options (merge datasource-options args)]
(format "jdbc:%s://%s:%s/%s?user=%s&password=%s"
(datasource-options :adapter) (datasource-options :server-name)
(datasource-options :port-number) (datasource-options :database-name)
(datasource-options :username) (datasource-options :password))))
(defn create-database [name]
(println {:connection-uri (db-jdbc-uri :database-name "")})
(jdbc/with-db-connection [conn {:connection-uri (db-jdbc-uri :database-name "")}]
(jdbc/db-do-commands conn false (str "CREATE DATABASE " name))))
(defn drop-database [name]
(jdbc/with-db-connection [conn {:connection-uri (db-jdbc-uri :database-name "")}]
(jdbc/db-do-commands conn false (str "DROP DATABASE " name) )))
Basically you need to connect without providing a database or connecting to a different database (not the one you are deleting)
This will get transilated to this code.
(defn create-database [name]
(jdbc/with-db-connection [conn {:connection-uri "jdbc:postgresql://localhost/postgres?user=<name>&password=<pass>"}]
(jdbc/db-do-commands conn false (str "CREATE DATABASE " name) )))