From 85063b279d28279b595e184908f5481c56110c1f Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 17 Jun 2014 22:23:22 +0200 Subject: [PATCH 01/69] Add paginator helper --- lib/pliny/helpers/paginator.rb | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 lib/pliny/helpers/paginator.rb diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb new file mode 100644 index 00000000..281e8730 --- /dev/null +++ b/lib/pliny/helpers/paginator.rb @@ -0,0 +1,34 @@ +module Pliny::Helpers + module Paginator + def paginator(count, options = {}) + options = + { + accepted_ranges: :id, + order: :id, + start: 0, + max: 200 + }.merge(options) + + options[:end] = options[:start] + options[:max] - 1 + + halt(400) unless options[:accepted_ranges].include?(options[:order].to_sym) + + headers 'Accept-Ranges' => options[:accepted_ranges].join(',') + + if count > options[:max] + status 206 + headers \ + 'Content-Range' => "#{options[:order]} #{options[:start]}..#{options[:end]}/#{count}; max=#{options[:max]}", + 'Next-Range' => "#{options[:order]} #{options[:end] + 1}..#{options[:end] + options[:max]}; max=#{options[:max]}" + else + status 200 + end + + { + order: options[:order], + start: options[:start], + limit: options[:max] + } + end + end +end From 4fb3df56ae9f18cdb1f54656f614a3ae711e3be8 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 17 Jun 2014 22:32:17 +0200 Subject: [PATCH 02/69] Set upper boundary to count in Next-Range --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 281e8730..f37898de 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -19,7 +19,7 @@ def paginator(count, options = {}) status 206 headers \ 'Content-Range' => "#{options[:order]} #{options[:start]}..#{options[:end]}/#{count}; max=#{options[:max]}", - 'Next-Range' => "#{options[:order]} #{options[:end] + 1}..#{options[:end] + options[:max]}; max=#{options[:max]}" + 'Next-Range' => "#{options[:order]} #{options[:end] + 1}..#{[options[:end] + options[:max], count].min}; max=#{options[:max]}" else status 200 end From 919e898a51fd9bdb0dbcef00500dd5edd1a7eab0 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 17 Jun 2014 22:33:40 +0200 Subject: [PATCH 03/69] accepted_ranges must be an array --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index f37898de..455bea9c 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -3,7 +3,7 @@ module Paginator def paginator(count, options = {}) options = { - accepted_ranges: :id, + accepted_ranges: [:id], order: :id, start: 0, max: 200 From 3818243a71406b3d61acdca8cde0d0396468b215 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Sun, 29 Jun 2014 22:53:24 +0200 Subject: [PATCH 04/69] Add Paginator::Paginator class --- lib/pliny/helpers/paginator.rb | 115 +++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 21 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 455bea9c..e12bfdeb 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -1,34 +1,107 @@ module Pliny::Helpers module Paginator def paginator(count, options = {}) - options = + Paginator.run(self, count, options) + end + + class Paginator + RANGE = /\A(?\S*)\s+(?\d+)(\.{2}(?\d+))?(;\s*(?.*))?;\z/ + + attr_reader :sinatra, :count, :options + attr_accessor :res + + class << self + def run(*args) + new(*args).run + end + end + + def initialize(sinatra, count, options = {}) + @sinatra = sinatra + @count = count + @options = options + end + + def run + validate_options + set_headers + { - accepted_ranges: [:id], - order: :id, - start: 0, - max: 200 - }.merge(options) + sort_by: res[:sort_by], + start: res[:start], + limit: res[:max] + } + end + + def res + return @res if @res - options[:end] = options[:start] + options[:max] - 1 + @res = + { + accepted_ranges: [:id], + sort_by: :id, + start: 0, + args: { max: 200 } + } + .merge(options) + .merge(request_options) + end - halt(400) unless options[:accepted_ranges].include?(options[:order].to_sym) + def request_options + return @request_options if @request_options - headers 'Accept-Ranges' => options[:accepted_ranges].join(',') + match = + RANGE.match(sinatra.request.env['Range']) - if count > options[:max] - status 206 - headers \ - 'Content-Range' => "#{options[:order]} #{options[:start]}..#{options[:end]}/#{count}; max=#{options[:max]}", - 'Next-Range' => "#{options[:order]} #{options[:end] + 1}..#{[options[:end] + options[:max], count].min}; max=#{options[:max]}" - else - status 200 + @request_options = {} + @request_options[:sort_by] = match[:sort_by] if match[:sort_by] + @request_options[:start] = match[:start].to_i if match[:start] + @request_options[:end] = match[:end].to_i if match[:end] + if match[:args] + args = + match[:args] + .split(/\s*,\s*/) + .map do |value| + k, v = value.split('=', 2) + [k.to_sym, v] + end + + @request_options[:args] = Hash[args] + end + + @request_options + end + + def validate_options + return if res[:accepted_ranges].include?(res[:sort_by].to_sym) + + sinatra.halt(400) end - { - order: options[:order], - start: options[:start], - limit: options[:max] - } + def set_headers + sinatra.header 'Accept-Ranges', res[:accepted_ranges].join(',') + + if count > res[:args][:max] + sinatra.status 206 + sinatra.headers \ + 'Content-Range' => "#{res[:sort_by]} #{res[:start]}..#{res[:end]}/#{count}; #{args_encoded}", + 'Next-Range' => "#{res[:sort_by]} #{res[:end] + 1}..#{limit}; #{args_encoded}" + else + sinatra.status 200 + end + end + + def args_encoded + @args_encoded ||= res[:args].map { |key, value| "#{key}=#{value}" }.join(',') + end + + def limit + [ + res[:end] + res[:args][:max], + count + ] + .min + end end end end From 04eb327c2446cfc770c1d5de9d3c4532f530cda9 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Sun, 29 Jun 2014 23:07:24 +0200 Subject: [PATCH 05/69] Remove semicolon --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index e12bfdeb..e42762d1 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -5,7 +5,7 @@ def paginator(count, options = {}) end class Paginator - RANGE = /\A(?\S*)\s+(?\d+)(\.{2}(?\d+))?(;\s*(?.*))?;\z/ + RANGE = /\A(?\S*)\s+(?\d+)(\.{2}(?\d+))?(;\s*(?.*))?\z/ attr_reader :sinatra, :count, :options attr_accessor :res From 345d2cd90d81ab9290e6f96e25d172bc4ff8de32 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Sun, 29 Jun 2014 23:08:01 +0200 Subject: [PATCH 06/69] Don't convert start and end to integer Could be an UUID --- lib/pliny/helpers/paginator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index e42762d1..6f65ed69 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -54,9 +54,9 @@ def request_options RANGE.match(sinatra.request.env['Range']) @request_options = {} - @request_options[:sort_by] = match[:sort_by] if match[:sort_by] - @request_options[:start] = match[:start].to_i if match[:start] - @request_options[:end] = match[:end].to_i if match[:end] + [:sort_by, :start, :end].each do |key| + @request_options[key] = match[key] if match[key] + end if match[:args] args = match[:args] From 68cd04e4ba9519543d50d727510f3aa0ce52ff46 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Sun, 29 Jun 2014 23:09:03 +0200 Subject: [PATCH 07/69] Halt with HTTP status code 416 Requested range not satisfiable --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 6f65ed69..ce83f59c 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -75,7 +75,7 @@ def request_options def validate_options return if res[:accepted_ranges].include?(res[:sort_by].to_sym) - sinatra.halt(400) + sinatra.halt(416) end def set_headers From 95218ba7ade79328312b34c5739df23ad433e4b4 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 00:01:16 +0200 Subject: [PATCH 08/69] Add specs --- lib/pliny/helpers/paginator.rb | 49 ++++++++------ test/helpers/paginator_test.rb | 118 +++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 20 deletions(-) create mode 100644 test/helpers/paginator_test.rb diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index ce83f59c..7d239051 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -5,7 +5,7 @@ def paginator(count, options = {}) end class Paginator - RANGE = /\A(?\S*)\s+(?\d+)(\.{2}(?\d+))?(;\s*(?.*))?\z/ + RANGE = /\A(?\S*)\s+(?[0-9a-f-]+)(\.{2}(?[0-9a-f-]+))?(;\s*(?.*))?\z/ attr_reader :sinatra, :count, :options attr_accessor :res @@ -48,33 +48,42 @@ def res end def request_options - return @request_options if @request_options + range = sinatra.request.env['Range'] + return {} if range.nil? || range.empty? match = - RANGE.match(sinatra.request.env['Range']) + RANGE.match(range) - @request_options = {} - [:sort_by, :start, :end].each do |key| - @request_options[key] = match[key] if match[key] - end - if match[:args] - args = - match[:args] - .split(/\s*,\s*/) - .map do |value| - k, v = value.split('=', 2) - [k.to_sym, v] - end - - @request_options[:args] = Hash[args] - end + if match + request_options = {} + + [:sort_by, :start, :end].each do |key| + request_options[key] = match[key] if match[key] + end + + if match[:args] + args = + match[:args] + .split(/\s*,\s*/) + .map do |value| + k, v = value.split('=', 2) + [k.to_sym, v] + end - @request_options + request_options[:args] = Hash[args] + end + + request_options + else + halt + end end def validate_options - return if res[:accepted_ranges].include?(res[:sort_by].to_sym) + halt unless res[:accepted_ranges].include?(res[:sort_by].to_sym) + end + def halt sinatra.halt(416) end diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb new file mode 100644 index 00000000..75a59c6f --- /dev/null +++ b/test/helpers/paginator_test.rb @@ -0,0 +1,118 @@ +require 'pliny/helpers/paginator' +require 'test_helper' + +describe Pliny::Helpers::Paginator do + let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } + let(:sinatra) { dummy_class.new } + + it 'calls Pagiantor.run' do + mock(Pliny::Helpers::Paginator::Paginator).run(sinatra, 12, sort_by: :foo) + sinatra.paginator(12, sort_by: :foo) + end +end + +describe Pliny::Helpers::Paginator::Paginator do + subject { Pliny::Helpers::Paginator::Paginator.new(sinatra, count, opts) } + let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } + let(:sinatra) { dummy_class.new } + let(:count) { 4 } + let(:opts) { {} } + + describe '#run' do + it 'returns Hash' do + mock(subject).validate_options + mock(subject).set_headers + stub(subject).res { {} } + + assert_kind_of Hash, subject.run + end + end + + describe '#request_options' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=200' } } + end + assert_kind_of Hash, subject.request_options + end + + describe 'when Range is nil' do + it 'returns empty Hash' do + stub(sinatra).request do |klass| + stub(klass).env { {} } + end + assert_kind_of Hash, subject.request_options + assert_empty subject.request_options + end + end + + describe 'when Range is an empty string' do + it 'returns empty Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => '' } } + end + assert_kind_of Hash, subject.request_options + assert_empty subject.request_options + end + end + + describe 'when Range is an invalid string' do + it 'halts' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'foo' } } + end + mock(subject).halt + subject.request_options + end + end + + describe 'when Range is valid' do + describe 'without args' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef' } } + end + result = + { + sort_by: 'id', + start: '01234567-89ab-cdef-0123-456789abcdef', + end: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal subject.request_options, result + end + end + + describe 'with one arg' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=200' } } + end + result = + { + sort_by: 'id', + start: '01234567-89ab-cdef-0123-456789abcdef', + end: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '200' } + } + assert_equal subject.request_options, result + end + end + + describe 'with more args' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=200,order=desc' } } + end + result = + { + sort_by: 'id', + start: '01234567-89ab-cdef-0123-456789abcdef', + end: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '200', order: 'desc' } + } + assert_equal subject.request_options, result + end + end + end + end +end From d116a2c9a64aabcc8f24b605d9e63a65839de65a Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 07:27:41 +0200 Subject: [PATCH 09/69] Run accepts block --- lib/pliny/helpers/paginator.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 7d239051..0273bcf9 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -11,8 +11,8 @@ class Paginator attr_accessor :res class << self - def run(*args) - new(*args).run + def run(*args, &block) + new(*args).run(&block) end end @@ -23,6 +23,8 @@ def initialize(sinatra, count, options = {}) end def run + yield(self) if block_given? + validate_options set_headers From e8973dfe936537468b2a8042f078f0b9979ceed0 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 07:28:08 +0200 Subject: [PATCH 10/69] Fix limit in #run hash --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 0273bcf9..22a71ddd 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -31,7 +31,7 @@ def run { sort_by: res[:sort_by], start: res[:start], - limit: res[:max] + limit: res[:args][:max] } end From 6453cd697edd148fab1724198640731a81d764cd Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 07:28:45 +0200 Subject: [PATCH 11/69] Add #will_paginate? --- lib/pliny/helpers/paginator.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 22a71ddd..7528ebd9 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -92,7 +92,7 @@ def halt def set_headers sinatra.header 'Accept-Ranges', res[:accepted_ranges].join(',') - if count > res[:args][:max] + if will_paginate? sinatra.status 206 sinatra.headers \ 'Content-Range' => "#{res[:sort_by]} #{res[:start]}..#{res[:end]}/#{count}; #{args_encoded}", @@ -112,6 +112,9 @@ def limit count ] .min + def will_paginate? + count > res[:args][:max] + end end end end From f0953388a725362bb4f986329d36fd754c6a0e07 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 07:29:33 +0200 Subject: [PATCH 12/69] Rename options --- lib/pliny/helpers/paginator.rb | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 7528ebd9..4df3faf8 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -30,7 +30,7 @@ def run { sort_by: res[:sort_by], - start: res[:start], + first: res[:first], limit: res[:args][:max] } end @@ -42,11 +42,23 @@ def res { accepted_ranges: [:id], sort_by: :id, - start: 0, + first: 0, args: { max: 200 } } .merge(options) .merge(request_options) + unless @res[:first].kind_of?(String) + @res[:last] ||= @res[:first] + @res[:args][:max] + @res[:next_first] ||= @res[:last] + 1 + @res[:next_last] ||= + [ + @res[:next_first] + @res[:args][:max], + count - 1 + ] + .min + end + + @res end def request_options @@ -59,7 +71,7 @@ def request_options if match request_options = {} - [:sort_by, :start, :end].each do |key| + [:sort_by, :first, :last].each do |key| request_options[key] = match[key] if match[key] end @@ -95,8 +107,8 @@ def set_headers if will_paginate? sinatra.status 206 sinatra.headers \ - 'Content-Range' => "#{res[:sort_by]} #{res[:start]}..#{res[:end]}/#{count}; #{args_encoded}", - 'Next-Range' => "#{res[:sort_by]} #{res[:end] + 1}..#{limit}; #{args_encoded}" + 'Content-Range' => "#{res[:sort_by]} #{res[:first]}..#{res[:last]}/#{count}; #{args_encoded}", + 'Next-Range' => "#{res[:sort_by]} #{res[:next_first]}..#{res[:next_last]}; #{args_encoded}" else sinatra.status 200 end @@ -106,12 +118,6 @@ def args_encoded @args_encoded ||= res[:args].map { |key, value| "#{key}=#{value}" }.join(',') end - def limit - [ - res[:end] + res[:args][:max], - count - ] - .min def will_paginate? count > res[:args][:max] end From 10d8756757a150efb0d127dfea01fd3170bf39ad Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 07:32:24 +0200 Subject: [PATCH 13/69] Fix regex --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 4df3faf8..e7129182 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -5,7 +5,7 @@ def paginator(count, options = {}) end class Paginator - RANGE = /\A(?\S*)\s+(?[0-9a-f-]+)(\.{2}(?[0-9a-f-]+))?(;\s*(?.*))?\z/ + RANGE = /\A(?\S*)\s+(?[0-9a-f-]+)(\.{2}(?[0-9a-f-]+))?(;\s*(?.*))?\z/ attr_reader :sinatra, :count, :options attr_accessor :res From 8488d9722f60b13982fdfd7a7703d4c9e213b29b Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 07:40:41 +0200 Subject: [PATCH 14/69] Fix specs --- test/helpers/paginator_test.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 75a59c6f..f6f95218 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -22,7 +22,7 @@ it 'returns Hash' do mock(subject).validate_options mock(subject).set_headers - stub(subject).res { {} } + stub(subject).res { { args: {} } } assert_kind_of Hash, subject.run end @@ -75,8 +75,8 @@ result = { sort_by: 'id', - start: '01234567-89ab-cdef-0123-456789abcdef', - end: '01234567-89ab-cdef-0123-456789abcdef' + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef' } assert_equal subject.request_options, result end @@ -90,8 +90,8 @@ result = { sort_by: 'id', - start: '01234567-89ab-cdef-0123-456789abcdef', - end: '01234567-89ab-cdef-0123-456789abcdef', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', args: { max: '200' } } assert_equal subject.request_options, result @@ -106,8 +106,8 @@ result = { sort_by: 'id', - start: '01234567-89ab-cdef-0123-456789abcdef', - end: '01234567-89ab-cdef-0123-456789abcdef', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', args: { max: '200', order: 'desc' } } assert_equal subject.request_options, result From ff8a729eeda023395b6bd5e7f29b9d212227b847 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 07:50:10 +0200 Subject: [PATCH 15/69] Add #[] and #[]= --- lib/pliny/helpers/paginator.rb | 7 +++++++ test/helpers/paginator_test.rb | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index e7129182..40580db9 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -121,6 +121,13 @@ def args_encoded def will_paginate? count > res[:args][:max] end + + def [](key) + res[key.to_sym] + end + + def []=(key, value) + res[key.to_sym] = value end end end diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index f6f95218..0fec62a3 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -115,4 +115,40 @@ end end end + + describe '#[]' do + describe 'allows to read #res with a convinience method' do + before :each do + mock(subject).res { { first: 1 } } + end + + it 'with symbol key' do + assert_equal subject[:first], 1 + end + + it 'with string key' do + assert_equal subject['first'], 1 + end + end + end + + describe '#[]=' do + describe 'allows to read #res with a convinience method' do + before :each do + subject.instance_variable_set(:@res, {}) + end + + it 'with symbol key' do + assert_equal nil, subject.res[:first] + subject[:first] = 1 + assert_equal 1, subject.res[:first] + end + + it 'with string key' do + assert_equal nil, subject.res[:first] + subject['first'] = 1 + assert_equal 1, subject.res[:first] + end + end + end end From feaff924fb5f84675b5d7b3984a7caf7a1ffb395 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 08:13:04 +0200 Subject: [PATCH 16/69] Support for count in regex Improve readability of regex --- lib/pliny/helpers/paginator.rb | 8 ++++- test/helpers/paginator_test.rb | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 40580db9..6c56864c 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -5,7 +5,13 @@ def paginator(count, options = {}) end class Paginator - RANGE = /\A(?\S*)\s+(?[0-9a-f-]+)(\.{2}(?[0-9a-f-]+))?(;\s*(?.*))?\z/ + SORT_BY = /(?\S*)/ + UUID = /[0-9a-f-]+/i + FIRST = /(?#{UUID})/ + LAST = /(?#{UUID})/ + COUNT = /(?:\/\d+)/ + ARGS = /(?.*)/ + RANGE = /\A#{SORT_BY}\s+#{FIRST}(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ attr_reader :sinatra, :count, :options attr_accessor :res diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 0fec62a3..4852a44b 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -67,6 +67,20 @@ end describe 'when Range is valid' do + describe 'with first only' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef' } } + end + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal subject.request_options, result + end + end + describe 'without args' do it 'returns Hash' do stub(sinatra).request do |klass| @@ -80,6 +94,21 @@ } assert_equal subject.request_options, result end + + describe 'with count' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400' } } + end + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal subject.request_options, result + end + end end describe 'with one arg' do @@ -96,6 +125,22 @@ } assert_equal subject.request_options, result end + + describe 'with count' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=200' } } + end + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '200' } + } + assert_equal subject.request_options, result + end + end end describe 'with more args' do @@ -112,6 +157,22 @@ } assert_equal subject.request_options, result end + + describe 'with count' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=200,order=desc' } } + end + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '200', order: 'desc' } + } + assert_equal subject.request_options, result + end + end end end end From 303f600979aa60d65140d0b874999a6c8525a10b Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 08:18:11 +0200 Subject: [PATCH 17/69] Add test for #run block --- test/helpers/paginator_test.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 4852a44b..15355b04 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -26,6 +26,19 @@ assert_kind_of Hash, subject.run end + + it 'evaluates block' do + mock(subject).validate_options + mock(subject).set_headers + subject.instance_variable_set(:@res, { args: { max: 200 } }) + + result = + subject.run do |paginator| + paginator[:first] = 42 + end + + assert_equal 42, result[:first] + end end describe '#request_options' do From 40c1930004c508c4fe6c62df04f43be674d1dbff Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 08:29:34 +0200 Subject: [PATCH 18/69] Improve test * max should be different to default * Fix order of assert_equal --- test/helpers/paginator_test.rb | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 15355b04..72e0b0b3 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -44,7 +44,7 @@ describe '#request_options' do it 'returns Hash' do stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=200' } } + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } } end assert_kind_of Hash, subject.request_options end @@ -90,7 +90,7 @@ sort_by: 'id', first: '01234567-89ab-cdef-0123-456789abcdef' } - assert_equal subject.request_options, result + assert_equal result, subject.request_options end end @@ -105,7 +105,7 @@ first: '01234567-89ab-cdef-0123-456789abcdef', last: '01234567-89ab-cdef-0123-456789abcdef' } - assert_equal subject.request_options, result + assert_equal result, subject.request_options end describe 'with count' do @@ -119,7 +119,7 @@ first: '01234567-89ab-cdef-0123-456789abcdef', last: '01234567-89ab-cdef-0123-456789abcdef' } - assert_equal subject.request_options, result + assert_equal result, subject.request_options end end end @@ -127,31 +127,31 @@ describe 'with one arg' do it 'returns Hash' do stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=200' } } + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } } end result = { sort_by: 'id', first: '01234567-89ab-cdef-0123-456789abcdef', last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '200' } + args: { max: '1000' } } - assert_equal subject.request_options, result + assert_equal result, subject.request_options end describe 'with count' do it 'returns Hash' do stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=200' } } + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000' } } end result = { sort_by: 'id', first: '01234567-89ab-cdef-0123-456789abcdef', last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '200' } + args: { max: '1000' } } - assert_equal subject.request_options, result + assert_equal result, subject.request_options end end end @@ -159,31 +159,31 @@ describe 'with more args' do it 'returns Hash' do stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=200,order=desc' } } + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000,order=desc' } } end result = { sort_by: 'id', first: '01234567-89ab-cdef-0123-456789abcdef', last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '200', order: 'desc' } + args: { max: '1000', order: 'desc' } } - assert_equal subject.request_options, result + assert_equal result, subject.request_options end describe 'with count' do it 'returns Hash' do stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=200,order=desc' } } + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } } end result = { sort_by: 'id', first: '01234567-89ab-cdef-0123-456789abcdef', last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '200', order: 'desc' } + args: { max: '1000', order: 'desc' } } - assert_equal subject.request_options, result + assert_equal result, subject.request_options end end end From 1796fd84e215a97dec094d997b4b244cd3654cdf Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 08:35:08 +0200 Subject: [PATCH 19/69] Convert max to integer in #will_paginate? --- lib/pliny/helpers/paginator.rb | 2 +- test/helpers/paginator_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 6c56864c..6853f767 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -125,7 +125,7 @@ def args_encoded end def will_paginate? - count > res[:args][:max] + count > res[:args][:max].to_i end def [](key) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 72e0b0b3..15974077 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -190,6 +190,14 @@ end end + describe '#will_paginate?' do + it 'converts max to integer' do + subject.instance_variable_set(:@res, { args: { max: '1000' } }) + stub(subject).count { 2000 } + assert_equal true, subject.will_paginate? + end + end + describe '#[]' do describe 'allows to read #res with a convinience method' do before :each do From 7a0cbd07b59e0cba5df5d05899587d216a454899 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 30 Jun 2014 08:37:03 +0200 Subject: [PATCH 20/69] Use assert --- test/helpers/paginator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 15974077..ff0aa2df 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -194,7 +194,7 @@ it 'converts max to integer' do subject.instance_variable_set(:@res, { args: { max: '1000' } }) stub(subject).count { 2000 } - assert_equal true, subject.will_paginate? + assert subject.will_paginate? end end From e6fec8c44a52e33049b86779b6e94d015dc705e5 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 07:59:42 +0200 Subject: [PATCH 21/69] Fix spelling error --- test/helpers/paginator_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index ff0aa2df..b8eec9f1 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -199,7 +199,7 @@ end describe '#[]' do - describe 'allows to read #res with a convinience method' do + describe 'allows to read #res with a convenience method' do before :each do mock(subject).res { { first: 1 } } end @@ -215,7 +215,7 @@ end describe '#[]=' do - describe 'allows to read #res with a convinience method' do + describe 'allows to read #res with a convenience method' do before :each do subject.instance_variable_set(:@res, {}) end From 218b812b8b2708c8eb4105e6e2cc4345e88cb32c Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 08:09:04 +0200 Subject: [PATCH 22/69] Fix code style --- lib/pliny/helpers/paginator.rb | 65 ++++++++++++++++++---------------- test/helpers/paginator_test.rb | 4 +-- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 6853f767..86c6caa4 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -30,7 +30,7 @@ def initialize(sinatra, count, options = {}) def run yield(self) if block_given? - + validate_options set_headers @@ -53,20 +53,22 @@ def res } .merge(options) .merge(request_options) - unless @res[:first].kind_of?(String) - @res[:last] ||= @res[:first] + @res[:args][:max] - @res[:next_first] ||= @res[:last] + 1 - @res[:next_last] ||= - [ - @res[:next_first] + @res[:args][:max], - count - 1 - ] - .min - end + calculate_pages unless @res[:first].is_a?(String) @res end + def calculate_pages + @res[:last] ||= @res[:first] + @res[:args][:max] + @res[:next_first] ||= @res[:last] + 1 + @res[:next_last] ||= + [ + @res[:next_first] + @res[:args][:max], + count - 1 + ] + .min + end + def request_options range = sinatra.request.env['Range'] return {} if range.nil? || range.empty? @@ -74,29 +76,29 @@ def request_options match = RANGE.match(range) - if match - request_options = {} + match ? parse_request_options(match) : halt + end - [:sort_by, :first, :last].each do |key| - request_options[key] = match[key] if match[key] - end + def parse_request_options(match) + request_options = {} - if match[:args] - args = - match[:args] - .split(/\s*,\s*/) - .map do |value| - k, v = value.split('=', 2) - [k.to_sym, v] - end + [:sort_by, :first, :last].each do |key| + request_options[key] = match[key] if match[key] + end - request_options[:args] = Hash[args] - end + if match[:args] + args = + match[:args] + .split(/\s*,\s*/) + .map do |value| + k, v = value.split('=', 2) + [k.to_sym, v] + end - request_options - else - halt + request_options[:args] = Hash[args] end + + request_options end def validate_options @@ -121,7 +123,10 @@ def set_headers end def args_encoded - @args_encoded ||= res[:args].map { |key, value| "#{key}=#{value}" }.join(',') + @args_encoded ||= + res[:args] + .map { |key, value| "#{key}=#{value}" } + .join(',') end def will_paginate? diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index b8eec9f1..2a0147c0 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -30,7 +30,7 @@ it 'evaluates block' do mock(subject).validate_options mock(subject).set_headers - subject.instance_variable_set(:@res, { args: { max: 200 } }) + subject.instance_variable_set(:@res, args: { max: 200 }) result = subject.run do |paginator| @@ -192,7 +192,7 @@ describe '#will_paginate?' do it 'converts max to integer' do - subject.instance_variable_set(:@res, { args: { max: '1000' } }) + subject.instance_variable_set(:@res, args: { max: '1000' }) stub(subject).count { 2000 } assert subject.will_paginate? end From 1874a7bec4a9d9f23ca7c9d6d4b281390c745f63 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 08:27:55 +0200 Subject: [PATCH 23/69] Add #build_range --- lib/pliny/helpers/paginator.rb | 21 ++++++++++----- test/helpers/paginator_test.rb | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 86c6caa4..7a35bba4 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -115,18 +115,25 @@ def set_headers if will_paginate? sinatra.status 206 sinatra.headers \ - 'Content-Range' => "#{res[:sort_by]} #{res[:first]}..#{res[:last]}/#{count}; #{args_encoded}", - 'Next-Range' => "#{res[:sort_by]} #{res[:next_first]}..#{res[:next_last]}; #{args_encoded}" + 'Content-Range' => build_range(res[:sort_by], res[:first], res[:last], res[:args], count), + 'Next-Range' => build_range(res[:sort_by], res[:next_first], res[:next_last], res[:args], nil) else sinatra.status 200 end end - def args_encoded - @args_encoded ||= - res[:args] - .map { |key, value| "#{key}=#{value}" } - .join(',') + def build_range(sort_by, first, last, args, count = nil) + range = sort_by.to_s + range << " #{[first, last].compact.join('..')}" if first + range << "/#{count}" if count + range << "; #{encode_args(args)}" if args + range + end + + def encode_args(args) + args + .map { |key, value| "#{key}=#{value}" } + .join(',') end def will_paginate? diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 2a0147c0..898849c4 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -190,6 +190,53 @@ end end + describe '#build_range' do + it 'only sort_by' do + assert_equal 'id', + subject.build_range(:id, nil, nil, nil) + end + + it 'only sort_by, first' do + assert_equal 'id 100', + subject.build_range(:id, 100, nil, nil) + end + + it 'only sort_by, first, last' do + assert_equal 'id 100..200', + subject.build_range(:id, 100, 200, nil) + end + + it 'only sort_by, last' do + assert_equal 'id', + subject.build_range(:id, nil, 200, nil) + end + + it 'only sort_by, first, args' do + assert_equal 'id 100; max=300,order=desc', + subject.build_range(:id, 100, nil, max: 300, order: 'desc') + end + + it 'only sort_by, first, last, args' do + assert_equal 'id 100..200; max=300,order=desc', + subject.build_range(:id, 100, 200, max: 300, order: 'desc') + end + + it 'only sort_by, first, count' do + assert_equal 'id 100/1200', + subject.build_range(:id, 100, nil, nil, 1200) + end + + it 'only sort_by, first, last, count' do + assert_equal 'id 100..200/1200', + subject.build_range(:id, 100, 200, nil, 1200) + end + + it 'only sort_by, first, args, count' do + assert_equal 'id 100/1200; max=300,order=desc', + subject.build_range(:id, 100, nil, { max: 300, order: 'desc' }, 1200) + end + end + describe '#will_paginate?' do it 'converts max to integer' do subject.instance_variable_set(:@res, args: { max: '1000' }) From 2b66b2bac3eb6f77ff9f711a350dea44c23669b9 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 08:34:32 +0200 Subject: [PATCH 24/69] Add block to #paginator --- lib/pliny/helpers/paginator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 7a35bba4..ba23518f 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -1,7 +1,7 @@ module Pliny::Helpers module Paginator - def paginator(count, options = {}) - Paginator.run(self, count, options) + def paginator(count, options = {}, &block) + Paginator.run(self, count, options, &block) end class Paginator From 3e44ee5b610f3b169d9d9782346fc61761609d90 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 08:43:14 +0200 Subject: [PATCH 25/69] Halt if :sort_by is nil --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index ba23518f..2794d495 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -102,7 +102,7 @@ def parse_request_options(match) end def validate_options - halt unless res[:accepted_ranges].include?(res[:sort_by].to_sym) + halt unless res[:sort_by] && res[:accepted_ranges].include?(res[:sort_by].to_sym) end def halt From 5b9358f0e7d04441d689f16505dcd89a19a15770 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 08:54:44 +0200 Subject: [PATCH 26/69] Move default options to initializer --- lib/pliny/helpers/paginator.rb | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 2794d495..45748acd 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -13,7 +13,7 @@ class Paginator ARGS = /(?.*)/ RANGE = /\A#{SORT_BY}\s+#{FIRST}(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ - attr_reader :sinatra, :count, :options + attr_reader :sinatra, :count attr_accessor :res class << self @@ -25,7 +25,14 @@ def run(*args, &block) def initialize(sinatra, count, options = {}) @sinatra = sinatra @count = count - @options = options + @options = + { + accepted_ranges: [:id], + sort_by: :id, + first: 0, + args: { max: 200 } + } + .merge(options) end def run @@ -44,15 +51,7 @@ def run def res return @res if @res - @res = - { - accepted_ranges: [:id], - sort_by: :id, - first: 0, - args: { max: 200 } - } - .merge(options) - .merge(request_options) + @res = @options.merge(request_options) calculate_pages unless @res[:first].is_a?(String) @res From c66fbd0f4a3a56f1dd994dd75d6a345b56a9b58b Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 08:59:58 +0200 Subject: [PATCH 27/69] Rename res to options --- lib/pliny/helpers/paginator.rb | 42 +++++++++++++++++----------------- test/helpers/paginator_test.rb | 22 +++++++++--------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 45748acd..36863e92 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -14,7 +14,7 @@ class Paginator RANGE = /\A#{SORT_BY}\s+#{FIRST}(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ attr_reader :sinatra, :count - attr_accessor :res + attr_writer :options class << self def run(*args, &block) @@ -25,7 +25,7 @@ def run(*args, &block) def initialize(sinatra, count, options = {}) @sinatra = sinatra @count = count - @options = + @opts = { accepted_ranges: [:id], sort_by: :id, @@ -42,27 +42,27 @@ def run set_headers { - sort_by: res[:sort_by], - first: res[:first], - limit: res[:args][:max] + sort_by: options[:sort_by], + first: options[:first], + limit: options[:args][:max] } end - def res - return @res if @res + def options + return @options if @options - @res = @options.merge(request_options) - calculate_pages unless @res[:first].is_a?(String) + @options = @opts.merge(request_options) + calculate_pages unless @options[:first].is_a?(String) - @res + @options end def calculate_pages - @res[:last] ||= @res[:first] + @res[:args][:max] - @res[:next_first] ||= @res[:last] + 1 - @res[:next_last] ||= + @options[:last] ||= @options[:first] + @options[:args][:max] + @options[:next_first] ||= @options[:last] + 1 + @options[:next_last] ||= [ - @res[:next_first] + @res[:args][:max], + @options[:next_first] + @options[:args][:max], count - 1 ] .min @@ -101,7 +101,7 @@ def parse_request_options(match) end def validate_options - halt unless res[:sort_by] && res[:accepted_ranges].include?(res[:sort_by].to_sym) + halt unless options[:sort_by] && options[:accepted_ranges].include?(options[:sort_by].to_sym) end def halt @@ -109,13 +109,13 @@ def halt end def set_headers - sinatra.header 'Accept-Ranges', res[:accepted_ranges].join(',') + sinatra.header 'Accept-Ranges', options[:accepted_ranges].join(',') if will_paginate? sinatra.status 206 sinatra.headers \ - 'Content-Range' => build_range(res[:sort_by], res[:first], res[:last], res[:args], count), - 'Next-Range' => build_range(res[:sort_by], res[:next_first], res[:next_last], res[:args], nil) + 'Content-Range' => build_range(options[:sort_by], options[:first], options[:last], options[:args], count), + 'Next-Range' => build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args], nil) else sinatra.status 200 end @@ -136,15 +136,15 @@ def encode_args(args) end def will_paginate? - count > res[:args][:max].to_i + count > options[:args][:max].to_i end def [](key) - res[key.to_sym] + options[key.to_sym] end def []=(key, value) - res[key.to_sym] = value + options[key.to_sym] = value end end end diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 898849c4..12877aa5 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -22,7 +22,7 @@ it 'returns Hash' do mock(subject).validate_options mock(subject).set_headers - stub(subject).res { { args: {} } } + stub(subject).options { { args: {} } } assert_kind_of Hash, subject.run end @@ -30,7 +30,7 @@ it 'evaluates block' do mock(subject).validate_options mock(subject).set_headers - subject.instance_variable_set(:@res, args: { max: 200 }) + subject.instance_variable_set(:@options, args: { max: 200 }) result = subject.run do |paginator| @@ -239,16 +239,16 @@ describe '#will_paginate?' do it 'converts max to integer' do - subject.instance_variable_set(:@res, args: { max: '1000' }) + subject.instance_variable_set(:@options, args: { max: '1000' }) stub(subject).count { 2000 } assert subject.will_paginate? end end describe '#[]' do - describe 'allows to read #res with a convenience method' do + describe 'allows to read #options with a convenience method' do before :each do - mock(subject).res { { first: 1 } } + mock(subject).options { { first: 1 } } end it 'with symbol key' do @@ -262,21 +262,21 @@ end describe '#[]=' do - describe 'allows to read #res with a convenience method' do + describe 'allows to read #options with a convenience method' do before :each do - subject.instance_variable_set(:@res, {}) + subject.instance_variable_set(:@options, {}) end it 'with symbol key' do - assert_equal nil, subject.res[:first] + assert_equal nil, subject.options[:first] subject[:first] = 1 - assert_equal 1, subject.res[:first] + assert_equal 1, subject.options[:first] end it 'with string key' do - assert_equal nil, subject.res[:first] + assert_equal nil, subject.options[:first] subject['first'] = 1 - assert_equal 1, subject.res[:first] + assert_equal 1, subject.options[:first] end end end From 6f969bc9f5a22b516ff28707f9fb5ba68a40ffad Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 09:06:53 +0200 Subject: [PATCH 28/69] Fix #set_headers * sinatra.header does not exist * Improve code style --- lib/pliny/helpers/paginator.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 36863e92..2685272a 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -109,13 +109,14 @@ def halt end def set_headers - sinatra.header 'Accept-Ranges', options[:accepted_ranges].join(',') + sinatra.headers 'Accept-Ranges' => options[:accepted_ranges].join(',') if will_paginate? sinatra.status 206 - sinatra.headers \ - 'Content-Range' => build_range(options[:sort_by], options[:first], options[:last], options[:args], count), - 'Next-Range' => build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args], nil) + cnt = build_range(options[:sort_by], options[:first], options[:last], options[:args], count) + nxt = build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args], nil) + sinatra.headers 'Content-Range' => cnt, + 'Next-Range' => nxt else sinatra.status 200 end From 6043cb6de53b453b9c291fe9c873ae667721343e Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 09:12:19 +0200 Subject: [PATCH 29/69] Remove optional argument --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 2685272a..44dc51d1 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -114,7 +114,7 @@ def set_headers if will_paginate? sinatra.status 206 cnt = build_range(options[:sort_by], options[:first], options[:last], options[:args], count) - nxt = build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args], nil) + nxt = build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args]) sinatra.headers 'Content-Range' => cnt, 'Next-Range' => nxt else From 55baa684918b90a1f90d0be10ac047b968f23c6e Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 15:16:52 +0200 Subject: [PATCH 30/69] Add tests for #calculate_pages --- lib/pliny/helpers/paginator.rb | 23 ++++++++++----- test/helpers/paginator_test.rb | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 44dc51d1..ece71222 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -58,14 +58,21 @@ def options end def calculate_pages - @options[:last] ||= @options[:first] + @options[:args][:max] - @options[:next_first] ||= @options[:last] + 1 - @options[:next_last] ||= - [ - @options[:next_first] + @options[:args][:max], - count - 1 - ] - .min + options[:last] ||= options[:first] + options[:args][:max] - 1 + + if options[:last] >= count - 1 + options[:last] = count - 1 + options[:next_first] = nil + options[:next_last] = nil + else + options[:next_first] ||= options[:last] + 1 + options[:next_last] ||= + [ + options[:next_first] + options[:args][:max] - 1, + count - 1 + ] + .min + end end def request_options diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 12877aa5..570cfe23 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -41,6 +41,59 @@ end end + describe '#calculate_pages' do + let(:opts) { { first: 100, args: { max: 300 } } } + + before :each do + subject.instance_variable_set(:@options, opts) + subject.calculate_pages + end + + describe 'when count < max in current range' do + let(:count) { 200 } + + it 'calculates :last' do + assert_equal 199, subject[:last] + end + + it 'calculates :next_first' do + assert_equal nil, subject[:next_first] + end + + it 'calculates :next_last' do + assert_equal nil, subject[:next_last] + end + end + + describe 'when count > max in current range' do + let(:count) { 3000 } + + it 'calculates :last' do + assert_equal 399, subject[:last] + end + + it 'calculates :next_first' do + assert_equal 400, subject[:next_first] + end + + describe 'when count < max in next range' do + let(:count) { 600 } + + it 'calculates :next_last' do + assert_equal 599, subject[:next_last] + end + end + + describe 'when count > max in next range' do + let(:count) { 3000 } + + it 'calculates :next_last' do + assert_equal 699, subject[:next_last] + end + end + end + end + describe '#request_options' do it 'returns Hash' do stub(sinatra).request do |klass| From d8ffa9276f0ea5b7aeefc5b1aae5016a5fa2bfcb Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 15:19:14 +0200 Subject: [PATCH 31/69] Only add Next-Range header if there is a next range --- lib/pliny/helpers/paginator.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index ece71222..eb1c9d76 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -120,10 +120,14 @@ def set_headers if will_paginate? sinatra.status 206 + cnt = build_range(options[:sort_by], options[:first], options[:last], options[:args], count) - nxt = build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args]) - sinatra.headers 'Content-Range' => cnt, - 'Next-Range' => nxt + sinatra.headers 'Content-Range' => cnt + + if options[:next_first] + nxt = build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args]) + sinatra.headers 'Next-Range' => nxt + end else sinatra.status 200 end From 4b6795f73615067f7b9914e0b8fcff03ebff699b Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 16:24:40 +0200 Subject: [PATCH 32/69] Support more than just UUID and Integer in Range --- lib/pliny/helpers/paginator.rb | 8 +++--- test/helpers/paginator_test.rb | 46 +++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index eb1c9d76..3069b551 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -5,10 +5,10 @@ def paginator(count, options = {}, &block) end class Paginator - SORT_BY = /(?\S*)/ - UUID = /[0-9a-f-]+/i - FIRST = /(?#{UUID})/ - LAST = /(?#{UUID})/ + SORT_BY = /(?\w+)/ + VALUE = /[^\.\s;\/]+/ + FIRST = /(?#{VALUE})/ + LAST = /(?#{VALUE})/ COUNT = /(?:\/\d+)/ ARGS = /(?.*)/ RANGE = /\A#{SORT_BY}\s+#{FIRST}(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 570cfe23..448de8a5 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -132,7 +132,7 @@ end end - describe 'when Range is valid' do + describe 'when Range is valid (UUIDs)' do describe 'with first only' do it 'returns Hash' do stub(sinatra).request do |klass| @@ -241,6 +241,50 @@ end end end + + describe 'when Range is valid (arbitrary)' do + before :each do + stub(sinatra).request do |klass| + stub(klass).env do + { 'Range' => "#{sort_by} #{first}..#{last}/400; max=1000,order=desc" } + end + end + end + + describe 'timestamp in iso8601' do + let(:sort_by) { 'created_at' } + let(:first) { '1985-09-24T00:00:00+00:00' } + let(:last) { '2014-07-01T15:54:32+02:00' } + + it 'returns Hash' do + result = + { + sort_by: sort_by, + first: first, + last: last, + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + + describe 'fruits' do + let(:sort_by) { 'fruit' } + let(:first) { 'Apple' } + let(:last) { 'Banana' } + + it 'returns Hash' do + result = + { + sort_by: sort_by, + first: first, + last: last, + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + end end describe '#build_range' do From 7d9e023ecd360b7f09c556b9897b554441dfaa4b Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 16:29:02 +0200 Subject: [PATCH 33/69] Refactor "when Range is valid" tests --- test/helpers/paginator_test.rb | 135 ++++++++++++++++----------------- 1 file changed, 65 insertions(+), 70 deletions(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 448de8a5..a9b5ebc3 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -132,40 +132,33 @@ end end - describe 'when Range is valid (UUIDs)' do - describe 'with first only' do - it 'returns Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef' } } + describe 'when Range is valid' do + before :each do + stub(sinatra).request do |klass| + stub(klass).env do + { 'Range' => range } end - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef' - } - assert_equal result, subject.request_options end end - describe 'without args' do - it 'returns Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef' } } + describe 'UUID' do + describe 'with first only' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal result, subject.request_options end - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef' - } - assert_equal result, subject.request_options end - describe 'with count' do + describe 'without args' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef' } + it 'returns Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400' } } - end result = { sort_by: 'id', @@ -174,29 +167,26 @@ } assert_equal result, subject.request_options end - end - end - describe 'with one arg' do - it 'returns Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } } + describe 'with count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal result, subject.request_options + end end - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000' } - } - assert_equal result, subject.request_options end - describe 'with count' do + describe 'with one arg' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } + it 'returns Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000' } } - end result = { sort_by: 'id', @@ -206,29 +196,27 @@ } assert_equal result, subject.request_options end - end - end - describe 'with more args' do - it 'returns Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000,order=desc' } } + describe 'with count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end end - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options end - describe 'with count' do + describe 'with more args' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000,order=desc' } + it 'returns Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } } - end result = { sort_by: 'id', @@ -238,20 +226,26 @@ } assert_equal result, subject.request_options end - end - end - end - describe 'when Range is valid (arbitrary)' do - before :each do - stub(sinatra).request do |klass| - stub(klass).env do - { 'Range' => "#{sort_by} #{first}..#{last}/400; max=1000,order=desc" } + describe 'with count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end end end end describe 'timestamp in iso8601' do + let(:range) { "#{sort_by} #{first}..#{last}/400; max=1000,order=desc" } let(:sort_by) { 'created_at' } let(:first) { '1985-09-24T00:00:00+00:00' } let(:last) { '2014-07-01T15:54:32+02:00' } @@ -269,6 +263,7 @@ end describe 'fruits' do + let(:range) { "#{sort_by} #{first}..#{last}/400; max=1000,order=desc" } let(:sort_by) { 'fruit' } let(:first) { 'Apple' } let(:last) { 'Banana' } From 8333a2e7c7030a8313a1a346e333d74bb920f3ce Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 18:12:03 +0200 Subject: [PATCH 34/69] #run shall return result of block --- lib/pliny/helpers/paginator.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 3069b551..3979a985 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -36,16 +36,20 @@ def initialize(sinatra, count, options = {}) end def run - yield(self) if block_given? + result = yield(self) if block_given? validate_options set_headers - { - sort_by: options[:sort_by], - first: options[:first], - limit: options[:args][:max] - } + if block_given? + result + else + { + sort_by: options[:sort_by], + first: options[:first], + limit: options[:args][:max] + } + end end def options From 0c1b87c307fe4b836e16814f1a75651bfa143765 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 18:12:50 +0200 Subject: [PATCH 35/69] Rename keys in hash of #run --- lib/pliny/helpers/paginator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 3979a985..10feeea4 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -45,8 +45,8 @@ def run result else { - sort_by: options[:sort_by], - first: options[:first], + order_by: options[:sort_by], + offset: options[:first], limit: options[:args][:max] } end From 13da84f38f8973fe78d766d47e948a35efadd156 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 18:26:51 +0200 Subject: [PATCH 36/69] Add pagination to endpoint scaffold --- lib/pliny/templates/endpoint_scaffold.erb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/pliny/templates/endpoint_scaffold.erb b/lib/pliny/templates/endpoint_scaffold.erb index 8700ab49..8e6a423f 100644 --- a/lib/pliny/templates/endpoint_scaffold.erb +++ b/lib/pliny/templates/endpoint_scaffold.erb @@ -6,7 +6,26 @@ module Endpoints end get do - encode serialize(<%= singular_class_name %>.all) + resources = + paginator(<%= singular_class_name %>.count) do |paginator| + resources = <%= singular_class_name %>.order(paginator[:sort_by]) + + If paginator.will_paginate? + next_resources = resources = resources.limit(paginator[:args][:max].to_i) + + resources = resources.where { uuid >= Sequel.cast(paginator[:first], :uuid) } + paginator[:first] = resources.get(:uuid) + paginator[:last] = resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) + + next_resources = next_resources.where { uuid > Sequel.cast(paginator[:last], :uuid) } + paginator[:next_first] = next_resources.get(:uuid) + paginator[:next_last] = next_resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) + end + + resources + end + + encode serialize(resources) end post do From 38c8b2941ff583f585174e0d26e0a8718ae79c35 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 19:12:36 +0200 Subject: [PATCH 37/69] Fix test --- test/helpers/paginator_test.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index a9b5ebc3..febc7e36 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -24,7 +24,17 @@ mock(subject).set_headers stub(subject).options { { args: {} } } - assert_kind_of Hash, subject.run + result = subject.run + assert_kind_of Hash, result + + exp = + { + order_by: nil, + offset: nil, + limit: nil + } + + assert_equal exp, result end it 'evaluates block' do @@ -37,7 +47,7 @@ paginator[:first] = 42 end - assert_equal 42, result[:first] + assert_equal 42, result end end From 1543e316e110771ad432d350935c5c99ab3c9a70 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 19:29:12 +0200 Subject: [PATCH 38/69] Fix next_last when offset is to high it would return nil. In this case, take last.uuid --- lib/pliny/templates/endpoint_scaffold.erb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pliny/templates/endpoint_scaffold.erb b/lib/pliny/templates/endpoint_scaffold.erb index 8e6a423f..1fbd73b2 100644 --- a/lib/pliny/templates/endpoint_scaffold.erb +++ b/lib/pliny/templates/endpoint_scaffold.erb @@ -19,7 +19,9 @@ module Endpoints next_resources = next_resources.where { uuid > Sequel.cast(paginator[:last], :uuid) } paginator[:next_first] = next_resources.get(:uuid) - paginator[:next_last] = next_resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) + paginator[:next_last] = + next_resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) || + next_resources.select(:uuid).last.uuid end resources From c6c9b54aac35c3ca9d9054fc2150faeef4ac168d Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 20:00:09 +0200 Subject: [PATCH 39/69] Add pagination to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c29ddfbd..f71862c0 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ And gems/helpers to tie these together and support operations: - [Rollbar](https://www.rollbar.com/) for tracking exceptions - [Log helper](spec/log_spec.rb) that logs in [data format](https://www.youtube.com/watch?v=rpmc-wHFUBs) [to stdout](https://adam.heroku.com/past/2011/4/1/logs_are_streams_not_files) - [Mediators](http://brandur.org/mediator) to help encapsulate more complex interactions +- [Pagination with Ranges](lib/pliny/helpers/paginator.rb) to paginate large amount of data - [Rspec](https://github.com/rspec/rspec) for lean and fast testing - [Puma](http://puma.io/) as the web server, [configured for optimal performance on Heroku](lib/template/config/puma.rb) - [Rack-test](https://github.com/brynary/rack-test) to test the API endpoints From 3a6e338f4e8012368bb29db2b99f557245ac16f0 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 20:16:26 +0200 Subject: [PATCH 40/69] Fix syntax error --- lib/pliny/templates/endpoint_scaffold.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/templates/endpoint_scaffold.erb b/lib/pliny/templates/endpoint_scaffold.erb index 1fbd73b2..69d46f52 100644 --- a/lib/pliny/templates/endpoint_scaffold.erb +++ b/lib/pliny/templates/endpoint_scaffold.erb @@ -10,7 +10,7 @@ module Endpoints paginator(<%= singular_class_name %>.count) do |paginator| resources = <%= singular_class_name %>.order(paginator[:sort_by]) - If paginator.will_paginate? + if paginator.will_paginate? next_resources = resources = resources.limit(paginator[:args][:max].to_i) resources = resources.where { uuid >= Sequel.cast(paginator[:first], :uuid) } From eaf896e77710069628204e03bd0f3a0f806bbff8 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 20:20:02 +0200 Subject: [PATCH 41/69] Require paginator --- lib/pliny.rb | 1 + test/helpers/paginator_test.rb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny.rb b/lib/pliny.rb index 0eff726e..4b7d4a4b 100644 --- a/lib/pliny.rb +++ b/lib/pliny.rb @@ -5,6 +5,7 @@ require_relative "pliny/errors" require_relative "pliny/extensions/instruments" require_relative "pliny/helpers/encode" +require_relative "pliny/helpers/paginator" require_relative "pliny/helpers/params" require_relative "pliny/log" require_relative "pliny/request_store" diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index febc7e36..c8c373d9 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -1,4 +1,3 @@ -require 'pliny/helpers/paginator' require 'test_helper' describe Pliny::Helpers::Paginator do From c59a853d865e98d0f7b5d8a92f7a03b7eb5a485b Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 20:24:18 +0200 Subject: [PATCH 42/69] Update default options --- lib/pliny/helpers/paginator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 10feeea4..9d7a899b 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -27,9 +27,9 @@ def initialize(sinatra, count, options = {}) @count = count @opts = { - accepted_ranges: [:id], - sort_by: :id, - first: 0, + accepted_ranges: [:uuid], + sort_by: :uuid, + first: nil, args: { max: 200 } } .merge(options) From cfd461c94b7cab2fa9868d09a946d32f8930e139 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 20:27:40 +0200 Subject: [PATCH 43/69] Skip calculate_pages when first is nil --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 9d7a899b..ecddf398 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -56,7 +56,7 @@ def options return @options if @options @options = @opts.merge(request_options) - calculate_pages unless @options[:first].is_a?(String) + calculate_pages unless @options[:first].nil? || @options[:first].is_a?(String) @options end From 733fb743da2c04f8710cc8b2ef878bd4f4f4fc69 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 20:34:12 +0200 Subject: [PATCH 44/69] Revert "Update default options" This reverts commit ddb6e46fb97dfbc9a2d6bcc9197b2289d9cff05c. --- lib/pliny/helpers/paginator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index ecddf398..e5816f5b 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -27,9 +27,9 @@ def initialize(sinatra, count, options = {}) @count = count @opts = { - accepted_ranges: [:uuid], - sort_by: :uuid, - first: nil, + accepted_ranges: [:id], + sort_by: :id, + first: 0, args: { max: 200 } } .merge(options) From f84c32ce26fb00ab111ce8529dbc96adf9805be5 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 20:38:17 +0200 Subject: [PATCH 45/69] Convert public facing "id" to "uuid" --- lib/pliny/templates/endpoint_scaffold.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pliny/templates/endpoint_scaffold.erb b/lib/pliny/templates/endpoint_scaffold.erb index 69d46f52..cf349b01 100644 --- a/lib/pliny/templates/endpoint_scaffold.erb +++ b/lib/pliny/templates/endpoint_scaffold.erb @@ -8,7 +8,8 @@ module Endpoints get do resources = paginator(<%= singular_class_name %>.count) do |paginator| - resources = <%= singular_class_name %>.order(paginator[:sort_by]) + sort_by_conversion = { id: :uuid } + resources = <%= singular_class_name %>.order(sort_by_conversion[paginator[:sort_by].to_sym]) if paginator.will_paginate? next_resources = resources = resources.limit(paginator[:args][:max].to_i) From 011303cc96584374d6d769d524573c566de9549f Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 20:40:59 +0200 Subject: [PATCH 46/69] first shall default to nil --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index e5816f5b..5adedf23 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -29,7 +29,7 @@ def initialize(sinatra, count, options = {}) { accepted_ranges: [:id], sort_by: :id, - first: 0, + first: nil, args: { max: 200 } } .merge(options) From 5f53eec2468c63b77e88c0499e2710852a787bf8 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 21:01:48 +0200 Subject: [PATCH 47/69] Introduce #uuid_paginator --- lib/pliny/helpers/paginator.rb | 23 +++++++++++++++++++++++ lib/pliny/templates/endpoint_scaffold.erb | 22 +--------------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 5adedf23..1516c6de 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -4,6 +4,29 @@ def paginator(count, options = {}, &block) Paginator.run(self, count, options, &block) end + def uuid_paginator(resource, options = {}) + paginator(resource.count, options) do |paginator| + sort_by_conversion = { id: :uuid } + resources = resource.order(sort_by_conversion[paginator[:sort_by].to_sym]) + + if paginator.will_paginate? + next_resources = resources = resources.limit(paginator[:args][:max].to_i) + + resources = resources.where { uuid >= Sequel.cast(paginator[:first], :uuid) } if paginator[:first] + paginator[:first] = resources.get(:uuid) + paginator[:last] = resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) + + next_resources = next_resources.where { uuid > Sequel.cast(paginator[:last], :uuid) } + paginator[:next_first] = next_resources.get(:uuid) + paginator[:next_last] = + next_resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) || + next_resources.select(:uuid).last.uuid + end + + resources + end + end + class Paginator SORT_BY = /(?\w+)/ VALUE = /[^\.\s;\/]+/ diff --git a/lib/pliny/templates/endpoint_scaffold.erb b/lib/pliny/templates/endpoint_scaffold.erb index cf349b01..49262d0c 100644 --- a/lib/pliny/templates/endpoint_scaffold.erb +++ b/lib/pliny/templates/endpoint_scaffold.erb @@ -6,27 +6,7 @@ module Endpoints end get do - resources = - paginator(<%= singular_class_name %>.count) do |paginator| - sort_by_conversion = { id: :uuid } - resources = <%= singular_class_name %>.order(sort_by_conversion[paginator[:sort_by].to_sym]) - - if paginator.will_paginate? - next_resources = resources = resources.limit(paginator[:args][:max].to_i) - - resources = resources.where { uuid >= Sequel.cast(paginator[:first], :uuid) } - paginator[:first] = resources.get(:uuid) - paginator[:last] = resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) - - next_resources = next_resources.where { uuid > Sequel.cast(paginator[:last], :uuid) } - paginator[:next_first] = next_resources.get(:uuid) - paginator[:next_last] = - next_resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) || - next_resources.select(:uuid).last.uuid - end - - resources - end + resources = uuid_paginator(<%= singular_class_name %>, args: { max: 10 }) encode serialize(resources) end From 888f6e083f453f18dff21e5bee9b99a932c46528 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 23:17:31 +0200 Subject: [PATCH 48/69] Improve regex --- lib/pliny/helpers/paginator.rb | 2 +- test/helpers/paginator_test.rb | 67 ++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 1516c6de..cfcb62c6 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -34,7 +34,7 @@ class Paginator LAST = /(?#{VALUE})/ COUNT = /(?:\/\d+)/ ARGS = /(?.*)/ - RANGE = /\A#{SORT_BY}\s+#{FIRST}(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ + RANGE = /\A#{SORT_BY}(?:\s+#{FIRST})?(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ attr_reader :sinatra, :count attr_writer :options diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index c8c373d9..d69463fe 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -131,16 +131,6 @@ end end - describe 'when Range is an invalid string' do - it 'halts' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'foo' } } - end - mock(subject).halt - subject.request_options - end - end - describe 'when Range is valid' do before :each do stub(sinatra).request do |klass| @@ -164,6 +154,43 @@ end end + describe 'without first' do + let(:range) { 'id' } + + it 'returns Hash' do + result = + { + sort_by: 'id' + } + assert_equal result, subject.request_options + end + + describe 'with args' do + let(:range) { 'id; max=1000' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end + end + + describe 'with count' do + let(:range) { 'id/400' } + + it 'returns Hash' do + result = + { + sort_by: 'id' + } + assert_equal result, subject.request_options + end + end + end + describe 'without args' do let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef' } @@ -302,16 +329,26 @@ subject.build_range(:id, 100, nil, nil) end - it 'only sort_by, first, last' do - assert_equal 'id 100..200', - subject.build_range(:id, 100, 200, nil) - end - it 'only sort_by, last' do assert_equal 'id', subject.build_range(:id, nil, 200, nil) end + it 'only sort_by, args' do + assert_equal 'id; max=300,order=desc', + subject.build_range(:id, nil, nil, max: 300, order: 'desc') + end + + it 'only sort_by, args' do + assert_equal 'id/1200', + subject.build_range(:id, nil, nil, nil, 1200) + end + + it 'only sort_by, first, last' do + assert_equal 'id 100..200', + subject.build_range(:id, 100, 200, nil) + end + it 'only sort_by, first, args' do assert_equal 'id 100; max=300,order=desc', subject.build_range(:id, 100, nil, max: 300, order: 'desc') From e9b9069a34148c23ed163b32df2e4642a974c038 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 23:44:09 +0200 Subject: [PATCH 49/69] Reorganize, add more tests --- test/helpers/paginator_test.rb | 195 +++++++++++++++++++++++---------- 1 file changed, 136 insertions(+), 59 deletions(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index d69463fe..58c6fac6 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -141,7 +141,19 @@ end describe 'UUID' do - describe 'with first only' do + describe 'only sort_by' do + let(:range) { 'id' } + + it 'returns Hash' do + result = + { + sort_by: 'id' + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, first' do let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef' } it 'returns Hash' do @@ -154,8 +166,21 @@ end end - describe 'without first' do - let(:range) { 'id' } + describe 'only sort_by, args' do + let(:range) { 'id; max=1000' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, count' do + let(:range) { 'id/400' } it 'returns Hash' do result = @@ -164,118 +189,155 @@ } assert_equal result, subject.request_options end + end + + describe 'only sort_by, first, last' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, first, args' do + end + + describe 'only sort_by, first, count' do + end - describe 'with args' do - let(:range) { 'id; max=1000' } + describe 'only sort_by, first, last, args' do + describe 'one arg' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } it 'returns Hash' do result = { sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', args: { max: '1000' } } assert_equal result, subject.request_options end end - describe 'with count' do - let(:range) { 'id/400' } + describe 'more args' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000,order=desc' } it 'returns Hash' do result = { - sort_by: 'id' + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000', order: 'desc' } } assert_equal result, subject.request_options end end end - describe 'without args' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef' } + describe 'only sort_by, first, last, args, count' do + describe 'one arg' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000' } - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef' - } - assert_equal result, subject.request_options + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end end - describe 'with count' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400' } + describe 'more args' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } it 'returns Hash' do result = { sort_by: 'id', first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef' + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000', order: 'desc' } } assert_equal result, subject.request_options end end end - describe 'with one arg' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } + describe 'only sort_by, first, last, count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400' } it 'returns Hash' do result = { sort_by: 'id', first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000' } + last: '01234567-89ab-cdef-0123-456789abcdef' } assert_equal result, subject.request_options end + end - describe 'with count' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000' } + describe 'only sort_by, first, args, count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000' } - } - assert_equal result, subject.request_options - end + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options end end - describe 'with more args' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000,order=desc' } + describe 'only sort_by, args' do + let(:range) { 'id; max=1000,order=desc' } it 'returns Hash' do result = { sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', args: { max: '1000', order: 'desc' } } assert_equal result, subject.request_options end + end - describe 'with count' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } + describe 'only sort_by, args, count' do + let(:range) { 'id/400; max=1000,order=desc' } - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options - end + it 'returns Hash' do + result = + { + sort_by: 'id', + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, count' do + let(:range) { 'id/400' } + + it 'returns Hash' do + result = + { + sort_by: 'id' + } + assert_equal result, subject.request_options end end end @@ -339,7 +401,7 @@ subject.build_range(:id, nil, nil, max: 300, order: 'desc') end - it 'only sort_by, args' do + it 'only sort_by, count' do assert_equal 'id/1200', subject.build_range(:id, nil, nil, nil, 1200) end @@ -354,16 +416,16 @@ subject.build_range(:id, 100, nil, max: 300, order: 'desc') end - it 'only sort_by, first, last, args' do - assert_equal 'id 100..200; max=300,order=desc', - subject.build_range(:id, 100, 200, max: 300, order: 'desc') - end - it 'only sort_by, first, count' do assert_equal 'id 100/1200', subject.build_range(:id, 100, nil, nil, 1200) end + it 'only sort_by, first, last, args' do + assert_equal 'id 100..200; max=300,order=desc', + subject.build_range(:id, 100, 200, max: 300, order: 'desc') + end + it 'only sort_by, first, last, count' do assert_equal 'id 100..200/1200', subject.build_range(:id, 100, 200, nil, 1200) @@ -373,6 +435,21 @@ assert_equal 'id 100/1200; max=300,order=desc', subject.build_range(:id, 100, nil, { max: 300, order: 'desc' }, 1200) end + + it 'only sort_by, args' do + assert_equal 'id; max=300,order=desc', + subject.build_range(:id, nil, nil, { max: 300, order: 'desc' }) + end + + it 'only sort_by, args, count' do + assert_equal 'id/1200; max=300,order=desc', + subject.build_range(:id, nil, nil, { max: 300, order: 'desc' }, 1200) + end + + it 'only sort_by, count' do + assert_equal 'id/1200', + subject.build_range(:id, nil, nil, nil, 1200) + end end describe '#will_paginate?' do From 5a7a88aba648c7af026a4307b7fbc62548879544 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Tue, 1 Jul 2014 23:48:26 +0200 Subject: [PATCH 50/69] Add more tests --- test/helpers/paginator_test.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 58c6fac6..8dff43cf 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -206,9 +206,30 @@ end describe 'only sort_by, first, args' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef; max=1000' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end end describe 'only sort_by, first, count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef/400' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal result, subject.request_options + end end describe 'only sort_by, first, last, args' do From a5e30043097190271bd66ed1ca91f78960e84f85 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 08:02:52 +0200 Subject: [PATCH 51/69] Improve queries in #uuid_paginator --- lib/pliny/helpers/paginator.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index cfcb62c6..2cfb475f 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -10,17 +10,18 @@ def uuid_paginator(resource, options = {}) resources = resource.order(sort_by_conversion[paginator[:sort_by].to_sym]) if paginator.will_paginate? - next_resources = resources = resources.limit(paginator[:args][:max].to_i) - + max = paginator[:args][:max].to_i + resources = resources.limit(max) resources = resources.where { uuid >= Sequel.cast(paginator[:first], :uuid) } if paginator[:first] - paginator[:first] = resources.get(:uuid) - paginator[:last] = resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) - next_resources = next_resources.where { uuid > Sequel.cast(paginator[:last], :uuid) } - paginator[:next_first] = next_resources.get(:uuid) + paginator[:first] = + resources.get(:uuid) + paginator[:last] = + resources.offset(max - 1).get(:uuid) + paginator[:next_first] = + resources.offset(max).get(:uuid) paginator[:next_last] = - next_resources.offset(paginator[:args][:max].to_i - 1).get(:uuid) || - next_resources.select(:uuid).last.uuid + resources.offset(2 * max - 1).get(:uuid) || resources.select(:uuid).last.uuid end resources From 90295a652aa78827d36d3f783892591e623a22c8 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 08:14:44 +0200 Subject: [PATCH 52/69] Use options.merge --- lib/pliny/helpers/paginator.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 2cfb475f..9ea11c97 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -14,14 +14,11 @@ def uuid_paginator(resource, options = {}) resources = resources.limit(max) resources = resources.where { uuid >= Sequel.cast(paginator[:first], :uuid) } if paginator[:first] - paginator[:first] = - resources.get(:uuid) - paginator[:last] = - resources.offset(max - 1).get(:uuid) - paginator[:next_first] = - resources.offset(max).get(:uuid) - paginator[:next_last] = - resources.offset(2 * max - 1).get(:uuid) || resources.select(:uuid).last.uuid + paginator.options.merge \ + first: resources.get(:uuid), + last: resources.offset(max - 1).get(:uuid), + next_first: resources.offset(max).get(:uuid), + next_last: resources.offset(2 * max - 1).get(:uuid) || resources.select(:uuid).last.uuid end resources From 115367be862d52d661a82a24a9c678a0a1392cd8 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 08:16:25 +0200 Subject: [PATCH 53/69] Improve code style --- test/helpers/paginator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 8dff43cf..1d173fae 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -459,7 +459,7 @@ it 'only sort_by, args' do assert_equal 'id; max=300,order=desc', - subject.build_range(:id, nil, nil, { max: 300, order: 'desc' }) + subject.build_range(:id, nil, nil, max: 300, order: 'desc') end it 'only sort_by, args, count' do From fc992ebbf3c71a7028deefea46e0ddce7b2f393a Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 08:27:19 +0200 Subject: [PATCH 54/69] Use options.merge! --- lib/pliny/helpers/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 9ea11c97..0d386db8 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -14,7 +14,7 @@ def uuid_paginator(resource, options = {}) resources = resources.limit(max) resources = resources.where { uuid >= Sequel.cast(paginator[:first], :uuid) } if paginator[:first] - paginator.options.merge \ + paginator.options.merge! \ first: resources.get(:uuid), last: resources.offset(max - 1).get(:uuid), next_first: resources.offset(max).get(:uuid), From 11098efb66f323f9618f6ebdf0b5038f24981beb Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 10:20:44 +0200 Subject: [PATCH 55/69] Add all options to default hash --- lib/pliny/helpers/paginator.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 0d386db8..f0bd672e 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -51,6 +51,9 @@ def initialize(sinatra, count, options = {}) accepted_ranges: [:id], sort_by: :id, first: nil, + last: nil, + next_first: nil, + next_last: nil, args: { max: 200 } } .merge(options) From b622848ae034ee72c9a1566dc2c36297f62d1295 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 11:49:09 +0200 Subject: [PATCH 56/69] Add description group to test --- test/helpers/paginator_test.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 1d173fae..ada6361a 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -4,9 +4,11 @@ let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } let(:sinatra) { dummy_class.new } - it 'calls Pagiantor.run' do - mock(Pliny::Helpers::Paginator::Paginator).run(sinatra, 12, sort_by: :foo) - sinatra.paginator(12, sort_by: :foo) + describe '#paginator' do + it 'calls Pagiantor.run' do + mock(Pliny::Helpers::Paginator::Paginator).run(sinatra, 12, sort_by: :foo) + sinatra.paginator(12, sort_by: :foo) + end end end From 6aa012e813c55e40788d88f26b702fd6488dcada Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 12:09:34 +0200 Subject: [PATCH 57/69] Add #integer_paginator --- lib/pliny/helpers/paginator.rb | 94 +++++++++++++------- test/helpers/paginator_test.rb | 158 ++++++++++++++++++--------------- 2 files changed, 148 insertions(+), 104 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index f0bd672e..0653592a 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -25,6 +25,10 @@ def uuid_paginator(resource, options = {}) end end + def integer_paginator(count, options = {}) + IntegerPaginator.run(self, count, options) + end + class Paginator SORT_BY = /(?\w+)/ VALUE = /[^\.\s;\/]+/ @@ -65,42 +69,11 @@ def run validate_options set_headers - if block_given? - result - else - { - order_by: options[:sort_by], - offset: options[:first], - limit: options[:args][:max] - } - end + result end def options - return @options if @options - - @options = @opts.merge(request_options) - calculate_pages unless @options[:first].nil? || @options[:first].is_a?(String) - - @options - end - - def calculate_pages - options[:last] ||= options[:first] + options[:args][:max] - 1 - - if options[:last] >= count - 1 - options[:last] = count - 1 - options[:next_first] = nil - options[:next_last] = nil - else - options[:next_first] ||= options[:last] + 1 - options[:next_last] ||= - [ - options[:next_first] + options[:args][:max] - 1, - count - 1 - ] - .min - end + @options ||= @opts.merge(request_options) end def request_options @@ -187,5 +160,60 @@ def []=(key, value) options[key.to_sym] = value end end + + class IntegerPaginator + attr_reader :sinatra, :count + attr_writer :options + + class << self + def run(*args, &block) + new(*args).run(&block) + end + end + + def initialize(sinatra, count, options = {}) + @sinatra = sinatra + @count = count + @opts = options + end + + def run + { + order_by: options[:sort_by], + offset: options[:first], + limit: options[:args][:max] + } + end + + def options + @options ||= calculate_pages + end + + def calculate_pages + Paginator.run(self, count, @opts) do |paginator| + max = paginator[:args][:max].to_i + paginator[:last] = + paginator[:first].to_i + max - 1 + + if paginator[:last] >= count - 1 + paginator.options.merge! \ + last: count - 1, + next_first: nil, + next_last: nil + else + paginator[:next_first] = + paginator[:last] + 1 + paginator[:next_last] = + [ + paginator[:next_first] + max - 1, + count - 1 + ] + .min + end + + paginator.options + end + end + end end end diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index ada6361a..327dc5b1 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -10,6 +10,13 @@ sinatra.paginator(12, sort_by: :foo) end end + + describe '#integer_paginator' do + it 'calls IntegerPaginator.run' do + mock(Pliny::Helpers::Paginator::IntegerPaginator).run(sinatra, 12, sort_by: :foo) + sinatra.integer_paginator(12, sort_by: :foo) + end + end end describe Pliny::Helpers::Paginator::Paginator do @@ -20,24 +27,6 @@ let(:opts) { {} } describe '#run' do - it 'returns Hash' do - mock(subject).validate_options - mock(subject).set_headers - stub(subject).options { { args: {} } } - - result = subject.run - assert_kind_of Hash, result - - exp = - { - order_by: nil, - offset: nil, - limit: nil - } - - assert_equal exp, result - end - it 'evaluates block' do mock(subject).validate_options mock(subject).set_headers @@ -52,59 +41,6 @@ end end - describe '#calculate_pages' do - let(:opts) { { first: 100, args: { max: 300 } } } - - before :each do - subject.instance_variable_set(:@options, opts) - subject.calculate_pages - end - - describe 'when count < max in current range' do - let(:count) { 200 } - - it 'calculates :last' do - assert_equal 199, subject[:last] - end - - it 'calculates :next_first' do - assert_equal nil, subject[:next_first] - end - - it 'calculates :next_last' do - assert_equal nil, subject[:next_last] - end - end - - describe 'when count > max in current range' do - let(:count) { 3000 } - - it 'calculates :last' do - assert_equal 399, subject[:last] - end - - it 'calculates :next_first' do - assert_equal 400, subject[:next_first] - end - - describe 'when count < max in next range' do - let(:count) { 600 } - - it 'calculates :next_last' do - assert_equal 599, subject[:next_last] - end - end - - describe 'when count > max in next range' do - let(:count) { 3000 } - - it 'calculates :next_last' do - assert_equal 699, subject[:next_last] - end - end - end - end - describe '#request_options' do it 'returns Hash' do stub(sinatra).request do |klass| @@ -519,3 +455,83 @@ end end end + +describe Pliny::Helpers::Paginator::IntegerPaginator do + subject { Pliny::Helpers::Paginator::IntegerPaginator.new(sinatra, count, opts) } + let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } + let(:sinatra) { dummy_class.new } + let(:count) { 4 } + let(:opts) { {} } + + before :each do + any_instance_of(Pliny::Helpers::Paginator::Paginator) do |klass| + stub(klass).request_options { {} } + stub(klass).set_headers + end + end + + describe '#run' do + it 'returns Hash' do + result = subject.run + + assert_kind_of Hash, result + + exp = + { + order_by: :id, + offset: nil, + limit: 200 + } + + assert_equal exp, result + end + end + + describe '#calculate_pages' do + let(:opts) { { first: 100, args: { max: 300 } } } + + describe 'when count < max in current range' do + let(:count) { 200 } + + it 'calculates :last' do + assert_equal 199, subject.calculate_pages[:last] + end + + it 'calculates :next_first' do + assert_equal nil, subject.calculate_pages[:next_first] + end + + it 'calculates :next_last' do + assert_equal nil, subject.calculate_pages[:next_last] + end + end + + describe 'when count > max in current range' do + let(:count) { 3000 } + + it 'calculates :last' do + assert_equal 399, subject.calculate_pages[:last] + end + + it 'calculates :next_first' do + assert_equal 400, subject.calculate_pages[:next_first] + end + + describe 'when count < max in next range' do + let(:count) { 600 } + + it 'calculates :next_last' do + assert_equal 599, subject.calculate_pages[:next_last] + end + end + + describe 'when count > max in next range' do + let(:count) { 3000 } + + it 'calculates :next_last' do + assert_equal 699, subject.calculate_pages[:next_last] + end + end + end + end +end From dddb88aeb15c43301b5940dbd54a4ad915344203 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 12:20:10 +0200 Subject: [PATCH 58/69] Split paginator.rb file --- lib/pliny.rb | 2 + lib/pliny/helpers/paginator.rb | 187 ------- .../helpers/paginator/integer_paginator.rb | 58 ++ lib/pliny/helpers/paginator/paginator.rb | 135 +++++ .../paginator/integer_paginator_test.rb | 81 +++ test/helpers/paginator/paginator_test.rb | 438 +++++++++++++++ test/helpers/paginator_test.rb | 517 ------------------ 7 files changed, 714 insertions(+), 704 deletions(-) create mode 100644 lib/pliny/helpers/paginator/integer_paginator.rb create mode 100644 lib/pliny/helpers/paginator/paginator.rb create mode 100644 test/helpers/paginator/integer_paginator_test.rb create mode 100644 test/helpers/paginator/paginator_test.rb diff --git a/lib/pliny.rb b/lib/pliny.rb index 4b7d4a4b..e5390bc2 100644 --- a/lib/pliny.rb +++ b/lib/pliny.rb @@ -6,6 +6,8 @@ require_relative "pliny/extensions/instruments" require_relative "pliny/helpers/encode" require_relative "pliny/helpers/paginator" +require_relative "pliny/helpers/paginator/paginator" +require_relative "pliny/helpers/paginator/integer_paginator" require_relative "pliny/helpers/params" require_relative "pliny/log" require_relative "pliny/request_store" diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 0653592a..1cdf825f 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -28,192 +28,5 @@ def uuid_paginator(resource, options = {}) def integer_paginator(count, options = {}) IntegerPaginator.run(self, count, options) end - - class Paginator - SORT_BY = /(?\w+)/ - VALUE = /[^\.\s;\/]+/ - FIRST = /(?#{VALUE})/ - LAST = /(?#{VALUE})/ - COUNT = /(?:\/\d+)/ - ARGS = /(?.*)/ - RANGE = /\A#{SORT_BY}(?:\s+#{FIRST})?(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ - - attr_reader :sinatra, :count - attr_writer :options - - class << self - def run(*args, &block) - new(*args).run(&block) - end - end - - def initialize(sinatra, count, options = {}) - @sinatra = sinatra - @count = count - @opts = - { - accepted_ranges: [:id], - sort_by: :id, - first: nil, - last: nil, - next_first: nil, - next_last: nil, - args: { max: 200 } - } - .merge(options) - end - - def run - result = yield(self) if block_given? - - validate_options - set_headers - - result - end - - def options - @options ||= @opts.merge(request_options) - end - - def request_options - range = sinatra.request.env['Range'] - return {} if range.nil? || range.empty? - - match = - RANGE.match(range) - - match ? parse_request_options(match) : halt - end - - def parse_request_options(match) - request_options = {} - - [:sort_by, :first, :last].each do |key| - request_options[key] = match[key] if match[key] - end - - if match[:args] - args = - match[:args] - .split(/\s*,\s*/) - .map do |value| - k, v = value.split('=', 2) - [k.to_sym, v] - end - - request_options[:args] = Hash[args] - end - - request_options - end - - def validate_options - halt unless options[:sort_by] && options[:accepted_ranges].include?(options[:sort_by].to_sym) - end - - def halt - sinatra.halt(416) - end - - def set_headers - sinatra.headers 'Accept-Ranges' => options[:accepted_ranges].join(',') - - if will_paginate? - sinatra.status 206 - - cnt = build_range(options[:sort_by], options[:first], options[:last], options[:args], count) - sinatra.headers 'Content-Range' => cnt - - if options[:next_first] - nxt = build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args]) - sinatra.headers 'Next-Range' => nxt - end - else - sinatra.status 200 - end - end - - def build_range(sort_by, first, last, args, count = nil) - range = sort_by.to_s - range << " #{[first, last].compact.join('..')}" if first - range << "/#{count}" if count - range << "; #{encode_args(args)}" if args - range - end - - def encode_args(args) - args - .map { |key, value| "#{key}=#{value}" } - .join(',') - end - - def will_paginate? - count > options[:args][:max].to_i - end - - def [](key) - options[key.to_sym] - end - - def []=(key, value) - options[key.to_sym] = value - end - end - - class IntegerPaginator - attr_reader :sinatra, :count - attr_writer :options - - class << self - def run(*args, &block) - new(*args).run(&block) - end - end - - def initialize(sinatra, count, options = {}) - @sinatra = sinatra - @count = count - @opts = options - end - - def run - { - order_by: options[:sort_by], - offset: options[:first], - limit: options[:args][:max] - } - end - - def options - @options ||= calculate_pages - end - - def calculate_pages - Paginator.run(self, count, @opts) do |paginator| - max = paginator[:args][:max].to_i - paginator[:last] = - paginator[:first].to_i + max - 1 - - if paginator[:last] >= count - 1 - paginator.options.merge! \ - last: count - 1, - next_first: nil, - next_last: nil - else - paginator[:next_first] = - paginator[:last] + 1 - paginator[:next_last] = - [ - paginator[:next_first] + max - 1, - count - 1 - ] - .min - end - - paginator.options - end - end - end end end diff --git a/lib/pliny/helpers/paginator/integer_paginator.rb b/lib/pliny/helpers/paginator/integer_paginator.rb new file mode 100644 index 00000000..a702c7fc --- /dev/null +++ b/lib/pliny/helpers/paginator/integer_paginator.rb @@ -0,0 +1,58 @@ +module Pliny::Helpers + module Paginator + class IntegerPaginator + attr_reader :sinatra, :count + attr_writer :options + + class << self + def run(*args, &block) + new(*args).run(&block) + end + end + + def initialize(sinatra, count, options = {}) + @sinatra = sinatra + @count = count + @opts = options + end + + def run + { + order_by: options[:sort_by], + offset: options[:first], + limit: options[:args][:max] + } + end + + def options + @options ||= calculate_pages + end + + def calculate_pages + Paginator.run(self, count, @opts) do |paginator| + max = paginator[:args][:max].to_i + paginator[:last] = + paginator[:first].to_i + max - 1 + + if paginator[:last] >= count - 1 + paginator.options.merge! \ + last: count - 1, + next_first: nil, + next_last: nil + else + paginator[:next_first] = + paginator[:last] + 1 + paginator[:next_last] = + [ + paginator[:next_first] + max - 1, + count - 1 + ] + .min + end + + paginator.options + end + end + end + end +end diff --git a/lib/pliny/helpers/paginator/paginator.rb b/lib/pliny/helpers/paginator/paginator.rb new file mode 100644 index 00000000..c00b4594 --- /dev/null +++ b/lib/pliny/helpers/paginator/paginator.rb @@ -0,0 +1,135 @@ +module Pliny::Helpers + module Paginator + class Paginator + SORT_BY = /(?\w+)/ + VALUE = /[^\.\s;\/]+/ + FIRST = /(?#{VALUE})/ + LAST = /(?#{VALUE})/ + COUNT = /(?:\/\d+)/ + ARGS = /(?.*)/ + RANGE = /\A#{SORT_BY}(?:\s+#{FIRST})?(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ + + attr_reader :sinatra, :count + attr_writer :options + + class << self + def run(*args, &block) + new(*args).run(&block) + end + end + + def initialize(sinatra, count, options = {}) + @sinatra = sinatra + @count = count + @opts = + { + accepted_ranges: [:id], + sort_by: :id, + first: nil, + last: nil, + next_first: nil, + next_last: nil, + args: { max: 200 } + } + .merge(options) + end + + def run + result = yield(self) if block_given? + + validate_options + set_headers + + result + end + + def options + @options ||= @opts.merge(request_options) + end + + def request_options + range = sinatra.request.env['Range'] + return {} if range.nil? || range.empty? + + match = + RANGE.match(range) + + match ? parse_request_options(match) : halt + end + + def parse_request_options(match) + request_options = {} + + [:sort_by, :first, :last].each do |key| + request_options[key] = match[key] if match[key] + end + + if match[:args] + args = + match[:args] + .split(/\s*,\s*/) + .map do |value| + k, v = value.split('=', 2) + [k.to_sym, v] + end + + request_options[:args] = Hash[args] + end + + request_options + end + + def validate_options + halt unless options[:sort_by] && options[:accepted_ranges].include?(options[:sort_by].to_sym) + end + + def halt + sinatra.halt(416) + end + + def set_headers + sinatra.headers 'Accept-Ranges' => options[:accepted_ranges].join(',') + + if will_paginate? + sinatra.status 206 + + cnt = build_range(options[:sort_by], options[:first], options[:last], options[:args], count) + sinatra.headers 'Content-Range' => cnt + + if options[:next_first] + nxt = build_range(options[:sort_by], options[:next_first], options[:next_last], options[:args]) + sinatra.headers 'Next-Range' => nxt + end + else + sinatra.status 200 + end + end + + def build_range(sort_by, first, last, args, count = nil) + range = sort_by.to_s + range << " #{[first, last].compact.join('..')}" if first + range << "/#{count}" if count + range << "; #{encode_args(args)}" if args + range + end + + def encode_args(args) + args + .map { |key, value| "#{key}=#{value}" } + .join(',') + end + + def will_paginate? + count > options[:args][:max].to_i + end + + def [](key) + options[key.to_sym] + end + + def []=(key, value) + options[key.to_sym] = value + end + end + end +end diff --git a/test/helpers/paginator/integer_paginator_test.rb b/test/helpers/paginator/integer_paginator_test.rb new file mode 100644 index 00000000..791ba824 --- /dev/null +++ b/test/helpers/paginator/integer_paginator_test.rb @@ -0,0 +1,81 @@ +require 'test_helper' + +describe Pliny::Helpers::Paginator::IntegerPaginator do + subject { Pliny::Helpers::Paginator::IntegerPaginator.new(sinatra, count, opts) } + let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } + let(:sinatra) { dummy_class.new } + let(:count) { 4 } + let(:opts) { {} } + + before :each do + any_instance_of(Pliny::Helpers::Paginator::Paginator) do |klass| + stub(klass).request_options { {} } + stub(klass).set_headers + end + end + + describe '#run' do + it 'returns Hash' do + result = subject.run + + assert_kind_of Hash, result + + exp = + { + order_by: :id, + offset: nil, + limit: 200 + } + + assert_equal exp, result + end + end + + describe '#calculate_pages' do + let(:opts) { { first: 100, args: { max: 300 } } } + + describe 'when count < max in current range' do + let(:count) { 200 } + + it 'calculates :last' do + assert_equal 199, subject.calculate_pages[:last] + end + + it 'calculates :next_first' do + assert_equal nil, subject.calculate_pages[:next_first] + end + + it 'calculates :next_last' do + assert_equal nil, subject.calculate_pages[:next_last] + end + end + + describe 'when count > max in current range' do + let(:count) { 3000 } + + it 'calculates :last' do + assert_equal 399, subject.calculate_pages[:last] + end + + it 'calculates :next_first' do + assert_equal 400, subject.calculate_pages[:next_first] + end + + describe 'when count < max in next range' do + let(:count) { 600 } + + it 'calculates :next_last' do + assert_equal 599, subject.calculate_pages[:next_last] + end + end + + describe 'when count > max in next range' do + let(:count) { 3000 } + + it 'calculates :next_last' do + assert_equal 699, subject.calculate_pages[:next_last] + end + end + end + end +end diff --git a/test/helpers/paginator/paginator_test.rb b/test/helpers/paginator/paginator_test.rb new file mode 100644 index 00000000..39c26769 --- /dev/null +++ b/test/helpers/paginator/paginator_test.rb @@ -0,0 +1,438 @@ +require 'test_helper' + +describe Pliny::Helpers::Paginator::Paginator do + subject { Pliny::Helpers::Paginator::Paginator.new(sinatra, count, opts) } + let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } + let(:sinatra) { dummy_class.new } + let(:count) { 4 } + let(:opts) { {} } + + describe '#run' do + it 'evaluates block' do + mock(subject).validate_options + mock(subject).set_headers + subject.instance_variable_set(:@options, args: { max: 200 }) + + result = + subject.run do |paginator| + paginator[:first] = 42 + end + + assert_equal 42, result + end + end + + describe '#request_options' do + it 'returns Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } } + end + assert_kind_of Hash, subject.request_options + end + + describe 'when Range is nil' do + it 'returns empty Hash' do + stub(sinatra).request do |klass| + stub(klass).env { {} } + end + assert_kind_of Hash, subject.request_options + assert_empty subject.request_options + end + end + + describe 'when Range is an empty string' do + it 'returns empty Hash' do + stub(sinatra).request do |klass| + stub(klass).env { { 'Range' => '' } } + end + assert_kind_of Hash, subject.request_options + assert_empty subject.request_options + end + end + + describe 'when Range is valid' do + before :each do + stub(sinatra).request do |klass| + stub(klass).env do + { 'Range' => range } + end + end + end + + describe 'UUID' do + describe 'only sort_by' do + let(:range) { 'id' } + + it 'returns Hash' do + result = + { + sort_by: 'id' + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, first' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, args' do + let(:range) { 'id; max=1000' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, count' do + let(:range) { 'id/400' } + + it 'returns Hash' do + result = + { + sort_by: 'id' + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, first, last' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, first, args' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef; max=1000' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, first, count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef/400' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, first, last, args' do + describe 'one arg' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end + end + + describe 'more args' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000,order=desc' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + end + + describe 'only sort_by, first, last, args, count' do + describe 'one arg' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000' } + } + assert_equal result, subject.request_options + end + end + + describe 'more args' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + end + + describe 'only sort_by, first, last, count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + last: '01234567-89ab-cdef-0123-456789abcdef' + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, first, args, count' do + let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + first: '01234567-89ab-cdef-0123-456789abcdef', + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, args' do + let(:range) { 'id; max=1000,order=desc' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, args, count' do + let(:range) { 'id/400; max=1000,order=desc' } + + it 'returns Hash' do + result = + { + sort_by: 'id', + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + + describe 'only sort_by, count' do + let(:range) { 'id/400' } + + it 'returns Hash' do + result = + { + sort_by: 'id' + } + assert_equal result, subject.request_options + end + end + end + + describe 'timestamp in iso8601' do + let(:range) { "#{sort_by} #{first}..#{last}/400; max=1000,order=desc" } + let(:sort_by) { 'created_at' } + let(:first) { '1985-09-24T00:00:00+00:00' } + let(:last) { '2014-07-01T15:54:32+02:00' } + + it 'returns Hash' do + result = + { + sort_by: sort_by, + first: first, + last: last, + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + + describe 'fruits' do + let(:range) { "#{sort_by} #{first}..#{last}/400; max=1000,order=desc" } + let(:sort_by) { 'fruit' } + let(:first) { 'Apple' } + let(:last) { 'Banana' } + + it 'returns Hash' do + result = + { + sort_by: sort_by, + first: first, + last: last, + args: { max: '1000', order: 'desc' } + } + assert_equal result, subject.request_options + end + end + end + end + + describe '#build_range' do + it 'only sort_by' do + assert_equal 'id', + subject.build_range(:id, nil, nil, nil) + end + + it 'only sort_by, first' do + assert_equal 'id 100', + subject.build_range(:id, 100, nil, nil) + end + + it 'only sort_by, last' do + assert_equal 'id', + subject.build_range(:id, nil, 200, nil) + end + + it 'only sort_by, args' do + assert_equal 'id; max=300,order=desc', + subject.build_range(:id, nil, nil, max: 300, order: 'desc') + end + + it 'only sort_by, count' do + assert_equal 'id/1200', + subject.build_range(:id, nil, nil, nil, 1200) + end + + it 'only sort_by, first, last' do + assert_equal 'id 100..200', + subject.build_range(:id, 100, 200, nil) + end + + it 'only sort_by, first, args' do + assert_equal 'id 100; max=300,order=desc', + subject.build_range(:id, 100, nil, max: 300, order: 'desc') + end + + it 'only sort_by, first, count' do + assert_equal 'id 100/1200', + subject.build_range(:id, 100, nil, nil, 1200) + end + + it 'only sort_by, first, last, args' do + assert_equal 'id 100..200; max=300,order=desc', + subject.build_range(:id, 100, 200, max: 300, order: 'desc') + end + + it 'only sort_by, first, last, count' do + assert_equal 'id 100..200/1200', + subject.build_range(:id, 100, 200, nil, 1200) + end + + it 'only sort_by, first, args, count' do + assert_equal 'id 100/1200; max=300,order=desc', + subject.build_range(:id, 100, nil, { max: 300, order: 'desc' }, 1200) + end + + it 'only sort_by, args' do + assert_equal 'id; max=300,order=desc', + subject.build_range(:id, nil, nil, max: 300, order: 'desc') + end + + it 'only sort_by, args, count' do + assert_equal 'id/1200; max=300,order=desc', + subject.build_range(:id, nil, nil, { max: 300, order: 'desc' }, 1200) + end + + it 'only sort_by, count' do + assert_equal 'id/1200', + subject.build_range(:id, nil, nil, nil, 1200) + end + end + + describe '#will_paginate?' do + it 'converts max to integer' do + subject.instance_variable_set(:@options, args: { max: '1000' }) + stub(subject).count { 2000 } + assert subject.will_paginate? + end + end + + describe '#[]' do + describe 'allows to read #options with a convenience method' do + before :each do + mock(subject).options { { first: 1 } } + end + + it 'with symbol key' do + assert_equal subject[:first], 1 + end + + it 'with string key' do + assert_equal subject['first'], 1 + end + end + end + + describe '#[]=' do + describe 'allows to read #options with a convenience method' do + before :each do + subject.instance_variable_set(:@options, {}) + end + + it 'with symbol key' do + assert_equal nil, subject.options[:first] + subject[:first] = 1 + assert_equal 1, subject.options[:first] + end + + it 'with string key' do + assert_equal nil, subject.options[:first] + subject['first'] = 1 + assert_equal 1, subject.options[:first] + end + end + end +end diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index 327dc5b1..ece82ad8 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -18,520 +18,3 @@ end end end - -describe Pliny::Helpers::Paginator::Paginator do - subject { Pliny::Helpers::Paginator::Paginator.new(sinatra, count, opts) } - let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } - let(:sinatra) { dummy_class.new } - let(:count) { 4 } - let(:opts) { {} } - - describe '#run' do - it 'evaluates block' do - mock(subject).validate_options - mock(subject).set_headers - subject.instance_variable_set(:@options, args: { max: 200 }) - - result = - subject.run do |paginator| - paginator[:first] = 42 - end - - assert_equal 42, result - end - end - - describe '#request_options' do - it 'returns Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } } - end - assert_kind_of Hash, subject.request_options - end - - describe 'when Range is nil' do - it 'returns empty Hash' do - stub(sinatra).request do |klass| - stub(klass).env { {} } - end - assert_kind_of Hash, subject.request_options - assert_empty subject.request_options - end - end - - describe 'when Range is an empty string' do - it 'returns empty Hash' do - stub(sinatra).request do |klass| - stub(klass).env { { 'Range' => '' } } - end - assert_kind_of Hash, subject.request_options - assert_empty subject.request_options - end - end - - describe 'when Range is valid' do - before :each do - stub(sinatra).request do |klass| - stub(klass).env do - { 'Range' => range } - end - end - end - - describe 'UUID' do - describe 'only sort_by' do - let(:range) { 'id' } - - it 'returns Hash' do - result = - { - sort_by: 'id' - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, first' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef' - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, args' do - let(:range) { 'id; max=1000' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - args: { max: '1000' } - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, count' do - let(:range) { 'id/400' } - - it 'returns Hash' do - result = - { - sort_by: 'id' - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, first, last' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef' - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, first, args' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef; max=1000' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000' } - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, first, count' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef/400' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef' - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, first, last, args' do - describe 'one arg' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000' } - } - assert_equal result, subject.request_options - end - end - - describe 'more args' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=1000,order=desc' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options - end - end - end - - describe 'only sort_by, first, last, args, count' do - describe 'one arg' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000' } - } - assert_equal result, subject.request_options - end - end - - describe 'more args' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options - end - end - end - - describe 'only sort_by, first, last, count' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - last: '01234567-89ab-cdef-0123-456789abcdef' - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, first, args, count' do - let(:range) { 'id 01234567-89ab-cdef-0123-456789abcdef/400; max=1000,order=desc' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - first: '01234567-89ab-cdef-0123-456789abcdef', - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, args' do - let(:range) { 'id; max=1000,order=desc' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, args, count' do - let(:range) { 'id/400; max=1000,order=desc' } - - it 'returns Hash' do - result = - { - sort_by: 'id', - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options - end - end - - describe 'only sort_by, count' do - let(:range) { 'id/400' } - - it 'returns Hash' do - result = - { - sort_by: 'id' - } - assert_equal result, subject.request_options - end - end - end - - describe 'timestamp in iso8601' do - let(:range) { "#{sort_by} #{first}..#{last}/400; max=1000,order=desc" } - let(:sort_by) { 'created_at' } - let(:first) { '1985-09-24T00:00:00+00:00' } - let(:last) { '2014-07-01T15:54:32+02:00' } - - it 'returns Hash' do - result = - { - sort_by: sort_by, - first: first, - last: last, - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options - end - end - - describe 'fruits' do - let(:range) { "#{sort_by} #{first}..#{last}/400; max=1000,order=desc" } - let(:sort_by) { 'fruit' } - let(:first) { 'Apple' } - let(:last) { 'Banana' } - - it 'returns Hash' do - result = - { - sort_by: sort_by, - first: first, - last: last, - args: { max: '1000', order: 'desc' } - } - assert_equal result, subject.request_options - end - end - end - end - - describe '#build_range' do - it 'only sort_by' do - assert_equal 'id', - subject.build_range(:id, nil, nil, nil) - end - - it 'only sort_by, first' do - assert_equal 'id 100', - subject.build_range(:id, 100, nil, nil) - end - - it 'only sort_by, last' do - assert_equal 'id', - subject.build_range(:id, nil, 200, nil) - end - - it 'only sort_by, args' do - assert_equal 'id; max=300,order=desc', - subject.build_range(:id, nil, nil, max: 300, order: 'desc') - end - - it 'only sort_by, count' do - assert_equal 'id/1200', - subject.build_range(:id, nil, nil, nil, 1200) - end - - it 'only sort_by, first, last' do - assert_equal 'id 100..200', - subject.build_range(:id, 100, 200, nil) - end - - it 'only sort_by, first, args' do - assert_equal 'id 100; max=300,order=desc', - subject.build_range(:id, 100, nil, max: 300, order: 'desc') - end - - it 'only sort_by, first, count' do - assert_equal 'id 100/1200', - subject.build_range(:id, 100, nil, nil, 1200) - end - - it 'only sort_by, first, last, args' do - assert_equal 'id 100..200; max=300,order=desc', - subject.build_range(:id, 100, 200, max: 300, order: 'desc') - end - - it 'only sort_by, first, last, count' do - assert_equal 'id 100..200/1200', - subject.build_range(:id, 100, 200, nil, 1200) - end - - it 'only sort_by, first, args, count' do - assert_equal 'id 100/1200; max=300,order=desc', - subject.build_range(:id, 100, nil, { max: 300, order: 'desc' }, 1200) - end - - it 'only sort_by, args' do - assert_equal 'id; max=300,order=desc', - subject.build_range(:id, nil, nil, max: 300, order: 'desc') - end - - it 'only sort_by, args, count' do - assert_equal 'id/1200; max=300,order=desc', - subject.build_range(:id, nil, nil, { max: 300, order: 'desc' }, 1200) - end - - it 'only sort_by, count' do - assert_equal 'id/1200', - subject.build_range(:id, nil, nil, nil, 1200) - end - end - - describe '#will_paginate?' do - it 'converts max to integer' do - subject.instance_variable_set(:@options, args: { max: '1000' }) - stub(subject).count { 2000 } - assert subject.will_paginate? - end - end - - describe '#[]' do - describe 'allows to read #options with a convenience method' do - before :each do - mock(subject).options { { first: 1 } } - end - - it 'with symbol key' do - assert_equal subject[:first], 1 - end - - it 'with string key' do - assert_equal subject['first'], 1 - end - end - end - - describe '#[]=' do - describe 'allows to read #options with a convenience method' do - before :each do - subject.instance_variable_set(:@options, {}) - end - - it 'with symbol key' do - assert_equal nil, subject.options[:first] - subject[:first] = 1 - assert_equal 1, subject.options[:first] - end - - it 'with string key' do - assert_equal nil, subject.options[:first] - subject['first'] = 1 - assert_equal 1, subject.options[:first] - end - end - end -end - -describe Pliny::Helpers::Paginator::IntegerPaginator do - subject { Pliny::Helpers::Paginator::IntegerPaginator.new(sinatra, count, opts) } - let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } - let(:sinatra) { dummy_class.new } - let(:count) { 4 } - let(:opts) { {} } - - before :each do - any_instance_of(Pliny::Helpers::Paginator::Paginator) do |klass| - stub(klass).request_options { {} } - stub(klass).set_headers - end - end - - describe '#run' do - it 'returns Hash' do - result = subject.run - - assert_kind_of Hash, result - - exp = - { - order_by: :id, - offset: nil, - limit: 200 - } - - assert_equal exp, result - end - end - - describe '#calculate_pages' do - let(:opts) { { first: 100, args: { max: 300 } } } - - describe 'when count < max in current range' do - let(:count) { 200 } - - it 'calculates :last' do - assert_equal 199, subject.calculate_pages[:last] - end - - it 'calculates :next_first' do - assert_equal nil, subject.calculate_pages[:next_first] - end - - it 'calculates :next_last' do - assert_equal nil, subject.calculate_pages[:next_last] - end - end - - describe 'when count > max in current range' do - let(:count) { 3000 } - - it 'calculates :last' do - assert_equal 399, subject.calculate_pages[:last] - end - - it 'calculates :next_first' do - assert_equal 400, subject.calculate_pages[:next_first] - end - - describe 'when count < max in next range' do - let(:count) { 600 } - - it 'calculates :next_last' do - assert_equal 599, subject.calculate_pages[:next_last] - end - end - - describe 'when count > max in next range' do - let(:count) { 3000 } - - it 'calculates :next_last' do - assert_equal 699, subject.calculate_pages[:next_last] - end - end - end - end -end From 3508c6ce781f9ab91bd4b28e8f143d26cf84d38d Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 12:25:41 +0200 Subject: [PATCH 59/69] Always apply limit --- lib/pliny/helpers/paginator.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 1cdf825f..4cc752d2 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -7,11 +7,13 @@ def paginator(count, options = {}, &block) def uuid_paginator(resource, options = {}) paginator(resource.count, options) do |paginator| sort_by_conversion = { id: :uuid } - resources = resource.order(sort_by_conversion[paginator[:sort_by].to_sym]) + max = paginator[:args][:max].to_i + resources = + resource + .order(sort_by_conversion[paginator[:sort_by].to_sym]) + .limit(max) if paginator.will_paginate? - max = paginator[:args][:max].to_i - resources = resources.limit(max) resources = resources.where { uuid >= Sequel.cast(paginator[:first], :uuid) } if paginator[:first] paginator.options.merge! \ From 91c5425b48fdf865b12434faf02c002c36ea5905 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Wed, 2 Jul 2014 12:30:14 +0200 Subject: [PATCH 60/69] Add test for #uuid_paginator --- test/helpers/paginator_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/helpers/paginator_test.rb b/test/helpers/paginator_test.rb index ece82ad8..b7ef407b 100644 --- a/test/helpers/paginator_test.rb +++ b/test/helpers/paginator_test.rb @@ -11,6 +11,15 @@ end end + describe '#uuid_paginator' do + it 'calls #paginator' do + resource = Class.new + stub(resource).count { 12 } + mock(sinatra).paginator(12, sort_by: :foo) + sinatra.uuid_paginator(resource, sort_by: :foo) + end + end + describe '#integer_paginator' do it 'calls IntegerPaginator.run' do mock(Pliny::Helpers::Paginator::IntegerPaginator).run(sinatra, 12, sort_by: :foo) From 0a10645e4252b2d1d5463e6af2f194a33401510f Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Sun, 6 Jul 2014 11:33:04 +0200 Subject: [PATCH 61/69] Rename #validate_options to #valid_options? --- lib/pliny/helpers/paginator/paginator.rb | 6 +++--- test/helpers/paginator/paginator_test.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pliny/helpers/paginator/paginator.rb b/lib/pliny/helpers/paginator/paginator.rb index c00b4594..cb21bbb9 100644 --- a/lib/pliny/helpers/paginator/paginator.rb +++ b/lib/pliny/helpers/paginator/paginator.rb @@ -37,7 +37,7 @@ def initialize(sinatra, count, options = {}) def run result = yield(self) if block_given? - validate_options + halt unless valid_options? set_headers result @@ -79,8 +79,8 @@ def parse_request_options(match) request_options end - def validate_options - halt unless options[:sort_by] && options[:accepted_ranges].include?(options[:sort_by].to_sym) + def valid_options? + options[:sort_by] && options[:accepted_ranges].include?(options[:sort_by].to_sym) end def halt diff --git a/test/helpers/paginator/paginator_test.rb b/test/helpers/paginator/paginator_test.rb index 39c26769..2ee1f0f9 100644 --- a/test/helpers/paginator/paginator_test.rb +++ b/test/helpers/paginator/paginator_test.rb @@ -9,7 +9,7 @@ describe '#run' do it 'evaluates block' do - mock(subject).validate_options + mock(subject).valid_options? { true } mock(subject).set_headers subject.instance_variable_set(:@options, args: { max: 200 }) From 8e503c4471a8bc14bd09f636be0bbd464f9ba4f6 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Sun, 6 Jul 2014 11:33:37 +0200 Subject: [PATCH 62/69] Remove all the "magic" from #options --- lib/pliny/helpers/paginator/integer_paginator.rb | 12 +++++------- lib/pliny/helpers/paginator/paginator.rb | 10 ++++------ test/helpers/paginator/paginator_test.rb | 1 + 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/pliny/helpers/paginator/integer_paginator.rb b/lib/pliny/helpers/paginator/integer_paginator.rb index a702c7fc..dbae8784 100644 --- a/lib/pliny/helpers/paginator/integer_paginator.rb +++ b/lib/pliny/helpers/paginator/integer_paginator.rb @@ -2,7 +2,7 @@ module Pliny::Helpers module Paginator class IntegerPaginator attr_reader :sinatra, :count - attr_writer :options + attr_accessor :options class << self def run(*args, &block) @@ -13,10 +13,12 @@ def run(*args, &block) def initialize(sinatra, count, options = {}) @sinatra = sinatra @count = count - @opts = options + @options = options end def run + options = calculate_pages + { order_by: options[:sort_by], offset: options[:first], @@ -24,12 +26,8 @@ def run } end - def options - @options ||= calculate_pages - end - def calculate_pages - Paginator.run(self, count, @opts) do |paginator| + Paginator.run(self, count, options) do |paginator| max = paginator[:args][:max].to_i paginator[:last] = paginator[:first].to_i + max - 1 diff --git a/lib/pliny/helpers/paginator/paginator.rb b/lib/pliny/helpers/paginator/paginator.rb index cb21bbb9..90eb5e32 100644 --- a/lib/pliny/helpers/paginator/paginator.rb +++ b/lib/pliny/helpers/paginator/paginator.rb @@ -9,8 +9,8 @@ class Paginator ARGS = /(?.*)/ RANGE = /\A#{SORT_BY}(?:\s+#{FIRST})?(\.{2}#{LAST})?#{COUNT}?(;\s*#{ARGS})?\z/ + attr_accessor :options attr_reader :sinatra, :count - attr_writer :options class << self def run(*args, &block) @@ -21,7 +21,7 @@ def run(*args, &block) def initialize(sinatra, count, options = {}) @sinatra = sinatra @count = count - @opts = + @options = { accepted_ranges: [:id], sort_by: :id, @@ -35,6 +35,8 @@ def initialize(sinatra, count, options = {}) end def run + options.merge!(request_options) + result = yield(self) if block_given? halt unless valid_options? @@ -43,10 +45,6 @@ def run result end - def options - @options ||= @opts.merge(request_options) - end - def request_options range = sinatra.request.env['Range'] return {} if range.nil? || range.empty? diff --git a/test/helpers/paginator/paginator_test.rb b/test/helpers/paginator/paginator_test.rb index 2ee1f0f9..6ef0b5cf 100644 --- a/test/helpers/paginator/paginator_test.rb +++ b/test/helpers/paginator/paginator_test.rb @@ -9,6 +9,7 @@ describe '#run' do it 'evaluates block' do + mock(subject).request_options { {} } mock(subject).valid_options? { true } mock(subject).set_headers subject.instance_variable_set(:@options, args: { max: 200 }) From 3fe462bd8c619565d76d7dcf0d23deaa29398037 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Sun, 6 Jul 2014 11:54:05 +0200 Subject: [PATCH 63/69] Improve endpoint scaffold --- lib/pliny/commands/generator/base.rb | 4 ++++ lib/pliny/commands/generator/endpoint.rb | 1 + lib/pliny/templates/endpoint_scaffold.erb | 5 ++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pliny/commands/generator/base.rb b/lib/pliny/commands/generator/base.rb index 90b24729..47c3fb20 100644 --- a/lib/pliny/commands/generator/base.rb +++ b/lib/pliny/commands/generator/base.rb @@ -27,6 +27,10 @@ def field_name name.tableize.singularize end + def fields_name + name.tableize.pluralize + end + def pluralized_file_name name.tableize end diff --git a/lib/pliny/commands/generator/endpoint.rb b/lib/pliny/commands/generator/endpoint.rb index 26aa92ce..16d2dff6 100644 --- a/lib/pliny/commands/generator/endpoint.rb +++ b/lib/pliny/commands/generator/endpoint.rb @@ -10,6 +10,7 @@ def create plural_class_name: plural_class_name, singular_class_name: singular_class_name, field_name: field_name, + fields_name: fields_name, url_path: url_path) display "created endpoint file #{endpoint}" display 'add the following to lib/routes.rb:' diff --git a/lib/pliny/templates/endpoint_scaffold.erb b/lib/pliny/templates/endpoint_scaffold.erb index 49262d0c..2251c36e 100644 --- a/lib/pliny/templates/endpoint_scaffold.erb +++ b/lib/pliny/templates/endpoint_scaffold.erb @@ -6,9 +6,8 @@ module Endpoints end get do - resources = uuid_paginator(<%= singular_class_name %>, args: { max: 10 }) - - encode serialize(resources) + <%= fields_name %> = uuid_paginator(<%= singular_class_name %>, args: { max: 10 }) + encode serialize(%= fields_name %) end post do From ef21f3798881fb4742bf60ac76d41176c8dfaca7 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Fri, 8 Aug 2014 17:56:53 +0200 Subject: [PATCH 64/69] Add basic documentation to paginator methods --- lib/pliny/helpers/paginator.rb | 34 ++++++++++++++++++++++++ lib/pliny/helpers/paginator/paginator.rb | 17 ++++++++++++ 2 files changed, 51 insertions(+) diff --git a/lib/pliny/helpers/paginator.rb b/lib/pliny/helpers/paginator.rb index 4cc752d2..9069abfc 100644 --- a/lib/pliny/helpers/paginator.rb +++ b/lib/pliny/helpers/paginator.rb @@ -1,9 +1,28 @@ module Pliny::Helpers module Paginator + + # Sets the HTTP Range header for pagination if necessary + # + # @see uuid_paginator + # @see integer_paginator + # @see Pliny::Helpers::Paginator::Paginator def paginator(count, options = {}, &block) Paginator.run(self, count, options, &block) end + # paginator for UUID columns + # + # @example call in the Endpoint + # articles = uuid_paginator(Article, args: { max: 10 }) + # + # @example HTTP header returned + # Content-Range: id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef/400; max=10 + # Next-Range: id 76543210-89ab-cdef-0123-456789abcdef..76543210-89ab-cdef-0123-456789abcdef/400; max=10 + # + # @param [Object] resource the resource to paginate + # @param [Hash] options + # @return [Object] modified resource (by order, limit and offset) + # @see paginator def uuid_paginator(resource, options = {}) paginator(resource.count, options) do |paginator| sort_by_conversion = { id: :uuid } @@ -27,6 +46,21 @@ def uuid_paginator(resource, options = {}) end end + # paginator for integer columns + # + # @example call in the Endpoint + # paginator = integer_paginator(User.count) + # users = User.order(paginator[:order_by]).limit(paginator[:limit]).offset(paginator[:offset]) + # + # @example HTTP header returned + # Content-Range: id 0..199/400; max=200 + # Next-Range: id 200..399/400; max=200 + # + # @param [Integer] count the count of resources + # @param [Hash] options + # @return [Hash] with :order_by and calculated :offset and :limit + # @see paginator + # @see Pliny::Helpers::Paginator::IntegerPaginator def integer_paginator(count, options = {}) IntegerPaginator.run(self, count, options) end diff --git a/lib/pliny/helpers/paginator/paginator.rb b/lib/pliny/helpers/paginator/paginator.rb index 90eb5e32..f9525085 100644 --- a/lib/pliny/helpers/paginator/paginator.rb +++ b/lib/pliny/helpers/paginator/paginator.rb @@ -18,6 +18,18 @@ def run(*args, &block) end end + # Initializes an instance of Paginator + # + # @param [Sinatra::Base] the controller calling the paginator + # @param [Integer] count the count of resources + # @param [Hash] options for the paginator + # @option options [Array] :accepted_ranges ([:id]) fields allowed to sort the listing + # @option options [Symbol] :sort_by (:id) field to sort the listing + # @option options [String] :first ID or name of the first element of the current page + # @option options [String] :last ID or name of the last element of the current page + # @option options [String] :next_first ID or name of the first element of the next page + # @option options [String] :next_last ID or name of the last element of the next page + # @option options [Hash] :args ({max: 200}) arguments for the HTTP Range header def initialize(sinatra, count, options = {}) @sinatra = sinatra @count = count @@ -34,6 +46,11 @@ def initialize(sinatra, count, options = {}) .merge(options) end + # executes the paginator and sets the HTTP headers if necessary + # + # @yieldparam paginator [Paginator] + # @yieldreturn [Object] + # @return [Object] the result of the block yielded def run options.merge!(request_options) From ee7e3e39e6b2f3e1917d41eda1b33a99b546dda8 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Thu, 4 Sep 2014 17:32:09 +0200 Subject: [PATCH 65/69] Fix indentation --- lib/pliny/templates/endpoint_scaffold.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/templates/endpoint_scaffold.erb b/lib/pliny/templates/endpoint_scaffold.erb index 2251c36e..2b3fbaf6 100644 --- a/lib/pliny/templates/endpoint_scaffold.erb +++ b/lib/pliny/templates/endpoint_scaffold.erb @@ -7,7 +7,7 @@ module Endpoints get do <%= fields_name %> = uuid_paginator(<%= singular_class_name %>, args: { max: 10 }) - encode serialize(%= fields_name %) + encode serialize(%= fields_name %) end post do From a69f552388cca4a842c0ec25c87d508cdb7034e4 Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 1 Dec 2014 16:11:27 +0100 Subject: [PATCH 66/69] Add paginator to Endpoints::Base Original pull request: https://github.com/interagent/pliny-template/pull/120 --- lib/template/lib/endpoints/base.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/template/lib/endpoints/base.rb b/lib/template/lib/endpoints/base.rb index e745816c..80f76601 100644 --- a/lib/template/lib/endpoints/base.rb +++ b/lib/template/lib/endpoints/base.rb @@ -6,6 +6,7 @@ class Base < Sinatra::Base helpers Pliny::Helpers::Encode helpers Pliny::Helpers::Params + helpers Pliny::Helpers::Paginator set :dump_errors, false set :raise_errors, true From 40d232fce01c30bdae36a2ea2e63e603b58c138f Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 1 Dec 2014 16:26:06 +0100 Subject: [PATCH 67/69] Fix call to fields_name in endpoint scaffold template [ci skip] --- lib/pliny/templates/endpoint_scaffold.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pliny/templates/endpoint_scaffold.erb b/lib/pliny/templates/endpoint_scaffold.erb index 2b3fbaf6..66da8d43 100644 --- a/lib/pliny/templates/endpoint_scaffold.erb +++ b/lib/pliny/templates/endpoint_scaffold.erb @@ -7,7 +7,7 @@ module Endpoints get do <%= fields_name %> = uuid_paginator(<%= singular_class_name %>, args: { max: 10 }) - encode serialize(%= fields_name %) + encode serialize(<%= fields_name %>) end post do From ec557b4094b0f757dbce1207ed0d0681bb41055c Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 29 Dec 2014 15:20:40 +0100 Subject: [PATCH 68/69] Move tests to spec directory --- .../helpers/paginator/integer_paginator_spec.rb | 2 +- .../helpers/paginator/paginator_spec.rb | 2 +- .../helpers/paginator_test.rb => spec/helpers/paginator_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename test/helpers/paginator/integer_paginator_test.rb => spec/helpers/paginator/integer_paginator_spec.rb (98%) rename test/helpers/paginator/paginator_test.rb => spec/helpers/paginator/paginator_spec.rb (99%) rename test/helpers/paginator_test.rb => spec/helpers/paginator_spec.rb (97%) diff --git a/test/helpers/paginator/integer_paginator_test.rb b/spec/helpers/paginator/integer_paginator_spec.rb similarity index 98% rename from test/helpers/paginator/integer_paginator_test.rb rename to spec/helpers/paginator/integer_paginator_spec.rb index 791ba824..d51f3aad 100644 --- a/test/helpers/paginator/integer_paginator_test.rb +++ b/spec/helpers/paginator/integer_paginator_spec.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require 'spec_helper' describe Pliny::Helpers::Paginator::IntegerPaginator do subject { Pliny::Helpers::Paginator::IntegerPaginator.new(sinatra, count, opts) } diff --git a/test/helpers/paginator/paginator_test.rb b/spec/helpers/paginator/paginator_spec.rb similarity index 99% rename from test/helpers/paginator/paginator_test.rb rename to spec/helpers/paginator/paginator_spec.rb index 6ef0b5cf..866107f9 100644 --- a/test/helpers/paginator/paginator_test.rb +++ b/spec/helpers/paginator/paginator_spec.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require 'spec_helper' describe Pliny::Helpers::Paginator::Paginator do subject { Pliny::Helpers::Paginator::Paginator.new(sinatra, count, opts) } diff --git a/test/helpers/paginator_test.rb b/spec/helpers/paginator_spec.rb similarity index 97% rename from test/helpers/paginator_test.rb rename to spec/helpers/paginator_spec.rb index b7ef407b..06d6064c 100644 --- a/test/helpers/paginator_test.rb +++ b/spec/helpers/paginator_spec.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require 'spec_helper' describe Pliny::Helpers::Paginator do let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } From 60d3bb372b95420facdf6bc71e4f3f57946ee6fd Mon Sep 17 00:00:00 2001 From: "Tobias L. Maier" Date: Mon, 29 Dec 2014 15:25:44 +0100 Subject: [PATCH 69/69] Use described_class method in specs --- spec/helpers/paginator/integer_paginator_spec.rb | 2 +- spec/helpers/paginator/paginator_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/helpers/paginator/integer_paginator_spec.rb b/spec/helpers/paginator/integer_paginator_spec.rb index d51f3aad..c53853b6 100644 --- a/spec/helpers/paginator/integer_paginator_spec.rb +++ b/spec/helpers/paginator/integer_paginator_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Pliny::Helpers::Paginator::IntegerPaginator do - subject { Pliny::Helpers::Paginator::IntegerPaginator.new(sinatra, count, opts) } + subject { described_class.new(sinatra, count, opts) } let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } let(:sinatra) { dummy_class.new } let(:count) { 4 } diff --git a/spec/helpers/paginator/paginator_spec.rb b/spec/helpers/paginator/paginator_spec.rb index 866107f9..241daebc 100644 --- a/spec/helpers/paginator/paginator_spec.rb +++ b/spec/helpers/paginator/paginator_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Pliny::Helpers::Paginator::Paginator do - subject { Pliny::Helpers::Paginator::Paginator.new(sinatra, count, opts) } + subject { described_class.new(sinatra, count, opts) } let(:dummy_class) { Class.new { include Pliny::Helpers::Paginator } } let(:sinatra) { dummy_class.new } let(:count) { 4 }