defmodule Defrag do @moduledoc """ Provides a convenient macro for creating Ecto fragments with interpolation syntax. This module offers the `defrag/1` macro that transforms string interpolation syntax into Ecto fragment queries, making them more readable and maintainable. """ @pattern ~r/\#\{([^\}]+)\}/ @doc """ Converts string interpolation syntax into an Ecto fragment. This macro allows you to write Ecto fragments using familiar string interpolation syntax (`\#{...}`). It automatically converts the interpolation into proper Ecto fragment parameters. ## Examples import Defrag # Instead of writing: fragment("date_part('year', ?)", some_date) # You can write: defrag("date_part('year', \#{some_date})") # Multiple interpolations are supported: defrag("BETWEEN \#{start_date} AND \#{end_date}") # Becomes: fragment("BETWEEN ? AND ?", start_date, end_date) ## Usage in Ecto Queries import Ecto.Query import Defrag def users_born_in_year(query, year) do from u in query, where: defrag("date_part('year', \#{u.birth_date}) = \#{^year}") end ## Notes - The macro converts each interpolation (`\#{...}`) into a parameter placeholder (`?`) - Variables used in interpolation must be in scope where the macro is called - The resulting fragment is safe against SQL injection as it uses Ecto's parameterization """ defmacro defrag(ast) do string = Macro.to_string(ast) fragment_string = Regex.replace(@pattern, string, "?") vars = Enum.map(Regex.scan(@pattern, string), fn [_, var] -> var end) Code.string_to_quoted!("fragment(#{Enum.join([fragment_string | vars], ", ")})") end end