Skip to content

Commit 72f7b37

Browse files
authored
Merge pull request #42 from tiagopog/pair-versions-0-4-and-0-5
Pair versions 0.4.x and 0.5.x
2 parents 1fbc941 + d13b579 commit 72f7b37

File tree

21 files changed

+620
-116
lines changed

21 files changed

+620
-116
lines changed

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM ruby:2.3.1
2+
3+
4+
RUN mkdir -p /jsonapi-utils
5+
WORKDIR /jsonapi-utils
6+
7+
COPY . ./
8+
9+
RUN gem install bundler
10+
RUN bundle install --jobs 2 --retry 10
11+
12+
ENTRYPOINT []

jsonapi-utils.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ Gem::Specification.new do |spec|
2828
spec.add_development_dependency 'rspec-rails', '~> 3.1'
2929
spec.add_development_dependency 'factory_girl', '~> 4.5'
3030
spec.add_development_dependency 'smart_rspec', '~> 0.1.4'
31+
spec.add_development_dependency 'pry', '~> 0.10.3'
32+
spec.add_development_dependency 'pry-byebug'
3133
end

lib/jsonapi/utils.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1+
12
require 'jsonapi-resources'
23
require 'jsonapi/utils/version'
34
require 'jsonapi/utils/exceptions'
45
require 'jsonapi/utils/request'
56
require 'jsonapi/utils/response'
7+
require 'jsonapi/utils/support/filter/custom'
8+
9+
JSONAPI::Resource.extend JSONAPI::Utils::Support::Filter::Custom
610

711
module JSONAPI
812
module Utils
913
include Request
1014
include Response
1115

1216
def self.included(base)
17+
base.include ActsAsResourceController
18+
1319
if base.respond_to?(:before_action)
1420
base.before_action :jsonapi_request_handling
1521
end

lib/jsonapi/utils/exceptions.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,24 @@ def errors
2323
end
2424

2525
class BadRequest < ::JSONAPI::Exceptions::Error
26-
def code; '400' end
26+
def code
27+
'400'
28+
end
2729

2830
def errors
2931
[JSONAPI::Error.new(
30-
code: code,
31-
status: :bad_request,
32-
title: 'Bad Request',
33-
detail: 'This request is not supported.'
34-
)]
32+
code: code,
33+
status: :bad_request,
34+
title: 'Bad Request',
35+
detail: 'This request is not supported.'
36+
)]
3537
end
3638
end
3739

3840
class InternalServerError < ::JSONAPI::Exceptions::Error
39-
def code; '500' end
41+
def code
42+
'500'
43+
end
4044

