Trying to understand nesting quotes

I am just learning macros (just trying some experiements after working through the exercises in Chris McCord’s book on metaprogramming).

I was inspired by a recent work example where we have these extremely formulaic Ecto query functions in all of our contexts. I started to experiment with a simple macro for creating a multi-clause function when given a key and that works fine. I then extended it using the basic approach outlined in Kernel.SpecialForms — Elixir v1.16.0 section (as was pointed out in some other macro questions).

That works, and is this version:

defmacro order_by_fields(fields_with_names) do
    quote bind_quoted: [fields_with_names: fields_with_names] do
      import Ecto.Query, only: [order_by: 3]

      Enum.each(fields_with_names, fn {field_name, function_name} ->
        def unquote(function_name)(query, direction) do
          order_by(query, [schema], [{^direction, field(schema, unquote(field_name))}])
        end

        def unquote(function_name)(query, binding, direction) do
          order_by(query, [{^binding, schema}], [
            {^direction, field(schema, unquote(field_name))}
          ])
        end
      end)
    end
  end

My final version I wanted to upgrade to was to allow for introspection of the schema to create the list of fields to generate these functions for. For example, so the interface could be something like order_by_fields(exclude: [:email, :inserted_at]).

To demonstrate what I mean, I layered in this extra section that attempts to introspect the caller’s schema definition (as a starting point before working in my opts). I am able to verify that I have access to the expected list (the computed fields_with_names) in the outer quote block as follows, but the entire innermost quote block does not run anymore:

defmacro order_by_fields(_opts) do
    quote do
      excluded_fields = ~w/
    __struct__
    __meta__
    __schema__
    __changeset__
    /a

      fields_with_names =
        __MODULE__
        |> Macro.struct!(__ENV__)
        |> Map.keys()
        |> Enum.reject(&(&1 in excluded_fields))
        |> Enum.map(&{&1, String.to_atom("order_by_#{&1}")})
        |> IO.inspect(label: "fields_with_names (called and verified content is as expected)")

      quote bind_quoted: [fields_with_names: fields_with_names] do
        import Ecto.Query, only: [order_by: 3]

        IO.inspect(fields_with_names, label: "Outside Enum.each (not called)")

        Enum.each(fields_with_names, fn {field_name, function_name} ->
          IO.inspect(fields_with_names, label: "Inside Enum.each (not called)")

          def unquote(function_name)(query, direction) do
            order_by(query, [schema], [{^direction, field(schema, unquote(field_name))}])
          end

          def unquote(function_name)(query, binding, direction) do
            order_by(query, [{^binding, schema}], [
              {^direction, field(schema, unquote(field_name))}
            ])
          end
        end)
      end
    end
  end

I’m clearly missing something about how to properly nest quote blocks. Any assistance would be greatly appreciated.

I discovered a solution but it doesn’t help me understand what was wrong with the prior version.

defmacro do_order_by_fields(fields_with_names) do
    quote bind_quoted: [fields_with_names: fields_with_names] do
      import Ecto.Query, only: [order_by: 3]

      Enum.each(fields_with_names, fn {field_name, function_name} ->
        IO.inspect(fields_with_names)

        def unquote(function_name)(query, direction) do
          order_by(query, [schema], [{^direction, field(schema, unquote(field_name))}])
        end

        def unquote(function_name)(query, binding, direction) do
          order_by(query, [{^binding, schema}], [
            {^direction, field(schema, unquote(field_name))}
          ])
        end
      end)
    end
  end

  defmacro order_by_fields(_opts) do
    quote do
      import unquote(__MODULE__)

      excluded_fields = ~w/
    __struct__
    __meta__
    __schema__
    __changeset__
    /a

      fields_with_names =
        __MODULE__
        |> Macro.struct!(__ENV__)
        |> Map.keys()
        |> Enum.reject(&(&1 in excluded_fields))
        |> Enum.map(&{&1, String.to_atom("order_by_#{&1}")})
        |> IO.inspect(label: "fields_with_names (called and verified content is as expected)")

      do_order_by_fields(fields_with_names)
    end
  end