Skip to content

Commit

Permalink
Move factory definitions to a macro
Browse files Browse the repository at this point in the history
  • Loading branch information
jsteiner committed Oct 9, 2015
1 parent 8f332ce commit 03c41f6
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 43 deletions.
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,22 @@ defmodule MyApp.Factories do
# It will automatically be used when calling `create`
use ExMachina.Ecto, repo: MyApp.Repo

def factory(:config) do
factory :config do
# Factories can be plain maps
%{url: "http://example.com"}
end

def factory(:article) do
factory :article do
%Article{
title: "My Awesome Article"
}
end

def factory(:comment, attrs) do
factory :comment do
%Comment{
body: "This is great!",
author_email: sequence(:email, &"email-#{&1}@example.com"),
article_id: assoc(attrs, :article).id
article_id: assoc(:article).id
}
end
end
Expand Down Expand Up @@ -85,7 +85,9 @@ defining `save_function/1` in your module.
defmodule MyApp.JsonFactories do
use ExMachina

def factory(:user), do: %User{name: "John"}
factory :user do
%User{name: "John"}
end

def save_record(record) do
# Poison is a library for working with JSON
Expand All @@ -105,7 +107,9 @@ or `create_json` to return encoded JSON objects.
defmodule MyApp.Factories do
use ExMachina.Ecto, repo: MyApp.Repo

def factory(:user), do: %User{name: "John"}
factory :user do
%User{name: "John"}
end

# builds the object and then encodes it as JSON
def build_json(factory_name, attrs) do
Expand Down
36 changes: 20 additions & 16 deletions lib/ex_machina.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule ExMachina do
quote do
@before_compile unquote(__MODULE__)

import ExMachina, only: [sequence: 2]
import ExMachina, only: [sequence: 2, factory: 2]

def build(factory_name, attrs \\ %{}) do
ExMachina.build(__MODULE__, factory_name, attrs)
Expand Down Expand Up @@ -69,12 +69,21 @@ defmodule ExMachina do
end
end

defmacro factory(factory_name, do: block) do
quote do
def factory(unquote(factory_name), var!(attrs)) do
!var!(attrs) # Removes unused variable warning if attrs wasn't used

This comment has been minimized.

Copy link
@josevalim

josevalim Oct 10, 2015

You can do _ = var!(attrs) or add a guard, especially if you expect it to be a map.

This comment has been minimized.

Copy link
@paulcsmith

paulcsmith Oct 10, 2015

Contributor

Great idea. Thanks, José!

unquote(block)
end
end
end

