jpie 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b3ade425c9173f2c516edbab70ee2991201648fb15d2e461b58667fda6a46a8
4
- data.tar.gz: 7783e2e57be4b1a2f7e50fec35240413d873af2e1ab98ba8ed7f8305af272c9e
3
+ metadata.gz: 17c72f6974e3f671d772c5acec6ccd40541e06c36797603b4e0625b2235834f2
4
+ data.tar.gz: 6e13c925f6c6605278a0f3544165c8c3f7c9c6feaf3f97c54265c033f0a498e5
5
5
  SHA512:
6
- metadata.gz: 582998bde4d3fc2b44c280a8a4e87e45477bd66f808790c07a90f7110ca405b7e4695f4c306280fdd8a85b4bf36d4f1413b55a542c40d96a340d828ff2aaf2b2
7
- data.tar.gz: 63d5d8f010159cb9aff0bcc99a5c8f5739c5d879b62611e980eb0c3ca0312ef02f31d550bfb14b5cbee0d0978c236fe28beb0df79a1006178c429c04d1457aa6
6
+ metadata.gz: 8a9d2f4a6505c5bd7e94798fd63c382150b4170f8cd610548439161bec82dc9bfc07807468e32cc50d980018e7bc5625724db509742a97281e32dfb069acbe47
7
+ data.tar.gz: 8e1eb70f4967a8d83c25583b234172a6fd9e9767ef1ea816a2fe5b6dc447f7bd11a68e67cab7e27851eee342395e616249d32033680cdc83050d896bd5726ec9
data/.cursorrules CHANGED
@@ -5,6 +5,10 @@
5
5
  - Follow Ruby style guide and RuboCop rules defined in .rubocop.yml
6
6
  - Prefer rubocop autocorrect
7
7
  - Always pass rubocop before committing to git
8
+ - Use `{data:}` rather than `{data: data}`
9
+ - Only use code comments when abasolutely necessary
10
+ - Keep any code comments short and concise
11
+ - Don't update the .rubocop.yml unless it's absolutely necessarry
8
12
 
9
13
  # Documentation requirements
10
14
  - Keep README.md up to date with installation and usage instructions
@@ -18,6 +22,7 @@
18
22
  - Test both success and error cases
19
23
  - Use modern RSpec features and syntax
20
24
  - Don't reduce test coverage (line, file, branch, or other)
25
+ - Only care about coverge reduction when running all specs
21
26
 
22
27
  # Git commit guidelines
23
28
  - Write clear, descriptive commit messages
@@ -33,6 +38,8 @@
33
38
  - Place tests in spec/jpie/
34
39
  - Use proper namespacing (JPie module)
35
40
  - Follow Ruby gem best practices
41
+ - Do not update the spec/jpie/database.rb unless it's absolutely necessarry
42
+ - Do not update the spec/jpie/resources.rb unless it's absolutely necessarry
36
43
 
37
44
  # Dependency management
38
45
  - Keep dependencies minimal and justified
@@ -56,14 +63,12 @@
56
63
  - Support Rails 8+ integration
57
64
  - Follow JSON:API specification strictly
58
65
 
59
- # Styleguide
60
- - Use `{data:}` rather than `{data: data}`
61
-
62
66
  # When implementing new features or refactoring
63
67
  - Always read the .aiconfig file
64
68
  - Always implement code slowly and methodically
65
69
  - Always test as you go
66
70
  - Always make sure rubocop passes
71
+ - Do not add any functionality for the new feature to existing tests unless absolutely necessarry.
67
72
 
68
73
  # Examples
69
74
  - The examples must _only_ include _required_ code
