Currently, I want to organize context functions in separate files as it’s getting cluttered
The below 2 lines are getting repeated in all __USING__ macros - I don’t want to repeat these lines instead declare once in the parent module
import Ecto.Query, warn: false alias Server.Repo
AS-IS (Working)
defmodule Server.Quiz do
use Server.Quiz.Access.Question
use Server.Quiz.Access.Choice
end
defmodule Server.Quiz.Access.Question do
defmacro __using__(_) do
quote do
import Ecto.Query, warn: false
alias Server.Repo
alias Server.Quiz.{Question, Choice}
def list_questions(resource) do
Question
|> where([q], q.tenant_id == ^resource.tenant.id)
|> order_by(desc: :updated_at)
|> Repo.all()
|> Repo.preload([choices: (from c in Choice, order_by: c.seq)])
end
end
end
end
TO-BE
defmodule Server.Quiz do
import Ecto.Query, warn: false
alias Server.Repo
use Server.Quiz.Access.Question
use Server.Quiz.Access.Choice
end
defmodule Server.Quiz.Access.Question do
defmacro __using__(_) do
quote do
alias Server.Quiz.{Question, Choice}
def list_questions(resource) do
Question
|> where([q], q.tenant_id == ^resource.tenant.id)
|> order_by(desc: :updated_at)
|> Repo.all()
|> Repo.preload([choices: (from c in Choice, order_by: c.seq)])
end
end
end
end
I don’t want to repeat below code in each refactor and just want once in module Server.Quiz
Formatting note: your examples would be significantly more readable with code-fences (leading and trailing ```) - especially for things like def __using__(_) that otherwise get interpreted as Markdown
What happens when you try the code listed under TO-BE in your post? I get the same results from putting the import in the using code as in the macro:
defmodule MacroThingy do
defmacro __using__(_) do
quote do
import Ecto.Query, warn: false
def inside_macro() do
from(u in Core.User, where: u.id < 0)
end
end
end
end
defmodule UserThingy do
use MacroThingy
def inside_user() do
from(u in Core.User, where: u.id > 0)
end
end
defmodule MacroThingy2 do
defmacro __using__(_) do
quote do
def inside_macro() do
from(u in Core.User, where: u.id < 0)
end
end
end
end
defmodule UserThingy2 do
import Ecto.Query, warn: false
use MacroThingy2
def inside_user() do
from(u in Core.User, where: u.id > 0)
end
end
iex(26)> UserThingy.inside_user()
#Ecto.Query<from u0 in Core.User, where: u0.id > 0>
iex(27)> UserThingy.inside_macro()
#Ecto.Query<from u0 in Core.User, where: u0.id < 0>
iex(28)> UserThingy2.inside_user()
#Ecto.Query<from u0 in Core.User, where: u0.id > 0>
iex(29)> UserThingy2.inside_macro()
#Ecto.Query<from u0 in Core.User, where: u0.id < 0>
I am getting Repo module not found as in macro I am just using Repo and not Server.Repo as I have mentioned it once in the parent module ( Server.Quiz)
My question is how can we use alias and imports of parent module in child 's __using__
This is a good goal, but I do not recommend doing it the way you are now. Macros essentially copy and paste code, and this makes things like stacktraces a lot worse. If you get an error with order_by for example, it will just point to the line where you do use Server.Quiz.Access.Question, it will not point to the order_by line.
Macros are not a code organizatino tool, they have other purposes. If you want to organize your code, make ordinary functions and defdelegate if you want some subset of those to show up on your root Quiz module. If you have hundreds of these, you probably want to find a way to narrow the scope of your public API.