defmacro content(_opts \\ [], do: block) do
quote location: :keep do
def page_view(var!(conn)) do
import Kernel, except: [div: 2, to_string: 1]
import ExAdmin.ViewHelpers
use Xain
_ = var!(conn)
markup safe: true do
unquote(block)
end
end
end
end
Which is used like that:
content do
# Whatever code
end
How can I pass the conn variable to the contentblock?
defmodule TestXain do
defmacro content(do: block) do
block =
quote do
_ = var!(conn)
import Kernel, except: [div: 2, to_string: 1]
use Xain
markup safe: true do
unquote(block)
end
end
quote location: :keep do
def page_view(var!(conn)), do: unquote(block)
end
end
end
defmodule TestXain.View do
import TestXain
content do
IO.inspect(conn)
end
end
iex(1)> TestXain.View.page_view(1)
1
{:safe, "1"}
This seems to work fine for me and circumvents the whole problem of unquote fragments.
Using var! makes the variable unhygienic, so it bleeds into the inner blocks scope. If you consider the resulting AST you’ll notice, that the inner block just refers to a variable like this {:conn, [], Some.Module}, where the last part is the context of the variable. var! makes it so that the contexts match, while without that they won’t.
The resulting AST doesn’t really care if it came out of a macro or not. With it it looks like you wrote:
def page_view(conn) do
import Kernel, except: [div: 2, to_string: 1]
import ExAdmin.ViewHelpers
use Xain
_ = conn
markup safe: true do
IO.inspect(conn)
end
end
The only thing that makes me slightly uncomfortable is that the variable is named so one needs to read the calling code (or the doc) to know what variables are available but I guess that’s how things go!
You can either decompose it to grab the head as the var, or just use it as a case context with full matching and all, which is also so easy to do in quotes.
I like very much your idea. Can you give an example how can I write a macro like this?
I would like to be able to call it like this:
wrap_with_connection do
channel -> publish_on_existing_channel(channel, @exchange, @outcome_routing_key, outcome_payload)
end
I can not figure out how to write the macro. What I have by now is:
defmodule Example do
def init_rabbitmq_channel do
...
end
def close_rabbitmq_channel(channel) do
....
end
def publish_on_existing_channel(channel, exchange, routing_key, payload) do
.....
end
defmacro wrap_with_connection(do: block) do
quote do
var!(channel) = init_rabbitmq_channel()
unquote(block)
close_rabbitmq_channel(channel)
end
end
end
You’d want to instead do this function as something like this instead:
defmacro wrap_with_connection(do: block) do
quote do
channel = init_rabbitmq_channel() # This is a different channel than what the user will match on
case channel do
unquote(block) # This might need to be `unquote_splice`, I forget, I usually use ast tuples...
end
close_rabbitmq_channel(channel)
end
end