ActiveRecord查询带有别名&#d;表名

时间:2015-02-23 22:54:14

标签: ruby-on-rails activerecord arel

使用包含范围的模型问题,知道嵌套和/或自引用查询的最佳方法是什么?

在我的一个问题中,我的范围与这些相似:

scope :current, ->(as_at = Time.now) { current_and_expired(as_at).current_and_future(as_at) }
scope :current_and_future, ->(as_at = Time.now) { where("#{upper_bound_column} IS NULL OR #{upper_bound_column} >= ?", as_at) }
scope :current_and_expired, ->(as_at = Time.now) { where("#{lower_bound_column} IS NULL OR #{lower_bound_column} <= ?", as_at) }

def self.lower_bound_column
  lower_bound_field
end
def self.upper_bound_column
  upper_bound_field
end

并通过has_many来提及,例如:has_many :company_users, -> { current }

如果进行ActiveRecord查询,该查询涉及包含问题的一些模型,则会导致“模糊列名”异常,这是有道理的。

为了帮助解决这个问题,我将列名帮助方法改为现在

def self.lower_bound_column
  "#{self.table_name}.#{lower_bound_field}"
end
def self.upper_bound_column
   "#{self.table_name}.#{upper_bound_field}"
end

哪种方法很有效,直到您需要自我引用查询。 Arel通过在生成的SQL中对表名进行别名来帮助缓解这些问题,例如:

LEFT OUTER JOIN "company_users" "company_users_companies" ON "company_users_companies"."company_id" = "companies"."id"

INNER JOIN "company_users" ON "users"."id" = "company_users"."user_id" WHERE "company_users"."company_id" = $2

这里的问题是self.table_name不再引用查询中的表名。这导致舌头在脸颊暗示:HINT: Perhaps you meant to reference the table alias "company_users_companies"

在尝试将这些查询迁移到Arel时,我将列名帮助方法更改为:

def self.lower_bound_column
  self.class.arel_table[lower_bound_field.to_sym]
end
def self.upper_bound_column
  self.class.arel_table[upper_bound_field.to_sym]
end

并更新范围以反映:

lower_bound_column.eq(nil).or(lower_bound_column.lteq(as_at))

但这只是移植了问题,因为无论查询如何,self.class.arel_table始终都是相同的。

我想我的问题是,我如何创建可用于自引用查询的范围,这些范围需要<=>=等运算符?


编辑

我已经创建了一个基本的应用程序来帮助展示这个问题。

git clone git@github.com:fattymiller/expirable_test.git
cd expirable_test
createdb expirable_test-development
bundle install
rake db:migrate
rake db:seed
rails s

调查结果和假设

  1. 适用于sqlite3,而不是Postgres。很可能是因为Postgres在SQL中强制执行查询顺序?

2 个答案:

答案 0 :(得分:11)

好吧,好吧,好吧。经过相当长的一段时间查看ArelActiveRecordRails问题的来源(似乎这不是新的),我能够找到访问当前{{{ 1}}对象,如果它们被使用,则arel_table在执行时table_aliases范围内。

这使得可以知道范围是否将在具有表名别名的current内使用,或者另一方面可以在实际表名上使用范围。

我刚刚将此方法添加到您的JOIN关注中:

Expirable

正如您所看到的,我使用current_scope作为基础对象来查找arel表,而不是先前使用def self.current_table_name current_table = current_scope.arel.source.left case current_table when Arel::Table current_table.name when Arel::Nodes::TableAlias current_table.right else fail end end 甚至self.class.arel_table的尝试,正如你所说,无论使用何种范围,它都保持不变。我只是在该对象上调用relation.arel_table来获取Arel::SelectManager,而similar question on SO会在source上为您提供当前表格。目前有两种选择:你有一个#left(没有别名,表名在Arel::Table上),或者你有一个#name,其{{1}上有别名}}。

使用该表格,您可以在您的范围内恢复为Arel::Nodes::TableAlias#right的首次尝试:

#{current_table_name}.#{lower_bound_field}

这个#{current_table_name}.#{upper_bound_field}方法在我看来对AR / Arel公共API有用,因此可以跨版本升级进行维护。你觉得怎么样?

如果您有兴趣,这里有一些我在路上使用的参考文献:

答案 1 :(得分:0)

我从@dgilperez稍作修改,使用了Arel的全部功能

def self.current_table_name
 current_table = current_scope.arel.source.left
end

现在您可以使用arel_table语法

修改您的方法
def self.lower_bound_column
 current_table[:lower_bound_field]
end

def self.upper_bound_column
  current_table[:upper_bound_field]
end

并像这样使用它查询

 lower_bound_column.eq(nil).or(lower_bound_column.lteq(as_at))