-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #81 from tiagopog/improvement/record-count-refacto…
…ring Improvement/record count refactoring
- Loading branch information
Showing
13 changed files
with
396 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
module JSONAPI | ||
module Utils | ||
VERSION = '0.7.0'.freeze | ||
VERSION = '0.7.1'.freeze | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
require 'rails_helper' | ||
|
||
## | ||
# Configs | ||
## | ||
|
||
# Resource | ||
class RecordCountTestResource < JSONAPI::Resource; end | ||
|
||
# Controller | ||
class RecordCountTestController < BaseController | ||
def explicit_count | ||
jsonapi_render json: User.all, options: { count: 42, resource: UserResource } | ||
end | ||
|
||
def array_count | ||
jsonapi_render json: User.all.to_a, options: { resource: UserResource } | ||
end | ||
|
||
def active_record_count | ||
jsonapi_render json: User.all, options: { resource: UserResource } | ||
end | ||
|
||
def active_record_count_with_eager_load | ||
users = User.all.includes(:posts) | ||
jsonapi_render json: users, options: { resource: UserResource } | ||
end | ||
|
||
def active_record_count_with_eager_load_and_where_clause | ||
users = User.all.includes(:posts).where(posts: { id: Post.first.id }) | ||
jsonapi_render json: users, options: { resource: UserResource } | ||
end | ||
end | ||
|
||
# Routes | ||
def TestApp.draw_record_count_test_routes | ||
JSONAPI.configuration.json_key_format = :underscored_key | ||
|
||
TestApp.routes.draw do | ||
controller :record_count_test do | ||
get :explicit_count | ||
get :array_count | ||
get :active_record_count | ||
get :active_record_count_with_eager_load | ||
get :active_record_count_with_eager_load_and_where_clause | ||
end | ||
end | ||
end | ||
|
||
## | ||
# Feature tests | ||
## | ||
|
||
describe RecordCountTestController, type: :controller do | ||
include_context 'JSON API headers' | ||
|
||
before(:all) do | ||
TestApp.draw_record_count_test_routes | ||
FactoryGirl.create_list(:user, 3, :with_posts) | ||
end | ||
|
||
describe 'explicit count' do | ||
it 'returns the count based on the passed "options"' do | ||
get :explicit_count | ||
expect(response).to have_meta_record_count(42) | ||
end | ||
end | ||
|
||
describe 'array count' do | ||
it 'returns the count based on the array length' do | ||
get :array_count | ||
expect(response).to have_meta_record_count(User.count) | ||
end | ||
end | ||
|
||
describe 'active record count' do | ||
it 'returns the count based on the AR\'s query result' do | ||
get :active_record_count | ||
expect(response).to have_meta_record_count(User.count) | ||
end | ||
end | ||
|
||
describe 'active record count with eager load' do | ||
it 'returns the count based on the AR\'s query result' do | ||
get :active_record_count_with_eager_load | ||
expect(response).to have_meta_record_count(User.count) | ||
end | ||
end | ||
|
||
describe 'active record count with eager load and where clause' do | ||
it 'returns the count based on the AR\'s query result' do | ||
get :active_record_count_with_eager_load_and_where_clause | ||
count = User.joins(:posts).where(posts: { id: Post.first.id }).count | ||
expect(response).to have_meta_record_count(count) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
require 'rails_helper' | ||
|
||
describe JSONAPI::Utils::Support::Pagination do | ||
subject do | ||
OpenStruct.new(params: {}).extend(JSONAPI::Utils::Support::Pagination) | ||
end | ||
|
||
before(:all) do | ||
FactoryGirl.create_list(:user, 2) | ||
end | ||
|
||
let(:options) { {} } | ||
|
||
## | ||
# Public API | ||
## | ||
|
||
describe '#record_count_for' do | ||
context 'with array' do | ||
let(:records) { User.all.to_a } | ||
|
||
it 'applies memoization on the record count' do | ||
expect(records).to receive(:length).and_return(records.length).once | ||
2.times { subject.record_count_for(records, options) } | ||
end | ||
end | ||
|
||
context 'with ActiveRecord object' do | ||
let(:records) { User.all } | ||
|
||
it 'applies memoization on the record count' do | ||
expect(records).to receive(:except).and_return(records).once | ||
2.times { subject.record_count_for(records, options) } | ||
end | ||
end | ||
end | ||
|
||
## | ||
# Private API | ||
## | ||
|
||
describe '#count_records' do | ||
shared_examples_for 'counting records' do | ||
it 'counts records' do | ||
expect(subject.send(:count_records, records, options)).to eq(count) | ||
end | ||
end | ||
|
||
context 'with count present within the options' do | ||
let(:records) { User.all } | ||
let(:options) { { count: 999 } } | ||
let(:count) { 999 } | ||
it_behaves_like 'counting records' | ||
end | ||
|
||
context 'with array' do | ||
let(:records) { User.all.to_a } | ||
let(:count) { records.length } | ||
it_behaves_like 'counting records' | ||
end | ||
|
||
context 'with ActiveRecord object' do | ||
let(:records) { User.all } | ||
let(:count) { records.count } | ||
it_behaves_like 'counting records' | ||
end | ||
|
||
context 'when no strategy can be applied' do | ||
let(:records) { Object.new } | ||
let(:count) { } | ||
|
||
it 'raises an error' do | ||
expect { | ||
subject.send(:count_records, records, options) | ||
}.to raise_error(JSONAPI::Utils::Support::Pagination::RecordCountError) | ||
end | ||
end | ||
end | ||
|
||
describe '#count_records_from_database' do | ||
shared_examples_for 'skipping eager load SQL when counting records' do | ||
it 'skips any eager load for the SQL count query (default)' do | ||
expect(records).to receive(:except) | ||
.with(:includes, :group, :order) | ||
.and_return(User.all) | ||
.once | ||
expect(records).to receive(:except) | ||
.with(:group, :order) | ||
.and_return(User.all) | ||
.exactly(0) | ||
.times | ||
subject.send(:count_records_from_database, records, options) | ||
end | ||
end | ||
|
||
context 'when not eager loading records' do | ||
let(:records) { User.all } | ||
it_behaves_like 'skipping eager load SQL when counting records' | ||
end | ||
|
||
context 'when eager loading records' do | ||
let(:records) { User.includes(:posts) } | ||
it_behaves_like 'skipping eager load SQL when counting records' | ||
end | ||
|
||
context 'when eager loading records and using where clause on associations' do | ||
let(:records) { User.includes(:posts).where(posts: { id: 1 }) } | ||
|
||
it 'fallbacks to the SQL count query with eager load' do | ||
expect(records).to receive(:except) | ||
.with(:includes, :group, :order) | ||
.and_raise(ActiveRecord::StatementInvalid) | ||
.once | ||
expect(records).to receive(:except) | ||
.with(:group, :order) | ||
.and_return(User.all) | ||
.once | ||
subject.send(:count_records_from_database, records, options) | ||
end | ||
end | ||
end | ||
|
||
describe '#distinct_count_sql' do | ||
let(:records) { OpenStruct.new(table_name: 'foos', primary_key: 'id') } | ||
|
||
it 'builds the distinct count SQL query' do | ||
expect(subject.send(:distinct_count_sql, records)).to eq('DISTINCT foos.id') | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
require 'spec_helper' | ||
|
||
require 'rails/all' | ||
require 'rails/test_help' | ||
require 'rspec/rails' | ||
|
||
require 'jsonapi-resources' | ||
require 'jsonapi/utils' | ||
|
||
require 'support/models' | ||
require 'support/factories' | ||
require 'support/resources' | ||
require 'support/controllers' | ||
require 'support/paginators' | ||
|
||
require 'support/shared/jsonapi_errors' | ||
require 'support/shared/jsonapi_request' | ||
|
||
require 'test_app' | ||
|
||
RSpec.configure do |config| | ||
config.before(:all) do | ||
TestApp.draw_app_routes | ||
|
||
%w[posts categories profiles users].each do |table_name| | ||
ActiveRecord::Base.connection.execute("DELETE FROM #{table_name}; VACUUM;") | ||
end | ||
end | ||
end |
Oops, something went wrong.