JMurphyWeb
JMurphyWeb

Reputation: 382

Elixir: How to alias a module that just imports a load of functions

I want to put my Accounts context functions into their own files, then import them into the context so that I can alias the App.Accounts in my controllers and use the functions that were imported into the context.

See below for the rough setup that I am after.

defmodule App.Accounts.UserAPI do
  alias App.Accounts.User

  def get_user!(id), do: User.get!(User, id) end

  defmacro __using__(_) do
    import App.Accounts.UserAPI
end

defmodule App.Accounts do
  alias App.Accounts.UserAPI
  use UserAPI
end

defmodule AppWeb.UserController do
  alias App.Accounts

  IO.inspect Accounts.module_info
  # [
    # module: Ev2.Accounts, 
    # exports: [__info__: 1, module_info: 0, module_info: 1],
    # attributes: [vsn: [234644860605005629180170678994286615550]],
    # compile: [options: [:debug_info], version: '7.0.4',
    # source: '/Users/.../accounts.ex'],
    # native: false,
    # md5: <<176, 134, 244, 210, 70, 244, 89, 41, 130, 7, 134, 109, 55, 131, 27, 254>>
  # ]
  def index(conn, %{"id" => id}) do
    Accounts.get_user(id) # Accounts.get_user/1 is not defined
  end
end

I currently have it working by wrapping the entire App.UserAPI in __using__ macro and quotes, but that feels verbose and wrapping in quote seems to prevent me from calling utility functions from within the UserAPI.

Why do the UserAPI functions not appear in the Accounts.module_info exports? And what is the "right" way to do this?

Thanks!

Upvotes: 0

Views: 716

Answers (1)

Dogbert
Dogbert

Reputation: 222040

The problem is that imported functions are not exported by a module. They're only available in the context of the import. You want to re-export imported functions. There are two ways I can think of.

  1. What you're currently doing according to your description: define all functions in a quote block in __using__:

    defmodule App.Accounts.UserAPI do
      defmacro __using__(_) do
        quote do
          alias App.Accounts.User
    
          def get_user!(id), do: User.get!(User, id) end
        end
      end
    end
    
  2. Use defdelegate to define a function which just calls another function:

    defmodule App.Accounts.UserAPI do
      def get_user!(id), do: User.get!(User, id) end
    
      defmacro __using__(_) do
        quote do
          defdelegate :get_user!(id), to: App.Accounts.UserAPI
        end
      end
    end
    

In both cases, a use App.Accounts.UserAPI will do what you want.

Upvotes: 3

Related Questions