В моем последнем посте мы начали создавать простое приложение React, которое использовало Rails API с Active Storage на бэкэнде для хранения информации о пользователях (включая файл изображения для их аватара). Мы создали пользователя в нашем исходном файле, прикрепили к нему файл изображения (в форме blob), и мы смогли успешно получить информацию об этом пользователе из серверной части и отобразить его аватар на интерфейсе.

В этом посте (часть 2) мы продолжим создавать наше приложение, давая пользователю возможность создать учетную запись из нашего интерфейса, включая возможность загружать изображение аватара со своего компьютера, и мы будем хранить эту информацию. на бэкэнде. Если вы пропустили часть 1 этого поста, вы можете найти ее здесь.

Разрешение выхода пользователя из системы

Прежде чем мы перейдем к загрузке изображения, давайте дадим нашим пользователям возможность выйти из системы. Для этого я сначала проведу рефакторинг своего компонента NavBar. Если пользователь уже вошел в систему, он не должен видеть параметры для входа или создания учетной записи на панели NavBar - вместо этого он должен видеть параметр выхода.

Обратите внимание, что теперь наш NavBar ожидает некоторых реквизитов. Давайте обязательно передадим их из нашего компонента приложения.

Теперь мы передаем состояние для нашего CurrentUser и передаем функцию logout (), которая сбрасывает состояние в нулевое значение, когда пользователь выходит из системы. Давайте также изменим наше условие для маршрута профиля, чтобы перенаправить пользователя на страницу входа, если он еще не вошел в систему, вместо повторной визуализации компонента входа. Для этого нам нужно добавить компонент перенаправления в список вещей, которые мы импортируем из «response-router-dom» в верхней части нашего файла.

import { Route, Switch, Redirect } from 'react-router-dom';

Это делает наш код немного чище и менее уязвимым.

Отправка изображений на серверную часть

Теперь перейдем к нашему компоненту CreateAccount и создадим эту форму.

Эта форма должна выглядеть очень знакомой - она ​​почти такая же, как наша форма входа, но с некоторыми незначительными (но очень важными) отличиями. Во-первых, обратите внимание, что теперь у нас есть дополнительное поле ввода с типом «файл». Это простое небольшое изменение дает вам кнопку «Выбрать файл» в вашей форме. Когда пользователь нажимает на эту кнопку, автоматически появляется окно (подобное окну Finder), в котором пользователь может выбрать файл, хранящийся на его компьютере.

Все три поля ввода используют функцию handleOnChange (), которая устанавливает состояние на основе значения каждого поля ввода. Однако обратите внимание, что когда мы заполняем эту форму, состояние аватара представляет собой странную строку «fakepath». Это не то, что нам нужно - нам действительно нужен сам файл.

Вернемся к нашей функции handleOnChange () и проведем небольшой рефакторинг ...

Поскольку то, что мы хотим от ввода нашего файла, - это не значение (которое представляет собой простую строку), а сам файл, мы можем реорганизовать нашу функцию, чтобы сначала проверить, имеет ли наше поле ввода имя 'avatar '- если это так, то нам нужен файл, который загрузил наш пользователь. Мы можем получить этот файл, используя

event.target.files[0]

Если поле ввода имеет имя «имя» или «пароль», мы будем брать значения из этих полей, как и раньше.

Если мы правильно захватили наш файл, мы должны увидеть его в состоянии…

Прохладный. Теперь this.state.avatar - это файловый объект с именем, размером и типом!

Давайте также дадим нашей форме прослушиватель событий onSubmit…

Чтобы сохранить информацию о нашем пользователе в серверной части, мы разделим это на несколько шагов. Сначала мы сделаем выборку сообщения для «/ users», чтобы создать нового пользователя, а затем напишем отдельный метод для обработки загрузки изображения.

На данный момент мы просто записываем нашу информацию в консоль - через мгновение мы рассмотрим, что мы собираемся делать с данными, которые мы получаем от нашего вызова fetch. Давайте сначала обратимся к нашему бэкэнду и заполним действие create в нашем пользовательском контроллере ...

Теперь, когда мы создаем нового пользователя, мы видим, что его информация записана в консоль.

После того, как наш экземпляр User был успешно сохранен на серверной части, теперь мы можем написать логику для загрузки его аватара. Мы сделаем это, написав функцию uploadFile (), которая будет принимать два аргумента: файл, который мы хотим загрузить, и объект пользователя, к которому мы хотим его прикрепить. Мы вызовем эту функцию, как только получим информацию о пользователе с сервера.

Теперь здесь проявляется магия нашей библиотеки activestorage из диспетчера пакетов узлов. В верхней части нашего файла CreateAccount.js нам нужно импортировать компонент DirectUpload из этой библиотеки ...

import { DirectUpload } from 'activestorage';

Затем мы создадим новый экземпляр этого компонента в нашей функции uploadFile (), передав ему два аргумента - файл, который мы хотим загрузить, и URL-адрес.

Если мы проверим наши маршруты на бэкэнде, мы увидим, что этот URL-адрес действительно является допустимым путем - он был предоставлен нам, когда мы установили Active Storage в наше приложение Rails.

Несмотря на то, что этот маршрут действительно существует, нам нужно явно добавить его к нашим маршрутам в нашей папке конфигурации на бэкэнде. * Не забудьте убить свой бэкэнд-сервер, а затем снова запустить его, чтобы принять эти изменения.

