让我们以此作为开头,这不是您要寻找的答案...
现在,除此之外,这就是我尝试的方法以及它与我在问题评论中发布的两个链接的关系。
class User < ActiveRecord::Base
# self-referential association - more on this later
belongs_to :myself, class_name: "User", foreign_key: :id
scope :w_computed_metric, ->() { select{[id, (id *2).as(computed_metric)]} }
scope :w_age_in_seconds, ->() { select{[id, (extract('epoch from now()') - extract('epoch from users.created_at')).as(age_in_seconds)]} }
scope :w_default_attributes, ->() { select{`*`} }
def self.compound_metric
scope = User.w_default_attributes.select{(b.age_in_seconds / a.computed_metric).as(compound_metric)}
scope = scope.joins{"inner join (" + User.w_computed_metric.to_sql + ") as a on a.id = users.id"}
scope = scope.joins{"inner join (" + User.w_age_in_seconds.to_sql + ") as b on b.id = users.id"}
end
sifter :sift_computed_metric do
(id * 2).as(computed_metric)
end
sifter :sift_age_in_seconds do
(extract(`epoch from now()`) - extract(`epoch from users.created_at`)).as(age_in_seconds)
end
def self.using_sifters_in_select
User.w_default_attributes.joins{myself}.select{[(myself.sift :sift_computed_metric), (myself.sift :sift_age_in_seconds)]}
end
def self.using_from
scope = User.w_default_attributes
scope = scope.select{[(age_in_seconds / computed_metric).as(compound_metric)]}
scope = scope.from{User.w_computed_metric.w_age_in_seconds}
end
end
因此,User.compound_metric
在控制台中运行将产生您正在寻找的结果 -User
具有附加属性的对象:computed_metric
、age_in_seconds
和compound_metric
. 不幸的是,这违反了您对可接受答案的第三个约束。那好吧...
我还尝试了其他一些事情(如您从上面看到的):
首先要注意的是自我参照关联,我对此感到非常自豪——尽管它并没有让我们到达我们想去的地方。
belongs_to :myself, class_name: "User", foreign_key: :id
这段漂亮的代码让您可以通过连接访问同一个对象。为什么这很重要?好吧,Squeel 只允许您通过该joins{}
方法访问关联,除非您向它传递一个 SQL 字符串。这让我们可以使用sifter
Squeel 的 s 特性——在这种情况下,不过滤结果,而是包含来自 db 的聚合列,让 Squeel 完成别名和连接语句的繁重工作。你可以用
def self.using_sifters_in_select
User.w_default_attributes.joins{myself}.select{[(myself.sift :sift_computed_metric), (myself.sift :sift_age_in_seconds)]}
end
实现这一点的筛选器的美妙之处在于可链接性和合成糖 - 它非常扁平且可读。
我尝试玩的最后一点是.from{}
. 在这个问题之前,我什至不知道它的存在。我很高兴我错过了一些简单的事情,比如包含查询的源(在这种情况下是子选择)。使用 using_from 进行测试
def self.using_from
scope = User.w_default_attributes
scope = scope.select{[(age_in_seconds / computed_metric).as(compound_metric)]}
scope = scope.from{User.w_computed_metric.w_age_in_seconds}
end
导致类型错误:
TypeError: Cannot visit Arel::SelectManager
from /home/prg10itd/projects/arel/lib/arel/visitors/visitor.rb:28:in `rescue in visit'
from /home/prg10itd/projects/arel/lib/arel/visitors/visitor.rb:19:in `visit'
from /home/prg10itd/projects/arel/lib/arel/visitors/to_sql.rb:348:in `visit_Arel_Nodes_JoinSource'
from /home/prg10itd/projects/arel/lib/arel/visitors/visitor.rb:21:in `visit'
from /home/prg10itd/projects/arel/lib/arel/visitors/to_sql.rb:139:in `visit_Arel_Nodes_SelectCore'
from /home/prg10itd/projects/arel/lib/arel/visitors/to_sql.rb:121:in `block in visit_Arel_Nodes_SelectStatement'
from /home/prg10itd/projects/arel/lib/arel/visitors/to_sql.rb:121:in `map'
from /home/prg10itd/projects/arel/lib/arel/visitors/to_sql.rb:121:in `visit_Arel_Nodes_SelectStatement'
from /home/prg10itd/projects/arel/lib/arel/visitors/visitor.rb:21:in `visit'
from /home/prg10itd/projects/arel/lib/arel/visitors/visitor.rb:5:in `accept'
from /home/prg10itd/projects/arel/lib/arel/visitors/to_sql.rb:19:in `accept'
(是的,我正在测试 Arel 和 Squeel 的本地副本)。我对 Arel 的内部运作不够熟悉,无法在没有进一步努力的情况下解决问题(很可能是 Arel 的一个分支)。看起来 Squeel 只是将from{}
方法传递给 Arelfrom()
方法而没有做任何事情(除了 Squeel 的其余魔法之外)。
那么,我们将何去何从?一个有效的解决方案,但不像我希望的那样漂亮和优雅 - 但也许其他人可以利用它来获得更好的解决方案。
PS - 这是 Rails v3.2.13 和 Arel 的相应版本。Rails v4 和 Arel 的源代码完全不同,因此未经测试。