ActiveRecord::StatementInvalid报错:PG语法错误排查
关联模型代码
User.rb
class User < ApplicationRecord has_many :quotes end
Quote.rb
class Quote < ApplicationRecord belongs_to :user has_many :country_quotes, dependent: :delete_all has_many :countries, through: :country_quotes, dependent: :delete_all end
Country.rb
class Country < ApplicationRecord has_many :country_quotes, dependent: :delete_all has_many :quotes, through: :country_quotes, dependent: :delete_all end
Country_Quote.rb
class CountryQuote < ApplicationRecord belongs_to :country, dependent: :delete belongs_to :quote, dependent: :delete end
控制台正常查询示例
在Rails控制台执行以下查询可得到预期结果:
theresults = Quote.joins(:countries).where("countries.name = 'España'") theresults.where(shippingtype: 'Urgente').first.cost
控制器报错场景
在使用Wicked gem的Orders::BuildController#update方法中执行类似查询时,出现如下报错:
ActiveRecord::StatementInvalid in Orders::BuildController#update
PG::SyntaxError: ERROR: syntax error at or near "order" LINE 1: ...RE "quotes"."user_id" = $1 AND (countries.name = @order.coun... ^
控制器代码片段:
class Orders::BuildController < ApplicationController def update temporderregularshippingprice = @user.quotes.joins(:countries).where("countries.name = @order.country" ) puts 'user.quotes2: ' + @user.quotes puts 'order.country2: ' + @order.country orderregularshippingprice = temporderregularshippingprice.where(shippingtype: 'Ordinario').first.cost # 触发报错的行 puts 'orderregularshippingprice: ' + orderregularshippingprice.to_s # ...其他代码 end end
日志显示@user.quotes和@order.country能被正确识别,但SQL语句中直接保留了@order.country变量名而非其值,导致PostgreSQL解析语法错误。
解决方法
正确写法1:参数化查询(推荐,防SQL注入)
将Ruby变量通过占位符?传入where条件,ActiveRecord会自动处理变量替换和SQL注入防护:
temporderregularshippingprice = @user.quotes.joins(:countries).where("countries.name = ?", @order.country)
正确写法2:哈希条件写法(更符合Rails风格)
使用哈希格式的where条件,无需手动拼接SQL字符串:
temporderregularshippingprice = @user.quotes.joins(:countries).where(countries: { name: @order.country })
优化写法:合并查询条件
可以直接合并两个where条件,减少查询次数,同时用pick(:cost)直接获取目标值(避免无记录时的NoMethodError):
orderregularshippingprice = @user.quotes.joins(:countries) .where(countries: { name: @order.country }, shippingtype: 'Ordinario') .pick(:cost)
错误原因说明
原代码中where("countries.name = @order.country")的写法错误:字符串内的@order.country不会被Ruby解析为变量值,而是直接作为SQL语句的一部分发送给PostgreSQL,PostgreSQL无法识别@order这个SQL变量,因此抛出语法错误。
内容的提问来源于stack exchange,提问作者malditojavi

