Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using macros in Clojure

I'm specifically trying to generate the boilerplate for crud functions to work with the Google App Engine datastore using appengine-magic in Clojure. I'm having difficulty working out how to generate values from a model that I've reproduced below.

(def *model* {:users [{:name "Adam"
                       :email "[email protected]"
                       :registered-on "07-05-2011"}
                      {:name "Greg"
                       :email "[email protected]"
                       :registered-on "11-05-2011"}]
              :post [{:title "A"
                      :authour "Adam"}
                     {:title "B"
                      :author "Greg"}]})

I'm fairly new to appengine-magic, but it provides a defentity which allows you to define entities that you can put into the datastore and save! which allows you to save predefined entities into the datastore.

These take the form of:

(ds/defentity Post [title author])
(ds/save! (Post. title author))

Now just to start with I've defined:

(defn list-entities [model]
  "Takes a representation of the model and lists the entities in preparation for generating defentities"
  (interleave (vec (map first (partition 1 (map (comp symbol capitalize #(str % ".") name) (keys model)))))
    (map vec (map keys (map first (vals model))))))

Calling it with:

(list-entities *model*)

Outputs:

(Users. [:name :email :registered-on] Post. [:title :author])

Now I am having difficulty defining gen-entities which will take the output above and repeatedly call ds/defentities defining as many entities as my model requires.

(defmacro gen-entities [entity fields]
  `(ds/defentity 'entity 'fields))

Additionally I am in no way certain that this is a reasonable way to go about solving this problem. I'm still very new to macros and probably making several mistakes. Any help/clarity would be appreciated.

NOTE:

That model I've realised is badly designed, the one below is a lot better:

(def *model* {:users [:name :email :registered-on]
              :post [:title :author]})

However it is more complex in terms of writing a macro so I will leave it as is.

like image 956
toofarsideways Avatar asked May 08 '11 01:05

toofarsideways


People also ask

What does do in Clojure?

( do expr*)Evaluates the expressions exprs in order and returns the value of the last. If no expressions are supplied, returns nil .


1 Answers

I think a macro is required, because defentity seems to define a type.

(defmacro gen-entities
  [model]
  `(do
     ~@(for [[entity-kw values] model]
         (let [entity-sym (-> entity-kw name capitalize symbol)
               fields     (map (comp symbol name) (keys (first values)))]
           `(ds/defentity ~entity-sym [~@fields])))))

You don't have to fiddle the keys and values from each other, just to put them together again with interleave. Mapping over a map will give you the key and the corresponding value in one go.

user=> (macroexpand-1 `(gen-entities ~model))
(do
  (ds/defentity Users [name registered-on email])
  (ds/defentity Post [title authour]))

Note: this won't work with the model stored in a Var. You'll have to specify the model in the call to gen-entities.

user=> (macroexpand-1 '(gen-entities model))
(
#<IllegalArgumentException java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol>
like image 74
kotarak Avatar answered Nov 15 '22 06:11

kotarak