Onorio Catenacci
Onorio Catenacci

Reputation: 15343

Why Aren't These Functions Available?

I know I must be something blindlingly obvious but for the life of me I can't see it. I'm trying to use a module attribute across modules. This is my first module:

defmodule PublishModuleAttributes do
  Module.register_attribute(__MODULE__, :author_name, persist: true)
  Module.register_attribute(__MODULE__, :author_email, persist: true)

  @author_name "Onorio Catenacci"
  @author_email "[email protected]" #Yes it's a fake email
end

and this is the module where I'm consuming the attributes:

defmodule UseModuleAttributes do
  alias PublishModuleAttributes

  defp get_author_name() do
    [name] = PublishModuleAttributes.__info__(:attributes)[:author_name]
    name
  end

  defp get_author_email() do
    [email] = PublishModuleAttributes.__info__(:attributes)[:author_email]
    email
  end    

  @author_name get_author_name()
  @author_email get_author_email()

  def show_results() do
    IO.inspect("Author name is #{@author_name}")
    IO.inspect("Author email is #{author_email}")
  end
end

But when I attempt to compile that second module I keep getting this message:

** (CompileError) lib/UseModuleAttributes.ex:14: undefined function get_author_name/0 (there is no such import)

I've tried changing it to def (as opposed to defp) and it makes no difference. I comment out the first (get_author_name) and then it has the same error on the second. If I comment out the two attribute lines I can get this to build and run under iex (with -S mix) and I can call the UseModuleAttributes.get_author_name call directly and it works as expected. What super-obvious, simple thing have I missed here? I've looked at the documentation for using a function with an attribute and I didn't see anything I've done wrong but I must be missing something.

EDIT:

Just a bit more on this. It definitely seems to be related to the way module attributes are handled by the compiler because this also works:

defmodule UseModuleAttributes do

  defp get_author_name() do
    [name] = PublishModuleAttributes.__info__(:attributes)[:author_name]
    name
  end

  defp get_author_email() do
    [email] = PublishModuleAttributes.__info__(:attributes)[:author_email]
    email
  end

  def show_results() do
    IO.inspect("Author name is #{get_author_name()}")
    IO.inspect("Author email is #{get_author_email()}")
  end
end

Upvotes: 0

Views: 332

Answers (1)

Konstantin Strukov
Konstantin Strukov

Reputation: 3029

Try defining these functions in a separate module and include this module before using them: module attributes must be resolved at compile time, module is a compilation "unit", so even if you define these functions lexically before referring to them in attributes (but within the same module) the compiler will not be able to resolve them.

Something like this might work (disclaimer: I'm not discussing is it good or not - just how to make it work based on your initial code):

defmodule PublishModuleAttributes do
  Module.register_attribute(__MODULE__, :author_name, persist: true)
  Module.register_attribute(__MODULE__, :author_email, persist: true)

  @author_name "Onorio Catenacci"
  @author_email "[email protected]" #Yes it's a fake email
end

defmodule MetaMethods do
  def get_author_name() do
    [name] = PublishModuleAttributes.__info__(:attributes)[:author_name]
    name
  end

  def get_author_email() do
    [email] = PublishModuleAttributes.__info__(:attributes)[:author_email]
    email
  end
end

defmodule UseModuleAttributes do
  import PublishModuleAttributes
  import MetaMethods 

  @author_name get_author_name()
  @author_email get_author_email()

  def show_results() do
    IO.inspect("Author name is #{@author_name}")
    IO.inspect("Author email is #{@author_email}")
  end
end

Upvotes: 1

Related Questions