I’m finding myself working with Ecto.Multi
more and more recently, and I’m trying to understand what are the best practices to work with them.
My question for this topic is: what is the suggested approach to factor out a common Multi operation that you want to execute in multiple places?
The approaches I’m aware of are:
Creating a function that accepts a Multi and returns a Multi
Example:
def create_foo(attrs) do
Multi.new()
|> Multi.insert(:foo, Foo.changeset(%Foo{}, attrs))
|> associate_foo_with_bars()
|> ...
end
def update_foo(foo, attrs) do
Multi.new()
|> Multi.insert(:foo, Foo.changeset(foo, attrs))
|> associate_foo_with_bars()
|> ...
end
defp associate_foo_with_bars(multi) do
multi
|> Multi.update_all(...)
end
Extracting the function you pass as argument to Multi.whatever
Example:
def create_foo(attrs) do
Multi.new()
|> Multi.insert(:foo, Foo.changeset(%Foo{}, attrs))
|> Multi.update_all(:associate, &associate_foo_with_bars/1, [])
|> ...
end
def update_foo(foo, attrs) do
Multi.new()
|> Multi.insert(:foo, Foo.changeset(foo, attrs))
|> Multi.update_all(:associate, &associate_foo_with_bars/1, [])
|> ...
end
defp associate_foo_with_bars(%{foo: foo}) do
from b in Bars,
...
end
Using Multi.merge
def create_foo(attrs) do
Multi.new()
|> Multi.insert(:foo, Foo.changeset(%Foo{}, attrs))
|> Multi.merge(&associate_foo_with_bars/1)
|> ...
end
def update_foo(foo, attrs) do
Multi.new()
|> Multi.insert(:foo, Foo.changeset(foo, attrs))
|> Multi.merge(&associate_foo_with_bars/1)
|> ...
end
defp associate_foo_with_bars(%{foo: foo}) do
Multi.new()
|> Multi.update_all(:associate, ...
from b in Bars,
...
end)
end
I’m interested in the pros and cons of the approaches (and possibly to other approaches), and what approach you think works best for specific situations.
Moreover, I’d like to know if you consider the name of the operations part of the API to compose your Multis.
I’ll expand on what I mean: in examples 2 and 3 above, my function accepts changes
, and I assert that there’s a :foo
key that contains the Foo
I need to use in the next operation.
Another approach could be pattern match on :foo
in the top Multi
and just pass the Foo
struct to the functions, that would not be coupled to the operation name.
Do you accept the whole changes
map in your Multi composition function or the single result of an operation?
Please note the aim of threads like this is for you to explain things in your own words. Please see this thread for details (videos/links to further reading are also welcome though!)