Динамически создавать отношения JSON-api в Elixir Phoenix

#elixir #phoenix-framework #ecto

#elixir #phoenix-framework #ecto

Вопрос:

У нас есть довольно много тестов, в которых мы вручную устанавливаем отношения. Мы делаем что-то вроде:

 defp build_relationships(relationship_map, user) do
  relationship_map |> Map.put(:user, %{data: %{id: user.id}})
end
 

Это работает нормально, но я хочу сделать его гибким, чтобы принимать записи любого типа (а не только пользователя). Для справки, user создается с ex-machina помощью as insert(:user) .

Есть ли способ получить type запись of, которая передается? Если бы я мог получить строку с надписью "user" , я мог бы использовать String.to_atom(param) и передать ее, Map.put но я не могу найти элегантный способ сделать это.

Реальный вопрос в том, как мне взять запись типа user и вернуть атом :user ?

Любая помощь приветствуется.

Ответ №1:

Вы можете получить некоторую информацию о структуре Ecto, используя .__struct__.__schema__/1 , например:

 iex(1)> %Post{}.__struct__.__schema__(:source)
"posts"
 

но поскольку нет сопоставления 1: 1 из схемы на фабрику ExMachina, я могу придумать 3 возможных способа:

  1. Переименуйте фабрики, чтобы использовать форму множественного числа, такую же, как имя таблицы, и используйте .__struct__.__schema__(:source) для получения имени. Это сделает ваш заводской код немного странным для чтения, поскольку затем вы будете использовать insert(:users) его для вставки одного пользователя.
  2. Продолжайте использовать имена в единственном числе, но используйте некоторую библиотеку для преобразования имен таблиц во множественном числе в единственные, например, что-то, что преобразует "users" в "user" и "posts" в "post" .
  3. Сохраняйте список сопоставлений имен схемы <->, например, так (или вы даже можете перенести сопоставление в отдельную функцию):
     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
     
  4. Используйте Module.split/1 , чтобы получить имя модели и использовать Macro.underscore/1 для преобразования его в строчное подчеркнутое имя, а затем используйте 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
     

    Это не будет работать корректно, если вы используете вложенные модули в качестве модели, например MyApp.Something.User .

Я бы выбрал 4, если все ваши модели имеют одинаковый уровень вложенности, или 3, если вы хотите быть более явным.

Комментарии:

1. Спасибо за быстрый ответ! Я не решаюсь попробовать третий, поскольку в этом проекте так много участников, и я не хочу писать помощника, который необходимо обновить позже. Я попробовал это, и это отлично работает! Я могу выбрать вариант 2. Спасибо за помощь!

2. @sbatson5 Пожалуйста, посмотрите четвертый вариант, который я только что добавил. Я подумал о лучшем способе, который может быть лучше для вашего использования.

3. Спасибо! Это именно то, что мне было нужно. В случае, если вам было любопытно, вы можете увидеть PR, реализующий это здесь: github.com/code-corps/code-corps-api/pull/358/files