Skip to content

Commit cc96793

Browse files
authored
Merge pull request #1287 from cerebris/test_on_pg
Fix for Postgres SQL generation error
2 parents 3145b8a + 99268a8 commit cc96793

File tree

10 files changed

+128
-77
lines changed

10 files changed

+128
-77
lines changed

.travis.yml

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
language: ruby
22
sudo: false
3+
services:
4+
- postgresql
35
env:
4-
- "RAILS_VERSION=4.2.11"
5-
- "RAILS_VERSION=5.0.7.2"
6-
- "RAILS_VERSION=5.1.7"
7-
- "RAILS_VERSION=5.2.3"
8-
- "RAILS_VERSION=6.0.0"
9-
# - "RAILS_VERSION=master"
6+
- RAILS_VERSION=6.0.0 DATABASE_URL=postgres://postgres@localhost/jr_test
7+
- RAILS_VERSION=6.0.0
8+
- RAILS_VERSION=5.2.3 DATABASE_URL=postgres://postgres@localhost/jr_test
9+
- RAILS_VERSION=5.2.3
10+
- RAILS_VERSION=5.1.7
11+
- RAILS_VERSION=5.0.7.2
12+
- RAILS_VERSION=4.2.11
1013
rvm:
11-
- 2.3.8
12-
- 2.4.7
13-
- 2.5.6
14-
- 2.6.4
14+
- 2.4.9
15+
- 2.5.7
16+
- 2.6.5
1517
matrix:
16-
allow_failures:
17-
- env: "RAILS_VERSION=master"
1818
exclude:
19-
- rvm: 2.6.4
19+
- rvm: 2.6.5
2020
env: "RAILS_VERSION=4.2.11"
21-
- rvm: 2.3.8
22-
env: "RAILS_VERSION=6.0.0"
23-
- rvm: 2.4.7
21+
- rvm: 2.4.9
2422
env: "RAILS_VERSION=6.0.0"
23+
- rvm: 2.4.9
24+
env: "RAILS_VERSION=6.0.0 DATABASE_URL=postgres://postgres@localhost/jr_test"
25+
- rvm: 2.4.9
26+
env: "RAILS_VERSION=5.2.3 DATABASE_URL=postgres://postgres@localhost/jr_test"
2527
before_install:
26-
- gem install bundler --version 1.17.3
28+
- gem install bundler --version 1.17.3
29+
before_script:
30+
- sh -c "if [ '$DATABASE_URL' = 'postgres://postgres@localhost/jr_test' ]; then psql -c 'DROP DATABASE IF EXISTS jr_test;' -U postgres; fi"
31+
- sh -c "if [ '$DATABASE_URL' = 'postgres://postgres@localhost/jr_test' ]; then psql -c 'CREATE DATABASE jr_test;' -U postgres; fi"

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ end
99
version = ENV['RAILS_VERSION'] || 'default'
1010

1111
platforms :ruby do
12+
gem 'pg'
13+
1214
if version.start_with?('4.2', '5.0')
1315
gem 'sqlite3', '~> 1.3.13'
1416
else

lib/jsonapi/active_relation_resource.rb

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ def find_fragments(filters, options = {})
153153
pluck_fields << Arel.sql("#{concat_table_field(resource_table_alias, model_field[:name])} AS #{resource_table_alias}_#{model_field[:name]}")
154154
end
155155

