Commit 22ec2dc8 authored by Robert May's avatar Robert May

Refactor page limiter concern

Various alterations to the PageLimiter concern to simplify
the flow and remove the class variables.
parent 7476bacb
...@@ -6,51 +6,37 @@ ...@@ -6,51 +6,37 @@
# Examples: # Examples:
# class MyController < ApplicationController # class MyController < ApplicationController
# include PageLimiter # include PageLimiter
# limit_pages 500
# #
# # Optionally provide a block to customize the response: # before_action only: [:index] do
# limit_pages 500 do # limit_pages(500)
# head :ok
# end # end
# #
# # Or override the default response method # # You can override the default response
# limit_pages 500
#
# def page_out_of_bounds # def page_out_of_bounds
# head :ok # head :ok
# end # end
# #
# # Or provide an entirely different handler:
# rescue_from PageOutOfBoundsError, with: :something
#
module PageLimiter module PageLimiter
extend ActiveSupport::Concern extend ActiveSupport::Concern
PageLimitNotANumberError = Class.new(StandardError) PageLimiterError = Class.new(StandardError)
PageLimitNotANumberError = Class.new(PageLimiterError)
PageLimitNotSensibleError = Class.new(PageLimiterError)
PageOutOfBoundsError = Class.new(PageLimiterError)
included do included do
around_action :check_page_number, if: :max_page_defined? attr_accessor :max_page_number
end helper_method :max_page_number
rescue_from PageOutOfBoundsError, with: :page_out_of_bounds
class_methods do
def limit_pages(number, &block)
set_max_page(number)
@page_limiter_block = block
end
def max_page
@max_page
end
def page_limiter_block
@page_limiter_block
end end
private def limit_pages(number)
set_max_page_number(number)
def set_max_page(value) check_page_number
raise PageLimitNotANumberError unless value.is_a?(Integer)
@max_page = value
end
end end
# Override this method in your controller to customize the response # Override this method in your controller to customize the response
...@@ -60,9 +46,11 @@ module PageLimiter ...@@ -60,9 +46,11 @@ module PageLimiter
private private
# Used to see whether the around_action should run or not def set_max_page_number(value)
def max_page_defined? raise PageLimitNotANumberError unless value.is_a?(Integer)
self.class.max_page.present? && self.class.max_page > 0 raise PageLimitNotSensibleError unless value > 0
self.max_page_number = value
end end
# If the page exceeds the defined maximum, either call the provided # If the page exceeds the defined maximum, either call the provided
...@@ -71,16 +59,9 @@ module PageLimiter ...@@ -71,16 +59,9 @@ module PageLimiter
# #
# If the page doesn't exceed the limit, it yields the controller action. # If the page doesn't exceed the limit, it yields the controller action.
def check_page_number def check_page_number
if params[:page].present? && params[:page].to_i > self.class.max_page if params[:page].present? && params[:page].to_i > max_page_number
record_interception record_interception
raise PageOutOfBoundsError
if self.class.page_limiter_block.present?
instance_eval(&self.class.page_limiter_block)
else
page_out_of_bounds
end
else
yield
end end
end end
......
...@@ -7,13 +7,14 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -7,13 +7,14 @@ class Explore::ProjectsController < Explore::ApplicationController
include SortingHelper include SortingHelper
include SortingPreference include SortingPreference
PAGE_LIMIT = 500
before_action :set_non_archived_param before_action :set_non_archived_param
before_action :set_sorting before_action :set_sorting
limit_pages PAGE_LIMIT before_action only: [:index, :trending, :starred] do
helper_method :max_page_number limit_pages(200)
end
rescue_from PageOutOfBoundsError, with: :page_out_of_bounds
def index def index
@projects = load_projects @projects = load_projects
...@@ -91,10 +92,6 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -91,10 +92,6 @@ class Explore::ProjectsController < Explore::ApplicationController
Project::SORTING_PREFERENCE_FIELD Project::SORTING_PREFERENCE_FIELD
end end
def max_page_number
PAGE_LIMIT
end
# Overrides the default in the PageLimiter concern # Overrides the default in the PageLimiter concern
def page_out_of_bounds def page_out_of_bounds
load_project_counts load_project_counts
......
...@@ -5,12 +5,12 @@ require 'spec_helper' ...@@ -5,12 +5,12 @@ require 'spec_helper'
class PageLimiterSpecController < ApplicationController class PageLimiterSpecController < ApplicationController
include PageLimiter include PageLimiter
limit_pages 2 do before_action do
raise "block response" limit_pages 200
end end
def page_out_of_bounds def index
raise "method response" head :ok
end end
end end
...@@ -36,129 +36,59 @@ describe PageLimiter do ...@@ -36,129 +36,59 @@ describe PageLimiter do
end end
end end
describe ".max_page" do describe "#limit_pages" do
subject { controller_class.max_page }
it { is_expected.to eq(2) }
end
describe ".page_limiter_block" do
subject { controller_class.page_limiter_block }
it "is an executable block" do
expect { subject.call }.to raise_error("block response")
end
end
describe ".set_max_page" do
subject { controller_class.send(:set_max_page, page) }
context "page is a number" do
let(:page) { 2 }
it { is_expected.to eq(page) }
end
context "page is a string" do
let(:page) { "2" }
it "raises an error" do
expect { subject }.to raise_error(PageLimiter::PageLimitNotANumberError)
end
end
context "page is nil" do
let(:page) { nil }
it "raises an error" do
expect { subject }.to raise_error(PageLimiter::PageLimitNotANumberError)
end
end
end
describe "#page_out_of_bounds" do
subject { instance.page_out_of_bounds }
it "returns a bad_request header" do
expect { subject }.to raise_error("method response")
end
end
describe "#max_page_defined?" do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
subject { instance.send(:max_page_defined?) }
where(:max_page, :result) do where(:max_page, :actual_page, :result) do
2 | true 2 | 1 | nil
nil | false 2 | 2 | nil
0 | false 2 | 3 | PageLimiter::PageOutOfBoundsError
nil | 1 | PageLimiter::PageLimitNotANumberError
0 | 1 | PageLimiter::PageLimitNotSensibleError
-1 | 1 | PageLimiter::PageLimitNotSensibleError
end end
with_them do with_them do
before do subject { instance.limit_pages(max_page) }
controller_class.instance_variable_set(:@max_page, max_page)
end
# Reset this afterwards to prevent polluting other specs
after do
controller_class.instance_variable_set(:@max_page, 2)
end
it { is_expected.to be(result) }
end
end
describe "#check_page_number" do
let(:max_page) { 2 }
subject { instance.send(:check_page_number) { "test" } }
before do before do
allow(instance).to receive(:params) { { page: page.to_s } } allow(instance).to receive(:params) { { page: actual_page.to_s } }
end end
context "page is over the limit" do it "returns the expected result" do
let(:page) { max_page + 1 } if result == PageLimiter::PageOutOfBoundsError
it "records the interception" do
expect(instance).to receive(:record_interception) expect(instance).to receive(:record_interception)
# Need this second expectation to cancel out the exception expect { subject }.to raise_error(result)
expect { subject }.to raise_error("block response") elsif result&.superclass == PageLimiter::PageLimiterError
expect { subject }.to raise_error(result)
else
expect(subject).to eq(result)
end end
context "block is given" do
it "calls the block" do
expect { subject }.to raise_error("block response")
end end
end end
context "block is not given" do
before do
allow(controller_class).to receive(:page_limiter_block) { nil }
end end
it "calls the #page_out_of_bounds method" do describe "#page_out_of_bounds" do
expect { subject }.to raise_error("method response") subject { instance.page_out_of_bounds }
end
end
end
context "page is not over the limit" do
let(:page) { max_page }
it "yields" do after do
expect(subject).to eq("test") subject
end end
it "returns a bad_request header" do
expect(instance).to receive(:head).with(:bad_request)
end end
end end
describe "#default_page_out_of_bounds_response" do describe "#default_page_out_of_bounds_response" do
subject { instance.send(:default_page_out_of_bounds_response) } subject { instance.send(:default_page_out_of_bounds_response) }
after do
subject
end
it "returns a bad_request header" do it "returns a bad_request header" do
expect(instance).to receive(:head).with(:bad_request) expect(instance).to receive(:head).with(:bad_request)
subject
end end
end end
......
...@@ -60,7 +60,7 @@ describe Explore::ProjectsController do ...@@ -60,7 +60,7 @@ describe Explore::ProjectsController do
end end
shared_examples "blocks high page numbers" do shared_examples "blocks high page numbers" do
let(:page_limit) { Explore::ProjectsController::PAGE_LIMIT } let(:page_limit) { 200 }
context "page number is too high" do context "page number is too high" do
[:index, :trending, :starred].each do |endpoint| [:index, :trending, :starred].each do |endpoint|
...@@ -95,7 +95,7 @@ describe Explore::ProjectsController do ...@@ -95,7 +95,7 @@ describe Explore::ProjectsController do
:gitlab_page_out_of_bounds, :gitlab_page_out_of_bounds,
controller: "explore/projects", controller: "explore/projects",
action: endpoint.to_s, action: endpoint.to_s,
agent: "Rails Testing" bot: false
) )
end end
end end
...@@ -162,11 +162,4 @@ describe Explore::ProjectsController do ...@@ -162,11 +162,4 @@ describe Explore::ProjectsController do
end end
end end
end end
describe "#max_page_number" do
subject { controller.send(:max_page_number) }
it { is_expected.to eq(Explore::ProjectsController::PAGE_LIMIT) }
it { is_expected.to be_an(Integer) }
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment