Skip to content

Commit dd05bd9

Browse files
takaokoujiclaude
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)