jpie 0.4.0 → 0.4.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 +4 -4
- data/{.aiconfig → .cursorrules} +14 -2
- data/CHANGELOG.md +19 -0
- data/README.md +139 -965
- data/examples/basic_example.md +146 -0
- data/examples/including_related_resources.md +491 -0
- data/examples/resource_attribute_configuration.md +147 -0
- data/examples/resource_meta_configuration.md +244 -0
- data/examples/single_table_inheritance.md +160 -0
- data/lib/jpie/controller/crud_actions.rb +10 -0
- data/lib/jpie/controller/error_handling.rb +168 -17
- data/lib/jpie/controller/json_api_validation.rb +171 -0
- data/lib/jpie/controller.rb +2 -0
- data/lib/jpie/errors.rb +41 -0
- data/lib/jpie/resource/attributable.rb +21 -2
- data/lib/jpie/resource.rb +26 -0
- data/lib/jpie/version.rb +1 -1
- metadata +9 -3
data/README.md
CHANGED
@@ -13,9 +13,11 @@ JPie is a modern, lightweight Rails library for developing JSON:API compliant se
|
|
13
13
|
⚡ **Powerful Generators** - Scaffold resources with relationships, meta attributes, and automatic inference
|
14
14
|
📊 **Polymorphic Support** - Full support for complex polymorphic associations
|
15
15
|
🔄 **STI Ready** - Single Table Inheritance works out of the box
|
16
|
+
🔗 **Through Associations** - Full support for Rails `:through` associations
|
16
17
|
⚡ **Performance Optimized** - Efficient serialization with intelligent deduplication
|
17
18
|
🛡️ **Authorization Ready** - Built-in scoping support for security
|
18
19
|
📋 **JSON:API Compliant** - Full specification compliance with sorting, includes, and meta
|
20
|
+
🚨 **Robust Error Handling** - Smart inheritance-aware error handling with full customization options
|
19
21
|
|
20
22
|
## Installation
|
21
23
|
|
@@ -25,9 +27,9 @@ Add JPie to your Rails application:
|
|
25
27
|
bundle add jpie
|
26
28
|
```
|
27
29
|
|
28
|
-
## Quick Start
|
30
|
+
## Quick Start
|
29
31
|
|
30
|
-
JPie works out of the box with minimal configuration
|
32
|
+
JPie works out of the box with minimal configuration:
|
31
33
|
|
32
34
|
### 1. Create Your Model
|
33
35
|
|
@@ -35,6 +37,9 @@ JPie works out of the box with minimal configuration. Here's a complete example
|
|
35
37
|
class User < ActiveRecord::Base
|
36
38
|
validates :name, presence: true
|
37
39
|
validates :email, presence: true, uniqueness: true
|
40
|
+
|
41
|
+
has_many :posts, dependent: :destroy
|
42
|
+
has_one :profile, dependent: :destroy
|
38
43
|
end
|
39
44
|
```
|
40
45
|
|
@@ -43,6 +48,10 @@ end
|
|
43
48
|
```ruby
|
44
49
|
class UserResource < JPie::Resource
|
45
50
|
attributes :name, :email
|
51
|
+
meta_attributes :created_at, :updated_at
|
52
|
+
|
53
|
+
has_many :posts
|
54
|
+
has_one :profile
|
46
55
|
end
|
47
56
|
```
|
48
57
|
|
@@ -62,16 +71,28 @@ Rails.application.routes.draw do
|
|
62
71
|
end
|
63
72
|
```
|
64
73
|
|
65
|
-
That's it! You now have a fully functional JSON:API compliant server.
|
74
|
+
That's it! You now have a fully functional JSON:API compliant server with automatic CRUD operations, sorting, includes, and validation.
|
75
|
+
|
76
|
+
## 📚 Comprehensive Examples
|
77
|
+
|
78
|
+
JPie includes a complete set of examples demonstrating all features:
|
79
|
+
|
80
|
+
- **[🚀 Basic Usage](https://github.com/emilkampp/jpie/blob/main/examples/basic_usage.rb)** - Fundamental setup and configuration
|
81
|
+
- **[🔗 Through Associations](https://github.com/emilkampp/jpie/blob/main/examples/through_associations.rb)** - Many-to-many relationships with `:through`
|
82
|
+
- **[🎨 Custom Attributes & Meta](https://github.com/emilkampp/jpie/blob/main/examples/custom_attributes_and_meta.rb)** - Custom computed attributes and meta data
|
83
|
+
- **[🔄 Polymorphic Associations](https://github.com/emilkampp/jpie/blob/main/examples/polymorphic_associations.rb)** - Complex polymorphic relationships
|
84
|
+
- **[🏗️ Single Table Inheritance](https://github.com/emilkampp/jpie/blob/main/examples/single_table_inheritance.rb)** - STI models and resources
|
85
|
+
- **[📊 Custom Sorting](https://github.com/emilkampp/jpie/blob/main/examples/custom_sorting.rb)** - Advanced sorting with complex algorithms
|
86
|
+
- **[⚠️ Error Handling](https://github.com/emilkampp/jpie/blob/main/examples/error_handling.rb)** - Comprehensive error handling strategies
|
87
|
+
|
88
|
+
Each example is self-contained with models, resources, controllers, and sample API requests/responses. **[📋 View all examples →](https://github.com/emilkampp/jpie/blob/main/examples/)**
|
66
89
|
|
67
90
|
## Generators
|
68
91
|
|
69
|
-
JPie includes a resource generator for quickly creating new resource classes
|
92
|
+
JPie includes a resource generator for quickly creating new resource classes:
|
70
93
|
|
71
94
|
### Basic Usage
|
72
95
|
|
73
|
-
The generator uses semantic field definitions that explicitly categorize each field by its JSON:API purpose:
|
74
|
-
|
75
96
|
```bash
|
76
97
|
# Generate a basic resource with semantic syntax
|
77
98
|
rails generate jpie:resource User attribute:name attribute:email meta:created_at
|
@@ -85,13 +106,10 @@ rails generate jpie:resource User attribute:name email created_at updated_at
|
|
85
106
|
|
86
107
|
**Generated file:**
|
87
108
|
```ruby
|
88
|
-
# frozen_string_literal: true
|
89
|
-
|
90
109
|
class UserResource < JPie::Resource
|
91
110
|
attributes :name, :email
|
92
|
-
|
93
111
|
meta_attributes :created_at, :updated_at
|
94
|
-
|
112
|
+
|
95
113
|
has_many :comments
|
96
114
|
has_one :author
|
97
115
|
end
|
@@ -99,122 +117,52 @@ end
|
|
99
117
|
|
100
118
|
### Semantic Field Syntax
|
101
119
|
|
102
|
-
The generator uses a semantic approach focused on JSON:API concepts rather than database types:
|
103
|
-
|
104
120
|
| Syntax | Purpose | Example |
|
105
121
|
|--------|---------|---------|
|
106
122
|
| `attribute:field` | Regular JSON:API attribute | `attribute:name` |
|
107
123
|
| `meta:field` | JSON:API meta attribute | `meta:created_at` |
|
108
124
|
| `has_many:resource` | JSON:API relationship | `has_many:posts` |
|
109
125
|
| `has_one:resource` | JSON:API relationship | `has_one:profile` |
|
110
|
-
| `relationship:type:resource` | Explicit relationship | `relationship:has_many:posts` |
|
111
|
-
|
112
|
-
### Advanced Examples
|
113
|
-
|
114
|
-
##### Comprehensive Resource
|
115
|
-
|
116
|
-
```bash
|
117
|
-
rails generate jpie:resource Article \
|
118
|
-
attribute:title \
|
119
|
-
attribute:content \
|
120
|
-
meta:published_at \
|
121
|
-
meta:created_at \
|
122
|
-
meta:updated_at \
|
123
|
-
has_one:author \
|
124
|
-
has_many:comments \
|
125
|
-
has_many:tags \
|
126
|
-
--model=Post
|
127
|
-
```
|
128
|
-
|
129
|
-
**Generated file:**
|
130
|
-
```ruby
|
131
|
-
# frozen_string_literal: true
|
132
|
-
|
133
|
-
class ArticleResource < JPie::Resource
|
134
|
-
model Post
|
135
126
|
|
136
|
-
|
137
|
-
|
138
|
-
meta_attributes :published_at, :created_at, :updated_at
|
139
|
-
|
140
|
-
has_one :author
|
141
|
-
has_many :comments
|
142
|
-
has_many :tags
|
143
|
-
end
|
144
|
-
```
|
127
|
+
### Generator Options
|
145
128
|
|
146
|
-
|
129
|
+
| Option | Description | Example |
|
130
|
+
|--------|-------------|---------|
|
131
|
+
| `--model=NAME` | Specify model class | `--model=Person` |
|
132
|
+
| `--skip-model` | Skip explicit model declaration | `--skip-model` |
|
147
133
|
|
148
|
-
|
149
|
-
rails generate jpie:resource User
|
150
|
-
```
|
134
|
+
## Core Features
|
151
135
|
|
152
|
-
|
153
|
-
```ruby
|
154
|
-
# frozen_string_literal: true
|
136
|
+
### JSON:API Compliance
|
155
137
|
|
156
|
-
|
157
|
-
# Define your attributes here:
|
158
|
-
# attributes :name, :email, :title
|
138
|
+
JPie automatically handles all JSON:API specification requirements:
|
159
139
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
# Define your relationships here:
|
164
|
-
# has_many :posts
|
165
|
-
# has_one :user
|
166
|
-
end
|
140
|
+
#### Sorting
|
141
|
+
```http
|
142
|
+
GET /users?sort=name,-created_at
|
167
143
|
```
|
168
144
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
```bash
|
174
|
-
# Legacy syntax (still works, types ignored)
|
175
|
-
rails generate jpie:resource User name:string email:string created_at:datetime
|
145
|
+
#### Includes
|
146
|
+
```http
|
147
|
+
GET /posts?include=author,comments,comments.author
|
176
148
|
```
|
177
149
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
| Option | Type | Description | Example |
|
183
|
-
|--------|------|-------------|---------|
|
184
|
-
| `--model=NAME` | String | Specify model class (overrides inference) | `--model=Person` |
|
185
|
-
| `--skip-model` | Boolean | Skip explicit model declaration | `--skip-model` |
|
150
|
+
#### Validation
|
151
|
+
- Request structure validation for POST/PATCH operations
|
152
|
+
- Include parameter validation
|
153
|
+
- Sort parameter validation with clear error messages
|
186
154
|
|
187
|
-
###
|
188
|
-
|
189
|
-
- **Model Inference**: Automatically infers model class from resource name
|
190
|
-
- **Resource Inference**: Automatically infers related resource classes for relationships
|
191
|
-
- **Meta Detection**: Auto-detects common meta attributes (`created_at`, `updated_at`, etc.)
|
192
|
-
- **Clean Output**: Generates well-structured, commented resource files
|
193
|
-
|
194
|
-
### Best Practices
|
195
|
-
|
196
|
-
1. **Use semantic syntax** for clarity and JSON:API-appropriate thinking
|
197
|
-
2. **Be explicit about categorization** when the intent might be unclear
|
198
|
-
3. **Let JPie handle inference** for standard naming conventions
|
199
|
-
4. **Use `--model` only when necessary** for non-standard model mappings
|
200
|
-
|
201
|
-
## Modern DSL Examples
|
202
|
-
|
203
|
-
JPie provides a clean, modern DSL that follows Rails conventions:
|
204
|
-
|
205
|
-
### Resource Definition
|
155
|
+
### Modern DSL
|
206
156
|
|
207
157
|
```ruby
|
208
158
|
class UserResource < JPie::Resource
|
209
|
-
#
|
159
|
+
# Multiple attributes at once
|
210
160
|
attributes :name, :email, :created_at
|
211
|
-
attribute :full_name
|
212
161
|
|
213
|
-
# Meta attributes
|
162
|
+
# Meta attributes (for additional data)
|
214
163
|
meta :account_status, :last_login
|
215
|
-
# or: meta_attributes :account_status, :last_login
|
216
164
|
|
217
|
-
#
|
165
|
+
# Relationships for includes
|
218
166
|
has_many :posts
|
219
167
|
has_one :profile
|
220
168
|
|
@@ -222,971 +170,197 @@ class UserResource < JPie::Resource
|
|
222
170
|
sortable :popularity do |query, direction|
|
223
171
|
query.order(likes_count: direction)
|
224
172
|
end
|
225
|
-
# or: sortable_by :popularity do |query, direction|
|
226
173
|
|
227
|
-
# Custom attribute methods
|
174
|
+
# Custom attribute methods (modern approach)
|
228
175
|
private
|
229
176
|
|
230
|
-
def full_name
|
231
|
-
"#{object.first_name} #{object.last_name}"
|
232
|
-
end
|
233
|
-
|
234
177
|
def account_status
|
235
178
|
object.active? ? 'active' : 'inactive'
|
236
179
|
end
|
237
180
|
end
|
238
181
|
```
|
239
182
|
|
240
|
-
###
|
241
|
-
|
242
|
-
```ruby
|
243
|
-
class UsersController < ApplicationController
|
244
|
-
include JPie::Controller
|
245
|
-
|
246
|
-
# Explicit resource (optional - auto-inferred by default)
|
247
|
-
resource UserResource
|
248
|
-
# or: jsonapi_resource UserResource
|
249
|
-
|
250
|
-
# Override methods as needed
|
251
|
-
def index
|
252
|
-
users = current_user.admin? ? User.all : User.active
|
253
|
-
render_jsonapi(users)
|
254
|
-
end
|
255
|
-
|
256
|
-
def create
|
257
|
-
attributes = deserialize_params
|
258
|
-
user = model_class.new(attributes)
|
259
|
-
user.created_by = current_user
|
260
|
-
user.save!
|
261
|
-
|
262
|
-
render_jsonapi(user, status: :created)
|
263
|
-
end
|
264
|
-
end
|
265
|
-
```
|
266
|
-
|
267
|
-
## Suported JSON:API features
|
268
|
-
|
269
|
-
### Sorting
|
270
|
-
All defined attributes are automatically sortable:
|
271
|
-
|
272
|
-
```http
|
273
|
-
GET /users?sort=name
|
274
|
-
HTTP/1.1 200 OK
|
275
|
-
Content-Type: application/vnd.api+json
|
276
|
-
{
|
277
|
-
"data": [
|
278
|
-
{
|
279
|
-
"id": "1",
|
280
|
-
"type": "users",
|
281
|
-
"attributes": {
|
282
|
-
"name": "Alice Anderson",
|
283
|
-
"email": "alice@example.com"
|
284
|
-
}
|
285
|
-
},
|
286
|
-
{
|
287
|
-
"id": "2",
|
288
|
-
"type": "users",
|
289
|
-
"attributes": {
|
290
|
-
"name": "Bob Brown",
|
291
|
-
"email": "bob@example.com"
|
292
|
-
}
|
293
|
-
},
|
294
|
-
{
|
295
|
-
"id": "3",
|
296
|
-
"type": "users",
|
297
|
-
"attributes": {
|
298
|
-
"name": "Carol Clark",
|
299
|
-
"email": "carol@example.com"
|
300
|
-
}
|
301
|
-
}
|
302
|
-
]
|
303
|
-
}
|
304
|
-
```
|
305
|
-
|
306
|
-
Or by name in reverse order by name:
|
183
|
+
### Through Associations
|
307
184
|
|
308
|
-
|
309
|
-
GET /users?sort=-name
|
310
|
-
HTTP/1.1 200 OK
|
311
|
-
Content-Type: application/vnd.api+json
|
312
|
-
{
|
313
|
-
"data": [
|
314
|
-
{
|
315
|
-
"id": "3",
|
316
|
-
"type": "users",
|
317
|
-
"attributes": {
|
318
|
-
"name": "Carol Clark",
|
319
|
-
"email": "carol@example.com"
|
320
|
-
}
|
321
|
-
},
|
322
|
-
{
|
323
|
-
"id": "2",
|
324
|
-
"type": "users",
|
325
|
-
"attributes": {
|
326
|
-
"name": "Bob Brown",
|
327
|
-
"email": "bob@example.com"
|
328
|
-
}
|
329
|
-
},
|
330
|
-
{
|
331
|
-
"id": "1",
|
332
|
-
"type": "users",
|
333
|
-
"attributes": {
|
334
|
-
"name": "Alice Anderson",
|
335
|
-
"email": "alice@example.com"
|
336
|
-
}
|
337
|
-
}
|
338
|
-
]
|
339
|
-
}
|
340
|
-
```
|
341
|
-
|
342
|
-
### Includes
|
343
|
-
|
344
|
-
JPie supports including related resources using the `?include=` parameter. Related resources are returned in the `included` section of the JSON:API response:
|
345
|
-
|
346
|
-
```http
|
347
|
-
GET /posts/1?include=author
|
348
|
-
HTTP/1.1 200 OK
|
349
|
-
Content-Type: application/vnd.api+json
|
350
|
-
{
|
351
|
-
"data": {
|
352
|
-
"id": "1",
|
353
|
-
"type": "posts",
|
354
|
-
"attributes": {
|
355
|
-
"title": "My First Post",
|
356
|
-
"content": "This is the content of my first post."
|
357
|
-
}
|
358
|
-
},
|
359
|
-
"included": [
|
360
|
-
{
|
361
|
-
"id": "5",
|
362
|
-
"type": "users",
|
363
|
-
"attributes": {
|
364
|
-
"name": "John Doe",
|
365
|
-
"email": "john@example.com"
|
366
|
-
}
|
367
|
-
}
|
368
|
-
]
|
369
|
-
}
|
370
|
-
```
|
371
|
-
|
372
|
-
Multiple includes and nested includes are also supported:
|
373
|
-
|
374
|
-
```http
|
375
|
-
GET /posts?include=author,comments,comments.author
|
376
|
-
```
|
377
|
-
|
378
|
-
## Customization and Overrides
|
379
|
-
|
380
|
-
Once you have the basic implementation working, you can customize JPie's behavior as needed:
|
381
|
-
|
382
|
-
### Resource Class Inference Override
|
383
|
-
|
384
|
-
JPie automatically infers the resource class from your controller name, but you can override this:
|
185
|
+
JPie seamlessly supports Rails `:through` associations:
|
385
186
|
|
386
187
|
```ruby
|
387
|
-
|
388
|
-
|
389
|
-
include JPie::Controller
|
390
|
-
# Automatically uses UserResource
|
391
|
-
end
|
392
|
-
|
393
|
-
# Explicit resource specification (override)
|
394
|
-
class UsersController < ApplicationController
|
395
|
-
include JPie::Controller
|
396
|
-
resource UserResource # Use a different resource class (modern syntax)
|
397
|
-
# or: jsonapi_resource UserResource # (backward compatible syntax)
|
398
|
-
end
|
399
|
-
```
|
400
|
-
|
401
|
-
### Model Specification Override
|
402
|
-
|
403
|
-
JPie automatically infers the model from your resource class name, but you can override this:
|
404
|
-
|
405
|
-
```ruby
|
406
|
-
# Automatic inference (default behavior)
|
407
|
-
class UserResource < JPie::Resource
|
408
|
-
attributes :name, :email
|
409
|
-
# Automatically uses User model
|
410
|
-
end
|
411
|
-
|
412
|
-
# Explicit model specification (override)
|
413
|
-
class UserResource < JPie::Resource
|
414
|
-
model CustomUser # Use a different model class
|
415
|
-
attributes :name, :email
|
416
|
-
end
|
417
|
-
```
|
418
|
-
|
419
|
-
### Controller Method Overrides
|
420
|
-
|
421
|
-
You can override any of the automatic CRUD methods:
|
422
|
-
|
423
|
-
```ruby
|
424
|
-
class UsersController < ApplicationController
|
425
|
-
include JPie::Controller
|
426
|
-
|
427
|
-
# Override index to add filtering
|
428
|
-
def index
|
429
|
-
users = User.where(active: true)
|
430
|
-
render_jsonapi(users)
|
431
|
-
end
|
432
|
-
|
433
|
-
# Override create to add custom logic
|
434
|
-
def create
|
435
|
-
attributes = deserialize_params
|
436
|
-
user = model_class.new(attributes)
|
437
|
-
user.created_by = current_user
|
438
|
-
user.save!
|
439
|
-
|
440
|
-
render_jsonapi(user, status: :created)
|
441
|
-
end
|
188
|
+
class CarResource < JPie::Resource
|
189
|
+
attributes :make, :model, :year
|
442
190
|
|
443
|
-
#
|
191
|
+
# JPie handles the through association automatically
|
192
|
+
has_many :drivers, through: :car_drivers
|
444
193
|
end
|
445
194
|
```
|
446
195
|
|
447
|
-
|
448
|
-
|
449
|
-
Add computed or transformed attributes to your resources using either blocks or method overrides:
|
196
|
+
The join table is completely hidden from the API response, providing a clean interface.
|
450
197
|
|
451
|
-
|
452
|
-
|
453
|
-
```ruby
|
454
|
-
class UserResource < JPie::Resource
|
455
|
-
attribute :display_name do
|
456
|
-
"#{object.first_name} #{object.last_name}"
|
457
|
-
end
|
458
|
-
|
459
|
-
attribute :admin_notes do
|
460
|
-
if context[:current_user]&.admin?
|
461
|
-
object.admin_notes
|
462
|
-
else
|
463
|
-
nil
|
464
|
-
end
|
465
|
-
end
|
466
|
-
end
|
467
|
-
```
|
468
|
-
|
469
|
-
#### Using Method Overrides (New Approach)
|
198
|
+
### Custom Attributes
|
470
199
|
|
471
|
-
|
200
|
+
Define custom computed attributes using method overrides:
|
472
201
|
|
473
202
|
```ruby
|
474
203
|
class UserResource < JPie::Resource
|
475
|
-
attributes :
|
204
|
+
attributes :first_name, :last_name
|
476
205
|
attribute :full_name
|
477
|
-
|
478
|
-
meta_attribute :user_stats
|
479
|
-
|
206
|
+
|
480
207
|
private
|
481
|
-
|
208
|
+
|
482
209
|
def full_name
|
483
210
|
"#{object.first_name} #{object.last_name}"
|
484
211
|
end
|
485
|
-
|
486
|
-
def display_name
|
487
|
-
if context[:admin]
|
488
|
-
"#{full_name} [ADMIN VIEW] - #{object.email}"
|
489
|
-
else
|
490
|
-
full_name
|
491
|
-
end
|
492
|
-
end
|
493
|
-
|
494
|
-
def user_stats
|
495
|
-
{
|
496
|
-
name_length: object.name.length,
|
497
|
-
email_domain: object.email.split('@').last,
|
498
|
-
account_status: object.active? ? 'active' : 'inactive'
|
499
|
-
}
|
500
|
-
end
|
501
212
|
end
|
502
213
|
```
|
503
214
|
|
504
|
-
|
505
|
-
- **Cleaner syntax** - No need for blocks
|
506
|
-
- **Better IDE support** - Full method definitions with proper syntax highlighting
|
507
|
-
- **Easier testing** - Methods can be tested individually
|
508
|
-
- **Private methods supported** - Use private methods for internal logic
|
509
|
-
- **Access to object and context** - Full access to `object` and `context` like blocks
|
215
|
+
### Authorization & Context
|
510
216
|
|
511
|
-
|
512
|
-
1. **Blocks** (highest priority) - `attribute :name do ... end`
|
513
|
-
2. **Options blocks** - `attribute :name, block: proc { ... }`
|
514
|
-
3. **Custom methods** - `def name; ...; end`
|
515
|
-
4. **Model attributes** (lowest priority) - Direct model attribute lookup
|
516
|
-
|
517
|
-
### Meta attributes
|
518
|
-
|
519
|
-
JPie supports adding meta data to your JSON:API resources in two ways: using the `meta_attributes` macro or by defining a custom `meta` method.
|
520
|
-
|
521
|
-
#### Using meta_attributes Macro
|
522
|
-
|
523
|
-
It's easy to add meta attributes:
|
217
|
+
Pass context for authorization-aware responses:
|
524
218
|
|
525
219
|
```ruby
|
526
|
-
class
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
#### Using Custom meta Method
|
533
|
-
|
534
|
-
For more complex meta data, you can define a `meta` method that returns a hash:
|
535
|
-
|
536
|
-
```ruby
|
537
|
-
class UserResource < JPie::Resource
|
538
|
-
attributes :name, :email
|
539
|
-
meta_attributes :created_at, :updated_at
|
540
|
-
|
541
|
-
def meta
|
542
|
-
super.merge(
|
543
|
-
full_name: "#{object.first_name} #{object.last_name}",
|
544
|
-
user_role: context[:current_user]&.role || 'guest',
|
545
|
-
account_status: object.active? ? 'active' : 'inactive',
|
546
|
-
last_seen: object.last_login_at&.iso8601
|
547
|
-
)
|
220
|
+
class UsersController < ApplicationController
|
221
|
+
include JPie::Controller
|
222
|
+
|
223
|
+
def show
|
224
|
+
user = User.find(params[:id])
|
225
|
+
render_jsonapi(user, context: { current_user: current_user })
|
548
226
|
end
|
549
227
|
end
|
550
228
|
```
|
551
229
|
|
552
|
-
|
553
|
-
- `super` - returns the hash from `meta_attributes`
|
554
|
-
- `object` - the underlying model instance
|
555
|
-
- `context` - any context passed during resource initialization
|
556
|
-
|
557
|
-
**Example JSON:API Response with Custom Meta:**
|
558
|
-
|
559
|
-
```json
|
560
|
-
{
|
561
|
-
"data": {
|
562
|
-
"id": "1",
|
563
|
-
"type": "users",
|
564
|
-
"attributes": {
|
565
|
-
"name": "John Doe",
|
566
|
-
"email": "john@example.com"
|
567
|
-
},
|
568
|
-
"meta": {
|
569
|
-
"created_at": "2024-01-01T12:00:00Z",
|
570
|
-
"updated_at": "2024-01-15T14:30:00Z",
|
571
|
-
"full_name": "John Doe",
|
572
|
-
"user_role": "admin",
|
573
|
-
"account_status": "active",
|
574
|
-
"last_seen": "2024-01-15T14:00:00Z"
|
575
|
-
}
|
576
|
-
}
|
577
|
-
}
|
578
|
-
```
|
579
|
-
|
580
|
-
#### Meta Method Inheritance
|
581
|
-
|
582
|
-
Meta methods work seamlessly with inheritance:
|
583
|
-
|
584
|
-
```ruby
|
585
|
-
class BaseResource < JPie::Resource
|
586
|
-
meta_attributes :created_at, :updated_at
|
230
|
+
## Error Handling
|
587
231
|
|
588
|
-
|
589
|
-
super.merge(
|
590
|
-
resource_version: '1.0',
|
591
|
-
timestamp: Time.current.iso8601
|
592
|
-
)
|
593
|
-
end
|
594
|
-
end
|
232
|
+
JPie provides robust error handling with full customization options:
|
595
233
|
|
596
|
-
|
597
|
-
attributes :name, :email
|
598
|
-
meta_attributes :last_login_at
|
234
|
+
### Default Error Handling
|
599
235
|
|
600
|
-
|
601
|
-
super.merge(
|
602
|
-
user_specific_data: calculate_user_metrics
|
603
|
-
)
|
604
|
-
end
|
605
|
-
|
606
|
-
private
|
607
|
-
|
608
|
-
def calculate_user_metrics
|
609
|
-
{
|
610
|
-
post_count: object.posts.count,
|
611
|
-
comment_count: object.comments.count
|
612
|
-
}
|
613
|
-
end
|
614
|
-
end
|
615
|
-
```
|
616
|
-
|
617
|
-
### Custom Sorting
|
618
|
-
|
619
|
-
Override the default sorting behavior with custom logic:
|
620
|
-
|
621
|
-
```ruby
|
622
|
-
class PostResource < JPie::Resource
|
623
|
-
attributes :title, :content
|
624
|
-
|
625
|
-
sortable_by :popularity do |query, direction|
|
626
|
-
if direction == :asc
|
627
|
-
query.order(:likes_count, :comments_count)
|
628
|
-
else
|
629
|
-
query.order(likes_count: :desc, comments_count: :desc)
|
630
|
-
end
|
631
|
-
end
|
632
|
-
end
|
633
|
-
```
|
236
|
+
JPie automatically handles common errors:
|
634
237
|
|
635
|
-
|
238
|
+
| Error Type | HTTP Status | Description |
|
239
|
+
|------------|-------------|-------------|
|
240
|
+
| `JPie::Errors::Error` | Varies | Base JPie errors with custom status |
|
241
|
+
| `ActiveRecord::RecordNotFound` | 404 | Missing records |
|
242
|
+
| `ActiveRecord::RecordInvalid` | 422 | Validation failures |
|
636
243
|
|
637
|
-
|
244
|
+
### Customization Options
|
638
245
|
|
639
|
-
####
|
246
|
+
#### Override Specific Handlers
|
640
247
|
|
641
248
|
```ruby
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
belongs_to :author, class_name: 'User'
|
646
|
-
|
647
|
-
validates :content, presence: true
|
648
|
-
end
|
649
|
-
|
650
|
-
# Post model with has_many polymorphic association
|
651
|
-
class Post < ActiveRecord::Base
|
652
|
-
has_many :comments, as: :commentable, dependent: :destroy
|
653
|
-
belongs_to :author, class_name: 'User'
|
654
|
-
|
655
|
-
validates :title, :content, presence: true
|
656
|
-
end
|
657
|
-
|
658
|
-
# Article model with has_many polymorphic association
|
659
|
-
class Article < ActiveRecord::Base
|
660
|
-
has_many :comments, as: :commentable, dependent: :destroy
|
661
|
-
belongs_to :author, class_name: 'User'
|
249
|
+
class ApplicationController < ActionController::Base
|
250
|
+
# Define your handlers first
|
251
|
+
rescue_from ActiveRecord::RecordNotFound, with: :my_not_found_handler
|
662
252
|
|
663
|
-
|
253
|
+
include JPie::Controller
|
254
|
+
# JPie will detect existing handler and won't override it
|
664
255
|
end
|
665
256
|
```
|
666
257
|
|
667
|
-
####
|
258
|
+
#### Extend JPie Handlers
|
668
259
|
|
669
260
|
```ruby
|
670
|
-
|
671
|
-
|
672
|
-
attributes :content, :created_at
|
673
|
-
|
674
|
-
# Define methods for includes
|
675
|
-
has_many :comments # For including nested comments
|
676
|
-
has_one :author # For including the comment author
|
677
|
-
|
678
|
-
private
|
679
|
-
|
680
|
-
def commentable
|
681
|
-
object.commentable
|
682
|
-
end
|
683
|
-
|
684
|
-
def author
|
685
|
-
object.author
|
686
|
-
end
|
687
|
-
end
|
688
|
-
|
689
|
-
# Post resource
|
690
|
-
class PostResource < JPie::Resource
|
691
|
-
attributes :title, :content, :published_at
|
692
|
-
|
693
|
-
# Define methods for includes
|
694
|
-
has_many :comments
|
695
|
-
has_one :author
|
696
|
-
|
697
|
-
private
|
698
|
-
|
699
|
-
def comments
|
700
|
-
object.comments
|
701
|
-
end
|
702
|
-
|
703
|
-
def author
|
704
|
-
object.author
|
705
|
-
end
|
706
|
-
end
|
707
|
-
|
708
|
-
# Article resource
|
709
|
-
class ArticleResource < JPie::Resource
|
710
|
-
attributes :title, :body, :published_at
|
711
|
-
|
712
|
-
# Define methods for includes
|
713
|
-
has_many :comments
|
714
|
-
has_one :author
|
261
|
+
class ApplicationController < ActionController::Base
|
262
|
+
include JPie::Controller
|
715
263
|
|
716
264
|
private
|
717
265
|
|
718
|
-
def
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
266
|
+
def render_jpie_validation_error(error)
|
267
|
+
# Add custom logging
|
268
|
+
Rails.logger.error "Validation failed: #{error.message}"
|
269
|
+
|
270
|
+
# Call the original method or implement your own
|
271
|
+
super
|
724
272
|
end
|
725
273
|
end
|
726
274
|
```
|
727
275
|
|
728
|
-
####
|
276
|
+
#### Disable All JPie Error Handlers
|
729
277
|
|
730
278
|
```ruby
|
731
|
-
class
|
279
|
+
class ApplicationController < ActionController::Base
|
732
280
|
include JPie::Controller
|
733
281
|
|
734
|
-
|
735
|
-
def create
|
736
|
-
attributes = deserialize_params
|
737
|
-
commentable = find_commentable
|
738
|
-
|
739
|
-
comment = commentable.comments.build(attributes)
|
740
|
-
comment.author = current_user
|
741
|
-
comment.save!
|
742
|
-
|
743
|
-
render_jsonapi(comment, status: :created)
|
744
|
-
end
|
745
|
-
|
746
|
-
private
|
282
|
+
disable_jpie_error_handlers
|
747
283
|
|
748
|
-
|
749
|
-
|
750
|
-
if params[:post_id]
|
751
|
-
Post.find(params[:post_id])
|
752
|
-
elsif params[:article_id]
|
753
|
-
Article.find(params[:article_id])
|
754
|
-
else
|
755
|
-
raise ArgumentError, "Commentable not specified"
|
756
|
-
end
|
757
|
-
end
|
758
|
-
end
|
759
|
-
|
760
|
-
class PostsController < ApplicationController
|
761
|
-
include JPie::Controller
|
762
|
-
# Uses default CRUD operations with polymorphic comments included
|
763
|
-
end
|
764
|
-
|
765
|
-
class ArticlesController < ApplicationController
|
766
|
-
include JPie::Controller
|
767
|
-
# Uses default CRUD operations with polymorphic comments included
|
284
|
+
# Define your own handlers
|
285
|
+
rescue_from StandardError, with: :handle_standard_error
|
768
286
|
end
|
769
287
|
```
|
770
288
|
|
771
|
-
|
289
|
+
### Custom JPie Errors
|
772
290
|
|
773
291
|
```ruby
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
end
|
778
|
-
|
779
|
-
resources :articles do
|
780
|
-
resources :comments, only: [:index, :create]
|
292
|
+
class CustomBusinessError < JPie::Errors::Error
|
293
|
+
def initialize(detail: 'Business logic error')
|
294
|
+
super(status: 422, title: 'Business Error', detail: detail)
|
781
295
|
end
|
782
|
-
|
783
|
-
resources :comments, only: [:show, :update, :destroy]
|
784
296
|
end
|
785
|
-
```
|
786
297
|
|
787
|
-
|
788
|
-
|
789
|
-
**GET /posts/1?include=comments,comments.author**
|
790
|
-
|
791
|
-
```json
|
792
|
-
{
|
793
|
-
"data": {
|
794
|
-
"id": "1",
|
795
|
-
"type": "posts",
|
796
|
-
"attributes": {
|
797
|
-
"title": "My First Post",
|
798
|
-
"content": "This is the content of my first post.",
|
799
|
-
"published_at": "2024-01-15T10:30:00Z"
|
800
|
-
}
|
801
|
-
},
|
802
|
-
"included": [
|
803
|
-
{
|
804
|
-
"id": "1",
|
805
|
-
"type": "comments",
|
806
|
-
"attributes": {
|
807
|
-
"content": "Great post!",
|
808
|
-
"created_at": "2024-01-15T11:00:00Z"
|
809
|
-
}
|
810
|
-
},
|
811
|
-
{
|
812
|
-
"id": "2",
|
813
|
-
"type": "comments",
|
814
|
-
"attributes": {
|
815
|
-
"content": "Thanks for sharing!",
|
816
|
-
"created_at": "2024-01-15T12:00:00Z"
|
817
|
-
}
|
818
|
-
},
|
819
|
-
{
|
820
|
-
"id": "5",
|
821
|
-
"type": "users",
|
822
|
-
"attributes": {
|
823
|
-
"name": "John Doe",
|
824
|
-
"email": "john@example.com"
|
825
|
-
}
|
826
|
-
},
|
827
|
-
{
|
828
|
-
"id": "6",
|
829
|
-
"type": "users",
|
830
|
-
"attributes": {
|
831
|
-
"name": "Jane Smith",
|
832
|
-
"email": "jane@example.com"
|
833
|
-
}
|
834
|
-
}
|
835
|
-
]
|
836
|
-
}
|
298
|
+
# Use in controllers
|
299
|
+
raise CustomBusinessError.new(detail: 'Custom validation failed')
|
837
300
|
```
|
838
301
|
|
839
|
-
|
302
|
+
## Advanced Features
|
840
303
|
|
841
|
-
|
304
|
+
### Polymorphic Associations
|
842
305
|
|
843
|
-
|
306
|
+
JPie supports polymorphic associations for complex data relationships:
|
844
307
|
|
845
308
|
```ruby
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
end
|
857
|
-
|
858
|
-
class Truck < Vehicle
|
859
|
-
validates :cargo_capacity, presence: true
|
309
|
+
class CommentResource < JPie::Resource
|
310
|
+
attributes :content
|
311
|
+
has_one :author
|
312
|
+
|
313
|
+
# Polymorphic commentable (posts, articles, videos, etc.)
|
314
|
+
private
|
315
|
+
|
316
|
+
def author
|
317
|
+
object.author
|
318
|
+
end
|
860
319
|
end
|
861
320
|
```
|
862
321
|
|
863
|
-
|
322
|
+
### Single Table Inheritance
|
864
323
|
|
865
|
-
JPie automatically handles STI
|
324
|
+
JPie automatically handles STI models:
|
866
325
|
|
867
326
|
```ruby
|
868
327
|
# Base resource
|
869
328
|
class VehicleResource < JPie::Resource
|
870
329
|
attributes :name, :brand, :year
|
871
|
-
meta_attributes :created_at, :updated_at
|
872
330
|
end
|
873
331
|
|
874
|
-
# STI resources inherit
|
332
|
+
# STI resources inherit automatically
|
875
333
|
class CarResource < VehicleResource
|
876
|
-
attributes :engine_size # Car-specific
|
877
|
-
end
|
878
|
-
|
879
|
-
class TruckResource < VehicleResource
|
880
|
-
attributes :cargo_capacity # Truck-specific attribute
|
334
|
+
attributes :engine_size, :doors # Car-specific attributes
|
881
335
|
end
|
882
336
|
```
|
883
337
|
|
884
|
-
|
885
|
-
|
886
|
-
JPie automatically infers the correct JSON:API type from the STI model class:
|
887
|
-
|
888
|
-
```ruby
|
889
|
-
car = Car.create!(name: 'Civic', brand: 'Honda', year: 2020, engine_size: 1500)
|
890
|
-
car_resource = CarResource.new(car)
|
891
|
-
|
892
|
-
car_resource.type # => "cars" (automatically inferred from Car model)
|
893
|
-
```
|
894
|
-
|
895
|
-
#### STI Serialization
|
896
|
-
|
897
|
-
Each STI model serializes with its specific type and attributes:
|
898
|
-
|
899
|
-
```ruby
|
900
|
-
# Car serialization
|
901
|
-
car_serializer = JPie::Serializer.new(CarResource)
|
902
|
-
result = car_serializer.serialize(car)
|
903
|
-
|
904
|
-
# Result:
|
905
|
-
{
|
906
|
-
"data": {
|
907
|
-
"id": "1",
|
908
|
-
"type": "cars", # STI type
|
909
|
-
"attributes": {
|
910
|
-
"name": "Civic",
|
911
|
-
"brand": "Honda",
|
912
|
-
"year": 2020,
|
913
|
-
"engine_size": 1500 # Car-specific attribute
|
914
|
-
}
|
915
|
-
}
|
916
|
-
}
|
917
|
-
|
918
|
-
# Truck serialization
|
919
|
-
truck_serializer = JPie::Serializer.new(TruckResource)
|
920
|
-
result = truck_serializer.serialize(truck)
|
921
|
-
|
922
|
-
# Result:
|
923
|
-
{
|
924
|
-
"data": {
|
925
|
-
"id": "2",
|
926
|
-
"type": "trucks", # STI type
|
927
|
-
"attributes": {
|
928
|
-
"name": "F-150",
|
929
|
-
"brand": "Ford",
|
930
|
-
"year": 2021,
|
931
|
-
"cargo_capacity": 1000 # Truck-specific attribute
|
932
|
-
}
|
933
|
-
}
|
934
|
-
}
|
935
|
-
```
|
936
|
-
|
937
|
-
#### STI Controllers
|
338
|
+
STI types are automatically inferred in JSON:API responses.
|
938
339
|
|
939
|
-
|
940
|
-
|
941
|
-
```ruby
|
942
|
-
class CarsController < ApplicationController
|
943
|
-
include JPie::Controller
|
944
|
-
# Automatically uses CarResource and Car model
|
945
|
-
end
|
946
|
-
|
947
|
-
class TrucksController < ApplicationController
|
948
|
-
include JPie::Controller
|
949
|
-
# Automatically uses TruckResource and Truck model
|
950
|
-
end
|
951
|
-
|
952
|
-
class VehiclesController < ApplicationController
|
953
|
-
include JPie::Controller
|
954
|
-
# Uses VehicleResource and returns all vehicles (cars, trucks, etc.)
|
955
|
-
end
|
956
|
-
```
|
957
|
-
|
958
|
-
#### STI Scoping
|
959
|
-
|
960
|
-
Each STI resource automatically scopes to its specific type:
|
961
|
-
|
962
|
-
```ruby
|
963
|
-
CarResource.scope # Returns only Car records
|
964
|
-
TruckResource.scope # Returns only Truck records
|
965
|
-
VehicleResource.scope # Returns all Vehicle records (including STI subclasses)
|
966
|
-
```
|
967
|
-
|
968
|
-
#### Complete STI Example
|
969
|
-
|
970
|
-
Here's a complete example showing STI in action with HTTP requests and responses:
|
340
|
+
### Custom Sorting
|
971
341
|
|
972
|
-
|
342
|
+
Implement complex sorting logic:
|
973
343
|
|
974
344
|
```ruby
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
t.string :name, null: false
|
981
|
-
t.string :brand, null: false
|
982
|
-
t.integer :year, null: false
|
983
|
-
t.integer :engine_size # Car-specific
|
984
|
-
t.integer :cargo_capacity # Truck-specific
|
985
|
-
t.timestamps
|
986
|
-
end
|
987
|
-
|
988
|
-
add_index :vehicles, :type
|
345
|
+
class PostResource < JPie::Resource
|
346
|
+
sortable :popularity do |query, direction|
|
347
|
+
query.joins(:likes, :comments)
|
348
|
+
.group('posts.id')
|
349
|
+
.order("COUNT(likes.id) + COUNT(comments.id) #{direction.to_s.upcase}")
|
989
350
|
end
|
990
351
|
end
|
991
352
|
```
|
992
353
|
|
993
|
-
|
994
|
-
|
995
|
-
```ruby
|
996
|
-
class Vehicle < ApplicationRecord
|
997
|
-
validates :name, :brand, :year, presence: true
|
998
|
-
end
|
999
|
-
|
1000
|
-
class Car < Vehicle
|
1001
|
-
validates :engine_size, presence: true
|
1002
|
-
end
|
1003
|
-
|
1004
|
-
class Truck < Vehicle
|
1005
|
-
validates :cargo_capacity, presence: true
|
1006
|
-
end
|
1007
|
-
```
|
1008
|
-
|
1009
|
-
**3. Resources**
|
1010
|
-
|
1011
|
-
```ruby
|
1012
|
-
class VehicleResource < JPie::Resource
|
1013
|
-
attributes :name, :brand, :year
|
1014
|
-
meta_attributes :created_at, :updated_at
|
1015
|
-
end
|
1016
|
-
|
1017
|
-
class CarResource < VehicleResource
|
1018
|
-
attributes :engine_size
|
1019
|
-
end
|
1020
|
-
|
1021
|
-
class TruckResource < VehicleResource
|
1022
|
-
attributes :cargo_capacity
|
1023
|
-
end
|
1024
|
-
```
|
1025
|
-
|
1026
|
-
**4. Controllers**
|
1027
|
-
|
1028
|
-
```ruby
|
1029
|
-
class VehiclesController < ApplicationController
|
1030
|
-
include JPie::Controller
|
1031
|
-
# Returns all vehicles (cars, trucks, etc.)
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
class CarsController < ApplicationController
|
1035
|
-
include JPie::Controller
|
1036
|
-
# Returns only cars with car-specific attributes
|
1037
|
-
end
|
1038
|
-
|
1039
|
-
class TrucksController < ApplicationController
|
1040
|
-
include JPie::Controller
|
1041
|
-
# Returns only trucks with truck-specific attributes
|
1042
|
-
end
|
1043
|
-
```
|
1044
|
-
|
1045
|
-
**5. Routes**
|
1046
|
-
|
1047
|
-
```ruby
|
1048
|
-
Rails.application.routes.draw do
|
1049
|
-
resources :vehicles, only: [:index, :show]
|
1050
|
-
resources :cars
|
1051
|
-
resources :trucks
|
1052
|
-
end
|
1053
|
-
```
|
1054
|
-
|
1055
|
-
**6. Example HTTP Requests and Responses**
|
1056
|
-
|
1057
|
-
**GET /cars/1**
|
1058
|
-
```json
|
1059
|
-
{
|
1060
|
-
"data": {
|
1061
|
-
"id": "1",
|
1062
|
-
"type": "cars",
|
1063
|
-
"attributes": {
|
1064
|
-
"name": "Model 3",
|
1065
|
-
"brand": "Tesla",
|
1066
|
-
"year": 2023,
|
1067
|
-
"engine_size": 0
|
1068
|
-
},
|
1069
|
-
"meta": {
|
1070
|
-
"created_at": "2024-01-15T10:00:00Z",
|
1071
|
-
"updated_at": "2024-01-15T10:00:00Z"
|
1072
|
-
}
|
1073
|
-
}
|
1074
|
-
}
|
1075
|
-
```
|
1076
|
-
|
1077
|
-
**GET /trucks/2**
|
1078
|
-
```json
|
1079
|
-
{
|
1080
|
-
"data": {
|
1081
|
-
"id": "2",
|
1082
|
-
"type": "trucks",
|
1083
|
-
"attributes": {
|
1084
|
-
"name": "F-150",
|
1085
|
-
"brand": "Ford",
|
1086
|
-
"year": 2023,
|
1087
|
-
"cargo_capacity": 1200
|
1088
|
-
},
|
1089
|
-
"meta": {
|
1090
|
-
"created_at": "2024-01-15T11:00:00Z",
|
1091
|
-
"updated_at": "2024-01-15T11:00:00Z"
|
1092
|
-
}
|
1093
|
-
}
|
1094
|
-
}
|
1095
|
-
```
|
1096
|
-
|
1097
|
-
**GET /vehicles (Mixed STI Collection)**
|
1098
|
-
```json
|
1099
|
-
{
|
1100
|
-
"data": [
|
1101
|
-
{
|
1102
|
-
"id": "1",
|
1103
|
-
"type": "cars",
|
1104
|
-
"attributes": {
|
1105
|
-
"name": "Model 3",
|
1106
|
-
"brand": "Tesla",
|
1107
|
-
"year": 2023,
|
1108
|
-
"engine_size": 0
|
1109
|
-
}
|
1110
|
-
},
|
1111
|
-
{
|
1112
|
-
"id": "2",
|
1113
|
-
"type": "trucks",
|
1114
|
-
"attributes": {
|
1115
|
-
"name": "F-150",
|
1116
|
-
"brand": "Ford",
|
1117
|
-
"year": 2023,
|
1118
|
-
"cargo_capacity": 1200
|
1119
|
-
}
|
1120
|
-
}
|
1121
|
-
]
|
1122
|
-
}
|
1123
|
-
```
|
1124
|
-
|
1125
|
-
**7. Creating STI Records**
|
1126
|
-
|
1127
|
-
**POST /cars**
|
1128
|
-
```json
|
1129
|
-
{
|
1130
|
-
"data": {
|
1131
|
-
"type": "cars",
|
1132
|
-
"attributes": {
|
1133
|
-
"name": "Model Y",
|
1134
|
-
"brand": "Tesla",
|
1135
|
-
"year": 2024,
|
1136
|
-
"engine_size": 0
|
1137
|
-
}
|
1138
|
-
}
|
1139
|
-
}
|
1140
|
-
```
|
1141
|
-
|
1142
|
-
#### Custom STI Types
|
1143
|
-
|
1144
|
-
You can override the automatic type inference if needed:
|
1145
|
-
|
1146
|
-
```ruby
|
1147
|
-
class CarResource < VehicleResource
|
1148
|
-
type 'automobiles' # Custom type instead of 'cars'
|
1149
|
-
attributes :engine_size
|
1150
|
-
end
|
1151
|
-
```
|
1152
|
-
|
1153
|
-
### Authorization and Scoping
|
354
|
+
## Performance & Best Practices
|
1154
355
|
|
1155
|
-
|
356
|
+
- **Efficient serialization** with automatic deduplication
|
357
|
+
- **Smart includes** with optimized queries
|
358
|
+
- **Validation caching** for improved performance
|
359
|
+
- **Error handling** that doesn't impact performance
|
1156
360
|
|
1157
|
-
|
1158
|
-
class PostResource < JPie::Resource
|
1159
|
-
attributes :title, :content
|
1160
|
-
|
1161
|
-
def self.scope(context = {})
|
1162
|
-
current_user = context[:current_user]
|
1163
|
-
return model.none unless current_user
|
1164
|
-
Pundit.policy_scope(current_user, model)
|
1165
|
-
end
|
1166
|
-
end
|
1167
|
-
```
|
361
|
+
## Contributing
|
1168
362
|
|
1169
|
-
|
1170
|
-
|
1171
|
-
Override the context building to pass additional data to resources:
|
1172
|
-
|
1173
|
-
```ruby
|
1174
|
-
class UsersController < ApplicationController
|
1175
|
-
include JPie::Controller
|
1176
|
-
|
1177
|
-
private
|
1178
|
-
|
1179
|
-
def build_context
|
1180
|
-
{
|
1181
|
-
current_user: current_user,
|
1182
|
-
controller: self,
|
1183
|
-
action: action_name,
|
1184
|
-
request_ip: request.remote_ip,
|
1185
|
-
user_agent: request.user_agent
|
1186
|
-
}
|
1187
|
-
end
|
1188
|
-
end
|
1189
|
-
```
|
363
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/emilkampp/jpie.
|
1190
364
|
|
1191
365
|
## License
|
1192
366
|
|