zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

multi.ex (29374B)


      1 defmodule Ecto.Multi do
      2   @moduledoc """
      3   `Ecto.Multi` is a data structure for grouping multiple Repo operations.
      4 
      5   `Ecto.Multi` makes it possible to pack operations that should be
      6   performed in a single database transaction and gives a way to introspect
      7   the queued operations without actually performing them. Each operation
      8   is given a name that is unique and will identify its result in case of
      9   success or failure.
     10 
     11   If a multi is valid (i.e. all the changesets in it are valid),
     12   all operations will be executed in the order they were added.
     13 
     14   The `Ecto.Multi` structure should be considered opaque. You can use
     15   `%Ecto.Multi{}` to pattern match the type, but accessing fields or
     16   directly modifying them is not advised.
     17 
     18   `Ecto.Multi.to_list/1` returns a canonical representation of the
     19   structure that can be used for introspection.
     20 
     21   ## Changesets
     22 
     23   If multi contains operations that accept changesets (like `insert/4`,
     24   `update/4` or `delete/4`) they will be checked before starting the
     25   transaction. If any changeset has errors, the transaction won't even
     26   be started and the error will be immediately returned.
     27 
     28   Note: `insert/4`, `update/4`, `insert_or_update/4`, and `delete/4`
     29   variants that accept a function are not performing such checks since
     30   the functions are executed after the transaction has started.
     31 
     32   ## Run
     33 
     34   Multi allows you to run arbitrary functions as part of your transaction
     35   via `run/3` and `run/5`. This is especially useful when an operation
     36   depends on the value of a previous operation. For this reason, the
     37   function given as a callback to `run/3` and `run/5` will receive the repo
     38   as the first argument, and all changes performed by the multi so far as a
     39   map for the second argument.
     40 
     41   The function given to `run` must return `{:ok, value}` or `{:error, value}`
     42   as its result. Returning an error will abort any further operations
     43   and make the whole multi fail.
     44 
     45   ## Example
     46 
     47   Let's look at an example definition and usage. The use case we'll be
     48   looking into is resetting a password. We need to update the account
     49   with proper information, log the request and remove all current sessions:
     50 
     51       defmodule PasswordManager do
     52         alias Ecto.Multi
     53 
     54         def reset(account, params) do
     55           Multi.new()
     56           |> Multi.update(:account, Account.password_reset_changeset(account, params))
     57           |> Multi.insert(:log, Log.password_reset_changeset(account, params))
     58           |> Multi.delete_all(:sessions, Ecto.assoc(account, :sessions))
     59         end
     60       end
     61 
     62   We can later execute it in the integration layer using Repo:
     63 
     64       Repo.transaction(PasswordManager.reset(account, params))
     65 
     66   By pattern matching on the result we can differentiate different conditions:
     67 
     68       case result do
     69         {:ok, %{account: account, log: log, sessions: sessions}} ->
     70           # Operation was successful, we can access results (exactly the same
     71           # we would get from running corresponding Repo functions) under keys
     72           # we used for naming the operations.
     73         {:error, failed_operation, failed_value, changes_so_far} ->
     74           # One of the operations failed. We can access the operation's failure
     75           # value (like changeset for operations on changesets) to prepare a
     76           # proper response. We also get access to the results of any operations
     77           # that succeeded before the indicated operation failed. However, any
     78           # successful operations would have been rolled back.
     79       end
     80 
     81   We can also easily unit test our transaction without actually running it.
     82   Since changesets can use in-memory-data, we can use an account that is
     83   constructed in memory as well (without persisting it to the database):
     84 
     85       test "dry run password reset" do
     86         account = %Account{password: "letmein"}
     87         multi = PasswordManager.reset(account, params)
     88 
     89         assert [
     90           {:account, {:update, account_changeset, []}},
     91           {:log, {:insert, log_changeset, []}},
     92           {:sessions, {:delete_all, query, []}}
     93         ] = Ecto.Multi.to_list(multi)
     94 
     95         # We can introspect changesets and query to see if everything
     96         # is as expected, for example:
     97         assert account_changeset.valid?
     98         assert log_changeset.valid?
     99         assert inspect(query) == "#Ecto.Query<from a in Session>"
    100       end
    101 
    102   The name of each operation does not have to be an atom. This can be particularly
    103   useful when you wish to update a collection of changesets at once, and track their
    104   errors individually:
    105 
    106       accounts = [%Account{id: 1}, %Account{id: 2}]
    107 
    108       Enum.reduce(accounts, Multi.new(), fn account, multi ->
    109         Multi.update(
    110           multi,
    111           {:account, account.id},
    112           Account.password_reset_changeset(account, params)
    113         )
    114       end)
    115   """
    116 
    117   alias __MODULE__
    118   alias Ecto.Changeset
    119 
    120   defstruct operations: [], names: MapSet.new()
    121 
    122   @type changes :: map
    123   @type run :: ((Ecto.Repo.t, changes) -> {:ok | :error, any}) | {module, atom, [any]}
    124   @type fun(result) :: (changes -> result)
    125   @type merge :: (changes -> t) | {module, atom, [any]}
    126   @typep schema_or_source :: binary | {binary, module} | module
    127   @typep operation :: {:changeset, Changeset.t, Keyword.t} |
    128                       {:run, run} |
    129                       {:put, any} |
    130                       {:inspect, Keyword.t} |
    131                       {:merge, merge} |
    132                       {:update_all, Ecto.Query.t, Keyword.t} |
    133                       {:delete_all, Ecto.Query.t, Keyword.t} |
    134                       {:insert_all, schema_or_source, [map | Keyword.t], Keyword.t}
    135   @typep operations :: [{name, operation}]
    136   @typep names :: MapSet.t
    137   @type name :: any
    138   @type t :: %__MODULE__{operations: operations, names: names}
    139 
    140   @doc """
    141   Returns an empty `Ecto.Multi` struct.
    142 
    143   ## Example
    144 
    145       iex> Ecto.Multi.new() |> Ecto.Multi.to_list()
    146       []
    147 
    148   """
    149   @spec new :: t
    150   def new do
    151     %Multi{}
    152   end
    153 
    154   @doc """
    155   Appends the second multi to the first one.
    156 
    157   All names must be unique between both structures.
    158 
    159   ## Example
    160 
    161       iex> lhs = Ecto.Multi.new() |> Ecto.Multi.run(:left, fn _, changes -> {:ok, changes} end)
    162       iex> rhs = Ecto.Multi.new() |> Ecto.Multi.run(:right, fn _, changes -> {:error, changes} end)
    163       iex> Ecto.Multi.append(lhs, rhs) |> Ecto.Multi.to_list |> Keyword.keys
    164       [:left, :right]
    165 
    166   """
    167   @spec append(t, t) :: t
    168   def append(lhs, rhs) do
    169     merge_structs(lhs, rhs, &(&2 ++ &1))
    170   end
    171 
    172   @doc """
    173   Prepends the second multi to the first one.
    174 
    175   All names must be unique between both structures.
    176 
    177   ## Example
    178 
    179       iex> lhs = Ecto.Multi.new() |> Ecto.Multi.run(:left, fn _, changes -> {:ok, changes} end)
    180       iex> rhs = Ecto.Multi.new() |> Ecto.Multi.run(:right, fn _, changes -> {:error, changes} end)
    181       iex> Ecto.Multi.prepend(lhs, rhs) |> Ecto.Multi.to_list |> Keyword.keys
    182       [:right, :left]
    183 
    184   """
    185   @spec prepend(t, t) :: t
    186   def prepend(lhs, rhs) do
    187     merge_structs(lhs, rhs, &(&1 ++ &2))
    188   end
    189 
    190   defp merge_structs(%Multi{} = lhs, %Multi{} = rhs, joiner) do
    191     %{names: lhs_names, operations: lhs_ops} = lhs
    192     %{names: rhs_names, operations: rhs_ops} = rhs
    193     case MapSet.intersection(lhs_names, rhs_names) |> MapSet.to_list do
    194       [] ->
    195         %Multi{names: MapSet.union(lhs_names, rhs_names),
    196                operations: joiner.(lhs_ops, rhs_ops)}
    197       common ->
    198         raise ArgumentError, """
    199         error when merging the following Ecto.Multi structs:
    200 
    201         #{Kernel.inspect lhs}
    202 
    203         #{Kernel.inspect rhs}
    204 
    205         both declared operations: #{Kernel.inspect common}
    206         """
    207     end
    208   end
    209 
    210   @doc """
    211   Merges a multi returned dynamically by an anonymous function.
    212 
    213   This function is useful when the multi to be merged requires information
    214   from the original multi. Hence the second argument is an anonymous function
    215   that receives the multi changes so far. The anonymous function must return
    216   another multi.
    217 
    218   If you would prefer to simply merge two multis together, see `append/2` or
    219   `prepend/2`.
    220 
    221   Duplicated operations are not allowed.
    222 
    223   ## Example
    224 
    225       multi =
    226         Ecto.Multi.new()
    227         |> Ecto.Multi.insert(:post, %Post{title: "first"})
    228 
    229       multi
    230       |> Ecto.Multi.merge(fn %{post: post} ->
    231         Ecto.Multi.new()
    232         |> Ecto.Multi.insert(:comment, Ecto.build_assoc(post, :comments))
    233       end)
    234       |> MyApp.Repo.transaction()
    235   """
    236   @spec merge(t, (changes -> t)) :: t
    237   def merge(%Multi{} = multi, merge) when is_function(merge, 1) do
    238     Map.update!(multi, :operations, &[{:merge, {:merge, merge}} | &1])
    239   end
    240 
    241   @doc """
    242   Merges a multi returned dynamically by calling `module` and `function` with `args`.
    243 
    244   Similar to `merge/2`, but allows to pass module name, function and arguments.
    245   The function should return an `Ecto.Multi`, and receives changes so far
    246   as the first argument (prepended to those passed in the call to the function).
    247 
    248   Duplicated operations are not allowed.
    249   """
    250   @spec merge(t, module, function, args) :: t when function: atom, args: [any]
    251   def merge(%Multi{} = multi, mod, fun, args)
    252       when is_atom(mod) and is_atom(fun) and is_list(args) do
    253     Map.update!(multi, :operations, &[{:merge, {:merge, {mod, fun, args}}} | &1])
    254   end
    255 
    256   @doc """
    257   Adds an insert operation to the multi.
    258 
    259   Accepts the same arguments and options as `c:Ecto.Repo.insert/2` does.
    260 
    261   ## Example
    262 
    263       Ecto.Multi.new()
    264       |> Ecto.Multi.insert(:insert, %Post{title: "first"})
    265       |> MyApp.Repo.transaction()
    266 
    267       Ecto.Multi.new()
    268       |> Ecto.Multi.insert(:post, %Post{title: "first"})
    269       |> Ecto.Multi.insert(:comment, fn %{post: post} ->
    270         Ecto.build_assoc(post, :comments)
    271       end)
    272       |> MyApp.Repo.transaction()
    273 
    274   """
    275   @spec insert(t, name, Changeset.t | Ecto.Schema.t | fun(Changeset.t | Ecto.Schema.t), Keyword.t) :: t
    276   def insert(multi, name, changeset_or_struct_or_fun, opts \\ [])
    277 
    278   def insert(multi, name, %Changeset{} = changeset, opts) do
    279     add_changeset(multi, :insert, name, changeset, opts)
    280   end
    281 
    282   def insert(multi, name, %_{} = struct, opts) do
    283     insert(multi, name, Changeset.change(struct), opts)
    284   end
    285 
    286   def insert(multi, name, fun, opts) when is_function(fun, 1) do
    287     run(multi, name, operation_fun({:insert, fun}, opts))
    288   end
    289 
    290   @doc """
    291   Adds an update operation to the multi.
    292 
    293   Accepts the same arguments and options as `c:Ecto.Repo.update/2` does.
    294 
    295   ## Example
    296 
    297       post = MyApp.Repo.get!(Post, 1)
    298       changeset = Ecto.Changeset.change(post, title: "New title")
    299       Ecto.Multi.new()
    300       |> Ecto.Multi.update(:update, changeset)
    301       |> MyApp.Repo.transaction()
    302 
    303       Ecto.Multi.new()
    304       |> Ecto.Multi.insert(:post, %Post{title: "first"})
    305       |> Ecto.Multi.update(:fun, fn %{post: post} ->
    306         Ecto.Changeset.change(post, title: "New title")
    307       end)
    308       |> MyApp.Repo.transaction()
    309 
    310   """
    311   @spec update(t, name, Changeset.t | fun(Changeset.t), Keyword.t) :: t
    312   def update(multi, name, changeset_or_fun, opts \\ [])
    313 
    314   def update(multi, name, %Changeset{} = changeset, opts) do
    315     add_changeset(multi, :update, name, changeset, opts)
    316   end
    317 
    318   def update(multi, name, fun, opts) when is_function(fun, 1) do
    319     run(multi, name, operation_fun({:update, fun}, opts))
    320   end
    321 
    322   @doc """
    323   Inserts or updates a changeset depending on whether the changeset was persisted or not.
    324 
    325   Accepts the same arguments and options as `c:Ecto.Repo.insert_or_update/2` does.
    326 
    327   ## Example
    328 
    329       changeset = Post.changeset(%Post{}, %{title: "New title"})
    330       Ecto.Multi.new()
    331       |> Ecto.Multi.insert_or_update(:insert_or_update, changeset)
    332       |> MyApp.Repo.transaction()
    333 
    334       Ecto.Multi.new()
    335       |> Ecto.Multi.run(:post, fn repo, _changes ->
    336         {:ok, repo.get(Post, 1) || %Post{}}
    337       end)
    338       |> Ecto.Multi.insert_or_update(:update, fn %{post: post} ->
    339         Ecto.Changeset.change(post, title: "New title")
    340       end)
    341       |> MyApp.Repo.transaction()
    342 
    343   """
    344   @spec insert_or_update(t, name, Changeset.t | fun(Changeset.t), Keyword.t) :: t
    345   def insert_or_update(multi, name, changeset_or_fun, opts \\ [])
    346 
    347   def insert_or_update(multi, name, %Changeset{data: %{__meta__: %{state: :loaded}}} = changeset, opts) do
    348     add_changeset(multi, :update, name, changeset, opts)
    349   end
    350 
    351   def insert_or_update(multi, name, %Changeset{} = changeset, opts) do
    352     add_changeset(multi, :insert, name, changeset, opts)
    353   end
    354 
    355   def insert_or_update(multi, name, fun, opts) when is_function(fun, 1) do
    356     run(multi, name, operation_fun({:insert_or_update, fun}, opts))
    357   end
    358 
    359   @doc """
    360   Adds a delete operation to the multi.
    361 
    362   Accepts the same arguments and options as `c:Ecto.Repo.delete/2` does.
    363 
    364   ## Example
    365 
    366       post = MyApp.Repo.get!(Post, 1)
    367       Ecto.Multi.new()
    368       |> Ecto.Multi.delete(:delete, post)
    369       |> MyApp.Repo.transaction()
    370 
    371       Ecto.Multi.new()
    372       |> Ecto.Multi.run(:post, fn repo, _changes ->
    373         case repo.get(Post, 1) do
    374           nil -> {:error, :not_found}
    375           post -> {:ok, post}
    376         end
    377       end)
    378       |> Ecto.Multi.delete(:delete, fn %{post: post} ->
    379         # Others validations
    380         post
    381       end)
    382       |> MyApp.Repo.transaction()
    383 
    384   """
    385   @spec delete(t, name, Changeset.t | Ecto.Schema.t | fun(Changeset.t | Ecto.Schema.t), Keyword.t) :: t
    386   def delete(multi, name, changeset_or_struct_fun, opts \\ [])
    387 
    388   def delete(multi, name, %Changeset{} = changeset, opts) do
    389     add_changeset(multi, :delete, name, changeset, opts)
    390   end
    391 
    392   def delete(multi, name, %_{} = struct, opts) do
    393     delete(multi, name, Changeset.change(struct), opts)
    394   end
    395 
    396   def delete(multi, name, fun, opts) when is_function(fun, 1) do
    397     run(multi, name, operation_fun({:delete, fun}, opts))
    398   end
    399 
    400   @doc """
    401   Runs a query expecting one result and stores it in the multi.
    402 
    403   Accepts the same arguments and options as `c:Ecto.Repo.one/2`.
    404 
    405   ## Example
    406 
    407       Ecto.Multi.new()
    408       |> Ecto.Multi.one(:post, Post)
    409       |> Ecto.Multi.one(:author, fn %{post: post} -> 
    410         from(a in Author, where: a.id == ^post.author_id)
    411       end)
    412       |> MyApp.Repo.transaction()
    413   """
    414   @spec one(
    415           t,
    416           name,
    417           queryable :: Ecto.Queryable.t | (any -> Ecto.Queryable.t),
    418           opts :: Keyword.t
    419         ) :: t
    420   def one(multi, name, queryable_or_fun, opts \\ [])
    421 
    422   def one(multi, name, fun, opts) when is_function(fun, 1) do
    423     run(multi, name, operation_fun({:one, fun}, opts))
    424   end
    425 
    426   def one(multi, name, queryable, opts) do
    427     run(multi, name, operation_fun({:one, fn _ -> queryable end}, opts))
    428   end
    429 
    430   @doc """
    431   Runs a query and stores all entries in the multi.
    432 
    433   Accepts the same arguments and options as `c:Ecto.Repo.all/2` does.
    434 
    435   ## Example
    436 
    437       Ecto.Multi.new()
    438       |> Ecto.Multi.all(:all, Post)
    439       |> MyApp.Repo.transaction()
    440 
    441       Ecto.Multi.new()
    442       |> Ecto.Multi.all(:all, fn _changes -> Post end)
    443       |> MyApp.Repo.transaction()
    444   """
    445   @spec all(
    446           t,
    447           name,
    448           queryable :: Ecto.Queryable.t | (any -> Ecto.Queryable.t),
    449           opts :: Keyword.t
    450         ) :: t
    451   def all(multi, name, queryable_or_fun, opts \\ [])
    452 
    453   def all(multi, name, fun, opts) when is_function(fun, 1) do
    454     run(multi, name, operation_fun({:all, fun}, opts))
    455   end
    456 
    457   def all(multi, name, queryable, opts) do
    458     run(multi, name, operation_fun({:all, fn _ -> queryable end}, opts))
    459   end
    460 
    461   defp add_changeset(multi, action, name, changeset, opts) when is_list(opts) do
    462     add_operation(multi, name, {:changeset, put_action(changeset, action), opts})
    463   end
    464 
    465   defp put_action(%{action: nil} = changeset, action) do
    466     %{changeset | action: action}
    467   end
    468 
    469   defp put_action(%{action: action} = changeset, action) do
    470     changeset
    471   end
    472 
    473   defp put_action(%{action: original}, action) do
    474     raise ArgumentError, "you provided a changeset with an action already set " <>
    475       "to #{Kernel.inspect original} when trying to #{action} it"
    476   end
    477 
    478   @doc """
    479   Causes the multi to fail with the given value.
    480 
    481   Running the multi in a transaction will execute
    482   no previous steps and returns the value of the first
    483   error added.
    484   """
    485   @spec error(t, name, error :: term) :: t
    486   def error(multi, name, value) do
    487     add_operation(multi, name, {:error, value})
    488   end
    489 
    490   @doc """
    491   Adds a function to run as part of the multi.
    492 
    493   The function should return either `{:ok, value}` or `{:error, value}`,
    494   and receives the repo as the first argument, and the changes so far
    495   as the second argument.
    496 
    497   ## Example
    498 
    499       Ecto.Multi.run(multi, :write, fn _repo, %{image: image} ->
    500         with :ok <- File.write(image.name, image.contents) do
    501           {:ok, nil}
    502         end
    503       end)
    504   """
    505   @spec run(t, name, run) :: t
    506   def run(multi, name, run) when is_function(run, 2) do
    507     add_operation(multi, name, {:run, run})
    508   end
    509 
    510   @doc """
    511   Adds a function to run as part of the multi.
    512 
    513   Similar to `run/3`, but allows to pass module name, function and arguments.
    514   The function should return either `{:ok, value}` or `{:error, value}`, and
    515   receives the repo as the first argument, and the changes so far as the
    516   second argument (prepended to those passed in the call to the function).
    517   """
    518   @spec run(t, name, module, function, args) :: t when function: atom, args: [any]
    519   def run(multi, name, mod, fun, args)
    520       when is_atom(mod) and is_atom(fun) and is_list(args) do
    521     add_operation(multi, name, {:run, {mod, fun, args}})
    522   end
    523 
    524   @doc """
    525   Adds an insert_all operation to the multi.
    526 
    527   Accepts the same arguments and options as `c:Ecto.Repo.insert_all/3` does.
    528 
    529   ## Example
    530 
    531       posts = [%{title: "My first post"}, %{title: "My second post"}]
    532       Ecto.Multi.new()
    533       |> Ecto.Multi.insert_all(:insert_all, Post, posts)
    534       |> MyApp.Repo.transaction()
    535 
    536       Ecto.Multi.new()
    537       |> Ecto.Multi.run(:post, fn repo, _changes ->
    538         case repo.get(Post, 1) do
    539           nil -> {:error, :not_found}
    540           post -> {:ok, post}
    541         end
    542       end)
    543       |> Ecto.Multi.insert_all(:insert_all, Comment, fn %{post: post} ->
    544         # Others validations
    545 
    546         entries
    547         |> Enum.map(fn comment ->
    548           Map.put(comment, :post_id, post.id)
    549         end)
    550       end)
    551       |> MyApp.Repo.transaction()
    552 
    553   """
    554   @spec insert_all(
    555           t,
    556           name,
    557           schema_or_source,
    558           entries_or_query_or_fun :: [map | Keyword.t()] | fun([map | Keyword.t()]) | Ecto.Query.t(),
    559           Keyword.t()
    560         ) :: t
    561   def insert_all(multi, name, schema_or_source, entries_or_query_or_fun, opts \\ [])
    562 
    563   def insert_all(multi, name, schema_or_source, entries_fun, opts)
    564       when is_function(entries_fun, 1) and is_list(opts) do
    565     run(multi, name, operation_fun({:insert_all, schema_or_source, entries_fun}, opts))
    566   end
    567 
    568   def insert_all(multi, name, schema_or_source, entries_or_query, opts) when is_list(opts) do
    569     add_operation(multi, name, {:insert_all, schema_or_source, entries_or_query, opts})
    570   end
    571 
    572   @doc """
    573   Adds an update_all operation to the multi.
    574 
    575   Accepts the same arguments and options as `c:Ecto.Repo.update_all/3` does.
    576 
    577   ## Example
    578 
    579       Ecto.Multi.new()
    580       |> Ecto.Multi.update_all(:update_all, Post, set: [title: "New title"])
    581       |> MyApp.Repo.transaction()
    582 
    583       Ecto.Multi.new()
    584       |> Ecto.Multi.run(:post, fn repo, _changes ->
    585         case repo.get(Post, 1) do
    586           nil -> {:error, :not_found}
    587           post -> {:ok, post}
    588         end
    589       end)
    590       |> Ecto.Multi.update_all(:update_all, fn %{post: post} ->
    591         # Others validations
    592         from(c in Comment, where: c.post_id == ^post.id, update: [set: [title: "New title"]])
    593       end, [])
    594       |> MyApp.Repo.transaction()
    595 
    596   """
    597   @spec update_all(t, name, Ecto.Queryable.t | fun(Ecto.Queryable.t), Keyword.t, Keyword.t) :: t
    598   def update_all(multi, name, queryable_or_fun, updates, opts \\ [])
    599 
    600   def update_all(multi, name, queryable_fun, updates, opts) when is_function(queryable_fun, 1) and is_list(opts) do
    601     run(multi, name, operation_fun({:update_all, queryable_fun, updates}, opts))
    602   end
    603 
    604   def update_all(multi, name, queryable, updates, opts) when is_list(opts) do
    605     query = Ecto.Queryable.to_query(queryable)
    606     add_operation(multi, name, {:update_all, query, updates, opts})
    607   end
    608 
    609   @doc """
    610   Adds a delete_all operation to the multi.
    611 
    612   Accepts the same arguments and options as `c:Ecto.Repo.delete_all/2` does.
    613 
    614   ## Example
    615 
    616       queryable = from(p in Post, where: p.id < 5)
    617       Ecto.Multi.new()
    618       |> Ecto.Multi.delete_all(:delete_all, queryable)
    619       |> MyApp.Repo.transaction()
    620 
    621       Ecto.Multi.new()
    622       |> Ecto.Multi.run(:post, fn repo, _changes ->
    623         case repo.get(Post, 1) do
    624           nil -> {:error, :not_found}
    625           post -> {:ok, post}
    626         end
    627       end)
    628       |> Ecto.Multi.delete_all(:delete_all, fn %{post: post} ->
    629         # Others validations
    630         from(c in Comment, where: c.post_id == ^post.id)
    631       end)
    632       |> MyApp.Repo.transaction()
    633 
    634   """
    635   @spec delete_all(t, name, Ecto.Queryable.t | fun(Ecto.Queryable.t), Keyword.t) :: t
    636   def delete_all(multi, name, queryable_or_fun, opts \\ [])
    637 
    638   def delete_all(multi, name, fun, opts) when is_function(fun, 1) and is_list(opts) do
    639     run(multi, name, operation_fun({:delete_all, fun}, opts))
    640   end
    641 
    642   def delete_all(multi, name, queryable, opts) when is_list(opts) do
    643     query = Ecto.Queryable.to_query(queryable)
    644     add_operation(multi, name, {:delete_all, query, opts})
    645   end
    646 
    647   defp add_operation(%Multi{} = multi, name, operation) do
    648     %{operations: operations, names: names} = multi
    649     if MapSet.member?(names, name) do
    650       raise "#{Kernel.inspect name} is already a member of the Ecto.Multi: \n#{Kernel.inspect multi}"
    651     else
    652       %{multi | operations: [{name, operation} | operations],
    653                 names: MapSet.put(names, name)}
    654     end
    655   end
    656 
    657   @doc """
    658   Returns the list of operations stored in `multi`.
    659 
    660   Always use this function when you need to access the operations you
    661   have defined in `Ecto.Multi`. Inspecting the `Ecto.Multi` struct internals
    662   directly is discouraged.
    663   """
    664   @spec to_list(t) :: [{name, term}]
    665   def to_list(%Multi{operations: operations}) do
    666     operations
    667     |> Enum.reverse
    668     |> Enum.map(&format_operation/1)
    669   end
    670 
    671   defp format_operation({name, {:changeset, changeset, opts}}),
    672     do: {name, {changeset.action, changeset, opts}}
    673   defp format_operation(other),
    674     do: other
    675 
    676   @doc """
    677   Adds a value to the changes so far under the given name.
    678 
    679   The given `value` is added to the multi before the transaction starts.
    680   If you would like to run arbitrary functions as part of your transaction,
    681   see `run/3` or `run/5`.
    682 
    683   ## Example
    684 
    685   Imagine there is an existing company schema that you retrieved from
    686   the database. You can insert it as a change in the multi using `put/3`:
    687 
    688       Ecto.Multi.new()
    689       |> Ecto.Multi.put(:company, company)
    690       |> Ecto.Multi.insert(:user, fn changes -> User.changeset(changes.company) end)
    691       |> Ecto.Multi.insert(:person, fn changes -> Person.changeset(changes.user, changes.company) end)
    692       |> MyApp.Repo.transaction()
    693 
    694   In the example above there isn't a large benefit in putting the
    695   `company` in the multi, because you could also access the
    696   `company` variable directly inside the anonymous function.
    697   
    698   However, the benefit of `put/3` is when composing `Ecto.Multi`s.
    699   If the insert operations above were defined in another module,
    700   you could use `put(:company, company)` to inject changes that
    701   will be accessed by other functions down the chain, removing
    702   the need to pass both `multi` and `company` values around.
    703   """
    704   @spec put(t, name, any) :: t
    705   def put(multi, name, value) do
    706     add_operation(multi, name, {:put, value})
    707   end
    708 
    709   @doc """
    710   Inspects results from a Multi
    711 
    712   By default, the name is shown as a label to the inspect, custom labels are
    713   supported through the `IO.inspect/2` `label` option.
    714 
    715   ## Options
    716 
    717   All options for IO.inspect/2 are supported, it also support the following ones:
    718 
    719     * `:only` - A field or a list of fields to inspect, will print the entire
    720       map by default.
    721 
    722   ## Examples
    723 
    724       Ecto.Multi.new()
    725       |> Ecto.Multi.insert(:person_a, changeset)
    726       |> Ecto.Multi.insert(:person_b, changeset)
    727       |> Ecto.Multi.inspect()
    728       |> MyApp.Repo.transaction()
    729 
    730   Prints:
    731       %{person_a: %Person{...}, person_b: %Person{...}}
    732 
    733   We can use the `:only` option to limit which fields will be printed:
    734 
    735       Ecto.Multi.new()
    736       |> Ecto.Multi.insert(:person_a, changeset)
    737       |> Ecto.Multi.insert(:person_b, changeset)
    738       |> Ecto.Multi.inspect(only: :person_a)
    739       |> MyApp.Repo.transaction()
    740 
    741   Prints:
    742       %{person_a: %Person{...}}
    743 
    744   """
    745   @spec inspect(t, Keyword.t) :: t
    746   def inspect(multi, opts \\ []) do
    747     Map.update!(multi, :operations, &[{:inspect, {:inspect, opts}} | &1])
    748   end
    749 
    750   @doc false
    751   @spec __apply__(t, Ecto.Repo.t, fun, (term -> no_return)) :: {:ok, term} | {:error, term}
    752   def __apply__(%Multi{} = multi, repo, wrap, return) do
    753     operations = Enum.reverse(multi.operations)
    754 
    755     with {:ok, operations} <- check_operations_valid(operations) do
    756       apply_operations(operations, multi.names, repo, wrap, return)
    757     end
    758   end
    759 
    760   defp check_operations_valid(operations) do
    761     Enum.find_value(operations, &invalid_operation/1) || {:ok, operations}
    762   end
    763 
    764   defp invalid_operation({name, {:changeset, %{valid?: false} = changeset, _}}),
    765     do: {:error, {name, changeset, %{}}}
    766   defp invalid_operation({name, {:error, value}}),
    767     do: {:error, {name, value, %{}}}
    768   defp invalid_operation(_operation),
    769     do: nil
    770 
    771   defp apply_operations([], _names, _repo, _wrap, _return), do: {:ok, %{}}
    772   defp apply_operations(operations, names, repo, wrap, return) do
    773     wrap.(fn ->
    774       operations
    775       |> Enum.reduce({%{}, names}, &apply_operation(&1, repo, wrap, return, &2))
    776       |> elem(0)
    777     end)
    778   end
    779 
    780   defp apply_operation({_, {:merge, merge}}, repo, wrap, return, {acc, names}) do
    781     case __apply__(apply_merge_fun(merge, acc), repo, wrap, return) do
    782       {:ok, value} ->
    783         merge_results(acc, value, names)
    784       {:error, {name, value, nested_acc}} ->
    785         {acc, _names} = merge_results(acc, nested_acc, names)
    786         return.({name, value, acc})
    787     end
    788   end
    789 
    790   defp apply_operation({_name, {:inspect, opts}}, _repo, _wrap_, _return, {acc, names}) do
    791     if opts[:only] do
    792       acc |> Map.take(List.wrap(opts[:only])) |> IO.inspect(opts)
    793     else
    794       IO.inspect(acc, opts)
    795     end
    796 
    797     {acc, names}
    798   end
    799 
    800   defp apply_operation({name, operation}, repo, wrap, return, {acc, names}) do
    801     case apply_operation(operation, acc, {wrap, return}, repo) do
    802       {:ok, value} ->
    803         {Map.put(acc, name, value), names}
    804       {:error, value} ->
    805         return.({name, value, acc})
    806       other ->
    807         raise "expected Ecto.Multi callback named `#{Kernel.inspect name}` to return either {:ok, value} or {:error, value}, got: #{Kernel.inspect other}"
    808     end
    809   end
    810 
    811   defp apply_operation({:changeset, changeset, opts}, _acc, _apply_args, repo),
    812     do: apply(repo, changeset.action, [changeset, opts])
    813   defp apply_operation({:run, run}, acc, _apply_args, repo),
    814     do: apply_run_fun(run, repo, acc)
    815   defp apply_operation({:error, value}, _acc, _apply_args, _repo),
    816     do: {:error, value}
    817   defp apply_operation({:insert_all, source, entries, opts}, _acc, _apply_args, repo),
    818     do: {:ok, repo.insert_all(source, entries, opts)}
    819   defp apply_operation({:update_all, query, updates, opts}, _acc, _apply_args, repo),
    820     do: {:ok, repo.update_all(query, updates, opts)}
    821   defp apply_operation({:delete_all, query, opts}, _acc, _apply_args, repo),
    822     do: {:ok, repo.delete_all(query, opts)}
    823   defp apply_operation({:put, value}, _acc, _apply_args, _repo),
    824     do: {:ok, value}
    825 
    826   defp apply_merge_fun({mod, fun, args}, acc), do: apply(mod, fun, [acc | args])
    827   defp apply_merge_fun(fun, acc), do: apply(fun, [acc])
    828 
    829   defp apply_run_fun({mod, fun, args}, repo, acc), do: apply(mod, fun, [repo, acc | args])
    830   defp apply_run_fun(fun, repo, acc), do: apply(fun, [repo, acc])
    831 
    832   defp merge_results(changes, new_changes, names) do
    833     new_names = new_changes |> Map.keys |> MapSet.new()
    834     case MapSet.intersection(names, new_names) |> MapSet.to_list do
    835       [] ->
    836         {Map.merge(changes, new_changes), MapSet.union(names, new_names)}
    837       common ->
    838         raise "cannot merge multi, the following operations were found in " <>
    839           "both Ecto.Multi: #{Kernel.inspect common}"
    840     end
    841   end
    842 
    843   defp operation_fun({:update_all, queryable_fun, updates}, opts) do
    844     fn repo, changes ->
    845       {:ok, repo.update_all(queryable_fun.(changes), updates, opts)}
    846     end
    847   end
    848 
    849   defp operation_fun({:insert_all, schema_or_source, entries_fun}, opts) do
    850     fn repo, changes ->
    851       {:ok, repo.insert_all(schema_or_source, entries_fun.(changes), opts)}
    852     end
    853   end
    854 
    855   defp operation_fun({:delete_all, fun}, opts) do
    856     fn repo, changes ->
    857       {:ok, repo.delete_all(fun.(changes), opts)}
    858     end
    859   end
    860 
    861   defp operation_fun({:one, fun}, opts) do
    862     fn repo, changes ->
    863       {:ok, repo.one(fun.(changes), opts)}
    864     end
    865   end
    866 
    867   defp operation_fun({:all, fun}, opts) do
    868     fn repo, changes ->
    869       {:ok, repo.all(fun.(changes), opts)}
    870     end
    871   end
    872 
    873   defp operation_fun({operation, fun}, opts) do
    874     fn repo, changes ->
    875       apply(repo, operation, [fun.(changes), opts])
    876     end
    877   end
    878 end