Это третья из серии статей, которые я написал о своем первом опыте создания серверного 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
больше не существуют. Теперь тест должен пройти, и текущему пользователю будут назначены новые сообщения. Вернуться к авторизациям!