Pattern match structure field

I’d like to implement a generic helper method that would work this way:

      def is_active(query \\ __MODULE__)
      def is_active(query) when :is_active in __MODULE__.__schema__(:fields) do
        where([q], q.is_active == true)
      end
      def is_active(query), do: query

But it doesn’t work as I can’t pattern match __MODULE__.__schema__. :frowning: Any solution to write this?

%__MODULE__{}.__schema__(:fields)

UPDATE: I am completely wrong, I need a :coffee:

1 Like

No, not really, as you cannot call remote functions at compile time for module you are compiling. However why would you define something like that? I assume that this is generated by macro. Maybe DB views would be something more readable (depending on the context).

Do you have to use pattern matching here? Why not just go with a simple if?

def is_active(query \\ __MODULE__) do
  if :is_active in __MODULE__.__schema__(:fields) do
    where(query, [q], q.is_active)
 else
    query
  end
end

You can not call remote functions in guards, as @hauleth wrote, because it happens in compile time. __MODULE__.__schema__/1 is a remote function. Just use cond (or if):

def is_active(query \\ __MODULE__) do
  cond do
    :is_active in __MODULE__.__schema__(:fields) -> where(query, [q], q.is_active == true)
    true -> query    
  end
end

Assuming this function is being defined by a macro and you don’t mind very slightly touching ecto internals, you could branch at compile time instead of runtime. You could check a module attribute directly and conditionally define the function, but your schemas would have to call your macro after defining all the fields. To get around that, I’d use @before_compile in your macro.

# in your main macro
@before_compile MyApp.IsActive

Then define another macro that will get called for the module before it’s compiled.

defmodule MyApp.IsActive do
  def __before_compile__(%{module: module}) do
    if {:is_active, :boolean} in Module.get_attribute(module, :ecto_fields, []) do
      quote do
        def is_active(query) do
          require Ecto.Query # if you aren't importing in main macro
          Ecto.Query.where(query, [q], q.is_active == true)
        end
      end
    else
      quote do
        def is_active(query) do
          query
        end
      end
    end
  end
end