Cookbook
View SourceRecipes for common factory patterns. The full API reference lives in the
FactoryMan module documentation.
Building associations
Use FactoryMan.assoc/4 to let callers pass a prebuilt association, params to build one from,
or nothing at all:
deffactory post(params \\ %{}), struct: Post do
base_params = %{title: FactoryMan.sequence("post")}
params =
Map.put(params, :author, FactoryMan.assoc(params, :author, &build_author_struct/1, struct: Author))
Map.merge(base_params, params)
end
build_post_struct() # builds a default author
build_post_struct(%{author: my_author}) # reuses the given struct
build_post_struct(%{author: %{name: "Ann"}}) # builds an author from paramsIn merge-style factories, resolve into params (as above), not into the defaults map — the
final Map.merge would clobber a resolved default with the caller's raw input. Factories
with body: :struct don't merge, so they can use FactoryMan.assoc/4 directly in field
position.
The struct: option raises on a struct of the wrong type — without it, a mistyped value
would be reused silently. See FactoryMan.assoc/4 for the full semantics (:inherit
defaults, optional associations via on_nil: :keep and on_missing: nil) and
FactoryMan.assoc_list/4 for has_many-style lists.
When the schema only needs a foreign key (and the record must exist), insert the association and use its ID:
deffactory comment(params \\ %{}), struct: Comment do
base_params = %{
body: "Nice post!",
post_id: Map.get_lazy(params, :post_id, fn -> insert_post().id end)
}
Map.merge(base_params, params)
endPost-build presets
Variants preprocess params — they cannot transform the built value. When a preset needs
to derive fields from the built struct, define a separate factory with body: :struct
whose body calls the base build function and transforms the result:
deffactory anonymized_user(params \\ %{}), struct: User, body: :struct do
user = build_user_struct(params)
%{user | email: "redacted+#{user.id}@example.com", display_name: "anonymous"}
endUnlike a hand-written build_anonymized_user/1 function, this keeps the whole generated
family: build_anonymized_user_params, the _list builders, and insert_anonymized_user
all come for free.
One caveat: an after_build_struct hook runs twice — once inside the base build call and
once for the wrapping factory. Insert hooks run once (only the wrapping factory's insert
is called).
Validated presets
A variant that promises a property of its result ("this post is always published") can be silently broken by caller params. To make the promise explicit, assert it over the merged result and raise:
defvariant published(params \\ %{}), for: :post do
base_params = %{published_at: DateTime.utc_now(), draft: false}
result_params = Map.merge(base_params, params)
if Post.published?(result_params),
do: result_params,
else: raise("params contradict the published preset")
endA misuse like build_published_post_struct(%{draft: true}) now fails at the factory
boundary instead of producing a self-contradictory record. The same shape works in the
opposite polarity (a draft variant raising when the params imply a published post).
Note the check runs on raw params, before lazy evaluation — the predicate cannot see resolved values of lazy (function) fields.