yap 1.4.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90a7fb8f804bd7b631c632deeb7241bfcdac00ee
4
- data.tar.gz: 4d2de49002fcdc9d79dd60786a3c191fac6275cb
3
+ metadata.gz: 04100f8b32c43f3281e414e58d2ac06bc736351f
4
+ data.tar.gz: 8242bbd5e3b635a25feb3d69264a057d491bffda
5
5
  SHA512:
6
- metadata.gz: 1669e8d881f42da049c61c49c1b1c483340ece46642f281831dd104f999198320eca72b82c097d8d1df534830b8c97223a559c080fdae695671c8f9a9a36a89e
7
- data.tar.gz: fac27392172ca4fbbf5fb96332f79bcf157893a87076335e094d50908589b9e85e6f1ee5cbaa55d900538bf688cbc7451751d985ec57af61a7e32af9db2d564e
6
+ metadata.gz: 276f3729be6894e0ce980a4625d3f48f7b4f03730c1a89cc2468441b08c85aba33abb5aa213f2a7e2d92c08a01cb5fb8d6275af2dc65570943e67b806d3d15d8
7
+ data.tar.gz: a3424e43274c8bc5c27c6b3ebe11a5a09576a00b07685fadd6c54b64fa9234f9a0f45a9b51ea03121dbeb5180557b6c76bad8b2c54a0f613cf19ee9056467d46
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/1and1/yap.svg?branch=master)](https://travis-ci.org/1and1/yap) [![Test Coverage](https://codeclimate.com/github/1and1/yap/badges/coverage.svg)](https://codeclimate.com/github/1and1/yap/coverage) [![Code Climate](https://codeclimate.com/github/1and1/yap/badges/gpa.svg)](https://codeclimate.com/github/1and1/yap)
2
+
1
3
  Yet another paginator for Ruby on Rails, which adds a `paginate` scope to your ActiveRecords.
2
4
 
3
5
  ## Setup
@@ -3,8 +3,8 @@ class StringInfinity < String
3
3
  String::INFINITY_NEGATIVE
4
4
  end
5
5
 
6
- def ==(value)
7
- self.class == value.class
6
+ def ==(other)
7
+ self.class == other.class
8
8
  end
9
9
  end
10
10
 
data/lib/yap.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'active_support/concern'
2
2
  require 'yap/active_record/relation'
3
- require 'yap/column_mapper'
3
+ require 'yap/params_extractor'
4
4
  require 'yap/filterable'
5
5
  require 'yap/exceptions'
6
6
  require 'string_infinity'
@@ -28,7 +28,7 @@ module Yap
28
28
  include Filterable
29
29
 
30
30
  DEFAULTS = Struct.new(:page, :per_page, :hard_limit, :sort, :direction, :disable_warnings)
31
- .new(1, 10, nil, :id, :asc, false)
31
+ .new(1, 10, nil, :id, :asc, false)
32
32
 
33
33
  def self.configure
34
34
  raise ArgumentError, 'No block given.' unless block_given?
@@ -40,85 +40,16 @@ module Yap
40
40
  end
41
41
 
42
42
  included do
43
- extend ColumnMapper
43
+ extend ParamsExtractor
44
44
 
45
45
  ##
46
46
  # The paginate scope takes a hash as parameter. All options are optional and can be combined arbitrarily.
47
47
  #
48
- # @param [Hash] params The parameters used for pagination (:page, :per_page, :sort, :direction)
48
+ # @param [Hash] params The parameters used for pagination (:page, :per_page, :sort, :direction, :filter)
49
49
  #
50
- scope :paginate, -> (params = {}) {
50
+ scope :paginate, lambda { |params = {}|
51
51
  page, per_page, order_by = extract_pagination_params(params)
52
- filter(params[:filter]).limit(per_page).offset((page-1)*per_page).order(order_by)
52
+ filter(params[:filter]).limit(per_page).offset((page - 1) * per_page).order(order_by)
53
53
  }
54
-
55
- private
56
-
57
- def self.extract_pagination_params(params)
58
- page = extract_number(params[:page], DEFAULTS.page)
59
- per_page = extract_number(params[:per_page], DEFAULTS.per_page)
60
- if DEFAULTS.hard_limit && per_page > DEFAULTS.hard_limit
61
- raise PaginationError.new("Not more than #{DEFAULTS.hard_limit} items per page accepted.")
62
- end
63
- sort = extract_order(params[:sort], params[:direction])
64
-
65
- return page, per_page, sort
66
- end
67
-
68
- def self.extract_number(number, default)
69
- number ||= default
70
- begin
71
- number = Integer(number)
72
- rescue
73
- raise PaginationError.new("'#{number}' is not a valid number.")
74
- end
75
-
76
- raise PaginationError.new('Only positive numbers are accepted.') unless number > 0
77
- number
78
- end
79
-
80
- def self.extract_order(sort, direction)
81
- sort = sort.split(',') if sort.is_a?(String) && sort =~ /,/
82
- direction = direction.split(',') if direction.is_a?(String) && direction =~ /,/
83
-
84
- case sort
85
- when Array
86
- order = []
87
- direction = Array.wrap direction
88
- sort.each_with_index do |s, i|
89
- order << build_order_by(s, direction[i] || DEFAULTS.direction)
90
- end
91
-
92
- order
93
- when Hash
94
- sort.map do |s, d|
95
- build_order_by(s, d)
96
- end
97
- else
98
- build_order_by sort, direction
99
- end
100
- end
101
-
102
- def self.build_order_by(sort, direction)
103
- sort = extract_column(sort)
104
- direction = extract_direction(direction)
105
-
106
- (sort =~ /\./ ? "#{sort} #{direction}" : { sort => direction })
107
- end
108
- private_class_method :build_order_by
109
-
110
- def self.extract_column(sort)
111
- sort ||= DEFAULTS.sort
112
- column = map_column(sort.to_s.downcase)
113
- raise PaginationError.new("Cannot sort by '#{sort}'.") unless column
114
- column
115
- end
116
-
117
- def self.extract_direction(direction)
118
- direction ||= DEFAULTS.direction
119
- dir = (direction).to_sym.downcase
120
- raise PaginationError.new("'#{direction}' is not a valid direction. Use 'asc' or 'desc'.") unless [:asc, :desc].include? dir
121
- dir
122
- end
123
54
  end
124
55
  end
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  # @return [Integer] Total number of results
21
21
  #
22
22
  def total
23
- @total ||= without_pagination { |rel| rel.count }
23
+ @total ||= without_pagination(&:count)
24
24
  end
25
25
 
26
26
  ##
@@ -42,8 +42,8 @@ module ActiveRecord
42
42
  # @return [Hash] Values defining the range of the current page.
43
43
  #
44
44
  def range(include_total = false)
45
- from = offset_value+1
46
- to = offset_value+limit_value
45
+ from = offset_value + 1
46
+ to = offset_value + limit_value
47
47
  to = total if total < to && include_total
48
48
 
49
49
  range = { from: from, to: to }
@@ -52,4 +52,4 @@ module ActiveRecord
52
52
  range.merge(total: total)
53
53
  end
54
54
  end
55
- end
55
+ end
@@ -1,12 +1,14 @@
1
1
  module ColumnMapper
2
- private
3
-
4
2
  def map_column(name)
5
- column_names.include?(name) ? name : if respond_to? :map_name_to_column
3
+ actual_column = name if column_names.include?(name)
4
+
5
+ column_alias = if respond_to? :map_name_to_column
6
6
  map_name_to_column(name)
7
- else
8
- warn "#{self.name} does not implement map_name_to_column. If you do not need column mapping set disable_warnings=true" unless Yap::DEFAULTS.disable_warnings
9
- nil
7
+ elsif actual_column.nil? && !Yap::DEFAULTS.disable_warnings
8
+ warn "#{self.name} does not implement map_name_to_column. If you do not need column mapping set " \
9
+ 'disable_warnings=true'
10
10
  end
11
+
12
+ column_alias || actual_column
11
13
  end
12
- end
14
+ end
@@ -2,4 +2,4 @@ module Yap
2
2
  class YapError < StandardError; end
3
3
  class PaginationError < YapError; end
4
4
  class FilterError < YapError; end
5
- end
5
+ end
@@ -1,24 +1,23 @@
1
1
  module Yap
2
+ ##
3
+ # Extends Range by StringInfinity.
4
+ #
2
5
  class ExtendedRange < Range
3
6
  def begin
4
- infinity_or_do_not_change super
7
+ handle_infinity super
5
8
  end
6
9
 
7
10
  def end
8
- infinity_or_do_not_change super
11
+ handle_infinity super
9
12
  end
10
13
 
11
- def infinity_or_do_not_change(value)
12
- if value.is_a? StringInfinity
13
- if value == String::INFINITY
14
- Float::INFINITY
15
- elsif value == -String::INFINITY
16
- -Float::INFINITY
17
- else
18
- raise ArgumentError, "Invalid value for StringInfinity."
19
- end
14
+ def handle_infinity(value)
15
+ return value unless value.is_a? StringInfinity
16
+
17
+ if value.is_a?(StringInfinityNegative)
18
+ -Float::INFINITY
20
19
  else
21
- value
20
+ Float::INFINITY
22
21
  end
23
22
  end
24
23
  end
@@ -1,58 +1,52 @@
1
- require 'yap/extended_range'
1
+ require 'yap/filter_column'
2
2
 
3
3
  module Yap
4
- class Filter < Hash
4
+ class Filter
5
5
  def initialize
6
- self[:where] = {}
7
- self[:not] = {}
6
+ @failed = []
7
+ @columns = []
8
8
  end
9
9
 
10
- def parse!(column, values)
11
- values.to_s.split(',').each do |value|
12
- # Perform negative match if value starts with '!'.
13
- if value =~/^!(.+)$/
14
- match = :not
15
- value = $1
16
- else
17
- match = :where
18
- end
10
+ def parse!(model, params)
11
+ params.each do |attribute, values|
12
+ parse_arrtibute(model, attribute, values)
13
+ end
19
14
 
20
- case value
21
- when /([<>]=?)(.+)/
22
- match, value = handle_comparison_operators(match, column, $1.to_sym, $2)
23
- when /(.+)\.{3}(.+)/
24
- value = $1...$2
25
- when /(.+)\.{2}(.+)/
26
- value = $1..$2
27
- else
28
- # Convert null to ruby nil to use 'IS NULL' in SQL.
29
- value = value.downcase == 'null' ? nil : value
30
- end
15
+ raise FilterError, "Cannot filter by: #{@failed.join(', ')}" unless @failed.empty?
16
+ end
31
17
 
32
- # Ensure filter contains an array to append to.
33
- self[match][column] ||= []
18
+ def where
19
+ extract_filters(:where)
20
+ end
34
21
 
35
- self[match][column] << value
36
- end
22
+ def not
23
+ extract_filters(:not)
37
24
  end
38
25
 
39
26
  private
40
27
 
41
- def handle_comparison_operators(match, column, operator, value)
42
- case operator
43
- when :<
44
- handle_comparison_operators(toggle_match(match), column, :>=, value)
45
- when :>
46
- handle_comparison_operators(toggle_match(match), column, :<=, value)
47
- when :<=
48
- return match, ExtendedRange.new(-String::INFINITY, value)
49
- when :>=
50
- return match, ExtendedRange.new(value, String::INFINITY)
28
+ def extract_filters(condition)
29
+ @columns.inject({}) do |filter, column|
30
+ values = column.send(condition)
31
+
32
+ if values.empty?
33
+ filter
34
+ else
35
+ filter.merge(column.name => values.size == 1 ? values.first : values)
36
+ end
51
37
  end
52
38
  end
53
39
 
54
- def toggle_match(match)
55
- match == :where ? :not : :where
40
+ def parse_arrtibute(model, attribute, values)
41
+ column = model.map_column(attribute.to_s.downcase)
42
+ if column.nil?
43
+ @failed << attribute
44
+ return
45
+ end
46
+
47
+ filter_column = FilterColumn.new(column)
48
+ filter_column.parse_values(values)
49
+ @columns << filter_column
56
50
  end
57
51
  end
58
52
  end
@@ -0,0 +1,35 @@
1
+ require 'yap/filter_value'
2
+
3
+ module Yap
4
+ ##
5
+ # Multiple filter for a column can be separated by comma (,).
6
+ #
7
+ class FilterColumn
8
+ attr_reader :name
9
+
10
+ def initialize(name)
11
+ @name = name
12
+ @values = []
13
+ end
14
+
15
+ def parse_values(values)
16
+ values.to_s.split(',').each do |value|
17
+ filter_value = FilterValue.new
18
+ filter_value.parse_value(value)
19
+ @values << filter_value
20
+ end
21
+ end
22
+
23
+ def where
24
+ @values.select do |v|
25
+ v.condition == :where
26
+ end.map(&:value)
27
+ end
28
+
29
+ def not
30
+ @values.select do |v|
31
+ v.condition == :not
32
+ end.map(&:value)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
1
+ require 'yap/extended_range'
2
+
3
+ module Yap
4
+ ##
5
+ # Filter values can have the following formats:
6
+ #
7
+ # * Integer - e.g. 100
8
+ # * String - e.g. text
9
+ # * Comparisons - e.g. >10, <=B
10
+ # * Dates - e.g. 1970-01-01 (depends on the database backend)
11
+ # * Ranges - e.g. 1...2, Jones..Smith
12
+ # * NULL - e.g. NULL, null, Null
13
+ # * Negation - e.g. !value, !1..10
14
+ #
15
+ class FilterValue
16
+ OPERATOR_INVERSION_MAP = { :< => :>=, :> => :<= }.freeze
17
+
18
+ attr_reader :condition, :value
19
+
20
+ def parse_value(value)
21
+ value = handle_negation(value)
22
+
23
+ @value = case value
24
+ when /([<>]=?)(.+)/
25
+ handle_comparison_operators($1.to_sym, $2)
26
+ when /(.+)(\.{2,3})(.+)/
27
+ Range.new $1, $3, $2 == '...'
28
+ else
29
+ handle_null(value)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def handle_negation(value)
36
+ if value =~ /^!(.+)$/
37
+ @condition = :not
38
+ value = $1
39
+ else
40
+ @condition = :where
41
+ end
42
+
43
+ value
44
+ end
45
+
46
+ def handle_comparison_operators(operator, value)
47
+ case operator
48
+ when :<, :> then invert_comparison_operator(operator, value)
49
+ when :<= then ExtendedRange.new(-String::INFINITY, value)
50
+ when :>= then ExtendedRange.new(value, String::INFINITY)
51
+ end
52
+ end
53
+
54
+ def invert_comparison_operator(operator, value)
55
+ toggle_condition!
56
+ handle_comparison_operators(OPERATOR_INVERSION_MAP[operator], value)
57
+ end
58
+
59
+ def toggle_condition!
60
+ @condition = @condition == :where ? :not : :where
61
+ end
62
+
63
+ def handle_null(value)
64
+ value.casecmp('null').zero? ? nil : value
65
+ end
66
+ end
67
+ end
@@ -32,31 +32,18 @@ module Yap
32
32
  #
33
33
  # @param [Hash] Attribute/value pairs to filter ( { 'gender' => 'f' } )
34
34
  #
35
- scope :filter, -> (params = nil) {
35
+ scope :filter, lambda { |params = nil|
36
36
  if params.blank?
37
37
  all
38
38
  else
39
39
  filter = extract_filter_params(params)
40
- where(filter[:where]).where.not(filter[:not])
40
+ where(filter.where).where.not(filter.not)
41
41
  end
42
42
  }
43
43
 
44
- private
45
-
46
44
  def self.extract_filter_params(params)
47
45
  filter = Filter.new
48
-
49
- failed = []
50
- params.each do |key, values|
51
- column = map_column(key.to_s.downcase)
52
- if column
53
- filter.parse!(column, values)
54
- else
55
- failed << key
56
- end
57
- end
58
-
59
- raise FilterError.new('Cannot filter by: ' + failed.join(', ')) unless failed.empty?
46
+ filter.parse!(self, params)
60
47
 
61
48
  filter
62
49
  end
@@ -0,0 +1,102 @@
1
+ require 'yap/column_mapper'
2
+
3
+ module Yap
4
+ ##
5
+ # Methods for extracting valid pagination parameters from rails params.
6
+ #
7
+ module ParamsExtractor
8
+ def extract_pagination_params(params)
9
+ page = extract_page(params)
10
+ per_page = extract_per_page(params)
11
+ sort = extract_order(params)
12
+
13
+ [page, per_page, sort]
14
+ end
15
+
16
+ def extract_page(params)
17
+ extract_number(params[:page], DEFAULTS.page)
18
+ end
19
+
20
+ def extract_per_page(params)
21
+ per_page = extract_number(params[:per_page], DEFAULTS.per_page)
22
+
23
+ if DEFAULTS.hard_limit && per_page > DEFAULTS.hard_limit
24
+ raise PaginationError, "Not more than #{DEFAULTS.hard_limit} items per page accepted."
25
+ end
26
+
27
+ per_page
28
+ end
29
+
30
+ def extract_number(number, default)
31
+ number ||= default
32
+ begin
33
+ number = Integer(number)
34
+ rescue ArgumentError
35
+ raise PaginationError, "'#{number}' is not a valid number."
36
+ end
37
+
38
+ raise PaginationError, 'Only positive numbers are accepted.' unless number > 0
39
+ number
40
+ end
41
+
42
+ def extract_order(params)
43
+ sort, direction = params.values_at(:sort, :direction)
44
+
45
+ case sort
46
+ when Array, String
47
+ build_order_from_array(sort, direction)
48
+ when Hash
49
+ build_order_from_hash(sort)
50
+ else # nil or symbol
51
+ build_order sort, direction
52
+ end
53
+ end
54
+
55
+ def build_order_from_array(sort, direction = [])
56
+ sort = convert_to_array(sort)
57
+ direction = convert_to_array(direction)
58
+ order = []
59
+ sort.each_with_index do |s, i|
60
+ order << build_order(s, direction[i] || DEFAULTS.direction)
61
+ end
62
+
63
+ order
64
+ end
65
+
66
+ def convert_to_array(object)
67
+ object = object.split(',') if object.is_a?(String)
68
+
69
+ Array.wrap(object)
70
+ end
71
+
72
+ def build_order_from_hash(sort)
73
+ sort.map do |s, d|
74
+ build_order(s, d)
75
+ end
76
+ end
77
+
78
+ def build_order(sort, direction)
79
+ sort = extract_column(sort || DEFAULTS.sort)
80
+ direction = extract_direction(direction)
81
+
82
+ (sort =~ /\./ ? "#{sort} #{direction}" : { sort => direction })
83
+ end
84
+
85
+ def extract_column(sort)
86
+ column = map_column(sort.to_s.downcase)
87
+ raise PaginationError, "Cannot sort by '#{sort}'." unless column
88
+
89
+ column
90
+ end
91
+
92
+ def extract_direction(direction)
93
+ direction ||= DEFAULTS.direction
94
+ dir = direction.to_sym.downcase
95
+ unless [:asc, :desc].include? dir
96
+ raise PaginationError, "'#{direction}' is not a valid direction. Use 'asc' or 'desc'."
97
+ end
98
+
99
+ dir
100
+ end
101
+ end
102
+ end
@@ -1,3 +1,3 @@
1
1
  module Yap
2
- VERSION = '1.4.0'
2
+ VERSION = '1.4.2'.freeze
3
3
  end
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Finn Glöe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-25 00:00:00.000000000 Z
11
+ date: 2017-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: '4.1'
22
+ version: '6'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.2'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: '4.1'
32
+ version: '6'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: sqlite3
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +44,21 @@ dependencies:
38
44
  - - "~>"
39
45
  - !ruby/object:Gem::Version
40
46
  version: '1.3'
41
- description: Yet another paginator for Ruby on Rails adding a pagination and filtering
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '11.1'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '11.1'
61
+ description: Yet another paginator for Ruby on Rails adding pagination and filtering
42
62
  interface to ActiveRecords.
43
63
  email: finn.gloee@1und1.de
44
64
  executables: []
@@ -54,7 +74,10 @@ files:
54
74
  - lib/yap/exceptions.rb
55
75
  - lib/yap/extended_range.rb
56
76
  - lib/yap/filter.rb
77
+ - lib/yap/filter_column.rb
78
+ - lib/yap/filter_value.rb
57
79
  - lib/yap/filterable.rb
80
+ - lib/yap/params_extractor.rb
58
81
  - lib/yap/version.rb
59
82
  homepage: https://github.com/1and1/yap
60
83
  licenses:
@@ -66,9 +89,9 @@ require_paths:
66
89
  - lib
67
90
  required_ruby_version: !ruby/object:Gem::Requirement
68
91
  requirements:
69
- - - "~>"
92
+ - - ">="
70
93
  - !ruby/object:Gem::Version
71
- version: '2.0'
94
+ version: 1.9.3
72
95
  required_rubygems_version: !ruby/object:Gem::Requirement
73
96
  requirements:
74
97
  - - ">="
@@ -76,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
99
  version: '0'
77
100
  requirements: []
78
101
  rubyforge_project:
79
- rubygems_version: 2.5.1
102
+ rubygems_version: 2.6.10
80
103
  signing_key:
81
104
  specification_version: 4
82
105
  summary: Yet another paginator for Ruby on Rails