model_driven_api 3.5.6 → 3.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/info_controller.rb +168 -0
- data/lib/model_driven_api/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df0f02193f91b162df1248961aabf8c4281440c49dc7f3147cb400db371f68d0
|
|
4
|
+
data.tar.gz: d0c42164dbde60bbb752110daa5e8b757ac8a7e0bfcc5e9b6a67898a02555ec8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4e3c99b867169e19e966832ef085d5d8ffd67399a6a4047fa504a950c75a63b1a96719502d8c9d01dca6efd5d32ae7d9b07d69c8c7ad361a561cde24b957d65
|
|
7
|
+
data.tar.gz: 6bcff8f0e6c408ead804305dd386e827edf582b0e950774bcf9c79cdb9c24fe7577dab86d813e0c1a9cb8ff7e3355079f451d23eb1a8327df658824cd876b167
|
|
@@ -1135,6 +1135,174 @@ Content-Type: application/json
|
|
|
1135
1135
|
"count": 156
|
|
1136
1136
|
}
|
|
1137
1137
|
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
## ActiveStorage Integration: React Frontend & Rails Backend
|
|
1141
|
+
|
|
1142
|
+
### Overview
|
|
1143
|
+
|
|
1144
|
+
This guide explains how to handle file uploads (via Camera or Gallery) and attachment deletions using a **React** frontend and a **Ruby on Rails** backend.
|
|
1145
|
+
|
|
1146
|
+
The Rails model uses a virtual attribute strategy for deletion:
|
|
1147
|
+
|
|
1148
|
+
* **Upload:** handled via `has_many_attached :assets`
|
|
1149
|
+
* **Deletion:** handled via `attr_accessor :remove_assets`
|
|
1150
|
+
|
|
1151
|
+
---
|
|
1152
|
+
|
|
1153
|
+
### 1. Handling File Objects (No "Paths" needed)
|
|
1154
|
+
|
|
1155
|
+
In a web/mobile context (React Web or PWA), you do not need a file system path. When a user takes a photo or selects a file, the browser creates a native **`File`** object (a type of `Blob`).
|
|
1156
|
+
|
|
1157
|
+
You must send this binary object to the backend using **`FormData`**.
|
|
1158
|
+
|
|
1159
|
+
#### React Component Example
|
|
1160
|
+
|
|
1161
|
+
This component handles:
|
|
1162
|
+
|
|
1163
|
+
1. **File Input:** Supports both gallery selection and direct camera capture on mobile.
|
|
1164
|
+
2. **FormData:** Constructs the payload correctly for Rails.
|
|
1165
|
+
3. **API Call:** Sends the data via `fetch`.
|
|
1166
|
+
|
|
1167
|
+
```jsx
|
|
1168
|
+
import React, { useState } from 'react';
|
|
1169
|
+
|
|
1170
|
+
const ProductForm = () => {
|
|
1171
|
+
const [title, setTitle] = useState('');
|
|
1172
|
+
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
1173
|
+
|
|
1174
|
+
// Handle file selection
|
|
1175
|
+
const handleFileChange = (event) => {
|
|
1176
|
+
// event.target.files is a FileList; convert to Array for convenience
|
|
1177
|
+
const filesArray = Array.from(event.target.files);
|
|
1178
|
+
setSelectedFiles(filesArray);
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
// Handle form submission
|
|
1182
|
+
const handleSubmit = async (event) => {
|
|
1183
|
+
event.preventDefault();
|
|
1184
|
+
|
|
1185
|
+
// 1. Create the FormData object
|
|
1186
|
+
const formData = new FormData();
|
|
1187
|
+
|
|
1188
|
+
// 2. Append text fields
|
|
1189
|
+
formData.append('product[title]', title);
|
|
1190
|
+
|
|
1191
|
+
// 3. Append FILES
|
|
1192
|
+
// It is crucial to use 'product[assets][]' with brackets.
|
|
1193
|
+
// This tells Rails to treat it as an array of attachments.
|
|
1194
|
+
selectedFiles.forEach((file) => {
|
|
1195
|
+
formData.append('product[assets][]', file);
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
try {
|
|
1199
|
+
const response = await fetch('http://localhost:3000/api/products', {
|
|
1200
|
+
method: 'POST',
|
|
1201
|
+
// IMPORTANT NOTE:
|
|
1202
|
+
// When using FormData, do NOT set 'Content-Type': 'application/json'
|
|
1203
|
+
// and do NOT manually set 'multipart/form-data'.
|
|
1204
|
+
// The browser will automatically set the header with the correct 'boundary'.
|
|
1205
|
+
body: formData,
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
if (response.ok) {
|
|
1209
|
+
console.log("Upload successful!");
|
|
1210
|
+
// Reset form or redirect...
|
|
1211
|
+
} else {
|
|
1212
|
+
console.error("Upload error");
|
|
1213
|
+
}
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
console.error("Network error:", error);
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
return (
|
|
1220
|
+
<form onSubmit={handleSubmit} style={{ padding: '20px' }}>
|
|
1221
|
+
<div>
|
|
1222
|
+
<label>Product Title:</label>
|
|
1223
|
+
<input
|
|
1224
|
+
type="text"
|
|
1225
|
+
value={title}
|
|
1226
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
1227
|
+
/>
|
|
1228
|
+
</div>
|
|
1229
|
+
|
|
1230
|
+
<div style={{ marginTop: '20px' }}>
|
|
1231
|
+
<label>Photos (Camera or Gallery):</label>
|
|
1232
|
+
{/* accept="image/*": Accepts only images.
|
|
1233
|
+
capture="environment": On mobile, opens the rear camera directly.
|
|
1234
|
+
Remove 'capture' if you want the user to choose between Gallery and Camera.
|
|
1235
|
+
multiple: Allows selecting multiple photos.
|
|
1236
|
+
*/}
|
|
1237
|
+
<input
|
|
1238
|
+
type="file"
|
|
1239
|
+
accept="image/*"
|
|
1240
|
+
multiple
|
|
1241
|
+
onChange={handleFileChange}
|
|
1242
|
+
/>
|
|
1243
|
+
</div>
|
|
1244
|
+
|
|
1245
|
+
<button type="submit" style={{ marginTop: '20px' }}>
|
|
1246
|
+
Save Product
|
|
1247
|
+
</button>
|
|
1248
|
+
</form>
|
|
1249
|
+
);
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
export default ProductForm;
|
|
1253
|
+
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
---
|
|
1257
|
+
|
|
1258
|
+
### 2. Key Implementation Details
|
|
1259
|
+
|
|
1260
|
+
#### A. The `capture` Attribute
|
|
1261
|
+
|
|
1262
|
+
* `<input type="file" capture="environment" />`: Opens the **rear camera** directly on iOS/Android.
|
|
1263
|
+
* `<input type="file" capture="user" />`: Opens the **front camera** (selfie mode).
|
|
1264
|
+
* **No `capture` attribute** (but with `accept="image/*"`): The device will prompt the user: *"Take Photo or Photo Library?"*. This is often the best UX.
|
|
1265
|
+
|
|
1266
|
+
#### B. The `forEach` Loop
|
|
1267
|
+
|
|
1268
|
+
You cannot pass an array directly into `FormData` (e.g., `formData.append('key', myArray)` will not work).
|
|
1269
|
+
Rails expects multiple values for the same key. You must append each file individually:
|
|
1270
|
+
|
|
1271
|
+
```javascript
|
|
1272
|
+
// Correct
|
|
1273
|
+
files.forEach(file => formData.append('product[assets][]', file));
|
|
1274
|
+
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
#### C. The Content-Type Header
|
|
1278
|
+
|
|
1279
|
+
This is a common pitfall. When using `fetch` or `axios` with a `FormData` body, **do not set the Content-Type header manually**.
|
|
1280
|
+
The browser must generate it automatically to include the boundary:
|
|
1281
|
+
`Content-Type: multipart/form-data; boundary=----WebKitFormBoundary...`
|
|
1282
|
+
|
|
1283
|
+
---
|
|
1284
|
+
|
|
1285
|
+
## 3. Handling Deletion (PATCH Request)
|
|
1286
|
+
|
|
1287
|
+
To remove specific attachments using the `remove_assets` virtual attribute defined in your Rails model, send the **Attachment IDs** (not the file objects).
|
|
1288
|
+
|
|
1289
|
+
```javascript
|
|
1290
|
+
const handleUpdate = async () => {
|
|
1291
|
+
const formData = new FormData();
|
|
1292
|
+
|
|
1293
|
+
// 1. Add new files (if any)
|
|
1294
|
+
newFiles.forEach(file => formData.append('product[assets][]', file));
|
|
1295
|
+
|
|
1296
|
+
// 2. Add IDs to remove
|
|
1297
|
+
// (e.g., idsToRemove is an array like [12, 45])
|
|
1298
|
+
idsToRemove.forEach(id => formData.append('product[remove_assets][]', id));
|
|
1299
|
+
|
|
1300
|
+
await fetch(`http://localhost:3000/api/products/${productId}`, {
|
|
1301
|
+
method: 'PATCH',
|
|
1302
|
+
body: formData
|
|
1303
|
+
});
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1138
1306
|
```
|
|
1139
1307
|
MARKDOWN
|
|
1140
1308
|
info
|