【问题标题】:ActiveRecord query with alias'd table names具有别名表名的 ActiveRecord 查询
【发布时间】:2015-04-25 10:44:28
【问题描述】:

使用包含范围的模型关注点,在知道可能存在嵌套和/或自引用查询的情况下编写这些关注点的最佳方式是什么?

在我的一个顾虑中,我的范围与以下类似:

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, -&gt; { 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 都将始终相同。

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


编辑

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

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

【问题讨论】:

标签: ruby-on-rails activerecord arel


【解决方案1】:

我有一个来自@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))

【讨论】:

    【解决方案2】:

    好吧,好吧,好吧。经过相当长的时间查看ArelActiveRecordRails 问题的来源(这似乎不是新的),我能够找到访问当前arel_table 对象的方法,它的@ 987654331@ 如果正在使用它们,则在执行时位于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 上会很有用,因此它可以在版本升级中得到维护。你怎么看?

    如果你有兴趣,这里有一些我在路上使用的参考资料:

    【讨论】:

    • 非常感谢您在此问题上的帮助和努力!我的研究显示了非常相似的建议——甚至建议解析生成的 SQL :|我认为他们没有包含与您的建议类似的内容是有原因的,但是绝对值得您与 Rails 团队讨论,看看他们是否会考虑将您的解决方案合并到核心 - 您的名称将与未来的编码人员看到的一段相当不错的代码相对应..
    • 我很高兴能提供帮助,我可以说我也从中学到了很多东西!相当挑战:) 我会在 Rails 问题上询问他们是否考虑 PR。谢谢!
    • @dgilperez,嘿.. 你觉得我修改后的方法怎么样?我很想听听你的建议..
    猜你喜欢
    • 2013-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多