next_page 0.1.3 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9789ab6b725aa655241fcfa9b43e66152b516a58b741088fd7797fe571a23915
4
- data.tar.gz: da2e4c3608a52b1286ee33aef1005a5f4ab229fc579cd99b7c86c3177409423d
3
+ metadata.gz: b3a633fa748d4bdcff2576c514993b13373a95d999680ef06ef0439ba9f849c3
4
+ data.tar.gz: 58f4616e60f4fe84583a03efe134eac635cf19fee0d5da85c99d9790203cdfc3
5
5
  SHA512:
6
- metadata.gz: c7c72b0294f2f4f4f62208fa3fc9671fd67e342c09e37338ff1df6c9596ef09a935e4251a5d0ec9870aada9d451eb724efe2a213f6406d39e75baab1327b99fb
7
- data.tar.gz: 9c8362bfcc1fa2694835fe30aa78afa43f957d7269bb0e2cad787cb452bb55582d36a064031061061640a55ff6553bd6f65c427cbe67c709b6be75eaf43997d7
6
+ metadata.gz: d91211f3d61c31c98ec05b8512fc0a277ad13490f654c272b5ec77fd1539cc83f6c17bd87169b18a4d66b8a713237da6858543cda2d45bf6041e3c145c4ecd20
7
+ data.tar.gz: 9617247dbad11b9ef5ef083f489728c565a901e632d77a59c184d25ae0ea48d3e55cf4dbd1e9f30a8e1d7c089dd7616822f786b5bebb21d245ea9890364a3fea
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![Gem Version](https://badge.fury.io/rb/next_page.svg)](https://badge.fury.io/rb/next_page)
2
+ [![RuboCop](https://github.com/RockSolt/next_page/workflows/RuboCop/badge.svg)](https://github.com/RockSolt/next_page/actions?query=workflow%3ARuboCop)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/0efe1a9b66a0bf161536/maintainability)](https://codeclimate.com/github/RockSolt/next_page/maintainability)
4
+
1
5
  # NextPage
2
6
  Basic pagination for Rails controllers.
3
7
 
@@ -43,6 +47,53 @@ resource:
43
47
  @photos = paginate_resource(@photos)
44
48
  ```
45
49
 
50
+ ### Sorting
51
+ Requests can specify sort order using the parameter `sort` with an attribute name or scope. Sorts can be prefixed with `+` or `-` to indicate ascending or descending. Multiple sorts can be specified either as a comma separated list or via bracket notation.
52
+
53
+ /photos?sort=-created_at
54
+ /photos?sort=location,-created_by
55
+ /photos?sort[]=location&photos[]=-created_by
56
+
57
+ The default sort order is primary key descending. It can be overridden by using the `default_sort` option of `paginate_with`. Use a string formatted just as url parameter would be formatted.
58
+
59
+ ```ruby
60
+ paginate_with default_sort: '-created_at'
61
+ ```
62
+
63
+ #### Nested Sorts
64
+
65
+ Nested attributes and scopes can be indicated by providing the association names separated by periods.
66
+
67
+ /photos?sort=user.name
68
+ /photos?sort=-user.address.state
69
+
70
+ #### Directional Scope Sorts
71
+
72
+ In order to use directions (`+` or `-`) with a scope, the scope must be defined as a class method and take a single parameter. The scope will receive either `'asc'` or `'desc'`. Here is an example of a valid directional scope.
73
+
74
+ ```ruby
75
+ def self.status(direction)
76
+ order("CASE status WHEN 'new' THEN 1 WHEN 'in progress' THEN 2 ELSE 3 END #{direction}")
77
+ end
78
+ ```
79
+
80
+ #### Scope Prefix / Suffix
81
+
82
+ In order to keep the peace between frontend and backend developers, scope names can include a prefix or suffix that the front end can ignore. For example, given a scope that sorts on a derived attribute (such as status in the _Direction Scope Sorts_ example), the backend developer might prefer to name the scope status_sort or sort_by_status, as a class method that shares the same name as an attribute might be unclear. However, the frontend developer does not want a query parameter that says <tt>sort=sort_by_status</tt>; it is an exception because it doesn't match the name of the attribute (and it's not pretty).
83
+
84
+ The configuration allows a prefix and or suffix to be specified. If either is specified, then in addition to looking for a scope that matches the parameter name, it will also look for a scope that matches the prefixed and/or suffixed name. Prefixes are defined by configuration option <tt>sort_scope_prefix</tt> and suffixes are defined by <tt>sort_scope_suffix</tt>.
85
+
86
+ For example, if the backend developer prefers <tt>sort_by_status</tt> then the following configuration can be used:
87
+
88
+ ```ruby
89
+ NextPage.configure do |config|
90
+ config.sort_scope_prefix = 'sort_by_'
91
+ end
92
+ ```
93
+ This allows the query parameter to be the following:
94
+
95
+ sort=status
96
+
46
97
 
47
98
  ### Default Limit
48
99
  The default size limit can be overridden with the `paginate_with` method for either type of paginagion. Pass option
@@ -60,12 +111,18 @@ paginate_with default_limit: 12, instance_variable_name: 'data'
60
111
  ```
61
112
 
62
113
  ### Link Helpers
63
- This gem does not do any rendering. It does provide helper methods for generating links. The resource will include the following additional methods:
114
+ This gem does not do any rendering. It does provide helper methods for generating links. The resource will include the following additional methods (when the request header Accept is `'application/vnd.api+json'`):
64
115
  - current_page
65
116
  - next_page
66
117
  - total_pages
67
118
  - per_page
68
119
 
120
+ #### Count Query
121
+ In some cases (such as grouping), calling count on the query does not provide an accurate representation. If that is the case, then there are two ways to override the default behavior:
122
+ - provide a count_query that can resolve the attributes
123
+ - specify the following attributes manually: current_page, total_count, and per_page
124
+
125
+
69
126
  ## Installation
70
127
  Add this line to your application's Gemfile:
71
128
 
data/lib/next_page.rb CHANGED
@@ -1,9 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'next_page/configuration'
4
+ require 'next_page/exceptions'
3
5
  require 'next_page/pagination'
4
6
  require 'next_page/pagination_attributes'
7
+ require 'next_page/sorter'
5
8
  require 'next_page/paginator'
6
9
 
7
10
  # = Next Page
8
11
  module NextPage
12
+ class << self
13
+ attr_writer :configuration
14
+ end
15
+
16
+ def self.configuration
17
+ @configuration ||= Configuration.new
18
+ end
19
+
20
+ def self.reset
21
+ @configuration = Configuration.new
22
+ end
23
+
24
+ def self.configure
25
+ yield(configuration)
26
+ end
9
27
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NextPage
4
+ # = Configuration
5
+ #
6
+ # Class Configuration stores the following settings:
7
+ # - sort_scope_prefix
8
+ # - sort_scope_suffix
9
+ #
10
+ # == Sort Scope Prefix
11
+ # Enables client to sort request to be mapped to a scope with a more specific name. For example, given a derived
12
+ # attribute named <tt>status</tt>, the query parameter can be <tt>sort=status</tt> but would map to a more explicitly
13
+ # named scope, such as <tt>sort_by_status</tt> (assuming the <tt>sort_scope_prefix</tt> value is 'sort_by_').
14
+ #
15
+ # == Sort Scope Suffix
16
+ # Enables client to sort request to be mapped to a scope with a more specific name. For example, given a derived
17
+ # attribute named <tt>status</tt>, the query parameter can be <tt>sort=status</tt> but would map to a more explicitly
18
+ # named scope, such as <tt>status_sort</tt> (assuming the <tt>sort_scope_suffix</tt> value is '_sort').
19
+ class Configuration
20
+ attr_reader :sort_scope_prefix, :sort_scope_suffix
21
+
22
+ def sort_scope_prefix=(value)
23
+ @sort_scope_prefix = value.to_s
24
+ end
25
+
26
+ def sort_scope_prefix?
27
+ @sort_scope_prefix.present?
28
+ end
29
+
30
+ def sort_scope_suffix=(value)
31
+ @sort_scope_suffix = value.to_s
32
+ end
33
+
34
+ def sort_scope_suffix?
35
+ @sort_scope_suffix.present?
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NextPage
4
+ module Exceptions
5
+ class NextPageError < StandardError
6
+ end
7
+ end
8
+ end
9
+
10
+ require 'next_page/exceptions/invalid_nested_sort'
11
+ require 'next_page/exceptions/invalid_sort_parameter'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NextPage
4
+ module Exceptions
5
+ # = Invalid Nested Sort
6
+ class InvalidNestedSort < NextPage::Exceptions::NextPageError
7
+ def initialize(model, association)
8
+ @model = model
9
+ @association = association
10
+ end
11
+
12
+ def message
13
+ "Invalid nested sort: Unable to find association #{@association} on model #{@model}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NextPage
4
+ module Exceptions
5
+ # = Invalid Sort Parameter
6
+ class InvalidSortParameter < NextPage::Exceptions::NextPageError
7
+ def initialize(segment)
8
+ @segment = segment
9
+ end
10
+
11
+ def message
12
+ "Invalid sort parameter (#{@segment}). Must be an attribute or scope."
13
+ end
14
+ end
15
+ end
16
+ end
@@ -52,29 +52,28 @@ module NextPage
52
52
  # - instance_variable_name: explicitly name the variable if it does not follow the convention
53
53
  # - model_class: explicitly specify the model name if it does not follow the convention
54
54
  # - default_limit: specify an alternate default
55
- def paginate_with(instance_variable_name: nil, model_class: nil, default_limit: nil)
56
- next_page_paginator.paginate_with(instance_variable_name, model_class, default_limit)
55
+ # - default_sort: sort parameter if none provided, use same format as url: created_at OR -updated_at
56
+ def paginate_with(instance_variable_name: nil, model_class: nil, default_limit: nil, default_sort: nil)
57
+ next_page_paginator.paginate_with(instance_variable_name, model_class, default_limit, default_sort)
57
58
  end
58
59
  end
59
60
 
60
61
  # Called with before_action in order to automatically paginate the resource.
61
62
  def apply_next_page_pagination
62
- self.class.next_page_paginator.paginate(self, params[:page])
63
+ self.class.next_page_paginator.paginate(self, params.slice(:page, :sort))
63
64
  end
64
65
 
65
66
  # Invokes pagination directly, the result must be stored as the resource itself is not modified.
66
67
  def paginate_resource(resource)
67
- self.class.next_page_paginator.paginate_resource(resource, params[:page])
68
+ self.class.next_page_paginator.paginate_resource(resource, params.slice(:page, :sort))
68
69
  end
69
70
 
70
71
  def render(*args) #:nodoc:
71
72
  return super unless action_name == 'index' && request.headers[:Accept] == 'application/vnd.api+json'
72
73
 
73
- options = args.first
74
- return super unless options.is_a?(Hash) && options.key?(:json)
75
-
76
- options[:meta] = options.fetch(:meta, {}).merge!(total_pages: options[:json].total_pages,
77
- total_count: options[:json].total_count)
74
+ self.class.next_page_paginator.decorate_meta!(args.first)
75
+ super
76
+ rescue StandardError
78
77
  super
79
78
  end
80
79
  end
@@ -3,19 +3,29 @@
3
3
  module NextPage
4
4
  # = Pagination Attributes
5
5
  #
6
- # Module PaginationAttributes adds in methods required for pagination links: current_page, next_page, and total_pages.
7
- # It reads the offset and limit on the query to determine the values.
6
+ # Module PaginationAttributes adds in methods required for pagination links: previous_page, current_page, next_page,
7
+ # and total_pages. It reads the offset and limit on the query to determine the values.
8
+ #
9
+ # In some cases the query will not support count. In that case, there are two ways to override the default behavior:
10
+ # - provide a count_query that can resolve the attributes
11
+ # - specify the following attributes manually: current_page, total_count, and per_page
8
12
  module PaginationAttributes
13
+ attr_writer :count_query, :current_page, :total_count, :per_page
14
+
15
+ def previous_page
16
+ current_page > 1 ? current_page - 1 : nil
17
+ end
18
+
9
19
  def current_page
10
- @current_page ||= offset_value + 1
20
+ @current_page ||= (count_query.offset_value || 0) + 1
11
21
  end
12
22
 
13
23
  def next_page
14
- current_page + 1
24
+ total_pages > current_page ? current_page + 1 : nil
15
25
  end
16
26
 
17
27
  def total_count
18
- @total_count ||= unscope(:limit).unscope(:offset).count
28
+ @total_count ||= count_query.unscope(:limit).unscope(:offset).count
19
29
  end
20
30
 
21
31
  def total_pages
@@ -23,7 +33,12 @@ module NextPage
23
33
  end
24
34
 
25
35
  def per_page
26
- @per_page ||= limit_value
36
+ @per_page ||= count_query.limit_value
37
+ end
38
+
39
+ # checks first to see if an override query has been provided, then fails back to self
40
+ def count_query
41
+ @count_query || self
27
42
  end
28
43
  end
29
44
  end
@@ -22,10 +22,11 @@ module NextPage
22
22
  @default_limit = DEFAULT_LIMIT
23
23
  end
24
24
 
25
- def paginate_with(instance_variable_name, model_class, default_limit)
25
+ def paginate_with(instance_variable_name, model_class, default_limit, default_sort)
26
26
  @default_limit = default_limit if default_limit.present?
27
27
  @instance_variable_name = instance_variable_name
28
28
  @model_class = model_class.is_a?(String) ? model_class.constantize : model_class
29
+ @default_sort = default_sort
29
30
  end
30
31
 
31
32
  def paginate(controller, page_params)
@@ -35,12 +36,19 @@ module NextPage
35
36
  controller.instance_variable_set(name, paginate_resource(data, page_params))
36
37
  end
37
38
 
38
- def paginate_resource(data, page_params)
39
- data.extend(NextPage::PaginationAttributes)
39
+ def paginate_resource(data, params)
40
+ assign_pagination_attributes(data, params)
41
+
42
+ data = sorter.sort(data, params.fetch(:sort, default_sort))
43
+ data.limit(data.per_page).offset((data.current_page - 1) * data.per_page)
44
+ end
40
45
 
41
- limit = page_size(page_params)
42
- offset = page_number(page_params) - 1
43
- data.limit(limit).offset(offset * limit)
46
+ def decorate_meta!(options)
47
+ return unless options.is_a?(Hash) && options.key?(:json) && !options[:json].is_a?(Hash)
48
+
49
+ resource = options[:json]
50
+ options[:meta] = options.fetch(:meta, {}).merge!(total_pages: resource.total_pages,
51
+ total_count: resource.total_count)
44
52
  end
45
53
 
46
54
  private
@@ -55,6 +63,16 @@ module NextPage
55
63
  @instance_variable_name ||= @controller_name
56
64
  end
57
65
 
66
+ def default_sort
67
+ @default_sort ||= "-#{@model_class.primary_key}"
68
+ end
69
+
70
+ def assign_pagination_attributes(data, params)
71
+ data.extend(NextPage::PaginationAttributes)
72
+ data.per_page = page_size(params[:page])
73
+ data.current_page = page_number(params[:page])
74
+ end
75
+
58
76
  def page_size(page)
59
77
  if page.present? && page[:size].present?
60
78
  page[:size]&.to_i
@@ -70,5 +88,9 @@ module NextPage
70
88
  1
71
89
  end
72
90
  end
91
+
92
+ def sorter
93
+ @sorter ||= NextPage::Sorter.new(model_class)
94
+ end
73
95
  end
74
96
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NextPage
4
+ module Sort
5
+ # = Name Evaluator
6
+ #
7
+ # Determines if a name represents an attribute or a scope, provides mapping if the scope requires a prefix or
8
+ # suffix. Scope is checked first, allowing it to override an attribute of the same name. For scopes, method
9
+ # <tt>directional_scope?</tt> checks to see if the scope is a class method that accepts one parameter; if so, then
10
+ # the scope will be invoked with the direction.
11
+ class NameEvaluator
12
+ attr_reader :scope_name
13
+
14
+ def initialize(model, name)
15
+ @model = model
16
+ @name = name.to_s
17
+ evaluate_for_scope
18
+ end
19
+
20
+ def scope?
21
+ @scope_name.present?
22
+ end
23
+
24
+ # true when scope is class method with one parameter
25
+ def directional_scope?
26
+ return false unless scope?
27
+
28
+ @model.method(@scope_name).arity == 1
29
+ end
30
+
31
+ def valid_attribute_name?
32
+ @model.attribute_names.include?(@name)
33
+ end
34
+
35
+ private
36
+
37
+ def evaluate_for_scope
38
+ assign_scope(@name) || assign_prefixed_scope || assign_suffixed_scope
39
+ end
40
+
41
+ def assign_scope(potential_scope)
42
+ return if @model.dangerous_class_method?(potential_scope) || !@model.respond_to?(potential_scope)
43
+
44
+ @scope_name = potential_scope
45
+ end
46
+
47
+ def assign_prefixed_scope
48
+ return unless NextPage.configuration.sort_scope_prefix?
49
+
50
+ assign_scope("#{NextPage.configuration.sort_scope_prefix}#{@name}")
51
+ end
52
+
53
+ def assign_suffixed_scope
54
+ return unless NextPage.configuration.sort_scope_suffix?
55
+
56
+ assign_scope("#{@name}#{NextPage.configuration.sort_scope_suffix}")
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NextPage
4
+ module Sort
5
+ # = Segment Parser
6
+ #
7
+ # Parses each sort segment to provide direction, associations, and name.
8
+ class SegmentParser
9
+ attr_reader :direction, :associations, :name
10
+
11
+ SEGMENT_REGEX = /(?<sign>[+|-]?)(?<names>.+)/.freeze
12
+
13
+ def initialize(segment)
14
+ @segment = segment
15
+ parsed = segment.match SEGMENT_REGEX
16
+ @direction = parsed['sign'] == '-' ? 'desc' : 'asc'
17
+ *@associations, @name = *parsed['names'].split('.')
18
+ end
19
+
20
+ def to_s
21
+ @segment
22
+ end
23
+
24
+ def attribute_with_direction
25
+ { @name => @direction }
26
+ end
27
+
28
+ def nested?
29
+ @associations.present?
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NextPage
4
+ module Sort
5
+ # = Sort Builder
6
+ class SortBuilder
7
+ def initialize(model)
8
+ @model = model
9
+ end
10
+
11
+ # TODO: support passing direction to scope
12
+ def build(segment)
13
+ @parser = NextPage::Sort::SegmentParser.new(segment)
14
+
15
+ if @parser.nested?
16
+ build_nested
17
+ else
18
+ build_non_nested
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def build_nested
25
+ sort_model = dig_association_model(@parser.associations)
26
+ joins = build_joins(@parser.associations)
27
+ evaluator = NextPage::Sort::NameEvaluator.new(sort_model, @parser.name)
28
+
29
+ if evaluator.scope?
30
+ build_nested_scope_sort(sort_model, joins, evaluator)
31
+ elsif evaluator.valid_attribute_name?
32
+ nested_attribute_sort(sort_model, joins, @parser.attribute_with_direction)
33
+ else
34
+ raise NextPage::Exceptions::InvalidSortParameter, @parser
35
+ end
36
+ end
37
+
38
+ def build_nested_scope_sort(sort_model, joins, evaluator)
39
+ if evaluator.directional_scope?
40
+ nested_directional_scope_sort(sort_model, joins, evaluator.scope_name, @parser.direction)
41
+ else
42
+ nested_scope_sort(sort_model, joins, evaluator.scope_name)
43
+ end
44
+ end
45
+
46
+ def nested_attribute_sort(model, joins, attribute_with_direction)
47
+ ->(query) { query.joins(joins).merge(model.order(attribute_with_direction)) }
48
+ end
49
+
50
+ def nested_scope_sort(model, joins, scope_name)
51
+ ->(query) { query.joins(joins).merge(model.public_send(scope_name)) }
52
+ end
53
+
54
+ def nested_directional_scope_sort(model, joins, scope_name, direction)
55
+ ->(query) { query.joins(joins).merge(model.public_send(scope_name, direction)) }
56
+ end
57
+
58
+ def build_non_nested
59
+ evaluator = NextPage::Sort::NameEvaluator.new(@model, @parser.name)
60
+
61
+ if evaluator.scope?
62
+ build_non_nested_scope_sort(evaluator)
63
+ elsif evaluator.valid_attribute_name?
64
+ attribute_sort(@parser.attribute_with_direction)
65
+ else
66
+ raise NextPage::Exceptions::InvalidSortParameter, @parser
67
+ end
68
+ end
69
+
70
+ def build_non_nested_scope_sort(evaluator)
71
+ if evaluator.directional_scope?
72
+ directional_scope_sort(evaluator.scope_name, @parser.direction)
73
+ else
74
+ scope_sort(evaluator.scope_name)
75
+ end
76
+ end
77
+
78
+ def attribute_sort(attribute_with_direction)
79
+ ->(query) { query.order(attribute_with_direction) }
80
+ end
81
+
82
+ def scope_sort(scope_name)
83
+ ->(query) { query.send(scope_name) }
84
+ end
85
+
86
+ def directional_scope_sort(scope_name, direction)
87
+ ->(query) { query.send(scope_name, direction) }
88
+ end
89
+
90
+ # traverse nested associations to find last association's model
91
+ def dig_association_model(associations)
92
+ associations.reduce(@model) do |model, association_name|
93
+ association = model.reflect_on_association(association_name)
94
+ raise NextPage::Exceptions::InvalidNestedSort.new(model, association_name) if association.nil?
95
+
96
+ association.klass
97
+ end
98
+ end
99
+
100
+ # transform associations array to nested hash
101
+ # ['team'] => [:team]
102
+ # ['team', 'coach'] => { team: :coach }
103
+ def build_joins(associations)
104
+ associations.map(&:to_sym)
105
+ .reverse
106
+ .reduce { |memo, association| memo.nil? ? association.to_sym : { association => memo } }
107
+ end
108
+
109
+ # TODO: consider prefix / suffix
110
+ def named_scope(sort_model)
111
+ # checking dangerous_class_method? excludes any names that cannot be scope names, such as "name"
112
+ return unless sort_model.respond_to?(@parser.name) && !sort_model.dangerous_class_method?(@parser.name)
113
+
114
+ @parser.name
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'next_page/sort/name_evaluator'
4
+ require 'next_page/sort/segment_parser'
5
+ require 'next_page/sort/sort_builder'
6
+
7
+ module NextPage
8
+ # = Sorter
9
+ #
10
+ # Class Sorter reads the sort parameter and applies the related ordering. Results for each parameter string are
11
+ # cached so evaluation only occurs once.
12
+ class Sorter
13
+ # Initializes a new sorter. The given model is used to validate sort attributes as well as build nested sorts.
14
+ def initialize(model)
15
+ @model = model
16
+ @cache = Hash.new { |hash, key| hash[key] = build_sort(key) }
17
+ end
18
+
19
+ # Adds sorting to given query based upon the param. Returns a new query; the existing query is NOT modified.
20
+ #
21
+ # The +query+ parameter is an ActiveRecord arel or model.
22
+ #
23
+ # The +sort_fields+ parameter is a string that conforms to the JSON-API specification for sorting fields:
24
+ # https://jsonapi.org/format/#fetching-sorting
25
+ def sort(query, sort_fields)
26
+ return from_array(query, sort_fields.split(',')) if sort_fields.include?(',')
27
+ return from_array(query, sort_fields) if sort_fields.is_a? Array
28
+
29
+ apply_sort(query, sort_fields)
30
+ end
31
+
32
+ private
33
+
34
+ def apply_sort(query, key)
35
+ @cache[key].call(query)
36
+ end
37
+
38
+ def from_array(query, param)
39
+ param.reduce(query) { |memo, key| apply_sort(memo, key) }
40
+ end
41
+
42
+ # returns a lambda that applies the appropriate sort, either from a scope, nested attribute, or attribute
43
+ def build_sort(key)
44
+ ActiveSupport::Notifications.instrument('build_sort.next_page', { key: key }) do
45
+ NextPage::Sort::SortBuilder.new(@model).build(key)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module NextPage
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.8'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: next_page
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Kummer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-05 00:00:00.000000000 Z
11
+ date: 2021-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 6.0.2
19
+ version: '6.0'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
22
  version: 6.0.2.2
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: 6.0.2
29
+ version: '6.0'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 6.0.2.2
@@ -106,14 +106,14 @@ dependencies:
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '0.77'
109
+ version: 0.82.0
110
110
  type: :development
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: '0.77'
116
+ version: 0.82.0
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: simplecov
119
119
  requirement: !ruby/object:Gem::Requirement
@@ -140,16 +140,24 @@ files:
140
140
  - README.md
141
141
  - Rakefile
142
142
  - lib/next_page.rb
143
+ - lib/next_page/configuration.rb
144
+ - lib/next_page/exceptions.rb
145
+ - lib/next_page/exceptions/invalid_nested_sort.rb
146
+ - lib/next_page/exceptions/invalid_sort_parameter.rb
143
147
  - lib/next_page/pagination.rb
144
148
  - lib/next_page/pagination_attributes.rb
145
149
  - lib/next_page/paginator.rb
150
+ - lib/next_page/sort/name_evaluator.rb
151
+ - lib/next_page/sort/segment_parser.rb
152
+ - lib/next_page/sort/sort_builder.rb
153
+ - lib/next_page/sorter.rb
146
154
  - lib/next_page/version.rb
147
155
  - lib/tasks/next_page_tasks.rake
148
156
  homepage: https://github.com/RockSolt/next_page
149
157
  licenses:
150
158
  - MIT
151
159
  metadata: {}
152
- post_install_message:
160
+ post_install_message:
153
161
  rdoc_options: []
154
162
  require_paths:
155
163
  - lib
@@ -165,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
173
  version: '0'
166
174
  requirements: []
167
175
  rubygems_version: 3.0.8
168
- signing_key:
176
+ signing_key:
169
177
  specification_version: 4
170
178
  summary: Pagination for Rails Controllers
171
179
  test_files: []