model_driven_api 3.5.6 → 3.5.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 394acca44589bd5e704f0cbf966eab8eded237d1aafdcbc0f2a14318b78ceadc
4
- data.tar.gz: 302dfcc51da16d380148f5fc1953065a0cf537657bb713bc705dab5184a8a4d2
3
+ metadata.gz: f9f41789e4969e40d7ea928d17463c69b067109d104f4c5c14d2d8885670c08d
4
+ data.tar.gz: df3716c785822a97d1a17374d64193eb40408735bfc93982f1d12f9ec044a2fe
5
5
  SHA512:
6
- metadata.gz: 7da1bbd9f1f565296c34479faefba15d9ba77036cea33904740e2c74c646b7aef9c90681e8fdcec8b254efea20818c0719188271f135d58dccac4064100e0c90
7
- data.tar.gz: 10702aeed1e9c0c925faa0214c01f0e2ed12187c09a23b559e5f9b1af89ce25cbe268883508bd7c9f75911aba49a2177190177eceb24c6e1947331420f0985dd
6
+ metadata.gz: 0d6a4d31ecdb991590f78fdafca0b955f833e01abce3796cc69b70626d65201023fe2582b610941aab91834a621849f37fead2241cec4b497a3c758fb3f5b376
7
+ data.tar.gz: de120a2d0bfbe673d69695a44e2105fe8f16c13b422cefefa6f97ad93a8e40e04fa376e06242fd6b6005b796d0e3e00580fb3b77da2fe7d44d34eeb576a9ac82
@@ -1135,6 +1135,230 @@ 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
+
1306
+ ```
1307
+
1308
+ Here is the addition to the documentation showing the raw HTTP requests.
1309
+
1310
+ ---
1311
+
1312
+ ## 4. Bare Metal HTTP Requests
1313
+
1314
+ If you were to inspect the network traffic or manually construct the request (e.g., using raw sockets or a tool like Postman/Insomnia), this is exactly what the payload looks like "over the wire."
1315
+
1316
+ ### A. POST Request (Upload)
1317
+
1318
+ Notice how `multipart/form-data` uses a **boundary** string (randomly generated by the client) to separate different fields.
1319
+
1320
+ ```http
1321
+ POST /api/products HTTP/1.1
1322
+ Host: localhost:3000
1323
+ Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
1324
+
1325
+ ------WebKitFormBoundary7MA4YWxkTrZu0gW
1326
+ Content-Disposition: form-data; name="product[title]"
1327
+
1328
+ My New Product
1329
+ ------WebKitFormBoundary7MA4YWxkTrZu0gW
1330
+ Content-Disposition: form-data; name="product[assets][]"; filename="camera_shot.jpg"
1331
+ Content-Type: image/jpeg
1332
+
1333
+ (Binary image data goes here...)
1334
+ ------WebKitFormBoundary7MA4YWxkTrZu0gW
1335
+ Content-Disposition: form-data; name="product[assets][]"; filename="gallery_photo.png"
1336
+ Content-Type: image/png
1337
+
1338
+ (Binary image data goes here...)
1339
+ ------WebKitFormBoundary7MA4YWxkTrZu0gW--
1340
+
1341
+ ```
1342
+
1343
+ ### B. PATCH Request (Deletion via IDs)
1344
+
1345
+ When sending the `remove_assets` array via `FormData`, the key is repeated for every ID. This is how HTTP handles arrays in form data.
1346
+
1347
+ ```http
1348
+ PATCH /api/products/100 HTTP/1.1
1349
+ Host: localhost:3000
1350
+ Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXyZ123
1351
+
1352
+ ------WebKitFormBoundaryXyZ123
1353
+ Content-Disposition: form-data; name="product[remove_assets][]"
1354
+
1355
+ 12
1356
+ ------WebKitFormBoundaryXyZ123
1357
+ Content-Disposition: form-data; name="product[remove_assets][]"
1358
+
1359
+ 45
1360
+ ------WebKitFormBoundaryXyZ123--
1361
+
1138
1362
  ```
1139
1363
  MARKDOWN
1140
1364
  info
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.5.6".freeze
2
+ VERSION = "3.5.8".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_driven_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.6
4
+ version: 3.5.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni