defmodule PhilColumns.Factory do alias Ecto.Changeset defmacro __using__(opts) do quote do @before_compile PhilColumns.Factory Module.register_attribute(__MODULE__, :repo, accumulate: false) Module.put_attribute(__MODULE__, :repo, unquote(opts[:repo])) import PhilColumns.Factory, only: [sequence: 1, sequence: 2] def build(factory_name, attrs \\ %{}) do PhilColumns.Factory.build(__MODULE__, factory_name, attrs) end # def build_pair(factory_name, attrs \\ %{}) do # PhilColumns.Factory.build_pair(__MODULE__, factory_name, attrs) # end # def build_list(number_of_factories, factory_name, attrs \\ %{}) do # PhilColumns.Factory.build_list(__MODULE__, factory_name, attrs) # end def insert(factory_name, attrs \\ %{}) do PhilColumns.Factory.insert(__MODULE__, factory_name, attrs) end def params_for(factory_name, attrs \\ %{}) do PhilColumns.Factory.params_for(__MODULE__, factory_name, attrs) end def factory(factory_name) do raise "UndefinedFactoryError: factory(:#{factory_name})" end def factory(factory_name, _) do raise "UndefinedFactoryError: factory(:#{factory_name}, attrs)" end defoverridable factory: 1, factory: 2 end end @doc false defmacro __before_compile__(env) do repo = Module.get_attribute(env.module, :repo) quote do def repo, do: unquote(repo) end end def build(module, factory_name, attrs \\ %{}) do attrs = Enum.into(attrs, %{}) apply(module, :factory, [factory_name]) |> handle_build(attrs) end def insert(module, factory_name, attrs \\ %{}) do attrs = Enum.into(attrs, %{}) repo = module.repo apply(module, :factory, [factory_name]) |> handle_insert(attrs, repo) end def params_for(module, factory_name, attrs \\ %{}) do attrs = Enum.into(attrs, %{}) # |> do_merge(attrs) apply(module, :factory, [factory_name]) |> handle_params_for(attrs) end def sequence(name), do: PhilColumns.Sequence.next(name) def sequence(name, formatter), do: PhilColumns.Sequence.next(name, formatter) # Private ########## defp handle_build(%Changeset{} = changeset, attrs) do changeset |> Changeset.apply_changes() |> do_merge(attrs) end defp handle_build(%{__meta__: _} = record, attrs) do record |> do_merge(attrs) end defp handle_insert(%Changeset{} = changeset, attrs, repo) do record = changeset |> Changeset.apply_changes() |> do_merge(attrs) record |> repo.insert! end defp handle_insert(%{__meta__: _} = record, attrs, repo) do record |> do_merge(attrs) |> repo.insert! end defp handle_params_for(%Changeset{} = changeset, attrs) do changeset |> Changeset.apply_changes() |> do_merge(attrs) |> drop_ecto_fields end defp handle_params_for(%{__meta__: _} = record, attrs) do record |> do_merge(attrs) |> drop_ecto_fields end defp do_merge(%{__struct__: _} = record, attrs) do struct!(record, attrs) end defp do_merge(record, attrs) do Map.merge(record, attrs) end defp drop_ecto_fields( record = %{__struct__: struct, __meta__: %{__struct__: Ecto.Schema.Metadata}} ) do record |> Map.from_struct() |> Map.delete(:__meta__) |> Map.drop(struct.__schema__(:associations)) |> Map.drop(struct.__schema__(:primary_key)) end defp drop_ecto_fields(record) do raise ArgumentError, "#{inspect(record)} is not an Ecto model. Use `build` instead." end # defp module_from_struct(%{__struct__: struct_name}) do # struct_name # end # defp name_from_struct(%{__struct__: struct_name}) do # struct_name # |> Module.split # |> List.last # |> underscore # |> String.downcase # |> String.to_atom # end # defp underscore(name) do # Regex.split(~r/(?=[A-Z])/, name) # |> Enum.join("_") # end end