156+
sort_fields = options.dig(:_relation_helper_options, :sort_fields)
157+
sort_fields.try(:each) do |field|
158+
pluck_fields << Arel.sql(field)
159+
end
160+
156161
fragments = {}
157162
rows = records.pluck(*pluck_fields)
158163
rows.each do |row|
@@ -445,6 +450,11 @@ def find_related_monomorphic_fragments(source_rids, relationship, options, conne
445450
pluck_fields << Arel.sql("#{concat_table_field(resource_table_alias, model_field[:name])} AS #{resource_table_alias}_#{model_field[:name]}")
446451
end
447452

453+
sort_fields = options.dig(:_relation_helper_options, :sort_fields)
454+
sort_fields.try(:each) do |field|
455+
pluck_fields << Arel.sql(field)
456+
end
457+
448458
fragments = {}
449459
rows = records.distinct.pluck(*pluck_fields)
450460
rows.each do |row|
@@ -680,24 +690,23 @@ def apply_request_settings_to_records(records:,
680690
paginator: nil,
681691
options: {})
682692

683-
opts = options.dup
684-
records = resource_klass.apply_joins(records, join_manager, opts)
693+
options[:_relation_helper_options] = { join_manager: join_manager, sort_fields: [] }
694+
695+
records = resource_klass.apply_joins(records, join_manager, options)
685696

686697
if primary_keys
687698
records = records.where(_primary_key => primary_keys)
688699
end
689700

690-
opts[:join_manager] = join_manager
691-
692701
unless filters.empty?
693-
records = resource_klass.filter_records(records, filters, opts)
702+
records = resource_klass.filter_records(records, filters, options)
694703
end
695704

696705
if sort_primary
697706
records = records.order(_primary_key => :asc)
698707
else
699708
order_options = resource_klass.construct_order_options(sort_criteria)
700-
records = resource_klass.sort_records(records, order_options, opts)
709+
records = resource_klass.sort_records(records, order_options, options)
701710
end
702711

703712
if paginator
@@ -731,12 +740,16 @@ def apply_single_sort(records, field, direction, options)
731740

732741
strategy = _allowed_sort.fetch(field.to_sym, {})[:apply]
733742

743+
options[:_relation_helper_options] ||= {}
744+
options[:_relation_helper_options][:sort_fields] ||= []
745+
734746
if strategy
735747
records = call_method_or_proc(strategy, records, direction, context)
736748
else
737-
join_manager = options[:join_manager]
738-
739-
records = records.order(Arel.sql("#{get_aliased_field(field, join_manager)} #{direction}"))
749+
join_manager = options.dig(:_relation_helper_options, :join_manager)
750+
sort_field = join_manager ? get_aliased_field(field, join_manager) : field
751+
options[:_relation_helper_options][:sort_fields].push("#{sort_field}")
752+
records = records.order(Arel.sql("#{sort_field} #{direction}"))
740753
end
741754
records
742755
end
@@ -825,8 +838,9 @@ def apply_filter(records, filter, value, options = {})
825838
if strategy
826839
records = call_method_or_proc(strategy, records, value, options)
827840
else
828-
join_manager = options[:join_manager]
829-
records = records.where(Arel.sql(get_aliased_field(filter, join_manager)) => value)
841+
join_manager = options.dig(:_relation_helper_options, :join_manager)
842+
field = join_manager ? get_aliased_field(filter, join_manager) : filter
843+
records = records.where(Arel.sql(field) => value)
830844
end
831845

832846
records

test/config/database.yml

Lines changed: 0 additions & 6 deletions
This file was deleted.

test/controllers/controller_test.rb

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ def set_content_type_header!
66

77
class PostsControllerTest < ActionController::TestCase
88
def setup
9+
super
910
JSONAPI.configuration.raise_if_parameters_not_allowed = true
1011
JSONAPI.configuration.always_include_to_one_linkage_data = false
1112
end
@@ -445,7 +446,7 @@ def test_sorting_asc
445446
assert_cacheable_get :index, params: {sort: 'title'}
446447

447448
assert_response :success
448-
assert_equal "A First Post", json_response['data'][0]['attributes']['title']
449+
assert_equal "A 1ST Post", json_response['data'][0]['attributes']['title']
449450
end
450451

451452
def test_sorting_desc
@@ -459,7 +460,7 @@ def test_sorting_by_multiple_fields
459460
assert_cacheable_get :index, params: {sort: 'title,body'}
460461

461462
assert_response :success
462-
assert_equal '14', json_response['data'][0]['id']
463+
assert_equal '15', json_response['data'][0]['id']
463464
end
464465

465466
def create_alphabetically_first_user_and_post
@@ -473,8 +474,15 @@ def test_sorting_by_relationship_field
473474

474475
assert_response :success
475476
assert json_response['data'].length > 10, 'there are enough records to show sort'
476-
assert_equal '17', json_response['data'][0]['id'], 'nil is at the top'
477-
assert_equal post.id.to_s, json_response['data'][1]['id'], 'alphabetically first user is second'
477+
478+
# Postgres sorts nulls last, whereas sqlite and mysql sort nulls first
479+
if ENV['DATABASE_URL'].starts_with?('postgres')
480+
assert_equal '17', json_response['data'][-1]['id'], 'nil is at the start'
481+
assert_equal post.id.to_s, json_response['data'][0]['id'], 'alphabetically first user is not first'
482+
else
483+
assert_equal '17', json_response['data'][0]['id'], 'nil is at the end'
484+
assert_equal post.id.to_s, json_response['data'][1]['id'], 'alphabetically first user is second'
485+
end
478486
end
479487

480488
def test_desc_sorting_by_relationship_field
@@ -483,8 +491,15 @@ def test_desc_sorting_by_relationship_field
483491

484492
assert_response :success
485493
assert json_response['data'].length > 10, 'there are enough records to show sort'
486-
assert_equal '17', json_response['data'][-1]['id'], 'nil is at the bottom'
487-
assert_equal post.id.to_s, json_response['data'][-2]['id'], 'alphabetically first user is second last'
494+
495+
# Postgres sorts nulls last, whereas sqlite and mysql sort nulls first
496+
if ENV['DATABASE_URL'].starts_with?('postgres')
497+
assert_equal '17', json_response['data'][0]['id'], 'nil is at the start'
498+
assert_equal post.id.to_s, json_response['data'][-1]['id']
499+
else
500+
assert_equal '17', json_response['data'][-1]['id'], 'nil is at the end'
501+
assert_equal post.id.to_s, json_response['data'][-2]['id'], 'alphabetically first user is second last'
502+
end
488503
end
489504

490505
def test_sorting_by_relationship_field_include
@@ -493,8 +508,14 @@ def test_sorting_by_relationship_field_include
493508

494509
assert_response :success
495510
assert json_response['data'].length > 10, 'there are enough records to show sort'
496-
assert_equal '17', json_response['data'][0]['id'], 'nil is at the top'
497-
assert_equal post.id.to_s, json_response['data'][1]['id'], 'alphabetically first user is second'
511+
512+
if ENV['DATABASE_URL'].starts_with?('postgres')
513+
assert_equal '17', json_response['data'][-1]['id'], 'nil is at the top'
514+
assert_equal post.id.to_s, json_response['data'][0]['id']
515+
else
516+
assert_equal '17', json_response['data'][0]['id'], 'nil is at the top'
517+
assert_equal post.id.to_s, json_response['data'][1]['id'], 'alphabetically first user is second'
518+
end
498519
end
499520

500521
def test_invalid_sort_param
@@ -3107,7 +3128,7 @@ def test_type_formatting
31073128
assert json_response['data'].is_a?(Hash)
31083129
assert_equal 'Jane Author', json_response['data']['attributes']['spouseName']
31093130
assert_equal 'First man to run across Antartica.', json_response['data']['attributes']['bio']
3110-
assert_equal 23.89/45.6, json_response['data']['attributes']['qualityRating']
3131+
assert_equal (23.89/45.6).round(5), json_response['data']['attributes']['qualityRating'].round(5)
31113132
assert_equal '47000.56', json_response['data']['attributes']['salary']
31123133
assert_equal '2013-08-07T20:25:00.000Z', json_response['data']['attributes']['dateTimeJoined']
31133134
assert_equal '1965-06-30', json_response['data']['attributes']['birthday']
@@ -4707,7 +4728,12 @@ def test_fetch_robots_with_sort_by_name
47074728
Robot.create! name: 'jane', version: 1
47084729
assert_cacheable_get :index, params: {sort: 'name'}
47094730
assert_response :success
4710-
assert_equal 'John', json_response['data'].first['attributes']['name']
4731+
4732+
if ENV['DATABASE_URL'].starts_with?('postgres')
4733+
assert_equal 'jane', json_response['data'].first['attributes']['name']
4734+
else
4735+
assert_equal 'John', json_response['data'].first['attributes']['name']
4736+
end
47114737
end
47124738

47134739
def test_fetch_robots_with_sort_by_lower_name

test/fixtures/active_record.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,7 +1650,7 @@ class CraterResource < JSONAPI::Resource
16501650

16511651
filter :description, apply: -> (records, value, options) {
16521652
fail "context not set" unless options[:context][:current_user] != nil && options[:context][:current_user] == $test_user
1653-
records.where(concat_table_field(options[:join_manager].source_join_details[:alias], :description) => value)
1653+
records.where(concat_table_field(options.dig(:_relation_helper_options, :join_manager).source_join_details[:alias], :description) => value)
16541654
}
16551655

16561656
def self.verify_key(key, context = nil)
@@ -1694,7 +1694,7 @@ class PictureResource < JSONAPI::Resource
16941694
has_one :file_properties, inverse_relationship: :fileable, :foreign_key_on => :related, polymorphic: true
16951695

16961696
filter 'imageable.name', perform_joins: true, apply: -> (records, value, options) {
1697-
join_manager = options[:join_manager]
1697+
join_manager = options.dig(:_relation_helper_options, :join_manager)
16981698
relationship = _relationship(:imageable)
16991699
or_parts = relationship.resource_types.collect do |type|
17001700
table_alias = join_manager.join_details_by_polymorphic_relationship(relationship, type)[:alias]
@@ -2038,7 +2038,7 @@ class AuthorResource < JSONAPI::Resource
20382038
relationship :author_detail, to: :one, foreign_key_on: :related
20392039

20402040
filter :name, apply: lambda { |records, value, options|
2041-
table_alias = options[:join_manager].source_join_details[:alias]
2041+
table_alias = options.dig(:_relation_helper_options, :join_manager).source_join_details[:alias]
20422042
t = Arel::Table.new(:people, as: table_alias)
20432043
records.where(t[:name].matches("%#{value[0]}%"))
20442044
}

test/fixtures/posts.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ post_14:
8585

8686
post_15:
8787
id: 15
88-
title: AAAA First Post
88+
title: A 1ST Post
8989
body: First!!!!!!!!!
9090
author_id: 1003
9191

test/integration/requests/request_test.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,13 +1438,16 @@ def test_sort_primary_attribute
14381438
end
14391439

14401440
def test_sort_included_attribute
1441+
# Postgres sorts nulls last, whereas sqlite and mysql sort nulls first
1442+
pg = ENV['DATABASE_URL'].starts_with?('postgres')
1443+
14411444
get '/api/v6/authors?sort=author_detail.author_stuff', headers: { 'Accept' => JSONAPI::MEDIA_TYPE }
14421445
assert_jsonapi_response 200
1443-
assert_equal '1000', json_response['data'][0]['id']
1446+
assert_equal pg ? '1001' : '1000', json_response['data'][0]['id']
14441447

14451448
get '/api/v6/authors?sort=-author_detail.author_stuff', headers: { 'Accept' => JSONAPI::MEDIA_TYPE }
14461449
assert_jsonapi_response 200
1447-
assert_equal '1002', json_response['data'][0]['id']
1450+
assert_equal pg ? '1000' : '1002', json_response['data'][0]['id']
14481451
end
14491452

14501453
def test_include_parameter_quoted

test/test_helper.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
end
2222
end
2323

24+
ENV['DATABASE_URL'] ||= "sqlite3:test_db"
25+
2426
require 'active_record/railtie'
2527
require 'rails/test_help'
2628
require 'minitest/mock'
@@ -68,6 +70,9 @@ class TestApp < Rails::Application
6870
end
6971
end
7072

73+
DatabaseCleaner.allow_remote_database_url = true
74+
DatabaseCleaner.strategy = :transaction
75+
7176
module MyEngine
7277
class Engine < ::Rails::Engine
7378
isolate_namespace MyEngine
@@ -477,8 +482,6 @@ class CatResource < JSONAPI::Resource
477482
jsonapi_resources :people
478483
end
479484

480-
DatabaseCleaner.strategy = :transaction
481-
482485
# Ensure backward compatibility with Minitest 4
483486
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
484487

0 commit comments

Comments
 (0)