jpie 0.4.1 → 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/.cursorrules +7 -3
- data/.overcommit.yml +35 -0
- data/CHANGELOG.md +14 -0
- data/README.md +58 -198
- data/examples/pagination.md +303 -0
- data/lib/jpie/controller/crud_actions.rb +23 -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 +6 -175
- data/lib/jpie/controller/json_api_validation.rb +55 -33
- data/lib/jpie/controller/parameter_parsing.rb +43 -0
- data/lib/jpie/controller/rendering.rb +95 -1
- data/lib/jpie/resource/attributable.rb +2 -7
- data/lib/jpie/resource.rb +22 -8
- data/lib/jpie/version.rb +1 -1
- metadata +6 -2
@@ -23,9 +23,15 @@ module JPie
|
|
23
23
|
end
|
24
24
|
|
25
25
|
# More concise method names following Rails conventions
|
26
|
-
def render_jsonapi(resource_or_resources, status: :ok, meta: nil)
|
26
|
+
def render_jsonapi(resource_or_resources, status: :ok, meta: nil, pagination: nil, original_scope: nil)
|
27
27
|
includes = parse_include_params
|
28
28
|
json_data = serializer.serialize(resource_or_resources, context, includes: includes)
|
29
|
+
|
30
|
+
# Add pagination metadata and links if pagination is provided and valid
|
31
|
+
if pagination && pagination[:per_page]
|
32
|
+
add_pagination_metadata(json_data, resource_or_resources, pagination, original_scope)
|
33
|
+
end
|
34
|
+
|
29
35
|
json_data[:meta] = meta if meta
|
30
36
|
|
31
37
|
render json: json_data, status:, content_type: 'application/vnd.api+json'
|
@@ -37,6 +43,94 @@ module JPie
|
|
37
43
|
|
38
44
|
private
|
39
45
|
|
46
|
+
def add_pagination_metadata(json_data, resources, pagination, original_scope)
|
47
|
+
page = pagination[:page] || 1
|
48
|
+
per_page = pagination[:per_page]
|
49
|
+
|
50
|
+
# Get total count from the original scope before pagination
|
51
|
+
total_count = get_total_count(resources, original_scope)
|
52
|
+
total_pages = (total_count.to_f / per_page).ceil
|
53
|
+
|
54
|
+
# Add pagination metadata
|
55
|
+
json_data[:meta] ||= {}
|
56
|
+
json_data[:meta][:pagination] = {
|
57
|
+
page: page,
|
58
|
+
per_page: per_page,
|
59
|
+
total_pages: total_pages,
|
60
|
+
total_count: total_count
|
61
|
+
}
|
62
|
+
|
63
|
+
# Add pagination links
|
64
|
+
json_data[:links] = build_pagination_links(page, per_page, total_pages)
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_total_count(resources, original_scope)
|
68
|
+
# Use original scope if provided, otherwise fall back to resources
|
69
|
+
scope_to_count = original_scope || resources
|
70
|
+
|
71
|
+
# If scope is an ActiveRecord relation, get the count
|
72
|
+
# Otherwise, if it's an array, get the length
|
73
|
+
if scope_to_count.respond_to?(:count) && !scope_to_count.loaded?
|
74
|
+
scope_to_count.count
|
75
|
+
elsif scope_to_count.respond_to?(:size)
|
76
|
+
scope_to_count.size
|
77
|
+
else
|
78
|
+
0
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_pagination_links(page, per_page, total_pages)
|
83
|
+
url_components = extract_url_components
|
84
|
+
pagination_data = { page: page, per_page: per_page, total_pages: total_pages }
|
85
|
+
|
86
|
+
links = build_base_pagination_links(url_components, pagination_data)
|
87
|
+
add_conditional_pagination_links(links, url_components, pagination_data)
|
88
|
+
|
89
|
+
links
|
90
|
+
end
|
91
|
+
|
92
|
+
def extract_url_components
|
93
|
+
base_url = request.respond_to?(:base_url) ? request.base_url : 'http://example.com'
|
94
|
+
path = request.respond_to?(:path) ? request.path : '/resources'
|
95
|
+
query_params = request.respond_to?(:query_parameters) ? request.query_parameters.except('page') : {}
|
96
|
+
|
97
|
+
{ base_url: base_url, path: path, query_params: query_params }
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_base_pagination_links(url_components, pagination_data)
|
101
|
+
full_url = url_components[:base_url] + url_components[:path]
|
102
|
+
query_params = url_components[:query_params]
|
103
|
+
page = pagination_data[:page]
|
104
|
+
per_page = pagination_data[:per_page]
|
105
|
+
total_pages = pagination_data[:total_pages]
|
106
|
+
|
107
|
+
{
|
108
|
+
self: build_page_url(full_url, query_params, page, per_page),
|
109
|
+
first: build_page_url(full_url, query_params, 1, per_page),
|
110
|
+
last: build_page_url(full_url, query_params, total_pages, per_page)
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_conditional_pagination_links(links, url_components, pagination_data)
|
115
|
+
full_url = url_components[:base_url] + url_components[:path]
|
116
|
+
query_params = url_components[:query_params]
|
117
|
+
page = pagination_data[:page]
|
118
|
+
per_page = pagination_data[:per_page]
|
119
|
+
total_pages = pagination_data[:total_pages]
|
120
|
+
|
121
|
+
links[:prev] = build_page_url(full_url, query_params, page - 1, per_page) if page > 1
|
122
|
+
links[:next] = build_page_url(full_url, query_params, page + 1, per_page) if page < total_pages
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_page_url(base_url, query_params, page_num, per_page)
|
126
|
+
params = query_params.merge(
|
127
|
+
'page' => page_num.to_s,
|
128
|
+
'per_page' => per_page.to_s
|
129
|
+
)
|
130
|
+
query_string = params.respond_to?(:to_query) ? params.to_query : params.map { |k, v| "#{k}=#{v}" }.join('&')
|
131
|
+
"#{base_url}?#{query_string}"
|
132
|
+
end
|
133
|
+
|
40
134
|
def infer_resource_class
|
41
135
|
# Convert controller name to resource class name
|
42
136
|
# e.g., "UsersController" -> "UserResource"
|
@@ -104,13 +104,8 @@ module JPie
|
|
104
104
|
|
105
105
|
# Handle through associations by calling the appropriate association method
|
106
106
|
def handle_through_association(name, options)
|
107
|
-
|
108
|
-
|
109
|
-
@object.public_send(options[:attr])
|
110
|
-
else
|
111
|
-
# Use the relationship name directly - Rails will handle the through association
|
112
|
-
@object.public_send(name)
|
113
|
-
end
|
107
|
+
attr_name = options[:attr] || name
|
108
|
+
@object.public_send(attr_name)
|
114
109
|
end
|
115
110
|
end
|
116
111
|
end
|
data/lib/jpie/resource.rb
CHANGED
@@ -51,17 +51,31 @@ module JPie
|
|
51
51
|
# Return supported sort fields for validation
|
52
52
|
# Override this method to customize supported sort fields
|
53
53
|
def supported_sort_fields
|
54
|
-
|
55
|
-
|
54
|
+
base_fields = extract_base_sort_fields
|
55
|
+
timestamp_fields = extract_timestamp_fields
|
56
|
+
(base_fields + timestamp_fields).uniq
|
57
|
+
end
|
56
58
|
|
57
|
-
|
58
|
-
if model.respond_to?(:column_names)
|
59
|
-
fields << 'created_at' if model.column_names.include?('created_at') && fields.exclude?('created_at')
|
59
|
+
private
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
def extract_base_sort_fields
|
62
|
+
(_attributes + _sortable_fields.keys).uniq.map(&:to_s)
|
63
|
+
end
|
64
|
+
|
65
|
+
def extract_timestamp_fields
|
66
|
+
return [] unless model.respond_to?(:column_names)
|
67
|
+
|
68
|
+
timestamp_fields = []
|
69
|
+
add_timestamp_field(timestamp_fields, 'created_at')
|
70
|
+
add_timestamp_field(timestamp_fields, 'updated_at')
|
71
|
+
timestamp_fields
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_timestamp_field(fields, field_name)
|
75
|
+
return unless model.column_names.include?(field_name)
|
76
|
+
return if fields.include?(field_name)
|
63
77
|
|
64
|
-
fields
|
78
|
+
fields << field_name
|
65
79
|
end
|
66
80
|
end
|
67
81
|
|
data/lib/jpie/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jpie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emil Kampp
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-05-
|
10
|
+
date: 2025-05-26 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activesupport
|
@@ -170,6 +170,7 @@ extensions: []
|
|
170
170
|
extra_rdoc_files: []
|
171
171
|
files:
|
172
172
|
- ".cursorrules"
|
173
|
+
- ".overcommit.yml"
|
173
174
|
- ".rubocop.yml"
|
174
175
|
- CHANGELOG.md
|
175
176
|
- LICENSE.txt
|
@@ -177,6 +178,7 @@ files:
|
|
177
178
|
- Rakefile
|
178
179
|
- examples/basic_example.md
|
179
180
|
- examples/including_related_resources.md
|
181
|
+
- examples/pagination.md
|
180
182
|
- examples/resource_attribute_configuration.md
|
181
183
|
- examples/resource_meta_configuration.md
|
182
184
|
- examples/single_table_inheritance.md
|
@@ -186,6 +188,8 @@ files:
|
|
186
188
|
- lib/jpie/controller.rb
|
187
189
|
- lib/jpie/controller/crud_actions.rb
|
188
190
|
- lib/jpie/controller/error_handling.rb
|
191
|
+
- lib/jpie/controller/error_handling/handler_setup.rb
|
192
|
+
- lib/jpie/controller/error_handling/handlers.rb
|
189
193
|
- lib/jpie/controller/json_api_validation.rb
|
190
194
|
- lib/jpie/controller/parameter_parsing.rb
|
191
195
|
- lib/jpie/controller/rendering.rb
|