data/.overcommit.yml ADDED
@@ -0,0 +1,35 @@
1
+ # Use this file to configure the Overcommit hooks you wish to use. This will
2
+ # extend the default configuration defined in:
3
+ # https://github.com/sds/overcommit/blob/master/config/default.yml
4
+
5
+ # Global settings
6
+ verify_signatures: false
7
+
8
+ PreCommit:
9
+ ALL:
10
+ problem_on_unmodified_line: ignore
11
+ requires_files: true
12
+ quiet: false
13
+
14
+ # Run RuboCop on Ruby files before commit
15
+ RuboCop:
16
+ enabled: true
17
+ command: ['bundle', 'exec', 'rubocop']
18
+ flags: ['--force-exclusion']
19
+ on_warn: fail
20
+
21
+ # Check for trailing whitespace
22
+ TrailingWhitespace:
23
+ enabled: true
24
+ exclude:
25
+ - '**/*.md'
26
+
27
+ # Check for merge conflicts
28
+ MergeConflicts:
29
+ enabled: true
30
+
31
+ PrePush:
32
+ # Run RSpec tests before push
33
+ RSpec:
34
+ enabled: true
35
+ command: ['bundle', 'exec', 'rspec']
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.2] - 2025-01-25
11
+
12
+ ### Added
13
+ - **Pagination Example**: Comprehensive pagination example demonstrating both simple and JSON:API standard pagination formats
14
+ - Simple pagination parameters (`page`, `per_page`)
15
+ - JSON:API standard pagination format (`page[number]`, `page[size]`)
16
+ - Pagination combined with sorting functionality
17
+ - Edge cases including last page and empty results
18
+ - Complete HTTP request/response examples following project format
19
+
20
+ ### Enhanced
21
+ - **Documentation**: Improved example coverage with detailed pagination use cases
22
+ - **Developer Experience**: Clear examples for implementing pagination in JPie applications
23
+
10
24
  ## [0.4.1] - 2025-01-25
11
25
 
12
26
  ### Fixed
data/README.md CHANGED
@@ -27,6 +27,63 @@ Add JPie to your Rails application:
27
27
  bundle add jpie
28
28
  ```
29
29
 
30
+ ## Development Setup
31
+
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
+
30
87
  ## Quick Start
31
88
 
32
89
  JPie works out of the box with minimal configuration:
@@ -131,27 +188,6 @@ end
131
188
  | `--model=NAME` | Specify model class | `--model=Person` |
132
189
  | `--skip-model` | Skip explicit model declaration | `--skip-model` |
133
190
 
134
- ## Core Features
135
-
136
- ### JSON:API Compliance
137
-
138
- JPie automatically handles all JSON:API specification requirements:
139
-
140
- #### Sorting
141
- ```http
142
- GET /users?sort=name,-created_at
143
- ```
144
-
145
- #### Includes
146
- ```http
147
- GET /posts?include=author,comments,comments.author
148
- ```
149
-
150
- #### Validation
151
- - Request structure validation for POST/PATCH operations
152
- - Include parameter validation
153
- - Sort parameter validation with clear error messages
154
-
155
191
  ### Modern DSL
156
192
 
157
193
  ```ruby
@@ -180,183 +216,7 @@ class UserResource < JPie::Resource
180
216
  end
