diff --git a/lib/ex_machina.ex b/lib/ex_machina.ex index c79ae80..0fc7354 100644 --- a/lib/ex_machina.ex +++ b/lib/ex_machina.ex @@ -13,16 +13,16 @@ defmodule ExMachina do defexception [:message] def exception(factory_name) do - message = - """ - No factory defined for #{inspect factory_name}. + message = """ + No factory defined for #{inspect(factory_name)}. - Please check for typos or define your factory: + Please check for typos or define your factory: + + def #{factory_name}_factory do + ... + end + """ - def #{factory_name}_factory do - ... - end - """ %UndefinedFactoryError{message: message} end end @@ -30,7 +30,7 @@ defmodule ExMachina do use Application @doc false - def start(_type, _args), do: ExMachina.Sequence.start_link + def start(_type, _args), do: ExMachina.Sequence.start_link() defmacro __using__(_opts) do quote do @@ -70,7 +70,7 @@ defmodule ExMachina do raise_function_replaced_error("create_list/3", "insert_list/3") end - @spec raise_function_replaced_error(String.t, String.t) :: no_return + @spec raise_function_replaced_error(String.t(), String.t()) :: no_return defp raise_function_replaced_error(old_function, new_function) do raise """ #{old_function} has been removed. @@ -82,12 +82,12 @@ defmodule ExMachina do """ end - defoverridable [create: 1, create: 2, create_pair: 2, create_list: 3] + defoverridable create: 1, create: 2, create_pair: 2, create_list: 3 end end @doc """ - Shortcut for creating unique string values. + Shortcut for creating unique string values. This is automatically imported into a model factory when you `use ExMachina`. @@ -114,7 +114,7 @@ defmodule ExMachina do } end """ - @spec sequence(String.t) :: String.t + @spec sequence(String.t()) :: String.t() def sequence(name), do: ExMachina.Sequence.next(name) @@ -157,15 +157,20 @@ defmodule ExMachina do %{name: "John Doe", admin: false} end + # Returns %{name: "John Doe", admin: false} + build(:user) + # Returns %{name: "John Doe", admin: true} build(:user, admin: true) """ + @callback build(factory_name :: atom) :: any @callback build(factory_name :: atom, attrs :: keyword | map) :: any @doc false def build(module, factory_name, attrs \\ %{}) do attrs = Enum.into(attrs, %{}) function_name = build_function_name(factory_name) + if Code.ensure_loaded?(module) && function_exported?(module, function_name, 0) do apply(module, function_name, []) |> do_merge(attrs) else @@ -175,9 +180,9 @@ defmodule ExMachina do defp build_function_name(factory_name) do factory_name - |> Atom.to_string + |> Atom.to_string() |> Kernel.<>("_factory") - |> String.to_atom + |> String.to_atom() end defp do_merge(%{__struct__: _} = record, attrs), do: struct!(record, attrs) @@ -193,6 +198,7 @@ defmodule ExMachina do # Returns a list of 2 users build_pair(:user) """ + @callback build_pair(factory_name :: atom) :: list @callback build_pair(factory_name :: atom, attrs :: keyword | map) :: list @doc false @@ -208,7 +214,9 @@ defmodule ExMachina do # Returns a list of 3 users build_list(3, :user) """ - @callback build_list(number_of_records :: integer, factory_name :: atom, attrs :: keyword | map) :: list + @callback build_list(number_of_records :: integer, factory_name :: atom) :: list + @callback build_list(number_of_records :: integer, factory_name :: atom, attrs :: keyword | map) :: + list @doc false def build_list(module, number_of_records, factory_name, attrs \\ %{}) do @@ -222,7 +230,7 @@ defmodule ExMachina do quote do @doc "Raises a helpful error if no factory is defined." @spec factory(any) :: no_return - def factory(factory_name), do: raise UndefinedFactoryError, factory_name + def factory(factory_name), do: raise(UndefinedFactoryError, factory_name) end end end diff --git a/lib/ex_machina/ecto.ex b/lib/ex_machina/ecto.ex index add2683..3893348 100644 --- a/lib/ex_machina/ecto.ex +++ b/lib/ex_machina/ecto.ex @@ -14,6 +14,7 @@ defmodule ExMachina.Ecto do """ defmacro __using__(opts) do verify_ecto_dep() + if repo = Keyword.get(opts, :repo) do quote do use ExMachina @@ -37,18 +38,18 @@ defmodule ExMachina.Ecto do end else raise ArgumentError, - """ - expected :repo to be given as an option. Example: + """ + expected :repo to be given as an option. Example: - use ExMachina.Ecto, repo: MyApp.Repo - """ + use ExMachina.Ecto, repo: MyApp.Repo + """ end end defp verify_ecto_dep do unless Code.ensure_loaded?(Ecto) do raise "You tried to use ExMachina.Ecto, but the Ecto module is not loaded. " <> - "Please add ecto to your dependencies." + "Please add ecto to your dependencies." end end @@ -57,6 +58,7 @@ defmodule ExMachina.Ecto do The arguments are the same as `c:ExMachina.build/2`. """ + @callback insert(factory_name :: atom) :: any @callback insert(factory_name :: atom, attrs :: keyword | map) :: any @doc """ @@ -64,6 +66,7 @@ defmodule ExMachina.Ecto do The arguments are the same as `c:ExMachina.build_pair/2`. """ + @callback insert_pair(factory_name :: atom) :: list @callback insert_pair(factory_name :: atom, attrs :: keyword | map) :: list @doc """ @@ -71,7 +74,12 @@ defmodule ExMachina.Ecto do The arguments are the same as `c:ExMachina.build_list/3`. """ - @callback insert_list(number_of_records :: integer, factory_name :: atom, attrs :: keyword | map) :: list + @callback insert_list(number_of_records :: integer, factory_name :: atom) :: list + @callback insert_list( + number_of_records :: integer, + factory_name :: atom, + attrs :: keyword | map + ) :: list @doc """ Builds a factory and returns only its fields. @@ -96,7 +104,11 @@ defmodule ExMachina.Ecto do # Returns %{name: "John Doe", admin: true} params_for(:user, admin: true) + + # Returns %{name: "John Doe", admin: false} + params_for(:user) """ + @callback params_for(factory_name :: atom) :: %{optional(atom) => any} @callback params_for(factory_name :: atom, attrs :: keyword | map) :: %{optional(atom) => any} @doc false @@ -121,7 +133,10 @@ defmodule ExMachina.Ecto do # Returns %{"name" => "John Doe", "admin" => true} string_params_for(:user, admin: true) """ - @callback string_params_for(factory_name :: atom, attrs :: keyword | map) :: %{optional(String.t) => any} + @callback string_params_for(factory_name :: atom) :: %{optional(String.t()) => any} + @callback string_params_for(factory_name :: atom, attrs :: keyword | map) :: %{ + optional(String.t()) => any + } @doc false def string_params_for(module, factory_name, attrs \\ %{}) do @@ -145,7 +160,10 @@ defmodule ExMachina.Ecto do # Inserts an author and returns %{title: "An Awesome Article", author_id: 12} params_with_assocs(:article) """ - @callback params_with_assocs(factory_name :: atom, attrs :: keyword | map) :: %{optional(atom) => any} + @callback params_with_assocs(factory_name :: atom) :: %{optional(atom) => any} + @callback params_with_assocs(factory_name :: atom, attrs :: keyword | map) :: %{ + optional(atom) => any + } @doc false def params_with_assocs(module, factory_name, attrs \\ %{}) do @@ -171,7 +189,10 @@ defmodule ExMachina.Ecto do # Inserts an author and returns %{"title" => "An Awesome Article", "author_id" => 12} string_params_with_assocs(:article) """ - @callback string_params_with_assocs(factory_name :: atom, attrs :: keyword | map) :: %{optional(String.t) => any} + @callback string_params_with_assocs(factory_name :: atom) :: %{optional(String.t()) => any} + @callback string_params_with_assocs(factory_name :: atom, attrs :: keyword | map) :: %{ + optional(String.t()) => any + } @doc false def string_params_with_assocs(module, factory_name, attrs \\ %{}) do @@ -180,7 +201,6 @@ defmodule ExMachina.Ecto do |> convert_atom_keys_to_strings end - defp recursively_strip(record = %{__struct__: _}) do record |> set_persisted_belongs_to_ids @@ -193,7 +213,7 @@ defmodule ExMachina.Ecto do defp recursively_strip(record), do: record defp handle_assocs(record = %{__struct__: struct}) do - Enum.reduce struct.__schema__(:associations), record, fn(association_name, record) -> + Enum.reduce(struct.__schema__(:associations), record, fn association_name, record -> case struct.__schema__(:association, association_name) do %{__struct__: Ecto.Association.BelongsTo} -> Map.delete(record, association_name) @@ -203,7 +223,7 @@ defmodule ExMachina.Ecto do |> Map.get(association_name) |> handle_assoc(record, association_name) end - end + end) end defp handle_assoc(original_assoc, record, association_name) do @@ -225,7 +245,7 @@ defmodule ExMachina.Ecto do end defp handle_embeds(record = %{__struct__: struct}) do - Enum.reduce(struct.__schema__(:embeds), record, fn(embed_name, record) -> + Enum.reduce(struct.__schema__(:embeds), record, fn embed_name, record -> record |> Map.get(embed_name) |> handle_embed(record, embed_name) @@ -237,16 +257,18 @@ defmodule ExMachina.Ecto do %{} -> embed = recursively_strip(original_embed) Map.put(record, embed_name, embed) + list when is_list(list) -> embeds_many = Enum.map(original_embed, &recursively_strip/1) Map.put(record, embed_name, embeds_many) + nil -> Map.delete(record, embed_name) end end defp set_persisted_belongs_to_ids(record = %{__struct__: struct}) do - Enum.reduce struct.__schema__(:associations), record, fn(association_name, record) -> + Enum.reduce(struct.__schema__(:associations), record, fn association_name, record -> association = struct.__schema__(:association, association_name) case association do @@ -254,11 +276,15 @@ defmodule ExMachina.Ecto do case Map.get(record, association_name) do belongs_to = %{__meta__: %{__struct__: Ecto.Schema.Metadata, state: :loaded}} -> set_belongs_to_primary_key(record, belongs_to, association) - _ -> record + + _ -> + record end - _ -> record + + _ -> + record end - end + end) end defp set_belongs_to_primary_key(record, belongs_to, association) do @@ -267,14 +293,15 @@ defmodule ExMachina.Ecto do end defp insert_belongs_to_assocs(record = %{__struct__: struct}, module) do - Enum.reduce struct.__schema__(:associations), record, fn(association_name, record) -> + Enum.reduce(struct.__schema__(:associations), record, fn association_name, record -> case struct.__schema__(:association, association_name) do association = %{__struct__: Ecto.Association.BelongsTo} -> insert_built_belongs_to_assoc(module, association, record) - _ -> record + _ -> + record end - end + end) end defp insert_built_belongs_to_assoc(module, association, record) do @@ -291,7 +318,7 @@ defmodule ExMachina.Ecto do @doc false def drop_ecto_fields(record = %{__struct__: struct}) do record - |> Map.from_struct + |> Map.from_struct() |> Map.delete(:__meta__) |> drop_autogenerated_ids(struct) end @@ -308,20 +335,23 @@ defmodule ExMachina.Ecto do defp drop_fields_with_nil_values(map) do map - |> Enum.reject(fn({_, value}) -> value == nil end) + |> Enum.reject(fn {_, value} -> value == nil end) |> Enum.into(%{}) end defp convert_atom_keys_to_strings(values) when is_list(values) do Enum.map(values, &convert_atom_keys_to_strings/1) end + defp convert_atom_keys_to_strings(%{__struct__: _} = record) when is_map(record) do Map.from_struct(record) |> convert_atom_keys_to_strings() end + defp convert_atom_keys_to_strings(record) when is_map(record) do - Enum.reduce record, Map.new, fn({key, value}, acc) -> + Enum.reduce(record, Map.new(), fn {key, value}, acc -> Map.put(acc, to_string(key), convert_atom_keys_to_strings(value)) - end + end) end + defp convert_atom_keys_to_strings(value), do: value end