Как загрузить изображение из React в Rails API, используя React-hook-form и Context API

Как загрузить изображение из React в Rails API, используя React-hook-form и Context API

11 ноября 2022 г.

Прочитав эту статью, вы сможете создать пользовательский интерфейс React, позволяющий использовать react-hook-form для загрузки изображения в конечную точку API. Он также решит проблемы, возникающие при несоблюдении соглашений.

Необходимое условие

Эта статья предназначена для читателей, которые уже знакомы со следующими темами.

  • создание приложения Rails только для API
  • с базовыми знаниями React

Рельсы

Итак, прежде всего, мы должны создать проект Rails только для API, следуя инструкциям

как показано ниже, введите команду для создания нового приложения rails со значением по умолчанию ==--api== и PostgreSQL в качестве базы данных по умолчанию.

rails new myprojectname -d postgresql --api

Перейдите в папку проекта и запустите bundle install

.

Поскольку мы используем PostgreSQL в качестве нашей базы данных. Давайте настроим базу данных, перейдя по следующему пути ==config/database.yml==, затем зададим хост, имя пользователя и пароль, как в примере ниже

default: &default
  adapter: postgresql
  encoding: unicode
  host: localhost
  username: postgres
  password: myPassword123

Запустите rails db:create, чтобы создать базу данных.

Наша база данных PostgreSQL готова.

Пришло время настроить кор, чтобы другие клиенты могли использовать наш API. Давайте откроем файл gem и добавим gem rack-cors, как показано ниже.

gem "rack-cors"

а затем запустите установку пакета

Затем в config/initializers/cors.rb установите в качестве источника URL-адрес вашего клиента, как показано ниже.

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    # origins "www.clienturl.com"
    origins 'http://localhost:3006'
    resource "*",
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

поскольку в этой статье будет рассмотрена только местная среда.

Давайте сделаем URL-адрес среды разработки по умолчанию. Перейдите к config/environments/development.rb и напишите приведенный ниже код.

Rails.application.routes.default_url_options = {
    host: "http://localhost:3000"
}

Начнем с модели Post, которую я создам с помощью генератора каркаса, введя команду ниже.

rails g scaffold Post caption:string

затем запустите миграцию

rails db:migrate

Наш REST API теперь готов, и мы можем выполнять вызовы API из терминала, но не можем загрузить изображение. Чтобы включить загрузку изображений, мы должны сначала установить активное хранилище, выполнив приведенную ниже команду.

bin/rails active_storage:install
rails db:migrate

Теперь, используя путь ==app/models/post.rb==, перейдите к модели публикации и добавьте код вложения изображения, как показано ниже.

class Post < ApplicationRecord
    has_one_attached :image

end

Вы также можете добавить проверку модели, включив приведенный ниже код.

 validates :caption, :image, presence: true

Затем в контроллере сообщений разрешите атрибут изображения, как показано ниже

 def post_params
    params.require(:post).permit(:caption, :image)
 end

Теперь API возвращает данные в формате JSON, как показано ниже.

[
  {
    "id": 14,
    "caption": "Jumping rope time",
    "created_at": "2022-10-31T06:00:32.614Z",
    "updated_at": "2022-10-31T06:00:32.645Z"
  },
  {
    "id": 15,
    "caption": "Today's fashion",
    "created_at": "2022-10-31T06:00:50.969Z",
    "updated_at": "2022-10-31T06:00:50.997Z"
  }
]

Приведенные выше данные являются атрибутами, возвращаемыми из нашего ответа REST API. мы видим ==id, caption, created_at,== и ==updated_at==, но без атрибута изображения. Нам нужен способ представления наших данных с помощью сериализаторов или представителей, чтобы исправить это. Вы можете использовать гем сериализатора активной модели, но я не буду вдаваться в подробности в этой статье. Я собираюсь создать свои представители, которые будут определять, какие атрибуты возвращаются в виде JSON, ограничивая и разрешая некоторые данные, например, вы можете скрыть дату-время< /strong> при отображении URL-адреса изображения.

Поскольку наши данные поступают из действия index в posts_controller.rb, метод выглядит следующим образом:

def index
  @posts = Post.all
  render json: @posts
end

Нам нужно заменить приведенный выше код на

def index
   @posts = Post.all 
   render json: PostRepresenter.new(@posts).as_json
end

Поскольку класс PostRepresenter не определен, как вы можете видеть выше, мы должны сначала открыть папку нашего приложения, затем создать папку с представителями и, наконец, создать файл с именем posts_representers.rb и добавить следующий код. к нему.

class PostsRepresenter
    def initialize(posts)
      @posts = posts
    end

    def as_json
      posts.map do |post|
        {
          id: post.id,
          caption: post.caption,
          image: post.imageUrl
        }
      end
    end

    private

    attr_reader :posts
  end

Класс PostsRepresenter, представленный приведенным выше кодом, имеет метод с именем as_json. Он использует метод блока карты для многократного перебора всех сообщений и отображения информация в формате JSON.

Как видите, мы отобразили атрибут изображения image: post.imageUrl, но ==imageUrl== не определен, поэтому давайте определим его в app/model/post.rb файл.

 def imageUrl   
    Rails.application.routes.url_helpers.url_for(image) if image.attached?
 end

В результате наша модель Post будет выглядеть так:

class Post < ApplicationRecord
    has_one_attached :image
    validates :caption, :image, presence: true

    def imageUrl   
      Rails.application.routes.url_helpers.url_for(image) if image.attached?
    end
end

Наш REST API теперь завершен, и любой может использовать его с помощью любого внешнего приложения, такого как React, Vue или других, и он возвращает ответ JSON, показанный ниже.

