abitdodgy

abitdodgy

How to inject macro functions at end of module

I’m using the __using__ macro to define some boilerplate code. One of the functions defined in the macro is a different arity of a function defined in the module that uses the macro. Here’s an example:

defmodule Obramax.ChannelsFactory do
  use Obramax.FactoryUtils
  alias Obramax.Channels.Channel

  def build(:channel) do
    %Channel{}
    |> struct(attrs_for(:channel))
  end
end

defmodule Obramax.FactoryUtils do
  defmacro __using__(_) do
    quote do
      def build(factory_name, attributes \\ []) do
        factory_name
        |> build()
        |> struct(attributes)
      end
    end
  end
end

I can’t get this to work because the macro will inject build/2 into ChannelsFactory before it has defined build/1, this throwing a CompileError: ** (CompileError) test/support/factories/channels_factory.ex:14: def build/1 conflicts with defaults from build/2

If I move use FactoryUtils to the bottom of ChannelsFactory I can get it to work, but that seems strange and might the code ambiguous.

Are there other solutions?

Marked As Solved

abitdodgy

abitdodgy

Thanks for the tip. I saw the compilation hooks. I changed FactoryUtils to use @before_compile.

defmodule Obramax.FactoryUtils do
  defmacro __using__(_) do
    quote do
      @before_compile Obramax.FactoryUtils
    end
  end

  defmacro __before_compile__(_env) do
    quote do
      def build(factory_name, attributes \\ []) do
        factory_name
        |> build()
        |> struct(attributes)
      end
    end
  end
end

And I didn’t have to change anything in ChannelsFactory.

defmodule Obramax.ChannelsFactory do
  use Obramax.FactoryUtils
  alias Obramax.Channels.Channel

  def build(:channel) do
    %Channel{}
    |> struct(attrs_for(:channel))
  end
end

Not sure if this is the best way, but it works.

Also Liked

Eiji

Eiji

I believe that you should add also @behaviour attribute into __using__ macro, so code would look cleaner. For now somebody reading __before_compile__ would not know from where build/1 comes and with behaviour you could require it.

Eiji

Eiji

The problem is that you are already defining build/1 without noticing that! :077:

Look that when you write such code in iex:

defmodule Example do
  def sample(first_arg, second_arg \\ nil), do: {first_arg, second_arg}
end

and after it you wrote Example. + hit <Tab> (for printing suggestions) then you would see:

> Example.sample
sample/1    sample/2

Which means you have defined sample/1 as well as sample/2 by just one function definition (using default argument). It’s working like:

defmodule Example do
  def sample(first_arg), do: sample(first_arg, nil)
  def sample(first_arg, second_arg), do: {first_arg, second_arg}
end

So your problem is build/1 function definition inside __before_compile__ - it’s why compiler does not show you any warning. Notice that when you remove default value you should see:

warning: function build/1 required by behaviour Obramax.Factory is not implemented (in module Obramax.ChannelsFactory)

Eiji

Eiji

Yeah or … just rename one function. :smiley:

For example in __before_compile__ you can have full_build/1 and full_build/2 and in behaviour you would have only build/1.

Also notice that build/1 from __before_compile__ conflicts with build/1 from behaviour, so in fact one of them (if I understand it correctly build/1 from behaviour) is never used.

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41989 114
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39467 209
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement