diff --git a/.rubocop.yml b/.rubocop.yml index 0a33e84..74036ef 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,7 @@ AllCops: NewCops: enable Metrics/AbcSize: - Max: 25 + Max: 30 # DSLs inheritly uses long blocks, like describe and contexts in RSpec, # therefore disable this rule for specs and for the gemspec file @@ -13,22 +13,22 @@ Metrics/BlockLength: - spec/**/* Metrics/ClassLength: - Max: 200 + Max: 205 Metrics/CyclomaticComplexity: Max: 15 Metrics/MethodLength: - Max: 25 + Max: 30 Metrics/ParameterLists: - Max: 7 + Max: 8 Metrics/PerceivedComplexity: - Max: 13 + Max: 14 Layout/LineLength: - Max: 80 + Max: 95 Naming/VariableNumber: EnforcedStyle: snake_case diff --git a/lib/rails_cursor_pagination/paginator.rb b/lib/rails_cursor_pagination/paginator.rb index 21876cb..8e79db3 100644 --- a/lib/rails_cursor_pagination/paginator.rb +++ b/lib/rails_cursor_pagination/paginator.rb @@ -42,15 +42,19 @@ class InvalidCursorError < ParameterError; end # ``` # @param order [Symbol, nil] # Ordering to apply, either `:asc` or `:desc`. Defaults to `:asc`. + # @param record_decorator [Object, proc { |obj| obj.itself }] + # Object to which each paginated record will be passed to. Default behavior + # simply returns the record itself. Object must be callable. # # @raise [RailsCursorPagination::Paginator::ParameterError] # If any parameter is not valid def initialize(relation, first: nil, after: nil, last: nil, before: nil, - order_by: nil, order: nil) + order_by: nil, order: nil, record_decorator: :itself.to_proc) order_by ||= :id order ||= :asc - ensure_valid_params!(relation, first, after, last, before, order) + ensure_valid_params!(relation, first, after, last, before, order, + record_decorator) @order_field = order_by @order_direction = order @@ -64,6 +68,8 @@ def initialize(relation, first: nil, after: nil, last: nil, before: nil, last || RailsCursorPagination::Configuration.instance.default_page_size + @record_decorator = record_decorator + @memos = {} end @@ -99,10 +105,12 @@ def fetch(with_total: false) # Optional, cannot be combined with `after` # @param order [Symbol] # Optional, must be :asc or :desc + # @param record_decorator [Object, proc { |obj| obj.itself }] + # Optional, must respond to `.call` # # @raise [RailsCursorPagination::Paginator::ParameterError] # If any parameter is not valid - def ensure_valid_params!(relation, first, after, last, before, order) + def ensure_valid_params!(relation, first, after, last, before, order, record_decorator) unless relation.is_a?(ActiveRecord::Relation) raise ParameterError, 'The first argument must be an ActiveRecord::Relation, but was '\ @@ -127,6 +135,9 @@ def ensure_valid_params!(relation, first, after, last, before, order) if last.present? && last.negative? raise ParameterError, "`last` cannot be negative, but was `#{last}`" end + unless record_decorator.respond_to?(:call) + raise ParameterError, '`record_decorator` must respond to .call' + end true end @@ -143,7 +154,8 @@ def page_info } end - # Get the records for the given page along with their cursors + # Get the records for the given page along with their cursors. + # Returned data will have been passed to record_decorator. # # @return [Array] List of hashes, each with a `cursor` and `data` def page @@ -151,7 +163,7 @@ def page records.map do |item| { cursor: cursor_for_record(item), - data: item + data: @record_decorator.call(item) } end end @@ -393,9 +405,7 @@ def relation_with_cursor_fields relation = @relation - unless @relation.select_values.include?(:id) - relation = relation.select(:id) - end + relation = relation.select(:id) unless @relation.select_values.include?(:id) if custom_order_field? && !@relation.select_values.include?(@order_field) relation = relation.select(@order_field) diff --git a/spec/rails_cursor_pagination/paginator_spec.rb b/spec/rails_cursor_pagination/paginator_spec.rb index d5e5d21..1f45979 100644 --- a/spec/rails_cursor_pagination/paginator_spec.rb +++ b/spec/rails_cursor_pagination/paginator_spec.rb @@ -124,6 +124,13 @@ include_examples 'for a ParameterError with the right message', '`last` cannot be negative, but was `-4`' end + + context 'passing an invalid `record_decorator`' do + let(:params) { super().merge(record_decorator: 'no') } + + include_examples 'for a ParameterError with the right message', + '`record_decorator` must respond to .call' + end end end @@ -274,6 +281,28 @@ expect(subject[:total]).to eq expected_total end end + + context 'when passing a valid proc as `&record_decorator`' do + let(:params) { { record_decorator: :class.to_proc } } + + it 'returns decorated records' do + expect(subject[:page].pluck(:data)).to all be Post + end + end + + context 'when passing a valid lambda as `&record_decorator`' do + let(:id_incrementer) { ->(item) { { next_id: item.id + 1 } } } + let(:params) { super().merge(record_decorator: id_incrementer) } + let(:expected_decorated_response) do + expected_posts.map do |post| + id_incrementer.call(post) + end + end + + it 'returns decorated records' do + expect(subject[:page].pluck(:data)).to eq expected_decorated_response + end + end end shared_examples_for 'a well working query that also supports SELECT' do