Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow more control over factory definitions #333

Merged
merged 1 commit into from
Feb 22, 2019
Merged

Conversation

germsvel
Copy link
Collaborator

@germsvel germsvel commented Feb 21, 2019

Closes: #309, #272, #271

Description

Currently build(:user) will grab the struct/map defined in user_factory/0 and merge any attributes passed into it via struct!/2 or Map.merge/2.

It would be nice to allow users to have more control of factories if they want to, while maintaining the ease of use for the majority of cases. It seems this can be done by allowing users to change the user_factory/0 to a factory that takes in the attributes being passed in.

We can do so by checking if they have defined a factory with /1 arity (meaning they allow parameters to be passed).

Example

Let's take a look at a scenario where this might be wanted:

def user_factory do
  name = "John"
  email = sequence(:email, &"#{name}-#{&1}@foo.com")

  %User{
    name: name,
    email: email,
    age: 20
  }
end

build(:user)
# => %User{name: "John", email: "[email protected]", age: 20}

build(:user, name: "James")
# => %User{name: "James", email: "[email protected]", age: 20}

As we can see the issue is that since we derive the email from the name, the email has the old name in it, even when we pass "James" as the name. It would be nice to be able to get the name from the attributes before we create the email.

So we could define this instead,

def user_factory(attrs) do
  name = Map.get(attrs, :name, "John")
  email = sequence(:email, &"#{name}-#{&1}@foo.com")

  user = %User{
    name: name,
    email: email,
    age: 20
  }

  merge_attributes(user, attrs)
end

build(:user)
# => %User{name: "John", email: "[email protected]", age: 20}

build(:user, name: "James", age: 30)
# => %User{name: "James", email: "[email protected]", age: 30}

Note the inclusion of merge_attributes/2 in our example above. We need that so that age gets properly overridden from 20 to 30.

The merge_attributes/2 function is a new function we expose to make it easier for users to merge attributes regardless of whether it is a struct or a map. It essentially delegates to struct!/2 or Map.merge/2.

Non-map factories

Because it provides full control over the factory, we can also now define non-map factories.

def room_number_factory(_) do
  sequence(:room_number, &"50#{&1}")
end

build(:room_number)
# => "500"

Description
-----------

Currently `build(:user)` will grab the struct/map defined in
`user_factory/0` and merge any attributes passed into it via `struct!/2`
or `Map.merge/2`.

It would be nice to allow users to have more control of factories if
they want to, while maintaining the ease of use for the majority of
cases. It seems this can be done by allowing users to change the
`user_factory/0` to a factory that takes in the attributes being passed
in.

We can do so by checking if they have defined a factory with `/1` arity
(meaning they allow parameters to be passed).

Example
---------

Let's take a look at a scenario where this might be wanted:

```elixir
def user_factory do
  name = "John"
  email = sequence(:email, &"#{name}-#{&1}@foo.com")

  %User{
    name: name,
    email: email,
    age: 20
  }
end

build(:user)

build(:user, name: "James")
```

As we can see the issue is that since we derive the `email` from the
`name`, the email has the old name in it, even when we pass "James" as
the name. It would be nice to be able to get the `name` from the
attributes _before_ we create the email.

So we could define this instead,

```elixir
def user_factory(attrs) do
  name = Map.get(attrs, :name, "John")
  email = sequence(:email, &"#{name}-#{&1}@foo.com")

  user = %User{
    name: name,
    email: email,
    age: 20
  }

  merge_attributes(user, attrs)
end

build(:user)

build(:user, name: "James", age: 30)
```

Note the inclusion of `merge_attributes/2` in our example above. We need
that so that `age` gets properly overridden from `20` to `30`. Without
it, the only attributes that will be set are `name` and `email`.

The `merge_attributes/2` function is a new function we expose to make it
easier for users to merge attributes regardless of whether it is a
struct or a map. It essentially delegates to `struct!/2` or
`Map.merge/2`.
Copy link

@sheharyarn sheharyarn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉🎉🎉

Copy link

@composerinteralia composerinteralia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know Elixir that well, but this seems like a reasonable approach and the code seems fairly easy to follow. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants