How do I use a module defined in lib/ folder of Phoenix project?

I’ve created a Module inside the lib/, more specifically lib/my_namespace/test_module.exs.

This is all that is defined within it:

defmodule MyNamespace.TestModule do
  def test do
    "This is a test"
  end
end

Calling the test() function of this module within a Phoenix Controller renders an error.

** (UndefinedFunctionError) function MyNamespace.TestModule.test/0 is undefined (module MyNamespace.TestModule is not available)
    MyNamespace.TestModule.test()

According to the Elixir 1.2.0 Changelog, it is my understanding that Elixir is meant to reload the code in lib/ directory, so my assumption is that I wasn’t going to need to do anything else.

I’m obviously wrong, and my own research hasn’t been yielding anything promising. The only thing I’ve gathered is that my module isn’t getting onto the ?loadpath? and I’m not to sure what to change so it is on the loadpath.

Could someone lend a hand and also point me in the direction of what documentation I should be reading?

Thanks in advance.

I believe it should be .ex instead of .exs for the extension.

2 Likes

Thanks bobby. That worked. Do you know where I can read about this convention in any documentation? If so, could you share it with me? Thanks again!

I think reload means the compiled sources. For sources with the ending .exs the bytecode will not be written to disk (see Scripted Mode).

1 Like

@Marcus hit the mark with the Elixir official guides. I think going through that would serve as a great introduction for the language and its conventions, so if you have the time, you might want to read that :slight_smile:

Just to be clear on this matter, you almost always want to use .ex files when working on a project. lib would usually contain only .ex, as well as web if you use Phoenix (well, aside from .eex files for templates and files in /static).

You use .exs for test files, seed scripts, migration files, basically anything that’s one-off and wouldn’t need to be compiled as beam files. Think ‘s’ in ‘exs’ as ‘script’, therefore you use .exs files for scripting.

2 Likes

Thanks for the feedback everyone, and for the pointers to the documentation. I’ve been learning from the Programming Elixir book by Dave Thomas (from Pragmatic Bookshelf). I seem to be misunderstanding the interpretation of the file extensions.

Here’s what Dave Thomas says:

“Once you tire of writing one-line programs in iex, you’ll want to start putting code into source files. These files will typically have the extension .ex or .exs. This is a convention—files ending in .ex are intended to be compiled into bytecodes and then run, whereas those ending in .exs are more like programs in scripting languages—they are effectively interpreted at the source level. ”

Excerpt From: Dave Thomas. “Programming Elixir 1.3.”

And here’s what the official documentation says:

In addition to the Elixir file extension .ex, Elixir also supports .exs files for scripting. Elixir treats both files exactly the same way, the only difference is in intention. .ex files are meant to be compiled while .exs files are used for scripting. When executed, both extensions compile and load their modules into memory, although only .ex files write their bytecode to disk in the format of .beam files.

I misunderstood these things to mean: use .exs if your code is going to be chopped and changed frequently and .ex for code like that in libraries which don’t need to recompiled as often. Given this, and that I’m chopping and changing these files frequently, going for a scripting modality seemed to make more sense to me than compilation, but that’s obviously wrong.

I’ll spend more time reading the documentation to make sure I understand this correctly. The way the documentation and Programming Elixir have explained these file extensions aren’t making it that obvious to me at this stage.

I guess the question I’m really asking is: Why does it have to be bytecode and not a script?

In any case, thanks for the help!

I think there might be some mis-understanding going on here. Bobby is correct in that test files should have the extension .exs, but this is not a test file. So, the file extension should indeed be .ex.

You should simply be able to create your new module under lib and it will be automatically available.

If this isn’t happening, check that elixirc_paths in your Phoenix app project configuration is being pointed at the lib directory.

Here is an un-altered, generated Phoenix mix.exs. Take note of the elixirc_paths option in the project() function. This essentially tells Phoenix to load up your lib and web folders. If this is missing, or has been tampered with, that could explain your problem.

defmodule LibModule.Mixfile do
  use Mix.Project

  def project do
    [app: :lib_module,
     version: "0.0.1",
     elixir: "~> 1.2",
     elixirc_paths: elixirc_paths(Mix.env),
     compilers: [:phoenix, :gettext] ++ Mix.compilers,
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     aliases: aliases(),
     deps: deps()]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [mod: {LibModule, []},
     applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :postgrex]]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
  defp elixirc_paths(_),     do: ["lib", "web"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [{:phoenix, "~> 1.2.0"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"}]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to create, migrate and run the seeds file at once:
  #
  #     $ mix ecto.setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
     "ecto.reset": ["ecto.drop", "ecto.setup"],
     "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
  end
end
2 Likes