具有别名表名称的活动记录查询



使用包含作用域的模型关注点,知道嵌套和/或自引用查询可能是最好的方法来编写这些问题是什么?

在我的一个担忧中,我有类似于这些的范围:

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 中的查询顺序?

嗯,好吧,好吧。在查看了大量时间ArelActiveRecordRails问题的来源(这似乎并不新鲜(之后,我能够找到访问当前arel_table对象的方法,以及其table_aliases是否正在使用它们,在执行时在current范围内。

这样就可以知道作用域是否要在具有表名别名的JOIN中使用,或者另一方面,作用域是否可以用于实际表名。

我刚刚将此方法添加到您的Expirable关注点:

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

如您所见,我使用 current_scope 作为查找 arel 表的基本对象,而不是之前使用 self.class.arel_table 甚至 relation.arel_table 的尝试,正如您所说,无论范围在哪里使用,它都保持不变。我只是在该对象上调用source以获取一个Arel::SelectManager,该反过来将为您提供#left上的当前表。目前有两种选择:您有一个Arel::Table(没有别名,表名在#name上(,或者您有一个Arel::Nodes::TableAlias,其#right上有别名。

有了这个table_name,您可以恢复到第一次尝试在作用域中进行#{current_table_name}.#{lower_bound_field}#{current_table_name}.#{upper_bound_field}

def self.lower_bound_column
  "#{current_table_name}.#{lower_bound_field}"
end
def self.upper_bound_column
  "#{current_table_name}.#{upper_bound_field}"
end
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) }

在我看来,这种current_table_name方法在AR/Arel公共API上很有用,因此可以在版本升级中维护它。你觉得怎么样?

如果您有兴趣,以下是我以后使用的一些参考资料:

  • 关于SO的类似问题,用大量代码回答,你可以使用它来代替你美丽简洁的能力。
  • 这个 Rails 问题和另一个问题。
  • 以及 github 上测试应用程序上的提交,使测试变为绿色!

我从@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))

相关内容

  • 没有找到相关文章

最新更新