Problems With App Specific IEx Customization

You can run some custom code when iex starts up by placing code in .iex.exs. I like to use this to add some per application conveniences. For example, if an app uses Ecto, it’s pretty nice to toss in a:

import Ecto.Query, only: [from: 2]

This makes it so you can call from/2 without a module prefix when tooling around in iex -S mix. The downside is that it’s now impossible to launch a bare iex in that directory. (I sometimes forget to add -S mix when I’m quickly trying experimenting with a core Elixir function.) It will toss a compile error when it can’t find Ecto.

This leads me to two questions:

  • Can anyone come up with a viable workaround? I’ve tried a few things that all failed.
  • Does anyone else think iex should just warn and launch without the customizations in this case? I can always create a patch if there’s interest.

One solution is to alias iex --dot-iex "" for bare iex. Passing a blank path to --dot-ex PATH disables loading .iex.exs

1 Like

You need to wrap the code in some check for example:

if Code.ensure_loaded?(Ecto) do
  # your ecto-based code goes here …
end
1 Like

This would help with runtime errors. My example is a compile time issue.

That’s not true. Just import and require are throwing compile time errors. This check standalone is good. Inside if you can call code like Code.require_file/1 which would not raise even if you call iex and don’t have target file.

if Code.ensure_loaded?(MyApp) do
  Code.require_file(".iex.project.exs")
end

# code not strictly related to project goes here …
1 Like

Here’s the error from your first suggestion:

$ cat .iex.exs
if Code.ensure_loaded?(Ecto) do
  import Ecto.Query, only: [from: 2]
end
$ iex
Erlang/OTP 21 [erts-10.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help)
Error while evaluating: /Users/jeg2/Documents/icanmakeitbetter/icmu/.iex.exs
** (CompileError) .iex.exs:2: module Ecto.Query is not loaded and could not be found
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1355: :lists.mapfoldl/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (elixir) expanding macro: Kernel.if/2

Your second suggestion introduces a new scope, so you are no longer modifying the iex shell. Here’s the error from that approach:

$ cat .iex.exs
if Code.ensure_loaded?(Ecto) do
  Code.require_file(".iex.project.exs")
end
$ cat .iex.project.exs
import Ecto.Query, only: [from: 2]
$ iex
Erlang/OTP 21 [erts-10.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
a
$ iex -S mix
Erlang/OTP 21 [erts-10.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help)
warning: unused import Ecto.Query
  .iex.project.exs:1

iex(1)> from(r in Reporter.Response, limit: 1)
** (CompileError) iex:1: undefined function from/2

iex(1)>
1 Like

It’s a total workaround, but in my current system at the root level we have an .iex.exs file something like this (SC is short for “shortcuts”):

defmodule SC do
  defmacro __using__(_) do
    import Ecto, only: [from: 2]
  end
end

So we settle for typing use SC in iex.

You could take it a step further and pass a parameter to use and switch on that inside the macro to do different things for different people, or for different subsections of the app, etc. But then it becomes a bit more cumbersome to type use SC, :db

5 Likes

The solution is to use import_if_available/2. There’s even an use_if_available/2 too!

8 Likes

heh, once again I forgot that iex works a bit differently - my bad :confused:

I though similarly to @gregvaughn’s suggestion (macro under my check), but I though that it (defining macro) was a bit too tricky for such simple case.

The best what I had in mind was to define lib/my_app/iex.ex with its own macro (again similarly to @gregvaughn’s suggestion) which could be simply called only if -S option is set (as any other call i.e. MyApp.IEx.prepare()), but @wojtekmach’s solution wins without any need for further discussion! Nice find!

I did not looked so deeply in IEx application documentation (to be honest I did not even expected such API there) - looks like a perfect time to change that. :smiley:

2 Likes