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.
@@ -0,0 +1,114 @@
1
+ # JSON:API Relationship Management
2
+
3
+ This example demonstrates how to manage relationships using JPie's JSON:API compliant relationship endpoints.
4
+
5
+ ## Setup
6
+
7
+ Define your resources with relationships:
8
+
9
+ ```ruby
10
+ # app/resources/user_resource.rb
11
+ class UserResource < JPie::Resource
12
+ model User
13
+ attributes :name, :email
14
+ has_many :posts
15
+ end
16
+
17
+ # app/resources/post_resource.rb
18
+ class PostResource < JPie::Resource
19
+ model Post
20
+ attributes :title, :content
21
+ has_one :author, resource: 'UserResource'
22
+ end
23
+
24
+ # app/controllers/users_controller.rb
25
+ class UsersController < ApplicationController
26
+ include JPie::Controller
27
+ end
28
+ ```
29
+
30
+ Configure routes:
31
+
32
+ ```ruby
33
+ # config/routes.rb
34
+ Rails.application.routes.draw do
35
+ jpie_resources :users
36
+ jpie_resources :posts
37
+ end
38
+ ```
39
+
40
+ ## Relationship Operations
41
+
42
+ ### Get Relationship Linkage
43
+
44
+ ```http
45
+ GET /users/1/relationships/posts
46
+
47
+ Response:
48
+ {
49
+ "data": [
50
+ { "type": "posts", "id": "1" },
51
+ { "type": "posts", "id": "3" }
52
+ ]
53
+ }
54
+ ```
55
+
56
+ ### Replace Relationship
57
+
58
+ ```http
59
+ PATCH /users/1/relationships/posts
60
+ Content-Type: application/vnd.api+json
61
+
62
+ {
63
+ "data": [
64
+ { "type": "posts", "id": "2" },
65
+ { "type": "posts", "id": "4" }
66
+ ]
67
+ }
68
+ ```
69
+
70
+ ### Add to Relationship
71
+
72
+ ```http
73
+ POST /users/1/relationships/posts
74
+ Content-Type: application/vnd.api+json
75
+
76
+ {
77
+ "data": [
78
+ { "type": "posts", "id": "5" }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ ### Remove from Relationship
84
+
85
+ ```http
86
+ DELETE /users/1/relationships/posts
87
+ Content-Type: application/vnd.api+json
88
+
89
+ {
90
+ "data": [
91
+ { "type": "posts", "id": "2" }
92
+ ]
93
+ }
94
+ ```
95
+
96
+ ### Get Related Resources
97
+
98
+ ```http
99
+ GET /users/1/posts
100
+
101
+ Response:
102
+ {
103
+ "data": [
104
+ {
105
+ "type": "posts",
106
+ "id": "1",
107
+ "attributes": {
108
+ "title": "First Post",
109
+ "content": "Hello world!"
110
+ }
111
+ }
112
+ ]
113
+ }
114
+ ```
@@ -39,7 +39,12 @@ module JPie
39
39
  resources = resource_class.scope(context)
40
40
  sort_fields = parse_sort_params
41
41
  resources = resource_class.sort(resources, sort_fields) if sort_fields.any?
42
- render_jsonapi(resources)
42
+
43
+ pagination_params = parse_pagination_params
44
+ original_resources = resources
45
+ resources = apply_pagination(resources, pagination_params)
46
+
47
+ render_jsonapi(resources, pagination: pagination_params, original_scope: original_resources)
43
48
  end
44
49
  end
45
50
 
@@ -86,7 +91,12 @@ module JPie
86
91
  resources = resource_class.scope(context)
87
92
  sort_fields = parse_sort_params
88
93
  resources = resource_class.sort(resources, sort_fields) if sort_fields.any?
89
- render_jsonapi(resources)
94
+
95
+ pagination_params = parse_pagination_params
96
+ original_resources = resources
97
+ resources = apply_pagination(resources, pagination_params)
98
+
99
+ render_jsonapi(resources, pagination: pagination_params, original_scope: original_resources)
90
100
  end
91
101
 
92
102
  def show
@@ -115,6 +125,17 @@ module JPie
115
125
  resource.destroy!
116
126
  head :no_content
117
127
  end
128
+
129
+ private
130
+
131
+ def apply_pagination(resources, pagination_params)
132
+ return resources unless pagination_params[:per_page]
133
+
134
+ page = pagination_params[:page] || 1
135
+ per_page = pagination_params[:per_page]
136
+
137
+ resources.limit(per_page).offset((page - 1) * per_page)
138
+ end
118
139
  end
119
140
  end
120
141
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JPie
4
+ module Controller
5
+ module ErrorHandling
6
+ module HandlerSetup
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ # Allow applications to easily disable all JPie error handlers
11
+ def disable_jpie_error_handlers
12
+ self.jpie_error_handlers_enabled = false
13
+ # Remove any already-added handlers
14
+ remove_jpie_handlers
15
+ end
16
+
17
+ # Allow applications to enable specific handlers
18
+ def enable_jpie_error_handler(error_class, method_name = nil)
19
+ method_name ||= :"handle_#{error_class.name.demodulize.underscore}"
20
+ rescue_from error_class, with: method_name
21
+ end
22
+
23
+ # Check for application-defined error handlers
24
+ def rescue_handler?(exception_class)
25
+ # Use Rails' rescue_handlers method to check for existing handlers
26
+ return false unless respond_to?(:rescue_handlers, true)
27
+
28
+ begin
29
+ rescue_handlers.any? { |handler| handler.first == exception_class.name }
30
+ rescue NoMethodError
31
+ false
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def setup_jpie_error_handlers
38
+ setup_jpie_specific_handlers
39
+ end
40
+
41
+ def setup_jpie_specific_handlers
42
+ setup_core_error_handlers
43
+ setup_activerecord_handlers
44
+ setup_json_api_compliance_handlers
45
+ end
46
+
47
+ def setup_core_error_handlers
48
+ return if rescue_handler?(JPie::Errors::Error)
49
+
50
+ rescue_from JPie::Errors::Error, with: :handle_jpie_error
51
+ end
52
+
53
+ def setup_activerecord_handlers
54
+ setup_not_found_handler
55
+ setup_invalid_record_handler
56
+ end
57
+
58
+ def setup_not_found_handler
59
+ return if rescue_handler?(ActiveRecord::RecordNotFound)
60
+
61
+ rescue_from ActiveRecord::RecordNotFound, with: :handle_record_not_found
62
+ end
63
+
64
+ def setup_invalid_record_handler
65
+ return if rescue_handler?(ActiveRecord::RecordInvalid)
66
+
67
+ rescue_from ActiveRecord::RecordInvalid, with: :handle_record_invalid
68
+ end
69
+
70
+ def setup_json_api_compliance_handlers
71
+ setup_json_api_request_handler
72
+ setup_include_handlers
73
+ setup_sort_handlers
74
+ end
75
+
76
+ def setup_json_api_request_handler
77
+ return if rescue_handler?(JPie::Errors::InvalidJsonApiRequestError)
78
+
79
+ rescue_from JPie::Errors::InvalidJsonApiRequestError, with: :handle_invalid_json_api_request
80
+ end
81
+
82
+ def setup_include_handlers
83
+ setup_unsupported_include_handler
84
+ setup_invalid_include_handler
85
+ end
86
+
87
+ def setup_unsupported_include_handler
88
+ return if rescue_handler?(JPie::Errors::UnsupportedIncludeError)
89
+
90
+ rescue_from JPie::Errors::UnsupportedIncludeError, with: :handle_unsupported_include
91
+ end
92
+
93
+ def setup_invalid_include_handler
94
+ return if rescue_handler?(JPie::Errors::InvalidIncludeParameterError)
95
+
96
+ rescue_from JPie::Errors::InvalidIncludeParameterError, with: :handle_invalid_include_parameter
97
+ end
98
+
99
+ def setup_sort_handlers
100
+ setup_unsupported_sort_handler
101
+ setup_invalid_sort_handler
102
+ end
103
+
104
+ def setup_unsupported_sort_handler
105
+ return if rescue_handler?(JPie::Errors::UnsupportedSortFieldError)
106
+
107
+ rescue_from JPie::Errors::UnsupportedSortFieldError, with: :handle_unsupported_sort_field
108
+ end
109
+
110
+ def setup_invalid_sort_handler
111
+ return if rescue_handler?(JPie::Errors::InvalidSortParameterError)
112
+
113
+ rescue_from JPie::Errors::InvalidSortParameterError, with: :handle_invalid_sort_parameter
114
+ end
115
+
116
+ def remove_jpie_handlers
117
+ # This is a placeholder - Rails doesn't provide an easy way to remove specific handlers
118
+ # In practice, applications should use the disable_jpie_error_handlers before including
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JPie
4
+ module Controller
5
+ module ErrorHandling
6
+ module Handlers
7
+ extend ActiveSupport::Concern
8
+
9
+ private
10
+
11
+ # Handle JPie-specific errors
12
+ def handle_jpie_error(error)
13
+ render_json_api_error(
14
+ status: error.status,
15
+ title: error.title,
16
+ detail: error.detail
17
+ )
18
+ end
19
+
20
+ # Handle ActiveRecord::RecordNotFound
21
+ def handle_record_not_found(error)
22
+ render_json_api_error(
23
+ status: 404,
24
+ title: 'Not Found',
25
+ detail: error.message
26
+ )
27
+ end
28
+
29
+ # Handle ActiveRecord::RecordInvalid
30
+ def handle_record_invalid(error)
31
+ errors = error.record.errors.full_messages.map do |message|
32
+ {
33
+ status: '422',
34
+ title: 'Validation Error',
35
+ detail: message
36
+ }
37
+ end
38
+
39
+ render json: { errors: errors }, status: :unprocessable_content
40
+ end
41
+
42
+ # Render a single JSON:API error
43
+ def render_json_api_error(status:, title:, detail:)
44
+ render json: {
45
+ errors: [{
46
+ status: status.to_s,
47
+ title: title,
48
+ detail: detail
49
+ }]
50
+ }, status: status
51
+ end
52
+
53
+ # Handle JSON:API compliance errors
54
+ def handle_invalid_json_api_request(error)
55
+ render_json_api_error(
56
+ status: error.status,
57
+ title: error.title || 'Invalid JSON:API Request',
58
+ detail: error.detail
59
+ )
60
+ end
61
+
62
+ def handle_unsupported_include(error)
63
+ render_json_api_error(
64
+ status: error.status,
65
+ title: error.title || 'Unsupported Include',
66
+ detail: error.detail
67
+ )
68
+ end
69
+
70
+ def handle_unsupported_sort_field(error)
71
+ render_json_api_error(
72
+ status: error.status,
73
+ title: error.title || 'Unsupported Sort Field',
74
+ detail: error.detail
75
+ )
76
+ end
77
+
78
+ def handle_invalid_sort_parameter(error)
79
+ render_json_api_error(
80
+ status: error.status,
81
+ title: error.title || 'Invalid Sort Parameter',
82
+ detail: error.detail
83
+ )
84
+ end
85
+
86
+ def handle_invalid_include_parameter(error)
87
+ render_json_api_error(
88
+ status: error.status,
89
+ title: error.title || 'Invalid Include Parameter',
90
+ detail: error.detail
91
+ )
92
+ end
93
+
94
+ # Backward compatibility aliases
95
+ alias jpie_handle_error handle_jpie_error
96
+ alias jpie_handle_not_found handle_record_not_found
97
+ alias jpie_handle_invalid handle_record_invalid
98
+
99
+ # Legacy method name aliases
100
+ alias render_jpie_error handle_jpie_error
101
+ alias render_jpie_not_found_error handle_record_not_found
102
+ alias render_jpie_validation_error handle_record_invalid
103
+ alias render_jsonapi_error handle_jpie_error
104
+ alias render_not_found_error handle_record_not_found
105
+ alias render_validation_error handle_record_invalid
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,10 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'error_handling/handler_setup'
4
+ require_relative 'error_handling/handlers'
5
+
3
6
  module JPie
4
7
  module Controller
5
8
  module ErrorHandling
6
9
  extend ActiveSupport::Concern
7
10
 
11
+ include HandlerSetup
12
+ include Handlers
13
+
8
14
  included do
9
15
  # Use class_attribute to allow easy overriding
10
16
  class_attribute :jpie_error_handlers_enabled, default: true
@@ -12,181 +18,6 @@ module JPie
12
18
  # Set up default handlers unless explicitly disabled
13
19
  setup_jpie_error_handlers if jpie_error_handlers_enabled
14
20
  end
15
-
16
- class_methods do
17
- # Allow applications to easily disable all JPie error handlers
18
- def disable_jpie_error_handlers
19
- self.jpie_error_handlers_enabled = false
20
- # Remove any already-added handlers
21
- remove_jpie_handlers
22
- end
23
-
24
- # Allow applications to enable specific handlers
25
- def enable_jpie_error_handler(error_class, method_name = nil)
26
- method_name ||= :"handle_#{error_class.name.demodulize.underscore}"
27
- rescue_from error_class, with: method_name
28
- end
29
-
30
- # Check for application-defined error handlers
31
- def rescue_handler?(exception_class)
32
- # Use Rails' rescue_handlers method to check for existing handlers
33
- return false unless respond_to?(:rescue_handlers, true)
34
-
35
- begin
36
- rescue_handlers.any? { |handler| handler.first == exception_class.name }
37
- rescue NoMethodError
38
- false
39
- end
40
- end
41
-
42
- private
43
-
44
- def setup_jpie_error_handlers
45
- setup_jpie_specific_handlers
46
- end
47
-
48
- def setup_jpie_specific_handlers
49
- # Only add handlers if they don't already exist
50
- rescue_from JPie::Errors::Error, with: :handle_jpie_error unless rescue_handler?(JPie::Errors::Error)
51
- unless rescue_handler?(ActiveRecord::RecordNotFound)
52
- rescue_from ActiveRecord::RecordNotFound,
53
- with: :handle_record_not_found
54
- end
55
- return if rescue_handler?(ActiveRecord::RecordInvalid)
56
-
57
- rescue_from ActiveRecord::RecordInvalid,
58
- with: :handle_record_invalid
59
-
60
- # JSON:API compliance error handlers
61
- unless rescue_handler?(JPie::Errors::InvalidJsonApiRequestError)
62
- rescue_from JPie::Errors::InvalidJsonApiRequestError,
63
- with: :handle_invalid_json_api_request
64
- end
65
-
66
- unless rescue_handler?(JPie::Errors::UnsupportedIncludeError)
67
- rescue_from JPie::Errors::UnsupportedIncludeError,
68
- with: :handle_unsupported_include
69
- end
70
-
71
- unless rescue_handler?(JPie::Errors::UnsupportedSortFieldError)
72
- rescue_from JPie::Errors::UnsupportedSortFieldError,
73
- with: :handle_unsupported_sort_field
74
- end
75
-
76
- unless rescue_handler?(JPie::Errors::InvalidSortParameterError)
77
- rescue_from JPie::Errors::InvalidSortParameterError,
78
- with: :handle_invalid_sort_parameter
79
- end
80
-
81
- return if rescue_handler?(JPie::Errors::InvalidIncludeParameterError)
82
-
83
- rescue_from JPie::Errors::InvalidIncludeParameterError,
84
- with: :handle_invalid_include_parameter
85
- end
86
-
87
- def remove_jpie_handlers
88
- # This is a placeholder - Rails doesn't provide an easy way to remove specific handlers
89
- # In practice, applications should use the disable_jpie_error_handlers before including
90
- end
91
- end
92
-
93
- private
94
-
95
- # Handle JPie-specific errors
96
- def handle_jpie_error(error)
97
- render_json_api_error(
98
- status: error.status,
99
- title: error.title,
100
- detail: error.detail
101
- )
102
- end
103
-
104
- # Handle ActiveRecord::RecordNotFound
105
- def handle_record_not_found(error)
106
- render_json_api_error(
107
- status: 404,
108
- title: 'Not Found',
109
- detail: error.message
110
- )
111
- end
112
-
113
- # Handle ActiveRecord::RecordInvalid
114
- def handle_record_invalid(error)
115
- errors = error.record.errors.full_messages.map do |message|
116
- {
117
- status: '422',
118
- title: 'Validation Error',
119
- detail: message
120
- }
121
- end
122
-
123
- render json: { errors: errors }, status: :unprocessable_content
124
- end
125
-
126
- # Render a single JSON:API error
127
- def render_json_api_error(status:, title:, detail:)
128
- render json: {
129
- errors: [{
130
- status: status.to_s,
131
- title: title,
132
- detail: detail
133
- }]
134
- }, status: status
135
- end
136
-
137
- # Handle JSON:API compliance errors
138
- def handle_invalid_json_api_request(error)
139
- render_json_api_error(
140
- status: error.status,
141
- title: error.title || 'Invalid JSON:API Request',
142
- detail: error.detail
143
- )
144
- end
145
-
146
- def handle_unsupported_include(error)
147
- render_json_api_error(
148
- status: error.status,
149
- title: error.title || 'Unsupported Include',
150
- detail: error.detail
151
- )
152
- end
153
-
154
- def handle_unsupported_sort_field(error)
155
- render_json_api_error(
156
- status: error.status,
157
- title: error.title || 'Unsupported Sort Field',
158
- detail: error.detail
159
- )
160
- end
161
-
162
- def handle_invalid_sort_parameter(error)
163
- render_json_api_error(
164
- status: error.status,
165
- title: error.title || 'Invalid Sort Parameter',
166
- detail: error.detail
167
- )
168
- end
169
-
170
- def handle_invalid_include_parameter(error)
171
- render_json_api_error(
172
- status: error.status,
173
- title: error.title || 'Invalid Include Parameter',
174
- detail: error.detail
175
- )
176
- end
177
-
178
- # Backward compatibility aliases
179
- alias jpie_handle_error handle_jpie_error
180
- alias jpie_handle_not_found handle_record_not_found
181
- alias jpie_handle_invalid handle_record_invalid
182
-
183
- # Legacy method name aliases
184
- alias render_jpie_error handle_jpie_error
185
- alias render_jpie_not_found_error handle_record_not_found
186
- alias render_jpie_validation_error handle_record_invalid
187
- alias render_jsonapi_error handle_jpie_error
188
- alias render_not_found_error handle_record_not_found
189
- alias render_validation_error handle_record_invalid
190
21
  end
191
22
  end
192
23
  end