How to create macro to implement get function of Ecto?

Yes, it’s because __CALLER__ is not yet compiled. You can create a MyApp.Queries module and pass MyApp.MySchema as second argument instead.

I’m writing it from memory, so I could make a mistake, but it should work:

defmodule Example do
  defmacro defquery(type, module_ast) do
    module = Macro.expand(module_ast, __ENV__)
    primary_keys = module.__schema__(:primary_key)
    module_snake_case = module |> Module.split() |> List.last() |> Macro.underscore()
    call_args = Enum.map(primary_keys, &Macro.var(&1, module))
    func_name = :"#{type}_#{module_snake_case}"
    repo_args = Enum.map(call_args, &{elem(&1, 0), &1})
    repo_func = get_repo_func(type)

    quote do
      def unquote(func_name)(unquote_splicing(call_args)) do
        apply(MyApp.Repo, unquote(repo_func), [unquote(module), unquote(repo_args)])
      end
    end
  end

  defp get_repo_func(:get), do: :get_by
end

defmodule MyApp.Queries do
  require Example

  Example.defquery(:get, MyApp.MySchema)
end

MyApp.Queries.get_my_schema(…)

This is fully generic macro. Feel free to edit it as you wish. :slight_smile:

It’s also possible to change this code to work inside MyApp.MySchema, but that’s not recommend as such modules are designed to handle different things (field definitions, changesets etc.) and business logic should be inside context modules. That was already well described on forum.

For me it looks like that author want to have instead: Schema.get(pk1, pk2). For me it does not looks that bad, because in Repo.get_by/2 you don’t know which keys are primary (h helper in iex and on documentation level). Here you could see them in generated documentation.

The only problem is that author probably want to generate such functions inside modules with schema definition. Instead I proposed example with same functions, but in context module.

In order words instead of:

MyApp.Repo.get_by(MyApp.Comment, [id: comment_id])
MyApp.Repo.get_by(MyApp.PostComment, [comment_id: comment_id, post_id: post_id])
MyApp.Repo.get_by(MyApp.Post, [id: post_id])

author wants:

MyApp.Comment.get(comment_id)
MyApp.Post.get(post_id)
MyApp.PostComment.get(comment_id, post_id)

and my macro allows to call like:

MyApp.Queries.get_comment(comment_id)
MyApp.Queries.get_post(post_id)
MyApp.Queries.get_post_comment(comment_id, post_id)