在 Rails 4 中将 find_by_sql 语法转换为正确的 Active Record AREL 语法

Converting find_by_sql syntax to proper Active Record AREL syntax in Rails 4

给定一个具有三维坐标 x、y 和 z 的系统模型,我编写了以下实例方法来为我提供所讨论的特定系统的设定范围内的所有其他系统。这是代码:

def systems_within(range = '15')
  System.find_by_sql(["SELECT * FROM systems WHERE Sqrt(Pow((:x - systems.x), 2) + Pow((:y - systems.y), 2) + Pow((:z - systems.z), 2)) <= :range AND id!= :current_id", {x: x, y: y, z: z, range: range, current_id: id}])
end

有没有 ActiveRecord 方法可以做到这一点?

由于您需要访问当前实例,因此您需要保留实例方法,但您可以通过将部分移动到作用域中来稍微分解它(我猜这就是您所说的 ActiveRecord 的意思)。

这样的事情可能会奏效。这是未经测试的代码。

scope :within_range, -> (x, y, z, range = '15') {
  where("Sqrt(Pow((:x - systems.x), 2) + Pow((:y - systems.y), 2) + Pow((:z - systems.z), 2)) <= :range", {x: x, y: y, z: z, range: range})
}
scope :excluding, -> (excluded_id) { where("id <> ?", excluded_id) }

def systems_within(range = 15)
  System.excluding(id).within_range(x, y, z, range)
end

您可以通过与 范围的平方:

square_range = range**2

"select * from systems"是隐含的,所以你只需要调用where子句。我会将这样的条件与复杂的计算一起保留为 SQL 片段:

System.where(
   'Pow((:x - x), 2) + Pow((:y - y), 2) + Pow((:z - z), 2)) <= :square_range', 
    {x: x, y: y, z: z, square_range: square_range}
).where.not(id: current_id)

请注意,您在这里只使用 x 而不是 systems.x,因为另一个 :x 只是参数而不是自己的数据库对象。

像 Brad Pauly 所建议的那样,将所有内容都放在范围内也是一个好主意。 顺便说一句,调用 class "System" 可能很危险,因为 ruby 有一个 system 方法来进行操作系统调用。