We have quite a few tests where we manually put in relationships. We do something like:
defp build_relationships(relationship_map, user) do
relationship_map |> Map.put(:user, %{data: %{id: user.id}})
end
This works fine, but I want to make it flexible to accept any type of record (not just a user). For reference, user
is created with ex-machina
as insert(:user)
.
Is there a way to get the type
of record that is passed in? If I could get a string that says "user"
, I could use String.to_atom(param)
and pass that to Map.put
but I can't find an elegant way to do this.
The real question is how do I take a record like user
and return an atom of :user
?
Any help is appreciated.
You can get some information about an Ecto struct using .__struct__.__schema__/1
, for example:
iex(1)> %Post{}.__struct__.__schema__(:source)
"posts"
but since there's no 1:1 mapping from a Schema to an ExMachina factory, there are 3 possible ways I can think of:
Rename the factories to use the plural form, same as the name of the table, and use .__struct__.__schema__(:source)
to get the name. This will make your factory code a bit weird to read as you'll then use insert(:users)
to insert one user.
Keep using singular names but use some library to convert plural table names to singular ones, e.g. something which converts "users"
to "user"
and "posts"
to "post"
.
Keep a list of Schema <-> Factory name mapping, like this (or you can even move the mapping to a separate function):
defp build_relationships(relationship_map, struct) do
mapping = %{MyApp.User => :user, MyApp.Post => :post} # add more here
relationship_map |> Map.put(mapping[struct.__struct__], %{data: %{id: struct.id}})
end
Use Module.split/1
to get the name of the model and use Macro.underscore/1
to convert it to a lowercase + underscored name, and then use String.to_existing_atom
:
defp build_relationships(relationship_map, struct) do
name = struct.__struct__ |> Module.split |> List.last |> Macro.underscore |> String.to_existing_atom
relationship_map |> Map.put(name, %{data: %{id: struct.id}})
end
This will not work correctly if you use nested modules as the model, e.g. MyApp.Something.User
.
I would choose 4 if all your models are at the same level of nesting, or 3, if you want to be more explicit.