model_driven_api 3.5.5 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61a965405c27cbeed7e06d994c4cd97cd69ec81ba21e9eb1cd1f6feef3b32741
4
- data.tar.gz: 414b957e13b3ccff5a2a3b999242ad3ae3a310f5c635f2edeab21264a2633cf2
3
+ metadata.gz: df0f02193f91b162df1248961aabf8c4281440c49dc7f3147cb400db371f68d0
4
+ data.tar.gz: d0c42164dbde60bbb752110daa5e8b757ac8a7e0bfcc5e9b6a67898a02555ec8
5
5
  SHA512:
6
- metadata.gz: 269993f8be409f5f3fdea9509df7037cdbf6927d21efb28152c0a65e8e6c10dc549ed784490c39d0b758caa3140052e89499675aa3f55e391982d5887c5f97ad
7
- data.tar.gz: d05fef4b0562fc228a3c5ff12ecb82f7a283fd5a5cbefe16a5482f8783fb9d03edac64689452f5b53c253b0432b02febcc233bf777faab4445fafae06863c1ee
6
+ metadata.gz: e4e3c99b867169e19e966832ef085d5d8ffd67399a6a4047fa504a950c75a63b1a96719502d8c9d01dca6efd5d32ae7d9b07d69c8c7ad361a561cde24b957d65
7
+ data.tar.gz: 6bcff8f0e6c408ead804305dd386e827edf582b0e950774bcf9c79cdb9c24fe7577dab86d813e0c1a9cb8ff7e3355079f451d23eb1a8327df658824cd876b167
@@ -40,7 +40,7 @@ class Api::V2::ApplicationController < ActionController::API
40
40
  response.set_header("Content-Range", "#{@model.table_name} #{range_start}-#{range_end}/#{@records.total_count}")
41
41
 
42
42
  # puts "ALL RECORDS FOUND: #{@records_all.inspect}"
43
- status = @records_count.zero? ? 404 : 200
43
+ status = @records_count.zero? && params[:always_ok].blank? ? 404 : 200
44
44
  # puts "If it's asked for page number, then paginate"
45
45
  return render json: @records.as_json(json_attrs), status: status if !page.blank? # (@json_attrs || {})
46
46
  #puts "if you ask for count, then return a json object with just the number of objects"
@@ -140,18 +140,17 @@ class Api::V2::ApplicationController < ActionController::API
140
140
  # or
141
141
  # [GET|PUT|POST|DELETE] :controller/custom_action/:custom_action/:id
142
142
  def check_for_custom_action
143
-
144
143
  custom_action, token = if !params[:do].blank?
145
- # This also responds to custom actions which have the bearer token in the custom action name. A workaround to remove for some IoT devices
146
- # Which don't support token in header or in querystring
147
- # This is for backward compatibility and in future it can ben removed
148
- params[:do].split("-")
149
- elsif request.url.include? "/custom_action/"
150
- [params[:action_name], nil]
151
- else
152
- # Not a custom action call
153
- false
154
- end
144
+ # This also responds to custom actions which have the bearer token in the custom action name. A workaround to remove for some IoT devices
145
+ # Which don't support token in header or in querystring
146
+ # This is for backward compatibility and in future it can ben removed
147
+ params[:do].split("-")
148
+ elsif request.url.include? "/custom_action/"
149
+ [params[:action_name], nil]
150
+ else
151
+ # Not a custom action call
152
+ false
153
+ end
155
154
  return false unless custom_action
156
155
  # Poor man's solution to avoid the possibility to
157
156
  # call an unwanted method in the AR Model.
@@ -176,7 +175,7 @@ class Api::V2::ApplicationController < ActionController::API
176
175
  # Custom endpoint does not exist or cannot be called
177
176
  raise NoMethodError
178
177
  end
179
-
178
+
180
179
  return true, body.to_json(json_attrs), status
181
180
  end
182
181
 
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.5.5".freeze
2
+ VERSION = "3.5.7".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.5
4
+ version: 3.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni