具有别名 table 名称的 ActiveRecord 查询
ActiveRecord query with alias'd table names
使用包含范围的模型关注点,在知道可能存在嵌套 and/or 自引用查询的情况下,编写这些关注点的最佳方式是什么?
在我的一个担忧中,我有类似于这些的范围:
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 查询引用了几个包含问题的模型,这会导致 'ambiguous column name' 异常,这是有道理的。
为了帮助克服这个问题,我现在将列名辅助方法更改为
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 中为 table 名称添加别名来帮助缓解这些问题,例如:
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" =
这里的问题是 self.table_name
不再引用查询中的 table 名称。这导致舌头在脸颊提示中: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
调查结果和假设
- 适用于 sqlite3,不适用于 Postgres。很可能是因为 Postgres 强制执行 SQL?
中的查询顺序
好吧好吧。经过相当长的时间查看 Arel
、ActiveRecord
和 Rails
问题的来源(似乎这不是新问题),我找到了访问当前 arel_table
对象,其 table_aliases
如果正在使用,则在执行时位于 current
范围内。
这使得可以知道范围是否将在具有 table 名称别名的 JOIN
中使用,或者另一方面,范围是否可以在真实环境中使用table 姓名。
我刚刚将此方法添加到您的 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
as the base object to look for the arel table, instead of the prior attempts of using self.class.arel_table
or even relation.arel_table
, which as you said remained the same regardless of where the scope was used. I'm just calling source
on that object to obtain an Arel::SelectManager
,它反过来会为您提供 #left
上的当前 table。目前有两个选择:你有一个 Arel::Table
(没有别名,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/ArelpublicAPI很有用,所以它可以跨版本升级维护。你怎么看?
如果您有兴趣,这里有一些我在路上使用的参考资料:
- A similar question on SO,用大量代码回答,你可以用它来代替你漂亮而简洁的能力。
- 这个Rails issue and this other one.
- 和 github 上的 the commit on your test app 使测试成为绿色!
我对@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))
使用包含范围的模型关注点,在知道可能存在嵌套 and/or 自引用查询的情况下,编写这些关注点的最佳方式是什么?
在我的一个担忧中,我有类似于这些的范围:
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 查询引用了几个包含问题的模型,这会导致 'ambiguous column name' 异常,这是有道理的。
为了帮助克服这个问题,我现在将列名辅助方法更改为
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 中为 table 名称添加别名来帮助缓解这些问题,例如:
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" =
这里的问题是 self.table_name
不再引用查询中的 table 名称。这导致舌头在脸颊提示中: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
调查结果和假设
- 适用于 sqlite3,不适用于 Postgres。很可能是因为 Postgres 强制执行 SQL? 中的查询顺序
好吧好吧。经过相当长的时间查看 Arel
、ActiveRecord
和 Rails
问题的来源(似乎这不是新问题),我找到了访问当前 arel_table
对象,其 table_aliases
如果正在使用,则在执行时位于 current
范围内。
这使得可以知道范围是否将在具有 table 名称别名的 JOIN
中使用,或者另一方面,范围是否可以在真实环境中使用table 姓名。
我刚刚将此方法添加到您的 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
as the base object to look for the arel table, instead of the prior attempts of using self.class.arel_table
or even relation.arel_table
, which as you said remained the same regardless of where the scope was used. I'm just calling source
on that object to obtain an Arel::SelectManager
,它反过来会为您提供 #left
上的当前 table。目前有两个选择:你有一个 Arel::Table
(没有别名,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/ArelpublicAPI很有用,所以它可以跨版本升级维护。你怎么看?
如果您有兴趣,这里有一些我在路上使用的参考资料:
- A similar question on SO,用大量代码回答,你可以用它来代替你漂亮而简洁的能力。
- 这个Rails issue and this other one.
- 和 github 上的 the commit on your test app 使测试成为绿色!
我对@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))