clojuremacrosrefactoringnoirhiccup

Clojure: Dynamically create functions from a map -- Time for a Macro?


I have a function that begins like this:

(defn data-one [suser]
    (def suser-first-name
       (select db/firstNames
            (fields :firstname)
            (where {:username suser})))
    (def suser-middle-name
        (select db/middleNames
            (fields :middlename)
            (where {:username suser})))
    (def suser-last-name
         (select db/middleNames
             (fields :lastname)
             (where {:username suser})))
    ;; And it just continues on and on...
        )

Of course, I don't like this at all. I have this pattern repeating in many areas in my code-base and I'd like to generalize this.

So, I came up with the following to start:

(def data-input {:one '[suser-first-name db/firstNames :firstname] 
                      '[suser-middle-name db/middleNames :middlename]
                      '[suser-last-name db/lastNames :lastname]})

(defpartial data-build [data-item suser]
    ;; data-item takes the arg :one in this case
     `(def (data-input data-item)
        (select (data-input data-item)
            (fields (data-input data-item))
            (where {:username suser}))))

There's really a few questions here:

-- How can I deconstruct the data-input so that it creates x functions when x is unknown, ie. that the values of :one is unknown, and that the quantities of keys in data-input is unknown.

-- I'm thinking that this is a time to create a macro, but I've never built one before, so I am hesitant on the idea.

And to give a little context, the functions must return values to be deconstructed, but I think once I get this piece solved, generalizing all of this will be doable:

(defpage "/page-one" []
    (let [suser (sesh/get :username)]       
    (data-one suser)
        [:p "Firat Name: " 
            [:i (let [[{fname :firstname}] suser-first-name]
                (format "%s" fname))]
        [:p "Middle Name: "  
            [:i (let [[{mname :emptype}] suser-middle-name]
                (format "%s" mname))]
        [:p "Last Name: " 
            [:i (let [[{lname :months}] suser-last-name]
                    (format "%s" lname))]]))

Solution

  • Some suggestions:

    I would therefore suggest something like:

    (def data-input [[:suser-first-name db/firstNames :firstname] 
                     [:suser-middle-name db/middleNames :middlename]
                     [:suser-last-name db/lastNames :lastname]])
    
    (def data-build [data-input suser]
      (loop [output {}
             items (seq data-input)]
        (if items
          (recur
            (let [[kw db fieldname] (first items)]
              (assoc output kw (select db (fields fieldname) (where {:username suser})))) 
            (next items))
          output)))
    

    Not tested as I don't have your database setup - but hopefully that gives you an idea of how to do this without either macros or mutable globals!