4145
def errors
4246
[JSONAPI::Error.new(

lib/jsonapi/utils/response/support.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
require 'jsonapi/utils/support/filter'
1+
require 'jsonapi/utils/support/error'
2+
require 'jsonapi/utils/support/filter/default'
23
require 'jsonapi/utils/support/pagination'
34
require 'jsonapi/utils/support/sort'
4-
require 'jsonapi/utils/support/error'
55

66
module JSONAPI
77
module Utils
88
module Response
99
module Support
1010
include ::JSONAPI::Utils::Support::Error
11-
include ::JSONAPI::Utils::Support::Filter
11+
include ::JSONAPI::Utils::Support::Filter::Default
1212
include ::JSONAPI::Utils::Support::Pagination
1313
include ::JSONAPI::Utils::Support::Sort
1414

1515
private
1616

1717
def correct_media_type
18-
if response.body.size > 0
18+
unless response.body.empty?
1919
response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
2020
end
2121
end

lib/jsonapi/utils/support/error.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module JSONAPI
22
module Utils
33
module Support
44
module Error
5-
MEMBERS = %i(title detail id code source links status meta)
5+
MEMBERS = %i(title detail id code source links status meta).freeze
66

77
module_function
88

lib/jsonapi/utils/support/filter.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ def apply_filter?(records, options = {})
1717

1818
def filter_params
1919
@_filter_params ||=
20-
if params[:filter].is_a?(Hash)
21-
params[:filter].keys.each_with_object({}) do |resource, hash|
22-
hash[resource] = params[:filter][resource]
20+
case params[:filter]
21+
when Hash, ActionController::Parameters
22+
default_filters.each_with_object({}) do |field, hash|
23+
unformatted_field = @request.unformat_key(field)
24+
hash[unformatted_field] = params[:filter][field]
2325
end
2426
end
2527
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module JSONAPI::Utils::Support::Filter
2+
module Custom
3+
def _custom_filters
4+
@_allowed_custom_filters || []
5+
end
6+
7+
def custom_filters(*attrs)
8+
attrs.each { |attr| custom_filter(attr) }
9+
end
10+
11+
def custom_filter(attr)
12+
attr = attr.to_sym
13+
@_allowed_filters[attr] = {}
14+
15+
if !@_allowed_custom_filters.is_a?(Array)
16+
@_allowed_custom_filters = Array(attr)
17+
elsif @_allowed_custom_filters.include?(attr)
18+
@_allowed_custom_filters.push(attr)
19+
end
20+
end
21+
end
22+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module JSONAPI::Utils::Support::Filter
2+
module Default
3+
# Apply default equality filters.
4+
# e.g.: User.where(name: 'Foobar')
5+
#
6+
# @param records [ActiveRecord::Relation, Array] collection of records
7+
# e.g.: User.all or [{ id: 1, name: 'Tiago' }, { id: 2, name: 'Doug' }]
8+
#
9+
# @param options [Hash] JU's options
10+
# e.g.: { filter: false, paginate: false }
11+
#
12+
# @return [ActiveRecord::Relation, Array]
13+
#
14+
# @api public
15+
def apply_filter(records, options = {})
16+
if apply_filter?(records, options)
17+
records.where(filter_params)
18+
else
19+
records
20+
end
21+
end
22+
23+
# Check whether default filters should be applied.
24+
#
25+
# @param records [ActiveRecord::Relation, Array] collection of records
26+
# e.g.: User.all or [{ id: 1, name: 'Tiago' }, { id: 2, name: 'Doug' }]
27+
#
28+
# @param options [Hash] JU's options
29+
# e.g.: { filter: false, paginate: false }
30+
#
31+
# @return [Boolean]
32+
#
33+
# @api public
34+
def apply_filter?(records, options = {})
35+
params[:filter].present? && records.respond_to?(:where) &&
36+
(options[:filter].nil? || options[:filter])
37+
end
38+
39+
# Build a Hash with the default filters.
40+
#
41+
# @return [Hash, NilClass]
42+
#
43+
# @api public
44+
def filter_params
45+
@_filter_params ||=
46+
case params[:filter]
47+
when Hash, ActionController::Parameters
48+
default_filters.each_with_object({}) do |field, hash|
49+
unformatted_field = @request.unformat_key(field)
50+
hash[unformatted_field] = params[:filter][field]
51+
end
52+
end
53+
end
54+
55+
private
56+
57+
# Take all allowed filters and remove the custom ones.
58+
#
59+
# @return [Array]
60+
#
61+
# @api private
62+
def default_filters
63+
params[:filter].keys.map(&:to_sym) - @request.resource_klass._custom_filters
64+
end
65+
end
66+
end

lib/jsonapi/utils/support/pagination.rb

Lines changed: 103 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,133 @@ module JSONAPI
22
module Utils
33
module Support
44
module Pagination
5+
# Apply proper pagination to the records.
6+
#
7+
# @param records [ActiveRecord::Relation, Array] collection of records
8+
# e.g.: User.all or [{ id: 1, name: 'Tiago' }, { id: 2, name: 'Doug' }]
9+
#
10+
# @param options [Hash] JU's options
11+
# e.g.: { resource: V2::UserResource, count: 100 }
12+
#
13+
# @return [ActiveRecord::Relation, Array]
14+
#
15+
# @api public
516
def apply_pagination(records, options = {})
617
return records unless apply_pagination?(options)
7-
pagination = set_pagination(options)
18+
records.is_a?(Array) ? records[paginate_with(:range)] : paginate_with(:paginator).apply(records, nil)
19+
end
820

9-
records =
10-
if records.is_a?(Array)
11-
records[pagination[:range]]
12-
else
13-
pagination[:paginator].apply(records, nil)
14-
end
21+
# Mount pagination params for JSONAPI::ResourcesOperationResult.
22+
# It can also be used anywhere else as a helper method.
23+
#
24+
# @param records [ActiveRecord::Relation, Array] collection of records
25+
# e.g.: User.all or [{ id: 1, name: 'Tiago' }, { id: 2, name: 'Doug' }]
26+
#
27+
# @param options [Hash] JU's options
28+
# e.g.: { resource: V2::UserResource, count: 100 }
29+
#
30+
# @return [Hash]
31+
# e.g.: {"first"=>{"number"=>1, "size"=>2}, "next"=>{"number"=>2, "size"=>2}, "last"=>{"number"=>2, "size"=>2}}
32+
#
33+
# @api public
34+
def pagination_params(records, options)
35+
return {} unless JSONAPI.configuration.top_level_links_include_pagination
36+
paginator.links_page_params(record_count: count_records(records, options))
37+
end
38+
39+
private
40+
41+
# Define the paginator object to be used in the response's pagination.
42+
#
43+
# @return [PagedPaginator, OffsetPaginator]
44+
#
45+
# @api private
46+
def paginator
47+
@paginator ||= paginator_klass.new(page_params)
1548
end
1649

50+
# Returns the paginator class to be used in the response's pagination.
51+
#
52+
# @return [Paginator]
53+
#
54+
# @api private
55+
def paginator_klass
56+
"#{JSONAPI.configuration.default_paginator}_paginator".classify.constantize
57+
end
58+
59+
# Check whether pagination should be applied to the response.
60+
#
61+
# @return [Boolean]
62+
#
63+
# @api private
1764
def apply_pagination?(options)
1865
JSONAPI.configuration.default_paginator != :none &&
1966
(options[:paginate].nil? || options[:paginate])
2067
end
2168

22-
def pagination_params(records, options)
23-
@paginator ||= paginator(params)
24-
if @paginator && JSONAPI.configuration.top_level_links_include_pagination
25-
options = {}
26-
@paginator.class.requires_record_count &&
27-
options[:record_count] = count_records(records, options)
28-
@paginator.links_page_params(options)
29-
else
30-
{}
69+
# Creates an instance of ActionController::Parameters for page params.
70+
#
71+
# @return [ActionController::Parameters]
72+
#
73+
# @api private
74+
def page_params
75+
@page_params ||= begin
76+
page = @request.params.to_unsafe_hash['page'] || {}
77+
ActionController::Parameters.new(page)
3178
end
3279
end
3380

34-
def paginator(params)
35-
page_params = ActionController::Parameters.new(params[:page])
36-
37-
@paginator ||=
38-
if JSONAPI.configuration.default_paginator == :paged
39-
PagedPaginator.new(page_params)
40-
elsif JSONAPI.configuration.default_paginator == :offset
41-
OffsetPaginator.new(page_params)
81+
# Define the paginator or range according to the pagination strategy.
82+
#
83+
# @param kind [Symbol] pagination object's kind
84+
# e.g.: :paginator or :range
85+
#
86+
# @return [PagedPaginator, OffsetPaginator, Range]
87+
# e.g.: #<PagedPaginator:0x00561ed06dc5a0 @number=1, @size=2>
88+
# 0..9
89+
#
90+
# @api private
91+
def paginate_with(kind)
92+
@pagination ||=
93+
case kind
94+
when :paginator then paginator
95+
when :range then pagination_range
4296
end
4397
end
4498

45-
def set_pagination(options)
46-
page_params = ActionController::Parameters.new(@request.params[:page])
47-
if JSONAPI.configuration.default_paginator == :paged
48-
@_paginator ||= PagedPaginator.new(page_params)
99+
# Define a pagination range for objects which quack like Arrays.
100+
#
101+
# @return [Range]
102+
# e.g.: 0..9
103+
#
104+
# @api private
105+
def pagination_range
106+
case JSONAPI.configuration.default_paginator
107+
when :paged
49108
number = page_params['number'].to_i.nonzero? || 1
50109
size = page_params['size'].to_i.nonzero? || JSONAPI.configuration.default_page_size
51-
{ paginator: @_paginator, range: (number - 1) * size..number * size - 1 }
52-
elsif JSONAPI.configuration.default_paginator == :offset
53-
@_paginator ||= OffsetPaginator.new(page_params)
110+
(number - 1) * size..number * size - 1
111+
when :offset
54112
offset = page_params['offset'].to_i.nonzero? || 0
55113
limit = page_params['limit'].to_i.nonzero? || JSONAPI.configuration.default_page_size
56-
{ paginator: @_paginator, range: offset..offset + limit - 1 }
114+
offset..offset + limit - 1
57115
else
58-
{}
116+
paginator.pagination_range(page_params)
59117
end
60118
end
61119

120+
# Count records in order to build a proper pagination and to fill up the "record_count" response's member.
121+
#
122+
# @param records [ActiveRecord::Relation, Array] collection of records
123+
# e.g.: User.all or [{ id: 1, name: 'Tiago' }, { id: 2, name: 'Doug' }]
124+
#
125+
# @param options [Hash] JU's options
126+
# e.g.: { resource: V2::UserResource, count: 100 }
127+
#
128+
# @return [Integer]
129+
# e.g.: 42
130+
#
131+
# @api private
62132
def count_records(records, options)
63133
if options[:count].present?
64134
options[:count]

0 commit comments

Comments
 (0)