2017-05-21 1 views
0

Disons que j'ai un module pour l'utilisateur et il a 2 fonctions qui acceptent un changeset et ajouter des modifications, comme celui-ci ->La réduction de plusieurs chainable update_all appels en une seule instruction UPDATE

defmodule MyApp.User do 

    def confirm(changeset_or_struct) do 
    changeset_or_struct 
    |> Ecto.Changeset.change(confirmed: true, confirmed_at: Timex.now()) 
    end 

    def update_session(changeset_or_struct, ip_address) do 
    changeset_or_struct 
    |> Ecto.Changeset.change(session_token: "token", ip_address: ip_address) 
    end 
end 

Alors si je dois appliquer ces deux changements à un utilisateur spécifique et de les enregistrer, je peux facilement enchaîner les appels de fonction comme celle-ci

some_user 
|> User.confirm() 
|> User.update_session("ip_address") 
|> Repo.update!() 

il est tout bon. Maintenant, disons que pour une raison quelconque, j'ai besoin de confirmer et de mettre à jour la session pour un grand nombre d'utilisateurs en même temps d'une manière atomique. Évidemment, obtenir tous ces utilisateurs, les verrouiller et ensuite les mettre à jour un par un dans une transaction n'est pas une très bonne option, donc nous devons utiliser la fonction update_all pour envoyer une seule instruction UPDATE.

donc ce que nous avons besoin est ce ->

User 
|> Repo.update_all([set: confirmed: true, confirmed_at: Timex.now(), session_token: "token", ip_address: "ip_address"]) 

Mais appeler ce code à l'extérieur du module User ne semble pas être une bonne idée parce que je veux que le module User de savoir comment gérer la confirmation et mises à jour de sessions. Donc la prochaine décision évidente est de créer une fonction dans le module User et de mettre ce code là mais le problème est qu'il n'est pas flexible car ces deux actions (confirmation et mise à jour de session) sont couplées maintenant. Mais nous pouvons tout casser en 2 fonctions

def confirm_many(query) do 
    query 
    |> Repo.update_all([set: confirmed: true, confirmed_at: Timex.now()]) 
end 


def update_session_many(query, ip_address) do 
    query 
    |> Repo.update_all([set: session_token: "token", ip_address: ip_address]) 
end 

Et même si maintenant ils ne sont pas accouplées maintenant, mais ni ils sont chainable si bien que je peux utiliser ces deux séparément, très probablement, je vais finir avec un morceau laid de code et un tas de UPDATE déclarations quand il pourrait facilement être réduit à un seul. Alors maintenant la question:

Comment ferais-je cela? Existe-t-il un moyen d'enchaîner un tas de mises à jour indépendantes qui pourraient éventuellement être réduites à un seul appel update_all?

Ainsi, la ligne de fond est que je voudrais être en mesure de faire quelque chose comme ceci:

User 
|> where([u], u.id in bunch_of_ids) 
|> User.confirm_many() 
|> User.update_session_many("ip_address") 
|> Repo.update_all() 

Répondre

1

Vous pouvez utiliser Ecto.Query.update pour construire progressivement une requête de mise à jour, et ne passer à Repo.update_all à la fin:

defmodule MyApp.User do 
    ... 
    def confirm_many(query) do 
    query 
    |> update([set: [confirmed: true, confirmed_at: ^Ecto.DateTime.utc()]]) 
    end 


    def update_session_many(query, ip_address) do 
    query 
    |> update([set: [session_token: "token", ip_address: ^ip_address]]) 
    end 
end 

Avec cela, le code suivant:

import Ecto.Query 
alias MyApp.{Repo, User} 

User 
|> where([u], u.id in [1, 2, 3]) 
|> User.confirm_many() 
|> User.update_session_many("ip_address") 
|> Repo.update_all([]) 

exécute la requête:

UPDATE "users" AS u0 
    SET "confirmed" = TRUE, 
     "confirmed_at" = $1, 
     "session_token" = 'token', 
     "ip_address" = $2 
    WHERE (u0."id" IN (1,2,3)) 
[{{2017, 5, 21}, {11, 13, 43, 0}}, "ip_address"]