Skip to content

Commit

Permalink
Save associated records in save_record
Browse files Browse the repository at this point in the history
  • Loading branch information
jsteiner committed Oct 9, 2015
1 parent b518285 commit 59cbef5
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 20 deletions.
7 changes: 6 additions & 1 deletion lib/ex_machina.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ defmodule ExMachina do
end

def create(built_record) when is_map(built_record) do
__MODULE__.save_record(built_record)
ExMachina.create(__MODULE__, built_record)
end

def create(factory_name, attrs \\ %{}) do
Expand Down Expand Up @@ -157,6 +157,11 @@ defmodule ExMachina do
# Saves and returns %{name: "John Doe", admin: true}
create(:user, admin: true)
"""

def create(module, built_record) when is_map(built_record) do
module.save_record(built_record)
end

def create(module, factory_name, attrs \\ %{}) do
ExMachina.build(module, factory_name, attrs) |> module.save_record
end
Expand Down
30 changes: 26 additions & 4 deletions lib/ex_machina/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule ExMachina.Ecto do
end

def save_record(record) do
ExMachina.Ecto.save_record(@repo, record)
ExMachina.Ecto.save_record(__MODULE__, @repo, record)
end
end
else
Expand Down Expand Up @@ -104,11 +104,33 @@ defmodule ExMachina.Ecto do
end

@doc """
Saves a record using `Repo.insert!` when `create` is called.
Saves a record and all associated records using `Repo.insert!`
"""
def save_record(repo, record) do
def save_record(module, repo, record) do
if repo do
repo.insert!(record)
record
|> associate_records(module)
|> repo.insert!
end
end

defp associate_records(built_record = %{__struct__: struct}, module) do
association_names = struct.__schema__(:associations)

Enum.reduce association_names, built_record, fn(association_name, record) ->
case association = Map.get(record, association_name) do
%{__meta__: %{state: :built}} ->
association = ExMachina.create(module, association)
put_assoc(record, association_name, association)
%{__meta__: %{state: :loaded}} ->
put_assoc(record, association_name, association)
_ -> record
end
end
end

defp put_assoc(record, association_name, association) do
association_id = "#{association_name}_id" |> String.to_atom
Map.put(record, association_id, association.id)
end
end
57 changes: 42 additions & 15 deletions test/ex_machina/ecto_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ defmodule ExMachina.EctoTest do
end
end

defmodule EctoFactories do
defmodule Factory do
use ExMachina.Ecto, repo: TestRepo

factory :user do
Expand All @@ -59,15 +59,15 @@ defmodule ExMachina.EctoTest do
factory :article do
%Article{
title: "My Awesome Article",
author_id: assoc(:author, factory: :user).id
author: assoc(:author, factory: :user)
}
end

factory :comment do
%Comment{
body: "Great article!",
article_id: assoc(:article).id,
user_id: assoc(:user).id
article: assoc(:article),
user: assoc(:user)
}
end
end
Expand All @@ -87,7 +87,7 @@ defmodule ExMachina.EctoTest do
end

test "fields_for/2 removes Ecto specific fields" do
assert EctoFactories.fields_for(:user) == %{
assert Factory.fields_for(:user) == %{
id: nil,
name: "John Doe",
admin: false
Expand All @@ -96,37 +96,57 @@ defmodule ExMachina.EctoTest do

test "fields_for/2 raises when passed a map" do
assert_raise ArgumentError, fn ->
EctoFactories.fields_for(:user_map)
Factory.fields_for(:user_map)
end
end

test "save_record/1 inserts the record into @repo" do
model = EctoFactories.save_record(%User{name: "John"})
model = Factory.save_record(%User{name: "John"})

new_user = TestRepo.one!(User)
assert model == new_user
end

test "save_record/1 saves associated records and sets the association id" do
author = Factory.build(:user)
article = Factory.save_record(%Article{title: "Ecto is Awesome", author: author})

assert article.author_id == 1
assert article.title == "Ecto is Awesome"
assert TestRepo.get_by(Article, title: "Ecto is Awesome", author_id: 1)
assert TestRepo.one(User)
end

test "save_record/1 assigns the id of already saved records" do
author = Factory.create(:user)
article = Factory.save_record(%Article{title: "Ecto is Awesome", author: author})

assert article.author_id == author.id
assert article.title == "Ecto is Awesome"
assert TestRepo.get_by(Article, title: "Ecto is Awesome", author_id: author.id)
assert TestRepo.one(User)
end

test "assoc/3 returns the passed in key if it exists" do
existing_account = %{id: 1, plan_type: "free"}
attrs = %{account: existing_account}

assert ExMachina.Ecto.assoc(EctoFactories, attrs, :account) == existing_account
assert ExMachina.Ecto.assoc(Factory, attrs, :account) == existing_account
end

test "assoc/3 does not insert a record if it exists" do
existing_account = %{id: 1, plan_type: "free"}
attrs = %{account: existing_account}

ExMachina.Ecto.assoc(EctoFactories, attrs, :account)
ExMachina.Ecto.assoc(Factory, attrs, :account)

TestRepo.all(User) == []
assert TestRepo.all(User) == []
end

test "assoc/3 builds and returns a factory if one was not in attrs" do
attrs = %{}

user = ExMachina.Ecto.assoc(EctoFactories, attrs, :user)
user = ExMachina.Ecto.assoc(Factory, attrs, :user)

refute TestRepo.one(User)
assert user.name == "John Doe"
Expand All @@ -136,17 +156,24 @@ defmodule ExMachina.EctoTest do
test "assoc/3 can specify a factory for the association" do
attrs = %{}

account = ExMachina.Ecto.assoc(EctoFactories, attrs, :account, factory: :user)
account = ExMachina.Ecto.assoc(Factory, attrs, :account, factory: :user)

assert account == EctoFactories.build(:user)
assert account == Factory.build(:user)
refute TestRepo.one(User)
end

test "can use assoc/3 in a factory to override associations" do
my_article = EctoFactories.create(:article, title: "So Deep")
my_article = Factory.create(:article, title: "So Deep")

comment = EctoFactories.create(:comment, article: my_article)
comment = Factory.create(:comment, article: my_article)

assert comment.article == my_article
end

test "chaining build and create" do
Factory.build(:article, title: "Ecto is Awesome") |> Factory.create

article = TestRepo.get_by!(Article, title: "Ecto is Awesome")
assert article.author_id
end
end

0 comments on commit 59cbef5

Please sign in to comment.