Hi, I am trying to build a little DSL to query a database. There are several steps.
Step 1. Write macros that yield composable queries, e.g., QP.title("Foo")
and QP.sort("title")
. Thus
Document |> QP.title("Foo") |> QP.title("Bar") |> QP.sort("title") |> Repo.all
finds all documents with “Foo” and “Bar” in the title and presents them in sorted order.
I’ve been able to do this.
Step 2. This is where I am stuck. I’d like to take a pair like ["title", "Magick"]
and turn it into the macro call QP.title("Magick")
, so that, for example, I can do
docs = Document |> QP.command(["title", "Magick"]) |> Repo.all
This fails, although following related code succeeds:
docs = Document |> QP.title("Magick") |> Repo.all
The error message is
** (CompileError) apps/koko/test/koko/doc_manager/qp_test.exs:23: invalid call "QP.title"(Document, "Magick")
(koko) expanding macro: Koko.DocManager.QP.command/2
test/koko/doc_manager/qp_test.exs:23: Koko.DocManagerTest."test command macro"/1
ETC.
Thus it looks like "QP.title"
is not evaluated, despite the fact that I have unquoted
it. Here, finally, is the code for QP.command
:
defmacro command(query, pair) do
[cmd, arg] = pair
quote do
unquote("QP.#{cmd}")(unquote(query), unquote(arg))
end
end
Steps 3 & 4. TBC
Added note (1) Maybe there is some kind of expand_macro
that has to be applied to the string "QP.title"
??
Update Using @marcuslankenau’s suggestion, I’ve modified things a bit:
defmacro command(query, pair) do
[cmd, arg] = pair
cmd = "QP.#{cmd}"
quote do
apply(unquote(cmd), unquote(query), unquote(arg))
end
end
but get this nicer error message
** (ArgumentError) argument error
stacktrace:
:erlang.apply("QP.title", Koko.DocManager.Document, "Magick")
test/koko/doc_manager/qp_test.exs:23: (test)
But "QP.title"
is still unevaluated.
Added Note (2) Well, I am trying to unquote
a string, so that won’t work. Here is another approach which almost works. I have a plain old function by_command
which accepts a list of pairs [cmd, arg]
and dispatches a call to the appropriate macro, QP.query1(commands)
, QP.query2(commands)
, etc., depending on the number of arguments. (Bad form, but it will do for the moment). I’ve tested the QP.query*(commands)
macros, which construct a concatenation of queries based on the list of pairs. See (Code) below. Unfortunately, I get this error when I run a simple test:
** (ArgumentError) argument error
:erlang.hd({:commands, [line: 15], nil})
expanding macro: Koko.DocManager.QP.query1/1
lib/koko/doc_manager/search.ex:15: Koko.DocManager.Search.by_command_list/1
Note that line 15 is indicated below.
Code
def by_command_list(commands) do
n = length commands
case n do
1 ->
QP.query1(commands) # LINE 15
2 ->
QP.query2(commands)
ETC.
end