Rodrigo Sasaki
Rodrigo Sasaki

Reputation: 7226

How do I define a join in ecto only if a parameter exists?

I'm trying to create a function that will do some dynamic queries. There is one condition on which I want to add a join with another table, but only if a specific field is present in the opts

def list_fields(opts) do
  query =
    from f in Field,
      select: %{
        id: f.id,
        type: f.type,
        value: f.value
      }

  with query <- by_type(opts[:type], query),
       query <- by_status(opts[:status], query) do
    Repo.all(query)
  end
end

and I have these functions to filter down the selection:

defp by_type(nil, query), do: query

defp by_type(type, query) do
  from [f] in query, where: f.type == ^type
end

defp by_status(nil, query), do: query

defp by_status(status, query) do
  from [f, d] in query, where: d.status == ^status
end

However the status is stored on a different table, and I don't want to add a join to it unless I have to to avoid duplication of the results.

The query with the join would look like this:

query =
  from f in Field,
  left_join: d in Data, on: d.field_id = f.id
  select: %{
    ...

How do I add this left_join only when the :status is present in opts?

Upvotes: 0

Views: 742

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 120990

Use the monadic magic of Kernel.SpecialForms.with/1.

{_, query} =
  with {status, query} when not is_nil(status) <-
          {opts[:status], by_type(opts[:type], query)},
    do: {:ok, by_status(opts[:status], query)}
Repo.all(query)

The trick here is if opts[:status] is nil, the first clause will return without going through do block and hence by_status.

Upvotes: 1

Related Questions