Это третья из серии статей, которые я написал о своем первом опыте создания серверного API в среде Elixir Phoenix. Я также писал сообщения о OAuth, Plug, авторизациях и пользовательских проверках. Попутно я узнал несколько небольших указателей, когда дело дошло до тестирования, маршрутизации и других вещей, которые, как мне кажется, полезно знать всем, кто набирает обороты на Phoenix.

Я хотел написать краткое руководство по тому, с чем столкнется почти любой, кто создает приложение Phoenix: ассоциациям. Предположим, у меня есть пользовательский ресурс из первого сообщения в этой коллекции, и я уже настроил базовый Post ресурс для API серверной части моего блога со схемой и миграцией, которые выглядят следующим образом:

Я хочу установить связь, согласно которой сообщение принадлежит пользователю, а у пользователя много сообщений. Давайте напишем тест, чтобы заявить о своих амбициях:

Это второй или третий раз, когда мы создаем тот же User для теста. Если я захочу что-то изменить в User, мне потребуется много работы, поэтому я очень быстро вставлю это в помощник по тестированию в test/support/test_helpers.ex:

Теперь назначение пользователя в setup можно заменить на

user = TestHelpers.user_fixture()

Хорошо, вернемся к настоящему тесту. Post будет связан с User через внешний ключ user_id, поэтому мы создаем пользователя, а затем назначаем его id новому полю Post user_id в строке 18. setup сохраняет эти вновь созданные тестовые ресурсы в context, который мы делаем доступным для тест, передавая его test/2 в строке 24. Если мы хотим получить доступ к ассоциациям ресурса, нам нужно предварительно загрузить их, когда мы получаем его из базы данных, поэтому строка 27 говорит, что мы хотим получить доступ к User, которому принадлежит Post. Это то, что позволяет нам вызывать post.user в утверждении. Запуск этого теста должен вызвать ошибку компиляции прямо сейчас, потому что ключ user_id еще не существует в нашем Post ресурсе. Мы можем сгенерировать миграцию, чтобы добавить столбец внешнего ключа в нашу posts таблицу с помощью

mix ecto.gen.migration add_user_id_to_posts_table,

и добавьте блок alter table следующим образом:

Прежде чем приступить к миграции, давайте добавим связи в схемы User и Post:

Теперь, если мы запустим mix ecto.migrate, наши тесты должны пройти! Если вы столкнетесь с ошибками, вы можете попробовать dropdb blog_api_test. Обычно, когда я сталкиваюсь с ошибками при выполнении тестов после того, как возился с базой данных, это и есть виноват.

Теперь наши отношения «принадлежит / имеет много» между Post и User установлены, и мы можем получить доступ к сообщениям User, вызвав Repo.preload(User, :posts) после загрузки ресурса User, а также для доступа к Post User. Давайте отразим эту взаимосвязь в маршрутизаторе, определив /posts ресурсы в /users:

Я добавил маршрут GET, который не ограничен /users для /posts, потому что действие index не нужно ограничивать пользователя, и пропустил действия index, new и edit из вложенного ресурса, потому что я просто обслуживаю вверх данные JSON, а не формы HTML. При добавлении param: "uuid" используется поле ресурсов uuid, чтобы идентифицировать его в маршруте. Это предпочтительнее использования поля serial id, потому что из uuid нельзя вывести ничего, что можно было бы использовать для идентификации другого ресурса в базе данных. Запуск mix phx.routes покажет нам наши новые маршруты и помощники по маршрутам:

Это нарушит работу многих PostController тестов, которые были сгенерированы Phoenix, потому что они не создают User, они реализуют post_path помощников маршрутов, которых больше не существует, и они маршрутизируют на основе идентификатора, а не UUID. Мы уже рассмотрели шаблонное действие и проверили, что Phoenix сгенерировал, когда мы создали наш Post ресурс, поэтому вот основные изменения, которые нам нужны, чтобы сделать связь в действии контроллера и снова пройти тест (вам нужно будет сделать аналогичные изменения в других тестах, чтобы они снова прошли):

Я создаю User и использую init_test_session для вставки их UUID в сеанс, чтобы наш плагин SetUser (из второго поста в этой коллекции) делал пользователя доступным через conn.assigns. Я также изменил Routes.post_path на Routes.user_post_path, потому что вызываемые пункты для Routes.post_path больше не существуют. Теперь тест должен пройти, и текущему пользователю будут назначены новые сообщения. Вернуться к авторизациям!