具有别名表名的ActiveRecord查询

fat*_*tty 8 activerecord ruby-on-rails 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
Run Code Online (Sandbox Code Playgroud)

并通过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
Run Code Online (Sandbox Code Playgroud)

哪种方法很有效,直到您需要自引用查询.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
Run Code Online (Sandbox Code Playgroud)

并更新范围以反映:

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
Run Code Online (Sandbox Code Playgroud)

调查结果和假设

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

dgi*_*rez 13

好吧,好吧.相当大的时间翻翻的来源后Arel,ActiveRecord和Rails问题(看来这是不是新的),我能找到访问当前的方式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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我正在使用current_scope作为基础对象来查找arel表,而不是先前使用self.class.arel_table或甚至尝试使用relation.arel_table,正如您所说的那样,无论使用何种范围,都保持不变.我只是调用source那个对象获取一个Arel::SelectManager反过来会给你当前的表#left.此时有两个选项:你有一个Arel::Table(没有别名,表名是打开的#name)或者你有一个Arel::Nodes::TableAlias别名#right.

与表名,你可以恢复到你的第一次尝试#{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) }
Run Code Online (Sandbox Code Playgroud)

current_table_name在我看来,这种方法对AR/Arel公共API有用,因此可以跨版本升级进行维护.你怎么看?

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