Теперь давайте попробуем создать эту загрузку. Метод create, предоставляемый Active Storage, принимает функцию обратного вызова. Эта функция обратного вызова принимает два аргумента - ошибку и большой двоичный объект. Если наш метод создания не увенчался успехом, давайте запишем ошибку в журнал, иначе давайте запишем, что ошибки нет.

Также обратите внимание, что этот маршрут идет к действию create внутри контроллера direct_uploads, которое поступает из Active Storage. Быстрый взгляд на ваши контроллеры на вашем сервере покажет, что этого контроллера не существует (по крайней мере, нигде, где мы можем его найти). Давайте построим это!

Обратите внимание, что наш новый DirectUploadsController наследуется от ActiveStorage :: DirectUploadsController. Давайте заполним нашу форму еще раз, чтобы создать новую учетную запись и посмотреть, что произойдет…

Ух ... мы получаем это ужасно уродливое и загадочное сообщение об ошибке - 422, Unprocessable Entity. Мы не нашли временную ошибку в бэкэнде, но мы видим, что наша функция create () из внешнего интерфейса действительно вызвала ошибку console.log.

Проблема здесь связана с подделкой межсайтовых запросов. Из документации Ruby on Rails:

Действия контроллера защищены от атак подделки межсайтовых запросов (CSRF) путем включения токена в отображаемый HTML-код вашего приложения. Этот токен хранится в сеансе как случайная строка, к которой злоумышленник не имеет доступа. Когда запрос достигает вашего приложения, Rails проверяет полученный токен с токеном в сеансе. Проверяются все запросы, кроме запросов GET, поскольку они должны быть идемпотентными. Имейте в виду, что все запросы, ориентированные на сеанс, по умолчанию защищены CSRF, включая запросы JavaScript и HTML.

Что ж, это нормально, но как нам это обойти? Давайте поместим вспомогательный метод skip_before_action в наш DirectUploadsController…

skip_before_action :verify_authenticity_token

* Отказ от ответственности - я новичок в программировании, и это был единственный метод, который я нашел, чтобы заставить activestorage работать с моим «игрушечным» приложением. Если вы создаете «настоящее» приложение (то есть приложение, которое реальные люди будут фактически использовать в реальной сети), вы не хотите просто пропускать какие-либо методы. которые предназначены для предотвращения проблем с CSRF!

Теперь, когда мы добавили эту строку кода, когда мы отправляем нашу форму, мы должны приостановить нашу прощальную ошибку от действия create в нашем терминале.

Присмотревшись к нашим параметрам, мы видим, что у нас есть объект, blob, со всей информацией нашего файла изображения. Мы будем использовать это для создания экземпляра Blob (с методом, предоставленным Active Storage).

Повторно отправив форму и проверив значение blob, мы видим, что у нас действительно есть объект Blob с его собственным идентификатором, ключом и всем остальным, что нам нужно отслеживать. Если мы также проверим базу данных, мы увидим, что теперь у нас есть этот экземпляр большого двоичного объекта, хранящийся в нашей таблице active_storage_blobs.

Прохладный! Теперь нам нужно отправить этот только что созданный большой двоичный объект обратно в наш интерфейс (в формате json), где мы сделаем еще один вызов fetch в бэкэнд, который обновит нашего пользователя, прикрепив файл аватара к экземпляру пользователя. Уф!

Используя частный метод direct_upload_json, мы анализируем наш объект blob в формате json, а затем отправляем эту информацию обратно во внешний интерфейс. Обратите внимание, что вместе с нашим объектом мы также передаем метод signed_id. Мы скоро воспользуемся этим методом в нашем интерфейсе. На данный момент я разместил отладчик в нашей функции uploadFile (), который сработает, если мы не получим сообщение об ошибке…

А теперь мы можем проверить значение blob…

Давайте теперь обновим нашего пользователя нашим новым экземпляром большого двоичного объекта, созданным только что с сервера, путем выполнения еще одного запроса на выборку.

Обратите внимание, что теперь мы используем этот метод signed_id. Чтобы прикрепить файл большого двоичного объекта к нашему пользователю, Active Record нужен ключ для их соединения - это то, что для нас делает signed_id. В нашем бэкэнде нам нужно заполнить действие обновления в нашем пользовательском контроллере.

Снова приостановив временную ошибку, мы можем проверить наш экземпляр пользователя после, который обновил свой аватар. Однако помните, что этот аватар не является атрибутом пользователя, а является прикрепленным файлом. Если мы проверим значение avatar_url, мы увидим, что он был создан.

Давайте также проверим нашу базу данных, на всякий случай ...

У нашего нового пользователя сверху (с идентификатором 18) действительно есть прикрепленный аватар! Blob_id этого аватара равен 10, поэтому давайте проверим нашу таблицу blob, чтобы убедиться, что он там ...

А есть blob 10! Потрясающие! Мы успешно загрузили файл от нашего пользователя, и прикрепили этот файл к этому пользователю! Теперь мы можем вернуться к интерфейсу и использовать эту информацию из нашего вызова fetch, чтобы установить состояние для наших CurrentUser и CurrentAvatar.

В нашем компоненте приложения мы можем написать функцию updateCurrentUser (), которая будет принимать данные, которые мы получаем из этого вызова выборки, и устанавливать состояние с нашей новой информацией. Затем мы передадим эту функцию в качестве свойств компоненту CreateAccount. И последнее, но не менее важное: давайте изменим последнюю часть нашего вызова fetch, чтобы вызывать эту функцию с данными, которые мы получаем обратно ...

Теперь создайте пользователя, а затем щелкните ссылку, чтобы перейти на страницу профиля этого пользователя ...

Бац! Хорошо, Харрисон.