next_page 0.1.2 → 0.1.7

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: 423a7c89235a411543d5c3a70cdedc6c09cca6af9d2039de6fe87dbbfe73bad9
4
- data.tar.gz: '09cac0c988dc7730d5899aa1ecf0fb4d7663225cbf036bc1161634531f0cca7e'
3
+ metadata.gz: 91b9a69827f83cb0e3fd5dcbbdab8537c61d859f89cea744a2afccf8afcf3570
4
+ data.tar.gz: 4474b5cdb0d3e44473df17a0ae10fbd389abf9a3e372062f5691fb656c8a21e4
5
5
  SHA512:
6
- metadata.gz: e5926d53cbe7ce204259be7a100a683f84c37044238033e5d582e636b5d07d5de84edea27d7a80e512bc186d8d85f277dd5a8574cc11d278a28062ecd8900662
7
- data.tar.gz: 1fac9c2164eafa53ed179d1dca474b6b63b0873d58c9983fa38c86d00f7ef40a07a52916097a20e2f1528c9a6c7b620d9cd7c7426b1d67e37e4c22f304143e77
6
+ metadata.gz: bf4b093bb8ae31e75c83416539bd3b9848956e81ff90fcecfd0ed90c08cfdbaf6f84e55651130f6115b3c070f77c0d46a336ce95e74bd785f8a0cd20b5d6dba6
7
+ data.tar.gz: b0d3559fc5db3431acf8102471db073e17fc428bd8bfc05bbf1b8f472547d4686b9da0eaed4872e9313b0c3d2600ae58f28b933b23732646d3027c1206fd3ac0
data/README.md CHANGED
@@ -1,3 +1,6 @@
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
+
1
4
  # NextPage
2
5
  Basic pagination for Rails controllers.
3
6
 
@@ -43,6 +46,53 @@ resource:
43
46
  @photos = paginate_resource(@photos)
44
47
  ```
45
48
 
49
+ ### Sorting
50
+ 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.
51
+
52
+ /photos?sort=-created_at
53
+ /photos?sort=location,-created_by
54
+ /photos?sort[]=location&photos[]=-created_by
55
+
56
+ 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.
57
+
58
+ ```ruby
59
+ paginate_with default_sort: '-created_at'
60
+ ```
61
+
62
+ #### Nested Sorts
63
+
64
+ Nested attributes and scopes can be indicated by providing the association names separated by periods.
65
+
66
+ /photos?sort=user.name
67
+ /photos?sort=-user.address.state
68
+
69
+ #### Directional Scope Sorts
70
+
71
+ 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.
72
+
73
+ ```ruby
74
+ def self.status(direction)
75
+ order("CASE status WHEN 'new' THEN 1 WHEN 'in progress' THEN 2 ELSE 3 END #{direction}")
76
+ end
77
+ ```
78
+
79
+ #### Scope Prefix / Suffix
80
+
81
+ 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).
82
+
83
+ 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>.
84
+
85
+ For example, if the backend developer prefers <tt>sort_by_status</tt> then the following configuration can be used:
86
+
87
+ ```ruby
88
+ NextPage.configure do |config|
89
+ config.sort_scope_prefix = 'sort_by_'
90
+ end
91
+ ```
92
+ This allows the query parameter to be the following:
93
+
94
+ sort=status
95
+
46
96
 
47
97
  ### Default Limit
48
98
  The default size limit can be overridden with the `paginate_with` method for either type of paginagion. Pass option
@@ -60,12 +110,18 @@ paginate_with default_limit: 12, instance_variable_name: 'data'
60
110
  ```
61
111
 
62
112
  ### 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:
113
+ 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
114
  - current_page
65
115
  - next_page
66
116
  - total_pages
67
117
  - per_page
68
118
 
119
+ #### Count Query
120
+ 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:
121
+ - provide a count_query that can resolve the attributes
122
+ - specify the following attributes manually: current_page, total_count, and per_page
123
+
124
+
69
125
  ## Installation
70
126
  Add this line to your application's Gemfile:
71
127
 
@@ -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,28 +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)
74
+ self.class.next_page_paginator.decorate_meta!(args.first)
75
+ super
76
+ rescue StandardError
77
77
  super
78
78
  end
79
79
  end
@@ -5,9 +5,15 @@ module NextPage
5
5
  #
6
6
  # Module PaginationAttributes adds in methods required for pagination links: current_page, next_page, and total_pages.
7
7
  # 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
+
9
15
  def current_page
10
- @current_page ||= offset_value + 1
16
+ @current_page ||= count_query.offset_value + 1
11
17
  end
12
18
 
13
19
  def next_page
@@ -15,7 +21,7 @@ module NextPage
15
21
  end
16
22
 
17
23
  def total_count
18
- @total_count ||= unscope(:limit).unscope(:offset).count
24
+ @total_count ||= count_query.unscope(:limit).unscope(:offset).count
19
25
  end
20
26
 
21
27
  def total_pages
@@ -23,7 +29,12 @@ module NextPage
23
29
  end
24
30
 
25
31
  def per_page
26
- @per_page ||= limit_value
32
+ @per_page ||= count_query.limit_value
33
+ end
34
+
35
+ # checks first to see if an override query has been provided, then fails back to self
36
+ def count_query
37
+ @count_query || self
27
38
  end
28
39
  end
29
40
  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.2'
2
+ VERSION = '0.1.7'
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.2
4
+ version: 0.1.7
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: 2020-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -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: []