jpie 0.4.0 → 0.4.2
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} +21 -5
- data/.overcommit.yml +35 -0
- data/CHANGELOG.md +33 -0
- data/README.md +97 -1063
- data/examples/basic_example.md +146 -0
- data/examples/including_related_resources.md +491 -0
- data/examples/pagination.md +303 -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 +33 -2
- data/lib/jpie/controller/error_handling/handler_setup.rb +124 -0
- data/lib/jpie/controller/error_handling/handlers.rb +109 -0
- data/lib/jpie/controller/error_handling.rb +10 -28
- data/lib/jpie/controller/json_api_validation.rb +193 -0
- data/lib/jpie/controller/parameter_parsing.rb +43 -0
- data/lib/jpie/controller/rendering.rb +95 -1
- data/lib/jpie/controller.rb +2 -0
- data/lib/jpie/errors.rb +41 -0
- data/lib/jpie/resource/attributable.rb +16 -2
- data/lib/jpie/resource.rb +40 -0
- data/lib/jpie/version.rb +1 -1
- metadata +13 -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,66 @@ Add JPie to your Rails application:
|
|
25
27
|
bundle add jpie
|
26
28
|
```
|
27
29
|
|
28
|
-
##
|
30
|
+
## Development Setup
|
29
31
|
|
30
|
-
|
32
|
+
This project uses [Overcommit](https://github.com/sds/overcommit) to enforce code quality through Git hooks. After cloning the repository:
|
33
|
+
|
34
|
+
### Quick Setup (Recommended)
|
35
|
+
|
36
|
+
```bash
|
37
|
+
# One command to set up everything
|
38
|
+
./bin/setup-hooks
|
39
|
+
```
|
40
|
+
|
41
|
+
### Manual Setup
|
42
|
+
|
43
|
+
If you prefer to set up manually:
|
44
|
+
|
45
|
+
```bash
|
46
|
+
# 1. Install dependencies
|
47
|
+
bundle install
|
48
|
+
|
49
|
+
# 2. Install overcommit globally (one-time setup)
|
50
|
+
gem install overcommit
|
51
|
+
|
52
|
+
# 3. Install the Git hooks for this project
|
53
|
+
overcommit --install
|
54
|
+
|
55
|
+
# 4. Sign the configuration (required for security)
|
56
|
+
overcommit --sign
|
57
|
+
```
|
58
|
+
|
59
|
+
### 3. Automated Quality Checks
|
60
|
+
|
61
|
+
The following checks run automatically:
|
62
|
+
|
63
|
+
**Pre-commit hooks:**
|
64
|
+
- ✅ **RuboCop** - Code style and quality analysis
|
65
|
+
- ✅ **Trailing whitespace** - Prevents whitespace issues
|
66
|
+
- ✅ **Merge conflicts** - Catches unresolved conflicts
|
67
|
+
|
68
|
+
**Pre-push hooks:**
|
69
|
+
- ✅ **RSpec** - Full test suite execution
|
70
|
+
|
71
|
+
### 4. Manual Quality Checks
|
72
|
+
|
73
|
+
You can run these checks manually:
|
74
|
+
|
75
|
+
```bash
|
76
|
+
# Run RuboCop with auto-fix
|
77
|
+
bundle exec rubocop -A
|
78
|
+
|
79
|
+
# Run tests
|
80
|
+
bundle exec rspec
|
81
|
+
|
82
|
+
# Test hooks without committing
|
83
|
+
overcommit --run pre-commit
|
84
|
+
overcommit --run pre-push
|
85
|
+
```
|
86
|
+
|
87
|
+
## Quick Start
|
88
|
+
|
89
|
+
JPie works out of the box with minimal configuration:
|
31
90
|
|
32
91
|
### 1. Create Your Model
|
33
92
|
|
@@ -35,6 +94,9 @@ JPie works out of the box with minimal configuration. Here's a complete example
|
|
35
94
|
class User < ActiveRecord::Base
|
36
95
|
validates :name, presence: true
|
37
96
|
validates :email, presence: true, uniqueness: true
|
97
|
+
|
98
|
+
has_many :posts, dependent: :destroy
|
99
|
+
has_one :profile, dependent: :destroy
|
38
100
|
end
|
39
101
|
```
|
40
102
|
|
@@ -43,6 +105,10 @@ end
|
|
43
105
|
```ruby
|
44
106
|
class UserResource < JPie::Resource
|
45
107
|
attributes :name, :email
|
108
|
+
meta_attributes :created_at, :updated_at
|
109
|
+
|
110
|
+
has_many :posts
|
111
|
+
has_one :profile
|
46
112
|
end
|
47
113
|
```
|
48
114
|
|
@@ -62,16 +128,28 @@ Rails.application.routes.draw do
|
|
62
128
|
end
|
63
129
|
```
|
64
130
|
|
65
|
-
That's it! You now have a fully functional JSON:API compliant server.
|
131
|
+
That's it! You now have a fully functional JSON:API compliant server with automatic CRUD operations, sorting, includes, and validation.
|
132
|
+
|
133
|
+
## 📚 Comprehensive Examples
|
134
|
+
|
135
|
+
JPie includes a complete set of examples demonstrating all features:
|
136
|
+
|
137
|
+
- **[🚀 Basic Usage](https://github.com/emilkampp/jpie/blob/main/examples/basic_usage.rb)** - Fundamental setup and configuration
|
138
|
+
- **[🔗 Through Associations](https://github.com/emilkampp/jpie/blob/main/examples/through_associations.rb)** - Many-to-many relationships with `:through`
|
139
|
+
- **[🎨 Custom Attributes & Meta](https://github.com/emilkampp/jpie/blob/main/examples/custom_attributes_and_meta.rb)** - Custom computed attributes and meta data
|
140
|
+
- **[🔄 Polymorphic Associations](https://github.com/emilkampp/jpie/blob/main/examples/polymorphic_associations.rb)** - Complex polymorphic relationships
|
141
|
+
- **[🏗️ Single Table Inheritance](https://github.com/emilkampp/jpie/blob/main/examples/single_table_inheritance.rb)** - STI models and resources
|
142
|
+
- **[📊 Custom Sorting](https://github.com/emilkampp/jpie/blob/main/examples/custom_sorting.rb)** - Advanced sorting with complex algorithms
|
143
|
+
- **[⚠️ Error Handling](https://github.com/emilkampp/jpie/blob/main/examples/error_handling.rb)** - Comprehensive error handling strategies
|
144
|
+
|
145
|
+
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
146
|
|
67
147
|
## Generators
|
68
148
|
|
69
|
-
JPie includes a resource generator for quickly creating new resource classes
|
149
|
+
JPie includes a resource generator for quickly creating new resource classes:
|
70
150
|
|
71
151
|
### Basic Usage
|
72
152
|
|
73
|
-
The generator uses semantic field definitions that explicitly categorize each field by its JSON:API purpose:
|
74
|
-
|
75
153
|
```bash
|
76
154
|
# Generate a basic resource with semantic syntax
|
77
155
|
rails generate jpie:resource User attribute:name attribute:email meta:created_at
|
@@ -85,13 +163,10 @@ rails generate jpie:resource User attribute:name email created_at updated_at
|
|
85
163
|
|
86
164
|
**Generated file:**
|
87
165
|
```ruby
|
88
|
-
# frozen_string_literal: true
|
89
|
-
|
90
166
|
class UserResource < JPie::Resource
|
91
167
|
attributes :name, :email
|
92
|
-
|
93
168
|
meta_attributes :created_at, :updated_at
|
94
|
-
|
169
|
+
|
95
170
|
has_many :comments
|
96
171
|
has_one :author
|
97
172
|
end
|
@@ -99,122 +174,31 @@ end
|
|
99
174
|
|
100
175
|
### Semantic Field Syntax
|
101
176
|
|
102
|
-
The generator uses a semantic approach focused on JSON:API concepts rather than database types:
|
103
|
-
|
104
177
|
| Syntax | Purpose | Example |
|
105
178
|
|--------|---------|---------|
|
106
179
|
| `attribute:field` | Regular JSON:API attribute | `attribute:name` |
|
107
180
|
| `meta:field` | JSON:API meta attribute | `meta:created_at` |
|
108
181
|
| `has_many:resource` | JSON:API relationship | `has_many:posts` |
|
109
182
|
| `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
|
-
|
136
|
-
attributes :title, :content
|
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
|
-
```
|
145
|
-
|
146
|
-
##### Empty Resource Template
|
147
|
-
|
148
|
-
```bash
|
149
|
-
rails generate jpie:resource User
|
150
|
-
```
|
151
|
-
|
152
|
-
**Generated file:**
|
153
|
-
```ruby
|
154
|
-
# frozen_string_literal: true
|
155
|
-
|
156
|
-
class UserResource < JPie::Resource
|
157
|
-
# Define your attributes here:
|
158
|
-
# attributes :name, :email, :title
|
159
|
-
|
160
|
-
# Define your meta attributes here:
|
161
|
-
# meta_attributes :created_at, :updated_at
|
162
|
-
|
163
|
-
# Define your relationships here:
|
164
|
-
# has_many :posts
|
165
|
-
# has_one :user
|
166
|
-
end
|
167
|
-
```
|
168
|
-
|
169
|
-
### Legacy Syntax Support
|
170
|
-
|
171
|
-
The generator maintains backward compatibility with the Rails-style `field:type` syntax, but ignores the type portion:
|
172
|
-
|
173
|
-
```bash
|
174
|
-
# Legacy syntax (still works, types ignored)
|
175
|
-
rails generate jpie:resource User name:string email:string created_at:datetime
|
176
|
-
```
|
177
|
-
|
178
|
-
This generates the same output as the semantic syntax, with automatic detection of meta attributes based on common field names.
|
179
183
|
|
180
184
|
### Generator Options
|
181
185
|
|
182
|
-
| Option |
|
183
|
-
|
184
|
-
| `--model=NAME` |
|
185
|
-
| `--skip-model` |
|
186
|
-
|
187
|
-
### Automatic Features
|
186
|
+
| Option | Description | Example |
|
187
|
+
|--------|-------------|---------|
|
188
|
+
| `--model=NAME` | Specify model class | `--model=Person` |
|
189
|
+
| `--skip-model` | Skip explicit model declaration | `--skip-model` |
|
188
190
|
|
189
|
-
|
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
|
191
|
+
### Modern DSL
|
206
192
|
|
207
193
|
```ruby
|
208
194
|
class UserResource < JPie::Resource
|
209
|
-
#
|
195
|
+
# Multiple attributes at once
|
210
196
|
attributes :name, :email, :created_at
|
211
|
-
attribute :full_name
|
212
197
|
|
213
|
-
# Meta attributes
|
198
|
+
# Meta attributes (for additional data)
|
214
199
|
meta :account_status, :last_login
|
215
|
-
# or: meta_attributes :account_status, :last_login
|
216
200
|
|
217
|
-
#
|
201
|
+
# Relationships for includes
|
218
202
|
has_many :posts
|
219
203
|
has_one :profile
|
220
204
|
|
@@ -222,971 +206,21 @@ class UserResource < JPie::Resource
|
|
222
206
|
sortable :popularity do |query, direction|
|
223
207
|
query.order(likes_count: direction)
|
224
208
|
end
|
225
|
-
# or: sortable_by :popularity do |query, direction|
|
226
209
|
|
227
|
-
# Custom attribute methods
|
210
|
+
# Custom attribute methods (modern approach)
|
228
211
|
private
|
229
212
|
|
230
|
-
def full_name
|
231
|
-
"#{object.first_name} #{object.last_name}"
|
232
|
-
end
|
233
|
-
|
234
213
|
def account_status
|
235
214
|
object.active? ? 'active' : 'inactive'
|
236
215
|
end
|
237
216
|
end
|
238
217
|
```
|
239
218
|
|
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:
|
307
|
-
|
308
|
-
```http
|
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:
|
385
|
-
|
386
|
-
```ruby
|
387
|
-
# Automatic inference (default behavior)
|
388
|
-
class UsersController < ApplicationController
|
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
|
442
|
-
|
443
|
-
# show, update, destroy still use the automatic implementations
|
444
|
-
end
|
445
|
-
```
|
446
|
-
|
447
|
-
### Custom Attributes
|
448
|
-
|
449
|
-
Add computed or transformed attributes to your resources using either blocks or method overrides:
|
450
|
-
|
451
|
-
#### Using Blocks (Original Approach)
|
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)
|
470
|
-
|
471
|
-
You can now define custom methods directly on your resource class instead of using blocks:
|
472
|
-
|
473
|
-
```ruby
|
474
|
-
class UserResource < JPie::Resource
|
475
|
-
attributes :name, :email
|
476
|
-
attribute :full_name
|
477
|
-
attribute :display_name
|
478
|
-
meta_attribute :user_stats
|
479
|
-
|
480
|
-
private
|
481
|
-
|
482
|
-
def full_name
|
483
|
-
"#{object.first_name} #{object.last_name}"
|
484
|
-
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
|
-
end
|
502
|
-
```
|
503
|
-
|
504
|
-
**Key Benefits of Method Overrides:**
|
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
|
510
|
-
|
511
|
-
**Method Precedence:**
|
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:
|
524
|
-
|
525
|
-
```ruby
|
526
|
-
class UserResource < JPie::Resource
|
527
|
-
meta_attributes :created_at, :updated_at
|
528
|
-
meta_attributes :last_login_at
|
529
|
-
end
|
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
|
-
)
|
548
|
-
end
|
549
|
-
end
|
550
|
-
```
|
551
|
-
|
552
|
-
The `meta` method has access to:
|
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
|
587
|
-
|
588
|
-
def meta
|
589
|
-
super.merge(
|
590
|
-
resource_version: '1.0',
|
591
|
-
timestamp: Time.current.iso8601
|
592
|
-
)
|
593
|
-
end
|
594
|
-
end
|
595
|
-
|
596
|
-
class UserResource < BaseResource
|
597
|
-
attributes :name, :email
|
598
|
-
meta_attributes :last_login_at
|
599
|
-
|
600
|
-
def meta
|
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
|
219
|
+
See the examples folder for more examples of how to use the DSL to solve various serialization/deserialization scenarios.
|
618
220
|
|
619
|
-
|
221
|
+
## Contributing
|
620
222
|
|
621
|
-
|
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
|
-
```
|
634
|
-
|
635
|
-
### Polymorphic Associations
|
636
|
-
|
637
|
-
JPie supports polymorphic associations for includes. Here's a complete example with comments that can belong to multiple types of commentable resources:
|
638
|
-
|
639
|
-
#### Models with Polymorphic Associations
|
640
|
-
|
641
|
-
```ruby
|
642
|
-
# Comment model with belongs_to polymorphic association
|
643
|
-
class Comment < ActiveRecord::Base
|
644
|
-
belongs_to :commentable, polymorphic: true
|
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'
|
662
|
-
|
663
|
-
validates :title, :body, presence: true
|
664
|
-
end
|
665
|
-
```
|
666
|
-
|
667
|
-
#### Resources for Polymorphic Associations
|
668
|
-
|
669
|
-
```ruby
|
670
|
-
# Comment resource
|
671
|
-
class CommentResource < JPie::Resource
|
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
|
715
|
-
|
716
|
-
private
|
717
|
-
|
718
|
-
def comments
|
719
|
-
object.comments
|
720
|
-
end
|
721
|
-
|
722
|
-
def author
|
723
|
-
object.author
|
724
|
-
end
|
725
|
-
end
|
726
|
-
```
|
727
|
-
|
728
|
-
#### Controllers for Polymorphic Resources
|
729
|
-
|
730
|
-
```ruby
|
731
|
-
class CommentsController < ApplicationController
|
732
|
-
include JPie::Controller
|
733
|
-
|
734
|
-
# Override create to handle polymorphic assignment
|
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
|
747
|
-
|
748
|
-
def find_commentable
|
749
|
-
# Extract commentable info from request path or parameters
|
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
|
768
|
-
end
|
769
|
-
```
|
770
|
-
|
771
|
-
#### Routes for Polymorphic Resources
|
772
|
-
|
773
|
-
```ruby
|
774
|
-
Rails.application.routes.draw do
|
775
|
-
resources :posts do
|
776
|
-
resources :comments, only: [:index, :create]
|
777
|
-
end
|
778
|
-
|
779
|
-
resources :articles do
|
780
|
-
resources :comments, only: [:index, :create]
|
781
|
-
end
|
782
|
-
|
783
|
-
resources :comments, only: [:show, :update, :destroy]
|
784
|
-
end
|
785
|
-
```
|
786
|
-
|
787
|
-
#### Example JSON:API Responses
|
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
|
-
}
|
837
|
-
```
|
838
|
-
|
839
|
-
### Single Table Inheritance (STI)
|
840
|
-
|
841
|
-
JPie provides comprehensive support for Rails Single Table Inheritance (STI) models. STI allows multiple models to share a single database table with a "type" column to differentiate between them.
|
842
|
-
|
843
|
-
#### STI Models
|
844
|
-
|
845
|
-
```ruby
|
846
|
-
# Base model
|
847
|
-
class Vehicle < ActiveRecord::Base
|
848
|
-
validates :name, presence: true
|
849
|
-
validates :brand, presence: true
|
850
|
-
validates :year, presence: true
|
851
|
-
end
|
852
|
-
|
853
|
-
# STI subclasses
|
854
|
-
class Car < Vehicle
|
855
|
-
validates :engine_size, presence: true
|
856
|
-
end
|
857
|
-
|
858
|
-
class Truck < Vehicle
|
859
|
-
validates :cargo_capacity, presence: true
|
860
|
-
end
|
861
|
-
```
|
862
|
-
|
863
|
-
#### STI Resources
|
864
|
-
|
865
|
-
JPie automatically handles STI type inference and resource inheritance:
|
866
|
-
|
867
|
-
```ruby
|
868
|
-
# Base resource
|
869
|
-
class VehicleResource < JPie::Resource
|
870
|
-
attributes :name, :brand, :year
|
871
|
-
meta_attributes :created_at, :updated_at
|
872
|
-
end
|
873
|
-
|
874
|
-
# STI resources inherit from base resource
|
875
|
-
class CarResource < VehicleResource
|
876
|
-
attributes :engine_size # Car-specific attribute
|
877
|
-
end
|
878
|
-
|
879
|
-
class TruckResource < VehicleResource
|
880
|
-
attributes :cargo_capacity # Truck-specific attribute
|
881
|
-
end
|
882
|
-
```
|
883
|
-
|
884
|
-
#### STI Type Inference
|
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
|
938
|
-
|
939
|
-
Controllers work seamlessly with STI models:
|
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:
|
971
|
-
|
972
|
-
**1. Database Setup**
|
973
|
-
|
974
|
-
```ruby
|
975
|
-
# Migration
|
976
|
-
class CreateVehicles < ActiveRecord::Migration[7.0]
|
977
|
-
def change
|
978
|
-
create_table :vehicles do |t|
|
979
|
-
t.string :type, null: false # STI discriminator column
|
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
|
989
|
-
end
|
990
|
-
end
|
991
|
-
```
|
992
|
-
|
993
|
-
**2. Models**
|
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
|
1154
|
-
|
1155
|
-
Override the default scope method to add authorization:
|
1156
|
-
|
1157
|
-
```ruby
|
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
|
-
```
|
1168
|
-
|
1169
|
-
### Custom Context
|
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
|
-
```
|
223
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/emilkampp/jpie.
|
1190
224
|
|
1191
225
|
## License
|
1192
226
|
|