lbyte-budget 0.1.1
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 +7 -0
- data/.simplecov +25 -0
- data/API_DOCUMENTATION.md +526 -0
- data/CHANGELOG.md +5 -0
- data/CLEANUP.md +85 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/INTEGRATION_GUIDE.md +452 -0
- data/LICENSE.txt +21 -0
- data/RAILS_USAGE.md +510 -0
- data/README.md +367 -0
- data/Rakefile +19 -0
- data/TESTING.md +243 -0
- data/TEST_IMPLEMENTATION_SUMMARY.md +246 -0
- data/TEST_SUITE.md +253 -0
- data/app/controllers/budget/application_controller.rb +10 -0
- data/app/controllers/budget/line_items_controller.rb +112 -0
- data/app/controllers/budget/payments_controller.rb +108 -0
- data/app/controllers/budget/quotes_controller.rb +180 -0
- data/app/models/budget/line_item.rb +59 -0
- data/app/models/budget/payment.rb +58 -0
- data/app/models/budget/quote.rb +158 -0
- data/app/views/budget/line_items/index.json.jbuilder +7 -0
- data/app/views/budget/line_items/show.json.jbuilder +6 -0
- data/app/views/budget/payments/index.json.jbuilder +6 -0
- data/app/views/budget/payments/show.json.jbuilder +5 -0
- data/app/views/budget/quotes/index.json.jbuilder +15 -0
- data/app/views/budget/quotes/show.json.jbuilder +23 -0
- data/config/routes.rb +12 -0
- data/db/migrate/20251129000001_create_budget_quotes.rb +17 -0
- data/db/migrate/20251129000002_create_budget_line_items.rb +17 -0
- data/db/migrate/20251129000003_create_budget_payments.rb +18 -0
- data/examples/basic_usage.rb +130 -0
- data/examples/test_examples.rb +113 -0
- data/lib/budget/engine.rb +25 -0
- data/lib/budget/line_item.rb +66 -0
- data/lib/budget/payment.rb +56 -0
- data/lib/budget/quote.rb +163 -0
- data/lib/budget/version.rb +5 -0
- data/lib/budget.rb +10 -0
- data/lib/generators/budget/INSTALL +46 -0
- data/lib/generators/budget/install_generator.rb +33 -0
- data/sig/budget.rbs +4 -0
- metadata +115 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f4a6790bd37c0ab98951fbe00bee4ebcb3ceb150621e5f9dea2f55084faf134e
|
|
4
|
+
data.tar.gz: f95b5d8a82477d7ec9d2585019e212c0de249875ff48353d98da26b63ae29b07
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 34a9ef72b6310b1dfacf16cc49e41877ebd9c7899ed2f4bdcc37bcf4cb6eacb4abfdbb398848deafb8e1c2a81c55d2b8e0d3146e5b41bb658b73d50d98dea514
|
|
7
|
+
data.tar.gz: 02c5d50f54bb79615644041b4788ac3e7427a1d5d38e116b36749881a9b6708dea580b6625ef7384aea7459b8d6ced3263392998ed68e8f8e629b9eb7373a884
|
data/.simplecov
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
SimpleCov.start do
|
|
4
|
+
add_filter '/spec/'
|
|
5
|
+
add_filter '/examples/'
|
|
6
|
+
add_filter '/db/'
|
|
7
|
+
add_filter '/config/'
|
|
8
|
+
|
|
9
|
+
add_group 'ActiveRecord Models', 'app/models'
|
|
10
|
+
add_group 'Controllers', 'app/controllers'
|
|
11
|
+
add_group 'Views', 'app/views'
|
|
12
|
+
add_group 'Engine', 'lib/budget/engine.rb'
|
|
13
|
+
add_group 'Main', 'lib/budget.rb'
|
|
14
|
+
|
|
15
|
+
# Coverage thresholds
|
|
16
|
+
minimum_coverage 99
|
|
17
|
+
minimum_coverage_by_file 90
|
|
18
|
+
|
|
19
|
+
# Configure formatters
|
|
20
|
+
if ENV['CI']
|
|
21
|
+
formatter SimpleCov::Formatter::SimpleFormatter
|
|
22
|
+
else
|
|
23
|
+
formatter SimpleCov::Formatter::HTMLFormatter
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# Budget Engine - JSON API Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Budget Engine provides a complete REST JSON API for managing quotes, line items, and payments. All endpoints return JSON responses using JBuilder.
|
|
6
|
+
|
|
7
|
+
## Base URL
|
|
8
|
+
|
|
9
|
+
When mounted in your Rails app at `/budget`:
|
|
10
|
+
```
|
|
11
|
+
https://your-app.com/budget
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Authentication
|
|
15
|
+
|
|
16
|
+
The engine doesn't include authentication. Implement authentication in your main Rails app using `before_action` callbacks.
|
|
17
|
+
|
|
18
|
+
## API Endpoints
|
|
19
|
+
|
|
20
|
+
### Quotes
|
|
21
|
+
|
|
22
|
+
#### List All Quotes
|
|
23
|
+
```
|
|
24
|
+
GET /budget/quotes
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Parameters:**
|
|
28
|
+
- `page` (optional) - Page number for pagination
|
|
29
|
+
- `per_page` (optional) - Items per page (default: 20)
|
|
30
|
+
|
|
31
|
+
**Response:**
|
|
32
|
+
```json
|
|
33
|
+
[
|
|
34
|
+
{
|
|
35
|
+
"id": 1,
|
|
36
|
+
"customer_name": "María González",
|
|
37
|
+
"customer_contact": "555-1234",
|
|
38
|
+
"quote_date": "2025-11-29T10:30:00.000Z",
|
|
39
|
+
"created_at": "2025-11-29T10:30:00.000Z",
|
|
40
|
+
"line_items_count": 3,
|
|
41
|
+
"payments_count": 1,
|
|
42
|
+
"totals": {
|
|
43
|
+
"total": 265.00,
|
|
44
|
+
"total_paid": 132.50,
|
|
45
|
+
"remaining_balance": 132.50,
|
|
46
|
+
"fully_paid": false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Get Quote Details
|
|
53
|
+
```
|
|
54
|
+
GET /budget/quotes/:id
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Response:**
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"id": 1,
|
|
61
|
+
"customer_name": "María González",
|
|
62
|
+
"customer_contact": "555-1234",
|
|
63
|
+
"notes": "Prefiere montura liviana",
|
|
64
|
+
"quote_date": "2025-11-29T10:30:00.000Z",
|
|
65
|
+
"created_at": "2025-11-29T10:30:00.000Z",
|
|
66
|
+
"updated_at": "2025-11-29T10:30:00.000Z",
|
|
67
|
+
"line_items": [
|
|
68
|
+
{
|
|
69
|
+
"id": 1,
|
|
70
|
+
"description": "Lentes progresivos alta gama",
|
|
71
|
+
"price": 150.00,
|
|
72
|
+
"category": "lente",
|
|
73
|
+
"quantity": 1,
|
|
74
|
+
"category_name": "Lente",
|
|
75
|
+
"subtotal": 150.00,
|
|
76
|
+
"created_at": "2025-11-29T10:31:00.000Z"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": 2,
|
|
80
|
+
"description": "Montura de titanio",
|
|
81
|
+
"price": 80.00,
|
|
82
|
+
"category": "montura",
|
|
83
|
+
"quantity": 1,
|
|
84
|
+
"category_name": "Montura",
|
|
85
|
+
"subtotal": 80.00,
|
|
86
|
+
"created_at": "2025-11-29T10:32:00.000Z"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"payments": [
|
|
90
|
+
{
|
|
91
|
+
"id": 1,
|
|
92
|
+
"amount": 132.50,
|
|
93
|
+
"payment_date": "2025-11-29T10:35:00.000Z",
|
|
94
|
+
"payment_method": "efectivo",
|
|
95
|
+
"payment_method_name": "Efectivo",
|
|
96
|
+
"notes": "Adelanto 50%",
|
|
97
|
+
"created_at": "2025-11-29T10:35:00.000Z"
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"totals": {
|
|
101
|
+
"total": 265.00,
|
|
102
|
+
"total_paid": 132.50,
|
|
103
|
+
"remaining_balance": 132.50,
|
|
104
|
+
"fully_paid": false
|
|
105
|
+
},
|
|
106
|
+
"category_breakdown": {
|
|
107
|
+
"lente": 150.00,
|
|
108
|
+
"montura": 80.00,
|
|
109
|
+
"tratamiento": 35.00
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Create Quote
|
|
115
|
+
```
|
|
116
|
+
POST /budget/quotes
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Request Body:**
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"quote": {
|
|
123
|
+
"customer_name": "María González",
|
|
124
|
+
"customer_contact": "555-1234",
|
|
125
|
+
"notes": "Cliente preferencial"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Response:** Same as GET /budget/quotes/:id (201 Created)
|
|
131
|
+
|
|
132
|
+
#### Update Quote
|
|
133
|
+
```
|
|
134
|
+
PATCH /budget/quotes/:id
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Request Body:**
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"quote": {
|
|
141
|
+
"customer_contact": "555-5678",
|
|
142
|
+
"notes": "Updated notes"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Response:** Same as GET /budget/quotes/:id (200 OK)
|
|
148
|
+
|
|
149
|
+
#### Delete Quote
|
|
150
|
+
```
|
|
151
|
+
DELETE /budget/quotes/:id
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Response:** 204 No Content
|
|
155
|
+
|
|
156
|
+
#### Get Quote Summary
|
|
157
|
+
```
|
|
158
|
+
GET /budget/quotes/:id/summary
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Response:**
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"id": 1,
|
|
165
|
+
"customer_name": "María González",
|
|
166
|
+
"customer_contact": "555-1234",
|
|
167
|
+
"date": "2025-11-29T10:30:00.000Z",
|
|
168
|
+
"line_items_count": 3,
|
|
169
|
+
"total": 265.00,
|
|
170
|
+
"total_paid": 132.50,
|
|
171
|
+
"remaining_balance": 132.50,
|
|
172
|
+
"fully_paid": false,
|
|
173
|
+
"category_breakdown": {
|
|
174
|
+
"lente": 150.00,
|
|
175
|
+
"montura": 80.00,
|
|
176
|
+
"tratamiento": 35.00
|
|
177
|
+
},
|
|
178
|
+
"payments_count": 1
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Line Items
|
|
183
|
+
|
|
184
|
+
#### List Quote Line Items
|
|
185
|
+
```
|
|
186
|
+
GET /budget/quotes/:quote_id/line_items
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Response:**
|
|
190
|
+
```json
|
|
191
|
+
[
|
|
192
|
+
{
|
|
193
|
+
"id": 1,
|
|
194
|
+
"description": "Lentes progresivos",
|
|
195
|
+
"price": 150.00,
|
|
196
|
+
"category": "lente",
|
|
197
|
+
"quantity": 1,
|
|
198
|
+
"category_name": "Lente",
|
|
199
|
+
"subtotal": 150.00,
|
|
200
|
+
"created_at": "2025-11-29T10:31:00.000Z"
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Get Line Item Details
|
|
206
|
+
```
|
|
207
|
+
GET /budget/quotes/:quote_id/line_items/:id
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Response:**
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"id": 1,
|
|
214
|
+
"description": "Lentes progresivos alta gama",
|
|
215
|
+
"price": 150.00,
|
|
216
|
+
"category": "lente",
|
|
217
|
+
"quantity": 1,
|
|
218
|
+
"category_name": "Lente",
|
|
219
|
+
"subtotal": 150.00,
|
|
220
|
+
"quote_id": 1,
|
|
221
|
+
"created_at": "2025-11-29T10:31:00.000Z",
|
|
222
|
+
"updated_at": "2025-11-29T10:31:00.000Z"
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Create Line Item
|
|
227
|
+
```
|
|
228
|
+
POST /budget/quotes/:quote_id/line_items
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Request Body:**
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"line_item": {
|
|
235
|
+
"description": "Lentes progresivos",
|
|
236
|
+
"price": 150.00,
|
|
237
|
+
"category": "lente",
|
|
238
|
+
"quantity": 1
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Categories:** `lente`, `montura`, `tratamiento`, `other`
|
|
244
|
+
|
|
245
|
+
**Response:** Same as GET line_item (201 Created)
|
|
246
|
+
|
|
247
|
+
#### Update Line Item
|
|
248
|
+
```
|
|
249
|
+
PATCH /budget/quotes/:quote_id/line_items/:id
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Request Body:**
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"line_item": {
|
|
256
|
+
"price": 160.00,
|
|
257
|
+
"quantity": 2
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Response:** Same as GET line_item (200 OK)
|
|
263
|
+
|
|
264
|
+
#### Delete Line Item
|
|
265
|
+
```
|
|
266
|
+
DELETE /budget/quotes/:quote_id/line_items/:id
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Response:** 204 No Content
|
|
270
|
+
|
|
271
|
+
### Payments
|
|
272
|
+
|
|
273
|
+
#### List Quote Payments
|
|
274
|
+
```
|
|
275
|
+
GET /budget/quotes/:quote_id/payments
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Response:**
|
|
279
|
+
```json
|
|
280
|
+
[
|
|
281
|
+
{
|
|
282
|
+
"id": 1,
|
|
283
|
+
"amount": 132.50,
|
|
284
|
+
"payment_date": "2025-11-29T10:35:00.000Z",
|
|
285
|
+
"payment_method": "efectivo",
|
|
286
|
+
"payment_method_name": "Efectivo",
|
|
287
|
+
"notes": "Adelanto 50%",
|
|
288
|
+
"created_at": "2025-11-29T10:35:00.000Z"
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### Get Payment Details
|
|
294
|
+
```
|
|
295
|
+
GET /budget/quotes/:quote_id/payments/:id
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Response:**
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"id": 1,
|
|
302
|
+
"amount": 132.50,
|
|
303
|
+
"payment_date": "2025-11-29T10:35:00.000Z",
|
|
304
|
+
"payment_method": "efectivo",
|
|
305
|
+
"payment_method_name": "Efectivo",
|
|
306
|
+
"notes": "Adelanto 50%",
|
|
307
|
+
"quote_id": 1,
|
|
308
|
+
"created_at": "2025-11-29T10:35:00.000Z",
|
|
309
|
+
"updated_at": "2025-11-29T10:35:00.000Z"
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Create Payment
|
|
314
|
+
```
|
|
315
|
+
POST /budget/quotes/:quote_id/payments
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Request Body:**
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"payment": {
|
|
322
|
+
"amount": 132.50,
|
|
323
|
+
"payment_method": "efectivo",
|
|
324
|
+
"notes": "Adelanto 50%"
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Payment Methods:** `efectivo`, `tarjeta`, `transferencia`, `cheque`, `other`
|
|
330
|
+
|
|
331
|
+
**Response:** Same as GET payment (201 Created)
|
|
332
|
+
|
|
333
|
+
#### Update Payment
|
|
334
|
+
```
|
|
335
|
+
PATCH /budget/quotes/:quote_id/payments/:id
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Request Body:**
|
|
339
|
+
```json
|
|
340
|
+
{
|
|
341
|
+
"payment": {
|
|
342
|
+
"amount": 150.00,
|
|
343
|
+
"notes": "Updated amount"
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Response:** Same as GET payment (200 OK)
|
|
349
|
+
|
|
350
|
+
#### Delete Payment
|
|
351
|
+
```
|
|
352
|
+
DELETE /budget/quotes/:quote_id/payments/:id
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Response:** 204 No Content
|
|
356
|
+
|
|
357
|
+
## Error Responses
|
|
358
|
+
|
|
359
|
+
### Validation Errors (422 Unprocessable Entity)
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"errors": [
|
|
363
|
+
"Customer name can't be blank",
|
|
364
|
+
"Price must be greater than or equal to 0"
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Not Found (404)
|
|
370
|
+
```json
|
|
371
|
+
{
|
|
372
|
+
"error": "Record not found"
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Example Usage
|
|
377
|
+
|
|
378
|
+
### Complete Workflow
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
// 1. Create a quote
|
|
382
|
+
const quote = await fetch('/budget/quotes', {
|
|
383
|
+
method: 'POST',
|
|
384
|
+
headers: { 'Content-Type': 'application/json' },
|
|
385
|
+
body: JSON.stringify({
|
|
386
|
+
quote: {
|
|
387
|
+
customer_name: 'María González',
|
|
388
|
+
customer_contact: 'maria@example.com'
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
}).then(r => r.json());
|
|
392
|
+
|
|
393
|
+
// 2. Add line items
|
|
394
|
+
await fetch(`/budget/quotes/${quote.id}/line_items`, {
|
|
395
|
+
method: 'POST',
|
|
396
|
+
headers: { 'Content-Type': 'application/json' },
|
|
397
|
+
body: JSON.stringify({
|
|
398
|
+
line_item: {
|
|
399
|
+
description: 'Lentes progresivos',
|
|
400
|
+
price: 150.00,
|
|
401
|
+
category: 'lente'
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
await fetch(`/budget/quotes/${quote.id}/line_items`, {
|
|
407
|
+
method: 'POST',
|
|
408
|
+
headers: { 'Content-Type': 'application/json' },
|
|
409
|
+
body: JSON.stringify({
|
|
410
|
+
line_item: {
|
|
411
|
+
description: 'Montura titanio',
|
|
412
|
+
price: 80.00,
|
|
413
|
+
category: 'montura'
|
|
414
|
+
}
|
|
415
|
+
})
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// 3. Get updated quote with totals
|
|
419
|
+
const updatedQuote = await fetch(`/budget/quotes/${quote.id}`)
|
|
420
|
+
.then(r => r.json());
|
|
421
|
+
|
|
422
|
+
console.log('Total:', updatedQuote.totals.total); // 230.00
|
|
423
|
+
|
|
424
|
+
// 4. Add payment
|
|
425
|
+
await fetch(`/budget/quotes/${quote.id}/payments`, {
|
|
426
|
+
method: 'POST',
|
|
427
|
+
headers: { 'Content-Type': 'application/json' },
|
|
428
|
+
body: JSON.stringify({
|
|
429
|
+
payment: {
|
|
430
|
+
amount: 115.00,
|
|
431
|
+
payment_method: 'efectivo',
|
|
432
|
+
notes: 'Adelanto 50%'
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// 5. Check remaining balance
|
|
438
|
+
const finalQuote = await fetch(`/budget/quotes/${quote.id}`)
|
|
439
|
+
.then(r => r.json());
|
|
440
|
+
|
|
441
|
+
console.log('Remaining:', finalQuote.totals.remaining_balance); // 115.00
|
|
442
|
+
console.log('Fully paid?', finalQuote.totals.fully_paid); // false
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Mounting in Your Rails App
|
|
446
|
+
|
|
447
|
+
In your main app's `config/routes.rb`:
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
mount Budget::Engine => '/budget'
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
This makes all endpoints available at `/budget/*`.
|
|
454
|
+
|
|
455
|
+
## Adding Authentication
|
|
456
|
+
|
|
457
|
+
In your main Rails app:
|
|
458
|
+
|
|
459
|
+
```ruby
|
|
460
|
+
# config/initializers/budget.rb
|
|
461
|
+
Budget::QuotesController.class_eval do
|
|
462
|
+
before_action :authenticate_user!
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
Budget::LineItemsController.class_eval do
|
|
466
|
+
before_action :authenticate_user!
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
Budget::PaymentsController.class_eval do
|
|
470
|
+
before_action :authenticate_user!
|
|
471
|
+
end
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Or use concerns:
|
|
475
|
+
|
|
476
|
+
```ruby
|
|
477
|
+
# app/controllers/concerns/budget_authentication.rb
|
|
478
|
+
module BudgetAuthentication
|
|
479
|
+
extend ActiveSupport::Concern
|
|
480
|
+
|
|
481
|
+
included do
|
|
482
|
+
before_action :authenticate_user!
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# config/initializers/budget.rb
|
|
487
|
+
[
|
|
488
|
+
Budget::QuotesController,
|
|
489
|
+
Budget::LineItemsController,
|
|
490
|
+
Budget::PaymentsController
|
|
491
|
+
].each do |controller|
|
|
492
|
+
controller.include BudgetAuthentication
|
|
493
|
+
end
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
## Pagination
|
|
497
|
+
|
|
498
|
+
The quotes index endpoint supports pagination via kaminari (if available in your app):
|
|
499
|
+
|
|
500
|
+
```
|
|
501
|
+
GET /budget/quotes?page=2&per_page=50
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Install kaminari in your main app:
|
|
505
|
+
```ruby
|
|
506
|
+
gem 'kaminari'
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## CORS Configuration
|
|
510
|
+
|
|
511
|
+
For API access from different domains, configure CORS in your main Rails app:
|
|
512
|
+
|
|
513
|
+
```ruby
|
|
514
|
+
# Gemfile
|
|
515
|
+
gem 'rack-cors'
|
|
516
|
+
|
|
517
|
+
# config/initializers/cors.rb
|
|
518
|
+
Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
|
519
|
+
allow do
|
|
520
|
+
origins '*' # or specific domains
|
|
521
|
+
resource '/budget/*',
|
|
522
|
+
headers: :any,
|
|
523
|
+
methods: [:get, :post, :patch, :delete, :options]
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
```
|
data/CHANGELOG.md
ADDED
data/CLEANUP.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Cleanup Guide
|
|
2
|
+
|
|
3
|
+
## Project Structure (Rails Engine Only)
|
|
4
|
+
|
|
5
|
+
This gem is now a **Rails Engine only** - PORO classes have been removed for simplicity.
|
|
6
|
+
|
|
7
|
+
## Essential Documentation
|
|
8
|
+
|
|
9
|
+
- ✅ **README.md** - Main documentation and installation guide
|
|
10
|
+
- ✅ **API_DOCUMENTATION.md** - Complete REST API reference
|
|
11
|
+
- ✅ **RAILS_USAGE.md** - Rails Engine usage guide
|
|
12
|
+
- ✅ **TESTING.md** - Test suite documentation
|
|
13
|
+
- ✅ **CHANGELOG.md** - Version history
|
|
14
|
+
- ✅ **CODE_OF_CONDUCT.md** - Community guidelines
|
|
15
|
+
- ✅ **LICENSE.txt** - MIT License
|
|
16
|
+
|
|
17
|
+
## Core Files
|
|
18
|
+
|
|
19
|
+
### Library Structure
|
|
20
|
+
```
|
|
21
|
+
lib/
|
|
22
|
+
├── budget.rb # Main module - loads Rails Engine
|
|
23
|
+
└── budget/
|
|
24
|
+
├── version.rb # Version constant
|
|
25
|
+
└── engine.rb # Rails Engine configuration
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Rails Engine Structure
|
|
29
|
+
```
|
|
30
|
+
app/
|
|
31
|
+
├── models/budget/ # ActiveRecord models
|
|
32
|
+
│ ├── quote.rb
|
|
33
|
+
│ ├── line_item.rb
|
|
34
|
+
│ └── payment.rb
|
|
35
|
+
├── controllers/budget/ # REST API controllers
|
|
36
|
+
│ ├── quotes_controller.rb
|
|
37
|
+
│ ├── line_items_controller.rb
|
|
38
|
+
│ └── payments_controller.rb
|
|
39
|
+
└── views/budget/ # JBuilder JSON views
|
|
40
|
+
├── quotes/
|
|
41
|
+
├── line_items/
|
|
42
|
+
└── payments/
|
|
43
|
+
|
|
44
|
+
db/migrate/ # Database migrations
|
|
45
|
+
├── 20251129000001_create_budget_quotes.rb
|
|
46
|
+
├── 20251129000002_create_budget_line_items.rb
|
|
47
|
+
└── 20251129000003_create_budget_payments.rb
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Test Files
|
|
51
|
+
```
|
|
52
|
+
spec/
|
|
53
|
+
├── rails_helper.rb # Rails test configuration
|
|
54
|
+
├── budget_spec.rb # Module tests
|
|
55
|
+
├── models/ # ActiveRecord model tests (145 examples)
|
|
56
|
+
├── controllers/ # API controller tests (80 examples)
|
|
57
|
+
└── lib/ # Engine tests (8 examples)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Examples
|
|
61
|
+
```
|
|
62
|
+
examples/
|
|
63
|
+
├── basic_usage.rb # Basic usage example (requires Rails)
|
|
64
|
+
└── test_examples.rb # Test scenarios (requires Rails)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Files Removed
|
|
68
|
+
|
|
69
|
+
The following PORO implementation files have been removed:
|
|
70
|
+
- `lib/budget/quote.rb` (PORO class)
|
|
71
|
+
- `lib/budget/line_item.rb` (PORO class)
|
|
72
|
+
- `lib/budget/payment.rb` (PORO class)
|
|
73
|
+
- `spec/budget/*.rb` (PORO tests)
|
|
74
|
+
- `spec/spec_helper.rb` (PORO test config - now minimal)
|
|
75
|
+
|
|
76
|
+
## Architecture Decision
|
|
77
|
+
|
|
78
|
+
**Rails-only approach** provides:
|
|
79
|
+
- ✅ Simpler codebase
|
|
80
|
+
- ✅ No class constant conflicts
|
|
81
|
+
- ✅ Better Rails integration
|
|
82
|
+
- ✅ Easier maintenance
|
|
83
|
+
- ✅ Clear focus on Rails Engine functionality
|
|
84
|
+
|
|
85
|
+
Users can still use ActiveRecord models in memory without persistence if needed.
|