@doc """
Create sequences for generating unique values
## Examples
def factory(:user) do
factory :user do
%{
# Will generate "[email protected]" then "[email protected]", etc.
email: sequence(:email, &"me-\#{&1}@foo.com")
Expand All @@ -88,7 +97,7 @@ defmodule ExMachina do
## Example
def factory(:user) do
factory :user do
%{name: "John Doe", admin: false}
end
Expand Down Expand Up @@ -137,7 +146,7 @@ defmodule ExMachina do
## Example
def factory(:user) do
factory :user do
%{name: "John Doe", admin: false}
end
Expand Down Expand Up @@ -176,19 +185,10 @@ defmodule ExMachina do

defmacro __before_compile__(_env) do
quote do
@doc """
Calls factory/1 with the passed in factory name
This allows you to define factories without the `attrs` param.
"""
def factory(factory_name, _attrs) do
__MODULE__.factory(factory_name)
end

@doc """
Raises a helpful error if no factory is defined.
"""
def factory(factory_name) do
def factory(factory_name, _) do
raise UndefinedFactory, factory_name
end

Expand All @@ -206,7 +206,9 @@ defmodule ExMachina do
defmodule MyApp.Factories do
use ExMachina.Ecto, repo: MyApp.Repo
def factory(:user), do: %User{name: "John"}
factory :user do
%User{name: "John"}
end
end
# Will build and save the record to the MyApp.Repo
Expand All @@ -216,7 +218,9 @@ defmodule ExMachina do
# Note, we are not using ExMachina.Ecto
use ExMachina
def factory(:user), do: %User{name: "John"}
factory :user do
%User{name: "John"}
end
def save_function(record) do
# Poison is a library for working with JSON
Expand Down
20 changes: 12 additions & 8 deletions lib/ex_machina/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,26 @@ defmodule ExMachina.Ecto do
quote do
use ExMachina

import ExMachina.Ecto, only: [assoc: 1, assoc: 2]

@repo Dict.fetch!(unquote(opts), :repo)

def fields_for(factory_name, attrs \\ %{}) do
ExMachina.Ecto.fields_for(__MODULE__, factory_name, attrs)
end

defp assoc(attrs, factory_name, opts \\ []) do
ExMachina.Ecto.assoc(__MODULE__, attrs, factory_name, opts)
end

def save_record(record) do
ExMachina.Ecto.save_record(__MODULE__, @repo, record)
end
end
end

defmacro assoc(factory_name, opts \\ []) do
quote do
ExMachina.Ecto.assoc(__MODULE__, var!(attrs), unquote(factory_name), unquote(opts))
end
end

@doc """
Builds a factory with the passed in factory_name and returns its fields
Expand All @@ -29,7 +33,7 @@ defmodule ExMachina.Ecto do
## Example
def factory(:user) do
factory :user do
%MyApp.User{name: "John Doe", admin: false}
end
Expand Down Expand Up @@ -58,15 +62,15 @@ defmodule ExMachina.Ecto do
attrs = %{user: %{name: "Someone"}}
# Returns attrs.user
assoc(attrs, :user)
assoc(:user)
attrs = %{}
# Creates and returns new instance based on :user factory
assoc(attrs, :user)
assoc(:user)
attrs = %{}
# Creates and returns new instance based on :user factory
assoc(attrs, :author, factory: :user)
assoc(:author, factory: :user)
"""
def assoc(module, attrs, factory_name, opts \\ []) do
case Map.get(attrs, factory_name) do
Expand Down
12 changes: 6 additions & 6 deletions test/ex_machina/ecto_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,32 @@ defmodule ExMachina.EctoTest do
defmodule MyApp.EctoFactories do
use ExMachina.Ecto, repo: TestRepo

def factory(:book) do
factory :book do
%MyApp.Book{
title: "Foo"
}
end

def factory(:user) do
factory :user do
%{
id: 3,
name: "John Doe",
admin: false
}
end

def factory(:article, attrs) do
factory :article do
%{
id: 1,
title: "My Awesome Article",
author_id: assoc(attrs, :author, factory: :user).id
author_id: assoc(:author, factory: :user).id
}
end

def factory(:comment, attrs) do
factory :comment do
%{
body: "This is great!",
article_id: assoc(attrs, :article).id
article_id: assoc(:article).id
}
end
end
Expand Down
12 changes: 5 additions & 7 deletions test/ex_machina_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ defmodule ExMachinaTest do
defmodule MyApp.Factories do
use ExMachina

def factory(:user) do
factory :user do
%{
id: 3,
name: "John Doe",
admin: false
}
end

def factory(:email) do
factory :email do
%{
email: sequence(:email, &"me-#{&1}@foo.com")
}
Expand All @@ -27,18 +27,16 @@ defmodule ExMachinaTest do
defmodule MyApp.NoSaveFunction do
use ExMachina

def factory(:foo), do: %{foo: :bar}
factory(:foo) do
%{foo: :bar}
end
end

test "sequence/2 sequences a value" do
assert "[email protected]" == MyApp.Factories.build(:email).email
assert "[email protected]" == MyApp.Factories.build(:email).email
end

test "factories can be defined without the attrs param" do
assert MyApp.Factories.build(:user) == MyApp.Factories.factory(:user)
end

test "raises a helpful error if the factory is not defined" do
assert_raise ExMachina.UndefinedFactory, "No factory defined for :foo", fn ->
MyApp.Factories.build(:foo)
Expand Down

0 comments on commit 03c41f6

Please sign in to comment.