Tuesday, February 7, 2017

My First Macro

I'm writing my first test code to cover my HugSQL.  I'm still on my training wheels, so I can't be more fancier here:

-- :name get-user-type :? :*
-- :doc get user by type
select id, name from users where type = :type order by id


Now comes the mythical test code. Let's suppose "db/add-user!" exists, performs database inserts into the users table and returns 1 as return value.

(deftest test-user
  (jdbc/with-db-transaction [t-conn *db*]
    (jdbc/db-set-rollback-only! t-conn)
    (is (= 1 (db/add-user!
              t-conn
              {:username      "u123"
               :type          "admin"})))
    (is (= {:username      "u123"
            :type          "admin"}
           (db/get-user-type t-conn {:type "admin"}))))))

But alas! This is an epic failure!

(expected: {:username "u123",
            :type "admin"}
  actual: ({:id 1,
            :username "u123",
            :type "admin"})

It appears that ":id" value is an auto-generated primary key and this equality test is not going to work.


Perhaps, I could define a function to remove the id before comparing? This might work as the id is a surrogated key automatically populated by the database.

(defn match-record
  [expected record]
  (= expected (dissoc record :id)))


Or perhaps I could raise the bar higher by writing a macro? After all, I've heard Clojure macro is a powerful language extension.

(defmacro match-record
  [expected record]
  `(= ~expected (dissoc ~record :id)))


This is really exciting - my first ever Clojure macro!


So, rewriting my test code gives me,

(deftest test-user
  (jdbc/with-db-transaction [t-conn *db*]
    (jdbc/db-set-rollback-only! t-conn)
    (is (= 1 (db/add-user!
              t-conn
              {:username      "u123"
               :type          "admin"})))
    (let [result (db/get-user-type t-conn {:type "admin"})]
        (is (match-record {:username   "u123"
                           :type       "admin"}
                          (first result))))))



I know my macro is sub-optimal here and there are better ways of doing this. But hey! I'm certain my dear mother will be proud of me!