Sheharyar Naseer

Using Custom Attributes in ExMachina Factories


It’s finally here! With the newest v2.3.0 release of ex_machina, we can finally customize how factories are called and built in Elixir. After a long, long discussion on Github, the team decided to add a secondary method of defining factories by passing in a list of attributes, which can be used however you like.

Here’s how a normal factory definition looks like:

def post_factory do
  title = sequence(:title, &"Blog Post #{&1}")
  author = build(:user)

  %Post{
    title: title,
    slug: Post.slug_from(title),
    author: author,
    blog: author.blog,
  }
end

But if you attempt to pass in a custom value for title when calling the factory, the slug would not reflect the passed title, unless you also pass in the slug:

insert(:post, title: "A different blog post")

Similarly, passing in a custom user would cause the Factory to be invalid because :blog will reference the blog of a different user that the definition automatically built:

custom_author = insert(:user, ...)
insert(:post, author: custom_author)

With the new update, you can write factory definitions with an arity of 1, receiving the inputs and building the factory according to them:

def post_factory(attrs) do
  title = attrs[:title] || sequence(:title, &"Blog Post #{&1}")
  author = Map.get_lazy(attrs, :author, fn -> insert(:user) end)

  post =
    %Post{
      title: title,
      slug: Post.slug_from(title),
      author: author,
      blog: author.blog,
    }

  merge_attributes(post, attrs)
end

There’s a lot more that’s possible with the new release, all of which I cover in a post on the Slab blog. You can check it out here →