The problem
------------
People often run into this problem: they want a different email per
account, but they do this:
```elixir
build_pair(:account, email: build(:email))
```
The problem is that `build/2` is just a function call. So the above is
equivalent to this:
```elixir
email = build(:email)
build_pair(:account, email: email) # same email
```
In other words, we get the same email factory for all of the accounts.
That's especially confusing if we're using a `sequence` in the `email`
factory.
The problem is made worse when using it with Ecto. We can imagine the
following scenario:
```elixir
insert_pair(:account, user: build(:user))
```
If the user factory has a uniqueness constraint, `insert_pair/2` will
raise an error because we'll try to insert a user with the same value
(even if using a sequence).
Solution
--------
The solution is to delay evaluation of the attributes. We do this
allowing attributes to be functions.
The trick then lies in the `build/2` function. We make it a terminal
function in that it will evaluate any lazy attributes recursively. To do
that, we update the `build/2` function to evaluate function attributes
after merging any passed-in attributes.
Previous implementations tried to solve the issue of delayed evaluation
by introducing a `build_lazy/2` function. One of those was a simple
alias to an anonymous function `fn -> build(:factory_name) end`. The
other was a more complex approach that introduced a new private struct
`%ExMachina.InstanceTemplate{}` to hold the data necessary to build the
instance of that factory.
We opt for the simpler approach because:
- (a) it leaves room for flexibility in the future (we can add something
like `build_lazy` alias if we want), and
- (b) it opens the door for allowing the parent factory to be passed
into the anonymous function in a factory definition:
```elixir
def account_factory do
%Account{
status: fn account -> build(:status, private: account.private) end
}
end
```
Not interacting with "full-control" factories
---------------------------------------------
We opt for not evaluating lazy attributes in "full-control" factories.
The whole point of allowing users to have full control of their factory
attributes is for them to do with them what they will.
We do expose a `evaluate_lazy_attributes/1` helper function, just like
we expose a `merge_attributes/2` function so that users can emulate
ExMachina's default behavior.