[
  {
    "id": 14,
    "caption": "Jumping rope time",
    "image": "http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBFdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a216f6670f89e8f6d39fbce189af62e3d45633d6/jump%20rope.jpg"
  },
  {
    "id": 15,
    "caption": "Today's fashion",
    "image": "http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBGQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--be43dde8cfaa13e097b95e936e1a9cfd817f21e3/fashion.png"
  },
  {
    "id": 25,
    "caption": "The sport I like",
    "imageUrl": "http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBIZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--31fcdcf03292d53f4d9df25c1a46dc12844992f5/jump%20rope.jpg"
  }
]

Часть React.js

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

npx create-react-app imageuploader

cd imageuploader

Поскольку API rails работает на порту 3000, давайте изменим интерфейсный порт на 3006, создав файл ==.env== и вставив приведенный ниже код.

PORT=3006

затем, введя следующие команды, мы установим зависимости, которые нам понадобятся для этого проекта.

npm install axios
npm install react-hook-form 

Поскольку AddPost.js и DisplayPost.js в настоящее время пусты, мы должны сначала создать их, прежде чем импортировать в App.js. Мы понимаем, что должны использовать контекстный API для управления состояниями. Чтобы использовать его во всех наших компонентах, мы создадим контекст PostContext. После этого мы применим этот контекст к возвращенному jsx. Любой компонент приложения сможет получить доступ к состоянию.

import { createContext, useState } from "react";
import AddPost from "./components/AddPost";
import DisplayPost from "./components/DisplayPost";

export const PostContext = createContext(null); // Defining the context

function App() {
  const [post, setPost] = useState(PostContext) // Defining the states using context
  return (
    <PostContext.Provider value={{post, setPost}}> // Wrapping the app with the context API
      <div className="container">
       <AddPost />
       <DisplayPost />
      </div>
    </PostContext.Provider>
  );
}

export default App;

Теперь мы должны сосредоточиться на компоненте AddPost.js. Давайте создадим форму, которая при отправке отправит запрос POST к API и сохранит ответ в состоянии. Чтобы использовать форму реакции-хука, мы должны сначала импортировать хук useForm. Этот хук имеет метод ==handleSubmit==, который получает обратный вызов sendDataToApi, который получает входные данные формы. Чтобы загрузить изображение, мы должны использовать объект FormData() и добавить входные данные формы. Обычной практикой является использование строки, содержащей rails =="modelname[attribute]"==. Если имя вашей модели Post и у нее есть такой атрибут, как image, вы можете использовать "post[image]" в качестве ключа для данные формы; в противном случае бэкэнд-приложение rails выдаст сообщение об ошибке Недопустимый параметр::image. Когда почтовый запрос выполнен успешно, вы должны сохранить состояние данных ответа в API контекста.

import React, { useContext, useState } from 'react'
import axios from 'axios';
import { useForm } from 'react-hook-form';
import { PostContext } from '../App';

const AddPost = () => {
  const { register, handleSubmit, reset } = useForm(); // 
  const {post, setPost} = useContext(PostContext);

  const sendDataToApi = (data) => {
    const formData = new FormData()
    const post = { ...data, image: data.image[0] }
    formData.append('post[caption]', post.caption)
    formData.append('post[image]', post.image)
    console.log(formData)
    axios.post('http://localhost:3000/posts', formData, {
      headers: { 'Content-Type': 'multipart/form-data' },
      withCredentials: true,
    })
    .then((response) => {
      if(response.data.status === 'created') {
        setPost(response.data.post)
      }
    })
    reset()
  };

 return (
    <div>
      <h1>Wall of fame</h1>

      <form className="form" onSubmit={handleSubmit(sendDataToApi)}>
        <div className="form-floating mb-2 col-10">
          <input type="file"  name="image"  {...register('image')} accept="image/*" />
          <label htmlFor="floatingInputImage">Image</label>
        </div>
        <div className="form-floating mb-2 col-10">
          <input type="file"  name="caption"  {...register('caption')} accept="image/*" />
          <label htmlFor="floatingInput">Caption</label>
        </div>

        <div className="form-floating mb-3 col-10">
           <button type="submit" className="btn btn-primary ">Add Car</button>
        </div>
      </form>
    </div>
  )
}

export default AddPost

Давайте перейдем к DisplayPost.js, здесь, на обновлении страницы, хук useEffect извлечет все сообщения с помощью запроса на получение и сохранит их в контекстном API с помощью метода setPost. См. приведенный ниже код, он хорошо объясняет это.

import React, { useContext, useEffect } from 'react'
import axios from 'axios'
import { AppContext } from '../App'
import Loading from './Loading';


const DisplayPost = () => {
    const {post, setPost} = useContext(AppContext);

    useEffect(() => {
        axios.get('http://localhost:3000/posts')
        .then((response) => {
        setPost(response.data)
        })
    }, [setPost])



  return (
    <div className='container border border-info'>
        <div className="row">
            {
                Array.from(post).map((data) => {
                    return (
                        <div className="col-3"  key = {data.id}>
                            <div className="card">
                                <img className="card-img-top" src={data.image} alt="url for foto" />
                                <div className="card-body">
                                  <p className="card-text">{data.caption}</p>
                                </div>
                            </div>
                        </div>
                    )
                })
            }
        </div>
    </div>
  )
}

export default DisplayPost

Теперь, когда внешний интерфейс завершен, вы можете локально загрузить изображение в серверную часть Ruby on Rails API. Чтобы это работало в производственной среде, мы должны хранить фотографии в таких сервисах, как сегменты AWS S3, о которых я расскажу в следующей статье.

Спасибо за ваше время.


Оригинал