ActiveRecord::UnknownAttributeReference:使用非属性参数调用的查询方法

You*_*suf 2 ruby ruby-on-rails spatial-query

我试图在 Rails 应用程序中使用空间距离,但当我尝试使用 order 方法时,我不断遇到“ActiveRecord::UnknownAttributeReference: 使用非属性参数调用的查询方法”错误。

这是我的代码:

def nearby_locations
  third_party_location_query = ThirdPartyLocation.where(id: id).select('geom').to_sql

  third_party.organisation.locations.active.
    select("locations.*, ROUND(ST_DistanceSphere(locations.geom, (#{third_party_location_query}))::numeric, 0)::integer distance").
    order("locations.geom <-> (#{third_party_location_query})").
    limit(10).as_json(methods: [:distance])
end
Run Code Online (Sandbox Code Playgroud)

我知道该错误是由于将非属性值传递给 order 方法而引起的,但我不确定在这种情况下如何避免它。如何在查询中使用空间距离而不会遇到此错误?

eng*_*nky 6

order从 Rails 6.0 开始,您不能在不传递对象的情况下在语句中使用非列引用Arel

根据您的情况,选项包括:

 order(Arel.sql("locations.geom <-> (#{third_party_location_query})"))
# Or 
 third_party_location_query = ThirdPartyLocation.select(:geom)
    .arel.where(ThirdPartyLocation.arel_table[:id].eq(id))
 order(
  Arel::Nodes::InfixOperation.new("<->", # operator
    Locations.arel_table[:geom], # left side
    third_party_location_query)  # right side (will add parens as a subquery)  
  ))
Run Code Online (Sandbox Code Playgroud)

我们甚至可以将这部分转换为 arel,但它不会很漂亮

# ROUND(ST_DistanceSphere(locations.geom, (#{third_party_location_query}))::numeric, 0)::integer distance
function = Arel::Nodes::NamedFunction
operation = Arel::Nodes::InfixOperation
operation.new('::',  
  function.new('ROUND',
    [operation.new('::',
      function.new('ST_DistanceSphere',[
        Location.arel_table[:geom],
        ThirdPartyLocation.select(:geom).arel.where(ThirdPartyLocation.arel_table[:id].eq(id))
      ]),
      Arel.sql('numeric')),
    0]),
  Arel.sql('integer')).as('distance')
Run Code Online (Sandbox Code Playgroud)

对于其他偶然发现这篇文章的人:

请注意,Arel#sql不会执行转义。

如果third_party_location_query需要转义,因为它来自第三方并且可能是危险的,所以可以而且应该使用其他技术来清理这些数据:

例如,如果不需要括号,则:

 Arel::Nodes::InfixOperation.new("<->",
    Locations.arel_table[:geom],
    Arel::Nodes.build_quoted(third_party_location_query))
Run Code Online (Sandbox Code Playgroud)

应该管用。

如果需要括号并且参数是单数或参数以逗号分隔。然后

third_party_location_query = "hello" 
Arel::Nodes::Grouping.new([Arel::Nodes.build_quoted(third_party_location_query)]).to_sql
#=> (N'hello')
# Or 
third_party_location_query = [1,2,3]
Arel::Nodes::Grouping.new(third_party_location_query ).to_sql
#=> (1,2,3)
Run Code Online (Sandbox Code Playgroud)

根据实现的不同,还有许多其他方法可以处理所需的转义。