Gli ORM sono meravigliosi, finché perdono . Tutti lo fanno, alla fine. Ecto è giovane (ad esempio, ha solo acquisito la capacità di OR
dove clausole insieme 30 giorni fa
), quindi semplicemente non è abbastanza maturo per aver sviluppato un'API che consideri le rotazioni SQL avanzate.
Sondando le possibili opzioni, non sei solo nella richiesta. L'incapacità di comprendere gli elenchi in frammenti (se come parte di order_by
o where
o in qualsiasi altro luogo) è stato menzionato in Ecto numero 1485
, su StackOverflow
, nel Forum Elixir
e questo articolo del blog
. Quest'ultimo è particolarmente istruttivo. Ne parleremo tra un po'. Per prima cosa, proviamo alcuni esperimenti.
Esperimento n. 1: Si potrebbe prima provare a utilizzare Kernel.apply/3
per passare l'elenco a fragment
, ma non funzionerà:
|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))
Esperimento n. 2: Quindi forse possiamo costruirlo con la manipolazione delle stringhe. Che ne dici di dare fragment
una stringa creata in fase di esecuzione con un numero sufficiente di segnaposto da poter estrarre dall'elenco:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))
Quale produrrebbe FIELD(id,?,?,?)
dato ids = [1, 2, 3]
. No, neanche questo funziona.
Esperimento n. 3: Creazione dell'intero SQL finale creato dagli ID, inserendo i valori ID grezzi direttamente nella stringa composta. Oltre ad essere orribile, non funziona neanche:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))
Esperimento n. 4: Questo mi porta a quel post sul blog che ho menzionato. In esso, l'autore aggira la mancanza di or_where
utilizzando un insieme di macro predefinite in base al numero di condizioni da mettere insieme:
defp orderby_fragment(query, [v1]) do
from u in query, order_by: fragment("FIELD(id,?)", ^v1)
end
defp orderby_fragment(query, [v1,v2]) do
from u in query, order_by: fragment("FIELD(id,?,?)", ^v1, ^v2)
end
defp orderby_fragment(query, [v1,v2,v3]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3)
end
defp orderby_fragment(query, [v1,v2,v3,v4]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3, ^v4)
end
Sebbene funzioni e utilizzi l'ORM "con il grano", per così dire, richiede che tu abbia un numero finito e gestibile di campi disponibili. Questo potrebbe essere o meno un punto di svolta.
La mia raccomandazione:non provare a destreggiarti tra le perdite di un ORM. Conosci la domanda migliore. Se l'ORM non lo accetta, scrivilo direttamente con SQL non elaborato e documenta il motivo per cui l'ORM non funziona. Proteggilo dietro una funzione o un modulo in modo da poterti riservare il diritto futuro di modificarne l'implementazione. Un giorno, quando l'ORM raggiunge il ritardo, puoi semplicemente riscriverlo bene senza effetti sul resto del sistema.