From 0e7fd51c1c7f5022101104cf465d83fa31e06641 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 21 Jul 2023 10:12:29 +0200 Subject: [PATCH 1/3] Rubocop fix: Use filter_map instead of map { ... }.compact Currently, builds are failing because of Rubocop complaining about these three lines. --- lib/jsonapi/deserialization.rb | 2 +- lib/jsonapi/fetching.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/jsonapi/deserialization.rb b/lib/jsonapi/deserialization.rb index 0a9d5de..dae089a 100644 --- a/lib/jsonapi/deserialization.rb +++ b/lib/jsonapi/deserialization.rb @@ -65,7 +65,7 @@ def jsonapi_deserialize(document, options = {}) rel_name = jsonapi_inflector.singularize(assoc_name) if assoc_data.is_a?(Array) - parsed["#{rel_name}_ids"] = assoc_data.map { |ri| ri['id'] }.compact + parsed["#{rel_name}_ids"] = assoc_data.filter_map { |ri| ri['id'] } next end diff --git a/lib/jsonapi/fetching.rb b/lib/jsonapi/fetching.rb index c394e8a..d5e91ec 100644 --- a/lib/jsonapi/fetching.rb +++ b/lib/jsonapi/fetching.rb @@ -17,7 +17,7 @@ def jsonapi_fields end params[:fields].each do |k, v| - extracted[k] = v.to_s.split(',').map(&:strip).compact + extracted[k] = v.to_s.split(',').filter_map(&:strip) end extracted @@ -29,7 +29,7 @@ def jsonapi_fields # # @return [Array] def jsonapi_include - params['include'].to_s.split(',').map(&:strip).compact + params['include'].to_s.split(',').filter_map(&:strip) end end end From bc4ff514f52bb59faeafe6f61c4b98306fc6b862 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 21 Jul 2023 10:25:21 +0200 Subject: [PATCH 2/3] Fix specs with Ransack 4.0 Ransack 4 requires explicitly allowlisting ransackable attributes and associations for each model. This causes the specs to fail with the most recent Ransack version. This fixes those spec failures. --- spec/dummy.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/dummy.rb b/spec/dummy.rb index fd4354b..9f714e1 100644 --- a/spec/dummy.rb +++ b/spec/dummy.rb @@ -30,12 +30,28 @@ class User < ActiveRecord::Base has_many :notes + + def self.ransackable_attributes(auth_object = nil) + %w(created_at first_name id last_name updated_at) + end + + def self.ransackable_associations(auth_object = nil) + %w(notes) + end end class Note < ActiveRecord::Base validates_format_of :title, without: /BAD_TITLE/ validates_numericality_of :quantity, less_than: 100, if: :quantity? belongs_to :user, required: true + + def self.ransackable_associations(auth_object = nil) + %w(user) + end + + def self.ransackable_attributes(auth_object = nil) + %w(created_at id quantity title updated_at user_id) + end end class CustomNoteSerializer From 1a5d075c4d000dc0b39c033a2fe5bdcd26d80258 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 20 Jul 2023 15:41:25 +0200 Subject: [PATCH 3/3] Allow decorating objects after pagination This simplification allows decorating objects after they are paginated, without losing the correct total object count. I'm using an instance variable on the including controller here, because the decorating the paginated collection will have us lose the instance variable we set on it. Here's the case where this happens: We have a complex ActiveRecord collection that we run through Ransack and Kaminari, but before rendering we want to convert each object in it using a `SimpleDelegator`. Here's a simplified version of the controller action we're looking at: ``` class UserDecorator < SimpleDelegator def fantastic_for_rendering "Whoah" end end def index allowed_fields = [ :first_name, :last_name, :created_at, :notes_created_at, :notes_quantity ] options = { sort_with_expressions: true } jsonapi_filter(User.all, allowed_fields, options) do |filtered| result = filtered.result jsonapi_paginate(result) do |paginated| paginated = paginated.map { |user| UserDecorator.new() } render jsonapi: paginated end end end ``` --- lib/jsonapi/pagination.rb | 15 ++++----------- spec/dummy.rb | 1 + spec/pagination_spec.rb | 23 ++++++++++++++++++++++- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/jsonapi/pagination.rb b/lib/jsonapi/pagination.rb index a98d149..5502e17 100644 --- a/lib/jsonapi/pagination.rb +++ b/lib/jsonapi/pagination.rb @@ -13,14 +13,13 @@ module Pagination def jsonapi_paginate(resources) offset, limit, _ = jsonapi_pagination_params + # Cache the original resources size to be used for pagination meta + @_jsonapi_original_size = resources.size + if resources.respond_to?(:offset) resources = resources.offset(offset).limit(limit) else - original_size = resources.size resources = resources[(offset)..(offset + limit - 1)] || [] - - # Cache the original resources size to be used for pagination meta - resources.instance_variable_set(:@original_size, original_size) end block_given? ? yield(resources) : resources @@ -64,13 +63,7 @@ def jsonapi_pagination_meta(resources) numbers = { current: page } - if resources.respond_to?(:unscope) - total = resources.unscope(:limit, :offset, :order).size - else - # Try to fetch the cached size first - total = resources.instance_variable_get(:@original_size) - total ||= resources.size - end + total = @_jsonapi_original_size last_page = [1, (total.to_f / limit).ceil].max diff --git a/spec/dummy.rb b/spec/dummy.rb index 9f714e1..af65acf 100644 --- a/spec/dummy.rb +++ b/spec/dummy.rb @@ -113,6 +113,7 @@ def index result = result.to_a if params[:as_list] jsonapi_paginate(result) do |paginated| + paginated = paginated.to_a if params[:decorate_after_pagination] render jsonapi: paginated end end diff --git a/spec/pagination_spec.rb b/spec/pagination_spec.rb index 622c81e..d417274 100644 --- a/spec/pagination_spec.rb +++ b/spec/pagination_spec.rb @@ -53,11 +53,13 @@ context 'on page 2 out of 3' do let(:as_list) { } + let(:decorate_after_pagination) { } let(:params) do { page: { number: 2, size: 1 }, sort: '-created_at', - as_list: as_list + as_list: as_list, + decorate_after_pagination: decorate_after_pagination }.compact_blank end @@ -80,6 +82,25 @@ end end + context 'when decorating objects after pagination' do + let(:decorate_after_pagination) { true } + + it do + expect(response).to have_http_status(:ok) + expect(response_json['data'].size).to eq(1) + expect(response_json['data'][0]).to have_id(second_user.id.to_s) + + expect(response_json['meta']['pagination']).to eq( + 'current' => 2, + 'first' => 1, + 'prev' => 1, + 'next' => 3, + 'last' => 3, + 'records' => 3 + ) + end + end + it do expect(response).to have_http_status(:ok) expect(response_json['data'].size).to eq(1)