具有别名表名的ActiveRecord查询
使用包含范围的模型关注点,知道嵌套和/或自引用查询的最佳方法是什么?
在我的一个问题中,我的范围与以下类似:
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查询,该查询引用包含关注点的少数模型,则会导致“模糊列名称”exception,这是有意义的。
为了帮助解决这个问题,我将列名称辅助方法更改为现在
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
调查结果和假设
- 适用于sqlite3,而不是Postgres。 很可能是因为Postgres在SQL中强制执行查询顺序?
好吧,好吧。 经过相当大的时间查看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
正如您所看到的,我正在使用current_scope
作为基础对象来查找arel表,而不是先前尝试使用self.class.arel_table
甚至是relation.arel_table
,正如您所说的那样,无论在哪里都保持不变范围被使用。 我只是在该对象上调用source
来获取Arel::SelectManager
,而Arel::SelectManager
将为您提供#left
上的当前表。 此时有两个选项:你有一个Arel::Table
(没有别名,表名在#name
)或者你有一个Arel::Nodes::TableAlias
和Arel::Nodes::TableAlias
上的别名。
使用该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 上的测试应用程序上提交 ,使测试变为绿色!
- 查找没有相关记录的所有记录
- Rails:AbstractController :: Helpers :: MissingHelperError – 缺少帮助文件application_helper.rb_helper.rb
- 在Rails模型中; 保存到DB时,符号会自动转换为YAML。 什么是正确的方法?
- activerecord通过关联找到
- 使用ActiveRecord和Rails将数据插入postgresql数据库会出现此错误:RuntimeError:ERROR C22003 Minteger o
- 如何(大规模)减少Rails应用程序中的SQL查询数量?
- Rails模型中的class_name foreign_key
- Rails计数记录而不再查询
- Rails分页现有的ActiveRecord结果数组
- 将类方法添加到ActiveRecord :: Base
- Rails gem rails3-jquery-autocomplete:如何查询多个字段