How to access attributes dynamically?

I’m using this module wormwood for absinthe testing. I want to access some attributes dynamiclly.

  defmacro query_gql(options \\ []) do
    quote do
      __MODULE__.__info__(:attributes) |> IO.inspect(label: "info")

      # Module.get_attribute(__MODULE__, :_wormwood_gql_query) |> IO.inspect(label: "attr")
      unquote(__CALLER__.module).__info__(:attributes) |> IO.inspect(label: "info")
      # Code.fetch_docs(__MODULE__) |> IO.inspect(label: "doc")

      if is_nil(@_wormwood_gql_query) do
        raise WormwoodSetupError, reason: :missing_declaration
      end

      Absinthe.run(
        @_wormwood_gql_query,
        @_wormwood_gql_schema,
        unquote(options)
      )
    end
  end

when I use Module.get_attribute(__MODULE__, :_wormwood_gql_query) |> IO.inspect(label: "attr")

it returns

** (ArgumentError) could not call Module.get_attribute/2 because the module AwBackendWeb.Schema.Mutation.Project.ProjectTest is already compiled. Use the Module.__info__/1 callback or Code.fetch_docs/1 instead

when I use unquote(__CALLER__.module).__info__(:attributes) |> IO.inspect(label: "info")

It returns

info: [vsn: [82393259405647694861055231657107459217]]

The list not contains @_wormwood_gql_query attribute.

when I use Code.fetch_docs(__MODULE__) |> IO.inspect(label: "doc"), it returns

{:error, :module_not_found}

So, how do I access the attributes dynamically?

Only attributes that are declared with persist: true will be available after the module is compiled:

iex(1)> defmodule Foo do
  @not_persisted "not persisted"

  Module.register_attribute(__MODULE__, :is_persisted, persist: true)
  @is_persisted "is persisted"
end
warning: module attribute @not_persisted was set but never used
  iex:2

iex(2)> Foo.__info__(:attributes)                       
[vsn: [245159702118266867653827558832398806603], is_persisted: ["is persisted"]]

Note that the compiler complains about @not_persisted never being read, since it can’t be accessed post-compilation. There’s no corresponding warning for @is_persisted.

Another simple way to capture the value of a compile-time-only module attribute is to declare a function that returns it:

defmodule Foo do                                                   
  @not_persisted "not persisted"                                     

  Module.register_attribute(__MODULE__, :is_persisted, persist: true)
  @is_persisted "is persisted"                                       

  def not_persisted() do
    @not_persisted
  end
end

iex(4)> Foo.__info__(:attributes)                                          
[vsn: [234502421792692061815367290682785245360], is_persisted: ["is persisted"]]

iex(5)> Foo.not_persisted()
"not persisted"
3 Likes