181
217
  ```
182
218
 
183
- ### Through Associations
184
-
185
- JPie seamlessly supports Rails `:through` associations:
186
-
187
- ```ruby
188
- class CarResource < JPie::Resource
189
- attributes :make, :model, :year
190
-
191
- # JPie handles the through association automatically
192
- has_many :drivers, through: :car_drivers
193
- end
194
- ```
195
-
196
- The join table is completely hidden from the API response, providing a clean interface.
197
-
198
- ### Custom Attributes
199
-
200
- Define custom computed attributes using method overrides:
201
-
202
- ```ruby
203
- class UserResource < JPie::Resource
204
- attributes :first_name, :last_name
205
- attribute :full_name
206
-
207
- private
208
-
209
- def full_name
210
- "#{object.first_name} #{object.last_name}"
211
- end
212
- end
213
- ```
214
-
215
- ### Authorization & Context
216
-
217
- Pass context for authorization-aware responses:
218
-
219
- ```ruby
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 })
226
- end
227
- end
228
- ```
229
-
230
- ## Error Handling
231
-
232
- JPie provides robust error handling with full customization options:
233
-
234
- ### Default Error Handling
235
-
236
- JPie automatically handles common errors:
237
-
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 |
243
-
244
- ### Customization Options
245
-
246
- #### Override Specific Handlers
247
-
248
- ```ruby
249
- class ApplicationController < ActionController::Base
250
- # Define your handlers first
251
- rescue_from ActiveRecord::RecordNotFound, with: :my_not_found_handler
252
-
253
- include JPie::Controller
254
- # JPie will detect existing handler and won't override it
255
- end
256
- ```
257
-
258
- #### Extend JPie Handlers
259
-
260
- ```ruby
261
- class ApplicationController < ActionController::Base
262
- include JPie::Controller
263
-
264
- private
265
-
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
272
- end
273
- end
274
- ```
275
-
276
- #### Disable All JPie Error Handlers
277
-
278
- ```ruby
279
- class ApplicationController < ActionController::Base
280
- include JPie::Controller
281
-
282
- disable_jpie_error_handlers
283
-
284
- # Define your own handlers
285
- rescue_from StandardError, with: :handle_standard_error
286
- end
287
- ```
288
-
289
- ### Custom JPie Errors
290
-
291
- ```ruby
292
- class CustomBusinessError < JPie::Errors::Error
293
- def initialize(detail: 'Business logic error')
294
- super(status: 422, title: 'Business Error', detail: detail)
295
- end
296
- end
297
-
298
- # Use in controllers
299
- raise CustomBusinessError.new(detail: 'Custom validation failed')
300
- ```
301
-
302
- ## Advanced Features
303
-
304
- ### Polymorphic Associations
305
-
306
- JPie supports polymorphic associations for complex data relationships:
307
-
308
- ```ruby
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
319
- end
320
- ```
321
-
322
- ### Single Table Inheritance
323
-
324
- JPie automatically handles STI models:
325
-
326
- ```ruby
327
- # Base resource
328
- class VehicleResource < JPie::Resource
329
- attributes :name, :brand, :year
330
- end
331
-
332
- # STI resources inherit automatically
333
- class CarResource < VehicleResource
334
- attributes :engine_size, :doors # Car-specific attributes
335
- end
336
- ```
337
-
338
- STI types are automatically inferred in JSON:API responses.
339
-
340
- ### Custom Sorting
341
-
342
- Implement complex sorting logic:
343
-
344
- ```ruby
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}")
350
- end
351
- end
352
- ```
353
-
354
- ## Performance & Best Practices
355
-
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
219
+ See the examples folder for more examples of how to use the DSL to solve various serialization/deserialization scenarios.
360
220
 
361
221
  ## Contributing
362
222
 
@@ -0,0 +1,303 @@
1
+ # Pagination Example
2
+
3
+ This example demonstrates how to implement pagination with JPie resources using both simple and JSON:API standard pagination parameters.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Model (`app/models/article.rb`)
8
+ ```ruby
9
+ class Article < ActiveRecord::Base
10
+ validates :title, presence: true
11
+ validates :content, presence: true
12
+ end
13
+ ```
14
+
15
+ ### 2. Resource (`app/resources/article_resource.rb`)
16
+ ```ruby
17
+ class ArticleResource < JPie::Resource
18
+ attributes :title, :content, :published_at
19
+ end
20
+ ```
21
+
22
+ ### 3. Controller (`app/controllers/articles_controller.rb`)
23
+ ```ruby
24
+ class ArticlesController < ApplicationController
25
+ include JPie::Controller
26
+ end
27
+ ```
28
+
29
+ ### 4. Routes (`config/routes.rb`)
30
+ ```ruby
31
+ Rails.application.routes.draw do
32
+ resources :articles
33
+ end
34
+ ```
35
+
36
+ ## HTTP Examples
37
+
38
+ ### Simple Pagination Parameters
39
+
40
+ #### Get First Page with 5 Articles
41
+ ```http
42
+ GET /articles?page=1&per_page=5
43
+ Accept: application/vnd.api+json
44
+
45
+ HTTP/1.1 200 OK
46
+ Content-Type: application/vnd.api+json
47
+
48
+ {
49
+ "data": [
50
+ {
51
+ "id": "1",
52
+ "type": "articles",
53
+ "attributes": {
54
+ "title": "First Article",
55
+ "content": "Content of first article",
56
+ "published_at": "2024-01-01T10:00:00Z"
57
+ }
58
+ },
59
+ {
60
+ "id": "2",
61
+ "type": "articles",
62
+ "attributes": {
63
+ "title": "Second Article",
64
+ "content": "Content of second article",
65
+ "published_at": "2024-01-02T10:00:00Z"
66
+ }
67
+ }
68
+ ],
69
+ "meta": {
70
+ "pagination": {
71
+ "page": 1,
72
+ "per_page": 5,
73
+ "total_pages": 4,
74
+ "total_count": 20
75
+ }
76
+ },
77
+ "links": {
78
+ "self": "http://example.com/articles?page=1&per_page=5",
79
+ "first": "http://example.com/articles?page=1&per_page=5",
80
+ "last": "http://example.com/articles?page=4&per_page=5",
81
+ "next": "http://example.com/articles?page=2&per_page=5"
82
+ }
83
+ }
84
+ ```
85
+
86
+ #### Get Second Page
87
+ ```http
88
+ GET /articles?page=2&per_page=5
89
+ Accept: application/vnd.api+json
90
+
91
+ HTTP/1.1 200 OK
92
+ Content-Type: application/vnd.api+json
93
+
94
+ {
95
+ "data": [
96
+ {
97
+ "id": "6",
98
+ "type": "articles",
99
+ "attributes": {
100
+ "title": "Sixth Article",
101
+ "content": "Content of sixth article",
102
+ "published_at": "2024-01-06T10:00:00Z"
103
+ }
104
+ }
105
+ ],
106
+ "meta": {
107
+ "pagination": {
108
+ "page": 2,
109
+ "per_page": 5,
110
+ "total_pages": 4,
111
+ "total_count": 20
112
+ }
113
+ },
114
+ "links": {
115
+ "self": "http://example.com/articles?page=2&per_page=5",
116
+ "first": "http://example.com/articles?page=1&per_page=5",
117
+ "last": "http://example.com/articles?page=4&per_page=5",
118
+ "prev": "http://example.com/articles?page=1&per_page=5",
119
+ "next": "http://example.com/articles?page=3&per_page=5"
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### JSON:API Standard Pagination Parameters
125
+
126
+ #### Get First Page Using JSON:API Format
127
+ ```http
128
+ GET /articles?page[number]=1&page[size]=3
129
+ Accept: application/vnd.api+json
130
+
131
+ HTTP/1.1 200 OK
132
+ Content-Type: application/vnd.api+json
133
+
134
+ {
135
+ "data": [
136
+ {
137
+ "id": "1",
138
+ "type": "articles",
139
+ "attributes": {
140
+ "title": "First Article",
141
+ "content": "Content of first article",
142
+ "published_at": "2024-01-01T10:00:00Z"
143
+ }
144
+ },
145
+ {
146
+ "id": "2",
147
+ "type": "articles",
148
+ "attributes": {
149
+ "title": "Second Article",
150
+ "content": "Content of second article",
151
+ "published_at": "2024-01-02T10:00:00Z"
152
+ }
153
+ },
154
+ {
155
+ "id": "3",
156
+ "type": "articles",
157
+ "attributes": {
158
+ "title": "Third Article",
159
+ "content": "Content of third article",
160
+ "published_at": "2024-01-03T10:00:00Z"
161
+ }
162
+ }
163
+ ],
164
+ "meta": {
165
+ "pagination": {
166
+ "page": 1,
167
+ "per_page": 3,
168
+ "total_pages": 7,
169
+ "total_count": 20
170
+ }
171
+ },
172
+ "links": {
173
+ "self": "http://example.com/articles?page=1&per_page=3",
174
+ "first": "http://example.com/articles?page=1&per_page=3",
175
+ "last": "http://example.com/articles?page=7&per_page=3",
176
+ "next": "http://example.com/articles?page=2&per_page=3"
177
+ }
178
+ }
179
+ ```
180
+
181
+ ### Pagination with Sorting
182
+
183
+ #### Get Sorted and Paginated Results
184
+ ```http
185
+ GET /articles?sort=-published_at&page=1&per_page=3
186
+ Accept: application/vnd.api+json
187
+
188
+ HTTP/1.1 200 OK
189
+ Content-Type: application/vnd.api+json
190
+
191
+ {
192
+ "data": [
193
+ {
194
+ "id": "20",
195
+ "type": "articles",
196
+ "attributes": {
197
+ "title": "Latest Article",
198
+ "content": "Most recent content",
199
+ "published_at": "2024-01-20T10:00:00Z"
200
+ }
201
+ },
202
+ {
203
+ "id": "19",
204
+ "type": "articles",
205
+ "attributes": {
206
+ "title": "Second Latest Article",
207
+ "content": "Second most recent content",
208
+ "published_at": "2024-01-19T10:00:00Z"
209
+ }
210
+ },
211
+ {
212
+ "id": "18",
213
+ "type": "articles",
214
+ "attributes": {
215
+ "title": "Third Latest Article",
216
+ "content": "Third most recent content",
217
+ "published_at": "2024-01-18T10:00:00Z"
218
+ }
219
+ }
220
+ ],
221
+ "meta": {
222
+ "pagination": {
223
+ "page": 1,
224
+ "per_page": 3,
225
+ "total_pages": 7,
226
+ "total_count": 20
227
+ }
228
+ },
229
+ "links": {
230
+ "self": "http://example.com/articles?sort=-published_at&page=1&per_page=3",
231
+ "first": "http://example.com/articles?sort=-published_at&page=1&per_page=3",
232
+ "last": "http://example.com/articles?sort=-published_at&page=7&per_page=3",
233
+ "next": "http://example.com/articles?sort=-published_at&page=2&per_page=3"
234
+ }
235
+ }
236
+ ```
237
+
238
+ ### Last Page Response
239
+
240
+ #### Get Last Page
241
+ ```http
242
+ GET /articles?page=4&per_page=5
243
+ Accept: application/vnd.api+json
244
+
245
+ HTTP/1.1 200 OK
246
+ Content-Type: application/vnd.api+json
247
+
248
+ {
249
+ "data": [
250
+ {
251
+ "id": "20",
252
+ "type": "articles",
253
+ "attributes": {
254
+ "title": "Last Article",
255
+ "content": "Content of last article",
256
+ "published_at": "2024-01-20T10:00:00Z"
257
+ }
258
+ }
259
+ ],
260
+ "meta": {
261
+ "pagination": {
262
+ "page": 4,
263
+ "per_page": 5,
264
+ "total_pages": 4,
265
+ "total_count": 20
266
+ }
267
+ },
268
+ "links": {
269
+ "self": "http://example.com/articles?page=4&per_page=5",
270
+ "first": "http://example.com/articles?page=1&per_page=5",
271
+ "last": "http://example.com/articles?page=4&per_page=5",
272
+ "prev": "http://example.com/articles?page=3&per_page=5"
273
+ }
274
+ }
275
+ ```
276
+
277
+ ### Empty Results
278
+
279
+ #### Get Page Beyond Available Data
280
+ ```http
281
+ GET /articles?page=10&per_page=5
282
+ Accept: application/vnd.api+json
283
+
284
+ HTTP/1.1 200 OK
285
+ Content-Type: application/vnd.api+json
286
+
287
+ {
288
+ "data": [],
289
+ "meta": {
290
+ "pagination": {
291
+ "page": 10,
292
+ "per_page": 5,
293
+ "total_pages": 4,
294
+ "total_count": 20
295
+ }
296
+ },
297
+ "links": {
298
+ "self": "http://example.com/articles?page=10&per_page=5",
299
+ "first": "http://example.com/articles?page=1&per_page=5",
300
+ "last": "http://example.com/articles?page=4&per_page=5"
301
+ }
302
+ }
303
+ ```