Les ORM sont merveilleux, jusqu'à ce qu'ils fuient . Tous le font, finalement. Ecto est jeune (c'est-à-dire qu'il a seulement acquis la capacité de OR
clauses where ensemble il y a 30 jours
), il n'est donc tout simplement pas assez mature pour avoir développé une API prenant en compte les fluctuations SQL avancées.
En examinant les options possibles, vous n'êtes pas seul dans la demande. L'incapacité à comprendre les listes en fragments (que ce soit dans le cadre de order_by
ou where
ou ailleurs) a été mentionné dans Ecto issue #1485
, sur StackOverflow
, sur le Forum Elixir
et ceci article de blog
. Ce dernier est particulièrement instructif. Plus à ce sujet dans un instant. Essayons d'abord quelques expériences.
Expérience 1 : On pourrait d'abord essayer d'utiliser Kernel.apply/3
pour passer la liste à fragment
, mais cela ne fonctionnera pas :
|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))
Expérience 2 : Ensuite, nous pouvons peut-être le construire avec la manipulation de chaînes. Que diriez-vous de donner fragment
une chaîne construite au moment de l'exécution avec suffisamment d'espaces réservés pour qu'elle puisse être extraite de la liste :
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))
Ce qui produirait FIELD(id,?,?,?)
donné ids = [1, 2, 3]
. Non, ça ne marche pas non plus.
Expérience 3 : Création de l'intégralité du SQL final construit à partir des identifiants, en plaçant les valeurs d'ID brutes directement dans la chaîne composée. En plus d'être horrible, ça ne marche pas non plus :
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))
Expérience n° 4 : Cela m'amène à cet article de blog que j'ai mentionné. Dans celui-ci, l'auteur contourne le manque de or_where
en utilisant un ensemble de macros prédéfinies en fonction du nombre de conditions à rassembler :
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
Bien que cela fonctionne et utilise l'ORM "avec le grain" pour ainsi dire, cela nécessite que vous disposiez d'un nombre fini et gérable de champs disponibles. Cela peut ou non changer la donne.
Ma recommandation :n'essayez pas de jongler avec les fuites d'un ORM. Vous connaissez la meilleure requête. Si l'ORM ne l'accepte pas, écrivez-le directement avec du SQL brut et expliquez pourquoi l'ORM ne fonctionne pas. Protégez-le derrière une fonction ou un module afin de pouvoir vous réserver le droit futur de modifier son implémentation. Un jour, lorsque l'ORM rattrape son retard, vous pouvez simplement le réécrire correctement sans aucun effet sur le reste du système.