Skip to content

Commit dd05bd9

Browse files
koji-takao-spclaude
andcommitted
Fix quote method to use database adapter for MySQL compatibility
The quote method was hardcoding double quotes (") which only works for PostgreSQL/SQLite. MySQL requires backticks (`). This change delegates to the Rails connection adapter's quote_column_name method to use the correct quoting style for each database. Changes: - Fix quote() to use _model_class.connection.quote_column_name() - Add unit tests for quote method with mock MySQL adapter - Add MySQL and PostgreSQL services to docker-compose for testing - Add mysql2 gem to Gemfile Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6facf3b commit dd05bd9

5 files changed

Lines changed: 188 additions & 2 deletions

File tree

Dockerfile.ruby3.1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ FROM ruby:3.1.5
44

55
# Install dependencies
66
RUN apt-get update -qq && \
7-
apt-get install -y build-essential libpq-dev nodejs && \
7+
apt-get install -y build-essential libpq-dev default-libmysqlclient-dev nodejs && \
88
apt-get clean && \
99
rm -rf /var/lib/apt/lists/*
1010

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ version = ENV['RAILS_VERSION'] || 'default'
1010

1111
platforms :ruby do
1212
gem 'pg'
13+
gem 'mysql2'
1314

1415
if version.start_with?('4.2', '5.0')
1516
gem 'sqlite3', '~> 1.3.13'

docker-compose.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,91 @@ services:
115115
- RAILS_VERSION=${RAILS_VERSION:-8.1.2}
116116
command: /bin/bash
117117

118+
# Database services
119+
mysql:
120+
image: mysql:8.0
121+
container_name: jsonapi-mysql
122+
environment:
123+
MYSQL_ROOT_PASSWORD: root
124+
MYSQL_DATABASE: jsonapi_test
125+
ports:
126+
- "3306:3306"
127+
volumes:
128+
- mysql-data:/var/lib/mysql
129+
healthcheck:
130+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
131+
interval: 10s
132+
timeout: 5s
133+
retries: 5
134+
135+
postgres:
136+
image: postgres:14
137+
container_name: jsonapi-postgres
138+
environment:
139+
POSTGRES_USER: postgres
140+
POSTGRES_PASSWORD: postgres
141+
POSTGRES_DB: jsonapi_test
142+
ports:
143+
- "5432:5432"
144+
volumes:
145+
- postgres-data:/var/lib/postgresql/data
146+
healthcheck:
147+
test: ["CMD-SHELL", "pg_isready -U postgres"]
148+
interval: 10s
149+
timeout: 5s
150+
retries: 5
151+
152+
# Rails 6.1 with MySQL
153+
rails-6.1-mysql:
154+
<<: *test-base
155+
container_name: jsonapi-rails-6.1-mysql
156+
depends_on:
157+
mysql:
158+
condition: service_healthy
159+
environment:
160+
- RAILS_VERSION=6.1.7.10
161+
- DATABASE_URL=mysql2://root:root@mysql:3306/jsonapi_test
162+
command: bash -c "rm -f Gemfile.lock && /usr/local/bin/bundler _2.4.14_ install && /usr/local/bin/bundler _2.4.14_ exec rake test"
163+
164+
# Rails 6.1 with PostgreSQL
165+
rails-6.1-postgres:
166+
<<: *test-base
167+
container_name: jsonapi-rails-6.1-postgres
168+
depends_on:
169+
postgres:
170+
condition: service_healthy
171+
environment:
172+
- RAILS_VERSION=6.1.7.10
173+
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/jsonapi_test
174+
command: bash -c "rm -f Gemfile.lock && /usr/local/bin/bundler _2.4.14_ install && /usr/local/bin/bundler _2.4.14_ exec rake test"
175+
176+
# Rails 7.0 with MySQL
177+
rails-7.0-mysql:
178+
<<: *test-base
179+
container_name: jsonapi-rails-7.0-mysql
180+
depends_on:
181+
mysql:
182+
condition: service_healthy
183+
environment:
184+
- RAILS_VERSION=7.0.10
185+
- DATABASE_URL=mysql2://root:root@mysql:3306/jsonapi_test
186+
command: bash -c "rm -f Gemfile.lock && /usr/local/bin/bundler _2.4.14_ install && /usr/local/bin/bundler _2.4.14_ exec rake test"
187+
188+
# Rails 7.0 with PostgreSQL
189+
rails-7.0-postgres:
190+
<<: *test-base
191+
container_name: jsonapi-rails-7.0-postgres
192+
depends_on:
193+
postgres:
194+
condition: service_healthy
195+
environment:
196+
- RAILS_VERSION=7.0.10
197+
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/jsonapi_test
198+
command: bash -c "rm -f Gemfile.lock && /usr/local/bin/bundler _2.4.14_ install && /usr/local/bin/bundler _2.4.14_ exec rake test"
199+
118200
volumes:
119201
bundle-cache-ruby31:
120202
bundle-cache-ruby27:
121203
bundle-cache-ruby34:
204+
mysql-data:
205+
postgres-data:

lib/jsonapi/active_relation_resource.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ def alias_table_field(table, field, quoted = false)
847847
end
848848

849849
def quote(field)
850-
"\"#{field.to_s}\""
850+
_model_class.connection.quote_column_name(field.to_s)
851851
end
852852

853853
def apply_filters(records, filters, options = {})
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
require File.expand_path('../../../test_helper', __FILE__)
2+
3+
class QuoteMethodTest < ActiveSupport::TestCase
4+
def test_quote_method_does_not_hardcode_double_quotes
5+
# The quote method should NOT hardcode double quotes
6+
# It should delegate to the database adapter's quote_column_name
7+
# This test verifies the implementation uses the adapter, not hardcoded quotes
8+
field_name = 'test_field'
9+
10+
# Get the actual implementation's result
11+
actual = PostResource.send(:quote, field_name)
12+
13+
# The implementation should use connection.quote_column_name, not hardcode "\"#{field}\""
14+
# We verify this by checking if the method delegates to the connection
15+
connection = PostResource._model_class.connection
16+
expected = connection.quote_column_name(field_name)
17+
18+
assert_equal expected, actual,
19+
"quote method should delegate to connection.quote_column_name, not hardcode double quotes"
20+
end
21+
22+
def test_quote_uses_mysql_style_backticks_with_mysql_adapter
23+
# This test verifies the quote method respects the database adapter's quoting style
24+
# MySQL uses backticks, PostgreSQL/SQLite use double quotes
25+
26+
field_name = 'test_field'
27+
28+
# Create a mock connection that uses MySQL-style backticks
29+
mock_connection = Minitest::Mock.new
30+
mock_connection.expect(:quote_column_name, "`#{field_name}`", [field_name.to_s])
31+
32+
# Stub the connection method to return our mock
33+
PostResource._model_class.stub(:connection, mock_connection) do
34+
actual = PostResource.send(:quote, field_name)
35+
assert_equal "`#{field_name}`", actual,
36+
"quote method should use backticks when MySQL adapter is used"
37+
end
38+
39+
mock_connection.verify
40+
end
41+
42+
def test_quote_uses_connection_adapter
43+
# The quote method should delegate to the database adapter's quote_column_name
44+
# This ensures proper quoting for different databases:
45+
# - SQLite/PostgreSQL: double quotes (")
46+
# - MySQL: backticks (`)
47+
field_name = 'test_field'
48+
expected = PostResource._model_class.connection.quote_column_name(field_name)
49+
actual = PostResource.send(:quote, field_name)
50+
51+
assert_equal expected, actual,
52+
"quote method should use connection adapter's quote_column_name"
53+
end
54+
55+
def test_quote_with_symbol_field
56+
field_name = :test_field
57+
expected = PostResource._model_class.connection.quote_column_name(field_name.to_s)
58+
actual = PostResource.send(:quote, field_name)
59+
60+
assert_equal expected, actual,
61+
"quote method should handle symbol field names"
62+
end
63+
64+
def test_concat_table_field_quoted
65+
table = 'posts'
66+
field = 'title'
67+
connection = PostResource._model_class.connection
68+
expected = "#{connection.quote_column_name(table)}.#{connection.quote_column_name(field)}"
69+
actual = PostResource.send(:concat_table_field, table, field, true)
70+
71+
assert_equal expected, actual,
72+
"concat_table_field with quoted=true should use proper database quoting"
73+
end
74+
75+
def test_alias_table_field_quoted
76+
table = 'posts'
77+
field = 'title'
78+
connection = PostResource._model_class.connection
79+
expected = connection.quote_column_name("#{table}_#{field}")
80+
actual = PostResource.send(:alias_table_field, table, field, true)
81+
82+
assert_equal expected, actual,
83+
"alias_table_field with quoted=true should use proper database quoting"
84+
end
85+
86+
def test_sql_field_with_alias_uses_proper_quoting
87+
table = 'posts'
88+
field = 'title'
89+
connection = PostResource._model_class.connection
90+
quoted_table_field = "#{connection.quote_column_name(table)}.#{connection.quote_column_name(field)}"
91+
quoted_alias = connection.quote_column_name("#{table}_#{field}")
92+
expected_sql = "#{quoted_table_field} AS #{quoted_alias}"
93+
94+
actual = PostResource.send(:sql_field_with_alias, table, field, true)
95+
96+
assert actual.is_a?(Arel::Nodes::SqlLiteral),
97+
"sql_field_with_alias should return an Arel::Nodes::SqlLiteral"
98+
assert_equal expected_sql, actual.to_s,
99+
"sql_field_with_alias should use proper database quoting"
100+
end
101+
end

0 commit comments

Comments
 (0)