macrosclojurelispnoir

Is there a clean way to add functions to a dynamically created namespace?


I am creating a noir webapp, and I need to dynamically create new views and models. I've been following the noir examples, in which the view and the controller for a resource have separate namespaces, and I've found it to be a very clean approach.

In keeping with this, I need to be able to dynamically create new namespaces corresponding to views and models, and then intern the appropriate functions in them. My idea was to have macros specified in a separate namespace which, when called in the new namespace, would provide the appropriate routes/partials/whatever.

For example (forgive my first defmacro):

(ns project.views.proto
  (:use noir.core
        hiccup.core
        hiccup.element
        hiccup.form))

(defmacro def-all-page
  [path]
  `(defpage ~path []
     (html
      [:h1 "Ya'll here"])))

is called from...

(ns project.proto
   (:use [clojure.contrib.with-ns :only [with-ns]])

(create-ns 'foo)
(intern 'foo 'path "path")  ; In reality, the path is dynamic which is why I intern it
(with-ns 'foo
    (clojure.core/refer-clojure)
    (use 'noir.core
         'hiccup.core
         'hiccup.element
         '[project.views.proto :only [def-all-page]])

    (def-all-page path)

However, calling this from within my new namespace gives me a NullPointerException. I'd greatly appreciate any help, and whether or not there is a better approach. Like, just using refer for a namespace which contains all the necessary definitions?

First post, and I don't think it's a repeat of this. Thanks!


Solution

  • First of all, this question has become a bit outdated. Both Noir and Clojure have evolved over the last year. For clarity's sake i'll take Noir out of the equation and try to answer your question about dynamically creating functions using macros.

    Follow along at the REPL:

    $ lein repl
    user=> (in-ns 'foo)
    #<Namespace foo>
    foo=> (clojure.core/refer-clojure)
    nil
    foo=> (defmacro say-hello-to
     #_=>           [name]
     #_=>           `(defn ~(symbol (str "hello-" name))
     #_=>                  []
     #_=>                  ~(str "hello: " name)))
    #'foo/say-hello-to
    

    Here we create a namespace 'foo' that contains a macro for defining 'hello-yourname' functions. Let's create another namespace:

    foo=> (in-ns 'bar)
    #<Namespace bar>
    bar=> (clojure.core/refer-clojure)
    nil
    bar=> (refer 'foo :only '[say-hello-to])
    nil
    bar=> (say-hello-to "tom") 
    #'bar/hello-tom
    bar=> (say-hello-to "jerry") 
    #'bar/hello-jerry
    

    Let's see if these actually work:

    bar=> (hello-tom)
    "hello: tom"
    bar=> (hello-jerry)
    "hello: jerry"
    

    I think this is pretty close to your original example.

    Hope this helps!