kaminari-surface 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +5 -0
- data/.rdoc_options +16 -0
- data/.rspec +1 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.md +96 -0
- data/Rakefile +25 -0
- data/TRACKER.md +17 -0
- data/capture.gif +0 -0
- data/example/.gitignore +17 -0
- data/example/Gemfile +52 -0
- data/example/Gemfile.lock +179 -0
- data/example/README.rdoc +28 -0
- data/example/Rakefile +6 -0
- data/example/app/assets/javascripts/application.js +16 -0
- data/example/app/assets/stylesheets/application.css +15 -0
- data/example/app/controllers/application_controller.rb +5 -0
- data/example/app/controllers/products_controller.rb +6 -0
- data/example/app/helpers/application_helper.rb +2 -0
- data/example/app/models/product.rb +2 -0
- data/example/app/views/layouts/application.html.erb +23 -0
- data/example/app/views/products/index.haml +18 -0
- data/example/bin/bundle +3 -0
- data/example/bin/rails +9 -0
- data/example/bin/rake +9 -0
- data/example/bin/setup +29 -0
- data/example/bin/spring +15 -0
- data/example/config.ru +4 -0
- data/example/config/application.rb +26 -0
- data/example/config/boot.rb +3 -0
- data/example/config/database.yml +25 -0
- data/example/config/environment.rb +5 -0
- data/example/config/environments/development.rb +41 -0
- data/example/config/environments/production.rb +79 -0
- data/example/config/environments/test.rb +42 -0
- data/example/config/initializers/assets.rb +11 -0
- data/example/config/initializers/backtrace_silencers.rb +7 -0
- data/example/config/initializers/cookies_serializer.rb +3 -0
- data/example/config/initializers/filter_parameter_logging.rb +4 -0
- data/example/config/initializers/inflections.rb +16 -0
- data/example/config/initializers/mime_types.rb +4 -0
- data/example/config/initializers/session_store.rb +3 -0
- data/example/config/initializers/surface.rb +74 -0
- data/example/config/initializers/wrap_parameters.rb +14 -0
- data/example/config/locales/en.yml +23 -0
- data/example/config/routes.rb +58 -0
- data/example/config/secrets.yml +22 -0
- data/example/db/migrate/20160501023444_create_products.rb +9 -0
- data/example/db/schema.rb +22 -0
- data/example/db/seeds.rb +7 -0
- data/example/lib/tasks/seed.rake +9 -0
- data/example/public/404.html +67 -0
- data/example/public/422.html +67 -0
- data/example/public/500.html +66 -0
- data/example/public/favicon.ico +0 -0
- data/example/public/robots.txt +5 -0
- data/example/test/fixtures/products.yml +7 -0
- data/example/test/models/product_test.rb +7 -0
- data/example/test/test_helper.rb +10 -0
- data/kaminari-surface.gemspec +51 -0
- data/lib/kaminari/surface.rb +15 -0
- data/lib/kaminari/surface/data_mapper_extension.rb +15 -0
- data/lib/kaminari/surface/page_scope_methods.rb +78 -0
- data/lib/kaminari/surface/paginatable_array_extension.rb +27 -0
- data/lib/kaminari/surface/version.rb +5 -0
- data/spec/acceptance/active_record_spec.rb +83 -0
- data/spec/acceptance/data_mapper_spec.rb +83 -0
- data/spec/acceptance/mongo_mapper_spec.rb +84 -0
- data/spec/acceptance/mongoid_spec.rb +84 -0
- data/spec/spec_helper.rb +112 -0
- data/spec/surface/page_scope_methods_spec.rb +129 -0
- data/spec/surface_spec.rb +8 -0
- metadata +364 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/500.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>We're sorry, but something went wrong.</h1>
|
62
|
+
</div>
|
63
|
+
<p>If you are the application owner check the logs for more information.</p>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|
File without changes
|
@@ -0,0 +1,10 @@
|
|
1
|
+
ENV['RAILS_ENV'] ||= 'test'
|
2
|
+
require File.expand_path('../../config/environment', __FILE__)
|
3
|
+
require 'rails/test_help'
|
4
|
+
|
5
|
+
class ActiveSupport::TestCase
|
6
|
+
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
|
7
|
+
fixtures :all
|
8
|
+
|
9
|
+
# Add more helper methods to be used by all tests here...
|
10
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'kaminari/surface/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = "kaminari-surface"
|
9
|
+
gem.version = Kaminari::Surface::VERSION
|
10
|
+
gem.summary = %q{Surface the final search result page when desirable}
|
11
|
+
gem.description = %q{An extension to Kaminari pagination that brings the final page of a search results to the surface, and prevents unnecessary paging requests.}
|
12
|
+
gem.license = "MIT"
|
13
|
+
gem.authors = ["James Kassemi"]
|
14
|
+
gem.email = "jkassemi@gmail.com"
|
15
|
+
gem.homepage = "https://github.com/jkassemi/kaminari-surface"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
|
19
|
+
`git submodule --quiet foreach --recursive pwd`.split($/).each do |submodule|
|
20
|
+
submodule.sub!("#{Dir.pwd}/",'')
|
21
|
+
|
22
|
+
Dir.chdir(submodule) do
|
23
|
+
`git ls-files`.split($/).map do |subpath|
|
24
|
+
gem.files << File.join(submodule,subpath)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
29
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
30
|
+
gem.require_paths = ['lib']
|
31
|
+
|
32
|
+
gem.add_runtime_dependency 'kaminari', '~> 0.16'
|
33
|
+
|
34
|
+
gem.add_development_dependency 'pry', '~> 0.10'
|
35
|
+
gem.add_development_dependency 'bundler', '~> 1'
|
36
|
+
gem.add_development_dependency 'faker', '~> 1.4'
|
37
|
+
gem.add_development_dependency 'rake', '~> 10.0'
|
38
|
+
gem.add_development_dependency 'rdoc', '~> 4.0'
|
39
|
+
gem.add_development_dependency 'rspec', '~> 3.0'
|
40
|
+
gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
41
|
+
|
42
|
+
gem.add_development_dependency 'activerecord'
|
43
|
+
gem.add_development_dependency 'sqlite3'
|
44
|
+
gem.add_development_dependency 'data_mapper'
|
45
|
+
gem.add_development_dependency 'dm-sqlite-adapter'
|
46
|
+
gem.add_development_dependency 'kaminari-data_mapper'
|
47
|
+
gem.add_development_dependency 'mongoid'
|
48
|
+
gem.add_development_dependency 'kaminari-mongoid'
|
49
|
+
gem.add_development_dependency 'mongo_mapper'
|
50
|
+
gem.add_development_dependency 'kaminari-mongo_mapper'
|
51
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'kaminari'
|
2
|
+
|
3
|
+
require 'kaminari/surface/version'
|
4
|
+
require 'kaminari/surface/page_scope_methods'
|
5
|
+
|
6
|
+
Kaminari::PageScopeMethods.prepend(Kaminari::Surface::PageScopeMethods)
|
7
|
+
|
8
|
+
require 'kaminari/surface/paginatable_array_extension'
|
9
|
+
require 'kaminari/models/array_extension'
|
10
|
+
|
11
|
+
Kaminari::PaginatableArray.prepend(Kaminari::Surface::PaginatableArrayExtension)
|
12
|
+
|
13
|
+
if defined?(DataMapper)
|
14
|
+
# require 'kaminari/surface/data_mapper_extension'
|
15
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Kaminari
|
2
|
+
module Surface
|
3
|
+
module PageScopeMethods
|
4
|
+
def per(num)
|
5
|
+
fail ArgumentError if defined?(@_surface_value)
|
6
|
+
@_per_value = num
|
7
|
+
extend_state_to(super)
|
8
|
+
end
|
9
|
+
|
10
|
+
def padding(num)
|
11
|
+
fail ArgumentError if defined?(@_surface_value)
|
12
|
+
_padding = num
|
13
|
+
extend_state_to(super)
|
14
|
+
end
|
15
|
+
|
16
|
+
def surface(num)
|
17
|
+
remaining = total_count - (offset_value + per_value)
|
18
|
+
|
19
|
+
if remaining > 0 && remaining <= num
|
20
|
+
result = limit(limit_value + remaining).offset(offset_value)
|
21
|
+
else
|
22
|
+
result = self
|
23
|
+
end
|
24
|
+
|
25
|
+
@_surface_value = num
|
26
|
+
|
27
|
+
extend_state_to(result.extend(Kaminari::PageScopeMethods))
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_page
|
31
|
+
offset_without_padding = offset_value
|
32
|
+
offset_without_padding -= @_padding if padding_enabled?
|
33
|
+
offset_without_padding = 0 if offset_without_padding < 0
|
34
|
+
|
35
|
+
(offset_without_padding / per_value) + 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def total_pages
|
39
|
+
count_without_padding = total_count
|
40
|
+
count_without_padding -= @_padding if padding_enabled?
|
41
|
+
count_without_padding = 0 if count_without_padding < 0
|
42
|
+
|
43
|
+
total_pages_count = ((count_without_padding - surface_value).to_f / per_value).ceil
|
44
|
+
|
45
|
+
if max_pages.present? && max_pages < total_pages_count
|
46
|
+
max_pages
|
47
|
+
else
|
48
|
+
total_pages_count
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def num_pages
|
53
|
+
total_pages
|
54
|
+
end
|
55
|
+
|
56
|
+
def per_value
|
57
|
+
(@_per_value || default_per_page).to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
def surface_value
|
61
|
+
(@_surface_value || 0).to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def padding_enabled?
|
67
|
+
defined?(@_padding) && @_padding
|
68
|
+
end
|
69
|
+
|
70
|
+
def extend_state_to(target)
|
71
|
+
target.instance_variable_set(:@_per_value, @_per_value)
|
72
|
+
target.instance_variable_set(:@_surface_value, @_surface_value)
|
73
|
+
target.instance_variable_set(:@_padding, @_padding)
|
74
|
+
target
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Kaminari
|
2
|
+
module Surface
|
3
|
+
module PaginatableArrayExtension
|
4
|
+
def limit(*_args)
|
5
|
+
copy_attrs(super)
|
6
|
+
end
|
7
|
+
|
8
|
+
def offset(*_args)
|
9
|
+
copy_attrs(super)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def copy_attrs(target)
|
15
|
+
if defined?(@_per_value)
|
16
|
+
target.instance_variable_set(:@_per_value, @_per_value)
|
17
|
+
end
|
18
|
+
|
19
|
+
if defined?(@_surface_value)
|
20
|
+
target.instance_variable_set(:@_surface_value, @_surface_value)
|
21
|
+
end
|
22
|
+
|
23
|
+
target
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'ActiveRecord integration' do
|
4
|
+
before(:context) do
|
5
|
+
23.times { SampleModels::ActiveRecord::Widget.create! }
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'first page when no surfacing requested' do
|
9
|
+
subject(:widgets) { SampleModels::ActiveRecord::Widget.page(1).per(5) }
|
10
|
+
|
11
|
+
it 'has 5 pages' do
|
12
|
+
expect(widgets.total_pages).to eq(5)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has 5 widgets' do
|
16
|
+
expect(widgets.size).to eq(5)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has 23 total widgets' do
|
20
|
+
expect(widgets.total_count).to eq(23)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'first page when surfacing is 0' do
|
25
|
+
subject(:widgets) { SampleModels::ActiveRecord::Widget.page(1).per(5) }
|
26
|
+
|
27
|
+
it 'has 5 pages' do
|
28
|
+
expect(widgets.total_pages).to eq(5)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'first page when surfacing is 3' do
|
33
|
+
subject(:widgets) { SampleModels::ActiveRecord::Widget.page(1).per(5).surface(3) }
|
34
|
+
|
35
|
+
it 'has 4 pages' do
|
36
|
+
expect(widgets.total_pages).to eq(4)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'has 5 widgets' do
|
40
|
+
expect(widgets.size).to eq(5)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'last page when surfacing is 3' do
|
45
|
+
subject(:widgets){ SampleModels::ActiveRecord::Widget.page(4).per(5).surface(3) }
|
46
|
+
|
47
|
+
it 'has 8 widgets' do
|
48
|
+
expect(widgets.size).to eq(8)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'last page when surfacing is 8 (per + remainder)' do
|
53
|
+
subject(:widgets) { SampleModels::ActiveRecord::Widget.page(3).per(5).surface(8) }
|
54
|
+
|
55
|
+
it 'has 3 pages' do
|
56
|
+
expect(widgets.total_pages).to eq(3)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'has 13 widgets' do
|
60
|
+
expect(widgets.size).to eq(13)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'has 23 total widgets' do
|
64
|
+
expect(widgets.total_count).to eq(23)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when `per` is called after `surface`' do
|
69
|
+
subject(:widgets){ ->{ SampleModels::ActiveRecord::Widget.page(1).surface(5).per(5) } }
|
70
|
+
|
71
|
+
it 'should raise an `ArgumentError`' do
|
72
|
+
expect(subject).to raise_error(ArgumentError)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when `padding` is called after `surface`' do
|
77
|
+
subject(:widgets){ ->{ SampleModels::ActiveRecord::Widget.page(1).surface(5).padding(5) } }
|
78
|
+
|
79
|
+
it 'should raise an `ArgumentError`' do
|
80
|
+
expect(subject).to raise_error(ArgumentError)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'DataMapper integration' do
|
4
|
+
before(:context) do
|
5
|
+
23.times { SampleModels::DataMapper::Widget.create }
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'first page when no surfacing requested' do
|
9
|
+
subject(:widgets) { SampleModels::DataMapper::Widget.page(1).per(5) }
|
10
|
+
|
11
|
+
it 'has 5 pages' do
|
12
|
+
expect(widgets.total_pages).to eq(5)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has 5 widgets' do
|
16
|
+
expect(widgets.size).to eq(5)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has 23 total widgets' do
|
20
|
+
expect(widgets.total_count).to eq(23)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'first page when surfacing is 0' do
|
25
|
+
subject(:widgets) { SampleModels::DataMapper::Widget.page(1).per(5) }
|
26
|
+
|
27
|
+
it 'has 5 pages' do
|
28
|
+
expect(widgets.total_pages).to eq(5)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'first page when surfacing is 3' do
|
33
|
+
subject(:widgets) { SampleModels::DataMapper::Widget.page(1).per(5).surface(3) }
|
34
|
+
|
35
|
+
it 'has 4 pages' do
|
36
|
+
expect(widgets.total_pages).to eq(4)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'has 5 widgets' do
|
40
|
+
expect(widgets.size).to eq(5)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'last page when surfacing is 3' do
|
45
|
+
subject(:widgets){ SampleModels::DataMapper::Widget.page(4).per(5).surface(3) }
|
46
|
+
|
47
|
+
it 'has 8 widgets' do
|
48
|
+
expect(widgets.size).to eq(8)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'last page when surfacing is 8 (per + remainder)' do
|
53
|
+
subject(:widgets) { SampleModels::DataMapper::Widget.page(3).per(5).surface(8) }
|
54
|
+
|
55
|
+
it 'has 3 pages' do
|
56
|
+
expect(widgets.total_pages).to eq(3)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'has 13 widgets' do
|
60
|
+
expect(widgets.size).to eq(13)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'has 23 total widgets' do
|
64
|
+
expect(widgets.total_count).to eq(23)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when `per` is called after `surface`' do
|
69
|
+
subject(:widgets){ ->{ SampleModels::DataMapper::Widget.page(1).surface(5).per(5) } }
|
70
|
+
|
71
|
+
it 'should raise an `ArgumentError`' do
|
72
|
+
expect(subject).to raise_error(ArgumentError)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when `padding` is called after `surface`' do
|
77
|
+
subject(:widgets){ ->{ SampleModels::DataMapper::Widget.page(1).surface(5).padding(5) } }
|
78
|
+
|
79
|
+
it 'should raise an `ArgumentError`' do
|
80
|
+
expect(subject).to raise_error(ArgumentError)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|