Javier Levio
Javier Levio

Reputation: 111

Should I use require or import while using Ecto?

I'm relatively new to the language, and have already read the differences between the require and import keywords. Nevertheless I can't seem to understand the following behavior while using Ecto.

So, I want to extract a fragment that's commonly used to get a value from a json field. Reading the Ecto docs I understand that in order to extract a fragment I have to wrap it on a dynamic macro. So I'm trying to implement it like this:

defmodule Foo do
  require Ecto.Query

  ...

  defp foo_fragment() do
    dynamic([t], fragment("?.json->>'foo'", t))
  end
end

To which I get the following warning

variable "t" does not exist and is being expanded to "t()", please use parentheses to remove the ambiguity or change the variable name

Which seems reasonable enough, but isn't mentioned on the Ecto documentation. Looking for answers I found this SO question where is recommended to use import Ecto.Query. I also noticed that it's used that way on the Ecto docs examples (probably for aesthetic reasons though).

To my surprise upon trying it, the warning on the previous code it's gone.

Is there something I'm missing regarding these keywords? I thought both were equivalent regarding the usage of macros, require being a more conservative approach as to keeping namespaces apart. Thanks!

Upvotes: 2

Views: 384

Answers (2)

sabiwara
sabiwara

Reputation: 3214

While import is probably the most standard way to work with Ecto.Query, you can perfectly use require as well if you prefer to avoid bringing all its macros and functions into your module namespace.

All the following are equivalent and work without any warning:

defmodule FragmentImport do
  import Ecto.Query

  def foo_fragment() do
    dynamic([t], fragment("?.json->>'foo'", t))
  end
end

With require, dynamic/2 will be undefined so you need to use the qualified name Ecto.Query.dynamic/2:

defmodule FragmentRequire do
  require Ecto.Query

  def foo_fragment() do
    Ecto.Query.dynamic([t], fragment("?.json->>'foo'", t))
  end
end

If it feels too verbose, you can use the :as option to alias the module as an intermediary solutions:

defmodule FragmentRequireAs do
  require Ecto.Query, as: Query

  def foo_fragment() do
    Query.dynamic([t], fragment("?.json->>'foo'", t))
  end
end

You can pick whichever style you prefer: require can help removing the ambiguity to know where a macro/function comes from, but when working with Ecto people are usually familiar with it enough so there is little ambiguity in practice.

Upvotes: 5

Everett
Everett

Reputation: 9628

To answer the question in the title of your post, normally you only need to specify import Ecto.Query at the top of a module that makes use of the Ecto queries.

To clarify the info in the body of your post: the error you are getting has nothing directly to do with Ecto or the differences between include and require: the error is telling you that the t variable is not set, so the compiler is instead treating it as a function name (remember that parentheses are optional in Elixir, so treating it like t() is a reasonable attempt at resolving it to a usable value).

Usually when I use fragment, the variable I am using is passed into the function, e.g.:

defmodule Foo do

  import Ecto.Query

  def get(name) do
    from u in User,
      where: fragment("lower(username) = ?", ^String.downcase(name))
  end

end

Upvotes: 2

Related Questions