Problem with dynamically creating a module using defmodule

Please help me figure this out.

I have a function to help me create a module dynamically:

defmodule MyApp.ECS do
  def dynamic_store(repo) do
    {:module, module, _, _} =
      defmodule String.to_atom("MyApp.MyStore.#{repo}") do
        @behaviour Elasticsearch.Store

        import Ecto.Query

        alias MyApp.DynamicRepo

        @impl true
        def stream(schema) do
          DynamicRepo.stream(repo, schema)
        end

        @impl true
        def transaction(fun) do
          {:ok, result} =
            DynamicRepo.transaction(repo, fun, timeout: :infinity)

          result
        end
      end

    module
  end
end

But I end up with an error, something like undefined function repo/0

So I thought it was a scoping problem & I read this scoping guide: Scoping Guide, specifically this section:

defmodule M do
  a = 1

  # 'a' inside unquote() unambiguously refers to 'a' defined
  # in the module's scope
  def a, do: unquote(a)

  # 'a' inside the body unambiguously refers to the function 'a/0'
  def a(b), do: a + b
end

Following that doesn’t work. I have this:

defmodule MyApp.ECS do
  def dynamic_store(repo) do
    x = "hello"

    defmodule String.to_atom("MyApp.MyStore.#{repo}") do
      def b, do: unquote(x)
    end
  end
end

and I get `variable x is undefined and is being expanded to x().

How can I get the first example to work? So that repo from the function paramter of dynamic_store is part of the module created?

You could use a module attribute:

defmodule MyApp.ECS do
  def dynamic_store(repo) do
    defmodule String.to_atom("MyApp.MyStore.#{repo}") do
      @repo repo

      def get_repo do
        @repo
      end
    end
  end
end

Edit: watch out with naming - if you’re expecting to be able to refer to the module as MyApp.MyStore.Something later, the string passed to String.to_atom needs the Elixir. prefix.

Can you elaborate on the problem that you are trying to solve? Creating modules this way is probably not what you want to do.

2 Likes

Great that works! I’m not sure why a module attribute changes the scoping. Thanks for the tip re: the Elixir. prefex