Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function query/2 #28

Open
joeljuca opened this issue Dec 15, 2024 · 1 comment
Open

Add function query/2 #28

joeljuca opened this issue Dec 15, 2024 · 1 comment

Comments

@joeljuca
Copy link
Owner

This issue is more of an idea than a well-formatted issue. I wrote a meta-function in one of my projects which would really work as a core SwissSchema fn. Here's now it looks overall:

defmacro __using__(_opts) do
    quote do
      import Ecto.Query, only: [from: 1, select: 3]
      alias MyApp.Repo

      @spec query(f :: (Ecto.Query.t() -> Ecto.Query.t())) ::
              {:ok, list(struct())} | {:error, term()}
      def query(f) do
        query =
          from(s in __MODULE__)
          |> select([s], s)
          |> then(f)

        {:ok, Repo.all(query)}
      rescue
        error -> {:error, error}
      end
    end
end

It's supposed to be used like this: you would pass a function of typespec (Ecto.Query.t() -> Ecto.Query.t()), receive a pre-formatted query (Ecto.Query), and augment it with your own querying criteria:

User.query(fn query ->
  query
  
  # Apply your own WHERE criteria
  |> where([u], u.username == "joeljuca")
  
  # Join the user table with other tables
  |> join(:left, [u], c in Comment, on: c.user_id == u.id)
  
  # Order, limit, etc.
  |> order_by([u], [desc: u.inserted_at])
  |> limit(100)
end)

I think this function needs some mechanism to provide a default function to prepare the query, as well as a way to call query/2 w/o this preparation (smt like User.query(fn q -> ... end, raw: true)? I'm not sure yet).

The main thing here is: I want to avoid creating very specific querying functions (eg.: get_user_by_this/1, get_user_by_that/1, get_users_by_smt_else/2`, etc.), and I'd like to avoid creating these complex functions with a huge map with params for each situation:

def get_users(%{} = params) do
  query = 
    params
    |> handle_param_A_if_present()
    |> handle_param_B_if_present()
    |> handle_param_C_if_present()
    |> handle_param_D_if_present()
    |> handle_param_E_if_present()
    |> handle_param_F_if_present()
    |> handle_param_G_if_present()
    # (...)
    |> prepare_query()


  with {...} <- query |> Repo.all() do
    # (...)
  end
end

I never loved this function style! The reason? Well, it's pretty much a DSL (Domain-Specific Language) for a single function! You need to remember the syntax of all these params and their specifics – and I never recall which one goes where, and their specific syntax, etc.

Well, Ecto is awesome – and Ecto.Query is an amazing DSL for writing SQL code in Elixir. I'd like to just using wherever and whenever I need. Hence query/2 was born. 😄

@joeljuca
Copy link
Owner Author

Note to myself: remember to support other SwissSchema features (eg.: custom repos).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant