Ruby on Rails һәм Active Storage серверга React TypeScript кушымтадан Ant Design һәм GraphQL API кулланып туры файл йөкләү ясавы.
Проблема
Бер яктан, Ant Design библиотекасының Upload компоненты файл сайлау һәм серверга йөкләү өчен кулланыла ала. Гадәттә, бу компонентны куллану бик җиңел: компонентка йөкләү URL-ны бирсәгез, Ant үзе файл кушып POST таләбе ясаячак.
import { Upload, Button } from 'antd';
const Test = () => (
<Upload
name='avatar'
action='https://example.com/avatar'
>
<Button>Click to Upload</Button>
</Upload>
);
Икенче яктан, Rails файл йөкләү кырыны күрсәтелештә ясарга тәкъдим итә:
<%= form.file_field :avatar, direct_upload: true %>
Проектыбызда без Rails күрсәтелешләрне кулланмадык һәм бөтен разметканы React-та гына ясадык. Шуңа күрә, безгә башка файл йөкләү ысулы кирәк.
Чишелеш
Чишелешебез Active Storage meets GraphQL: Direct Uploads, How to Use ActiveStorage Outside of a Rails View мәкаләләргә һәм бу StackOverflow җавапка нигезләнә.
Active Storage-ның туры файл йөкләү берничә адымдан тора:
- Клиент файлдан метамәгълүматны ала
- Клиент метамәгълүматны серверга җибәрә
- Сервер Сервис белән файл йөкләүне әзерли
- Сервер Клиентка йөкләү URL-ны һәм кирәкле башламаларны җибәрә
- Клиент йөкләү URL-ны һәм башламаларны кулланып Сервиска файлны йөкли
Бу үрнәктә без GraphQL-ны кулланабыз, шуңа күрә адымнар 2, 3, 4 GraphQL мутацияне кулланачаклар.
Сервер ягы
Йөкләү көйләүләре файл метамагълүматларга бәйле:
- Файл исеме
- Мәгълүмат тибы
- Контроль суммасы (моның турында түбәндә карагыз)
- Файл зурлыгы
Rails GraphQL контроллер ясау өчен без
graphql
гемны
кулланабыз.
Алдарак әйтелгәнчә, безнең бер мутациябез булачак һәм ул кирәкле файл метамәгълүматны алып, кирәкле йөкләү көйләүләрне бирәчәк:
module Mutations
class CreateDirectUpload < BaseMutation
argument :filename, String, required: true
argument :byte_size, Int, required: true
argument :checksum, String, required: true
argument :content_type, String, required: true
field :url, String, null: false
field :headers, String, 'JSON of required HTTP headers', null: false
field :blob_id, ID, null: false
field :signed_blob_id, ID, null: false
end
end
resolve
ысулы блобны төзәчәк һәм йөкләү көйләүләрне кайтарачак:
module Mutations
class CreateDirectUpload < BaseMutation
def resolve(filename:, byte_size:, checksum:, content_type:)
blob = ActiveStorage::Blob.create_before_direct_upload!(
filename: filename,
byte_size: byte_size,
checksum: checksum,
content_type: content_type
)
{
url: blob.service_url_for_direct_upload,
headers: blob.service_headers_for_direct_upload.to_json,
blob_id: blob.id,
signed_blob_id: blob.signed_id
}
end
end
end
Хәзер клиент бу мутацияне кулланып туры йөкләүне әзерли ала.
Клиент ягы
Мутациянең
checksum
-дан бөтен параметрларның мәгънәсе ачык күренелә. Контроль
сумма юлы махсус ысулда төзеләргә тиеш. Бу ысул
@rails/activestorage
пакетында
урнашкан.
Бонус! TypeScript өчен тип билгеләмәләр
@types/rails__activestorage
пакетында
торалар.
import { FileChecksum } from '@rails/activestorage/src/file_checksum';
const calculateChecksum = (file: File): Promise<string> => (
new Promise((resolve, reject) => (
FileChecksum.create(file, (error, checksum) => {
if (error) {
reject(error);
return;
}
resolve(checksum);
})
))
);
Ant библиотекасындагы Upload компоненты
beforeUpload
функцияне ала, һәм без бу функциядә сервердан йөкләү көйләүләрне
алачакбыз. Бу үрнәктә без бер файл гына йөклибез һәм көйләүләрне
компонентның халәттә саклыйбыз.
import { RcFile } from 'antd/lib/upload';
class Test extends React.Component {
async beforeUpload(file: RcFile): Promise<void> {
// createDirectUploadMutation is a placeholder for your GraphQL request method
const { url, headers } = createDirectUploadMutation({
checksum: await calculateChecksum(file),
filename: file.name.
contentType: file.type,
byteSize: file.size
});
this.setState({ url, headers: JSON.parse(headers) });
}
}
Хәзер без туры йөкләү XHR таләп итә торган функцияне ясый алабыз:
import { RcCustomRequestOptions } from 'antd/lib/upload/interface';
import { BlobUpload } from '@rails/activestorage/src/blob_upload';
class Test extends React.Component {
customRequest(options: RcCustomRequestOptions): void {
const { file, action, headers } = options;
const upload = new BlobUpload({
file,
directUploadData: {
headers: headers as Record<string, string>;
url: action;
}
});
upload.xhr.addEventListener('progress', event => {
const percent = (event.loaded / event.total) * 100;
options.onProgress({ percent }, file);
});
upload.create((error: Error, response: object) => {
if (error) {
options.onError(error);
} else {
options.onSuccess(response, file);
}
});
}
}
Аннары без
beforeUpload
һәм
customRequest
функцияләрне Upload компонентның һукларында куллана алабыз:
class Test extends React.Component {
render() {
return (
<Upload
method='put' // important!
multiple={false}
beforeUpload={(file): Promise<void> => this.beforeUpload(file)}
action={this.state.url}
customRequest={(options): void => this.customRequest(options)}
>
<Button>Click to Upload!</Button>
</Upload>
);
}
}
Rails-ның юлларын яңартырга онытмагыз. Әгәр сез бөтен таләпләрне React-ка юнәлешсәгез:
match '*path', to: 'react#index', via: :all
Сез бу кагыйдән Active Storage юлларны шулай чыгара аласыз:
match '*path', to: 'react#index', via: :all,
constraints: ->(req) { req.path.exclude? 'rails/active_storage' }
Нәкъ менә шулай. Туры йөкләүләрегездә бәхетле булуыгызны телим!