Rails AR 有办法将一个查询虚拟成 has_one 关联吗?(答案是可以)

mizuhashi · 2016年12月19日 · 最后由 mizuhashi 回复于 2016年12月21日 · 1950 次阅读

场景:

原来 model 里有 product has_one review_summary,review_summary 是一个 reviews 的统计数据,目前由定时任务以及回调更新。
现在这个 summary 的逻辑简化了,可以通过一个简单的查询查出来,所以想将原来的 has_one 关联替换成一个查询,这样可以沿用之前 includes 的代码,逻辑上也是一致的。

我知道视图可以做这个,但是希望可以在 ruby 的层面上做这个事情,不知可有好的方法?

没太清楚这场景,但是 has_one 貌似必须有个 foreign_key 吧。。。写个方法调用吧。。。

#1 楼 @flemon1986 foreign_key 有的,例如select products.id as product_id, avg(stars) as avg_star from products join reviews,可以把 products_id 作为这个表的外键

has_one :review_summary, -> { where(is_default: true) }

是这种吗?

#3 楼 @flemon1986 我找到办法了

class Product
  has_many :reviews
  has_one :review_summary
end

class ReviewSummary < ApplicationRecord
  self.table_name = 'reviews'
  default_scope {
    from('products').
      joins('INNER JOIN reviews ON reviews.product_id = products.id').
      select('products.id as product_id, avg(stars) as avg_star')
  }

end

然后

p = Product.includes(:review_summary).first
p.review_summary.avg_star # => 平均评分

这样就可以利用 includes 把统计数据都查出来了,生成的查询是这样的

SELECT products.id as product_id, avg(stars) as avg_star FROM products INNER JOIN reviews ON reviews.product_id = products.id WHERE "reviews"."product_id" IN (1, 2)

上面是用 reviews.product_id 用来做 where 条件的,可能有点巧合的感觉,一种更好的配法是

class ReviewSummary < ApplicationRecord
  self.table_name = 'products'

  default_scope {
    joins('INNER JOIN reviews ON reviews.product_id = products.id').
      select('products.id as id, avg(stars) as avg_star')
  }

end

class Product < ApplicationRecord
  has_many :reviews
  has_one :review_summary, foreign_key: :id
end

生成的查询是:

SELECT products.id as product_id, avg(stars) as avg_star FROM products INNER JOIN reviews ON reviews.product_id = products.id WHERE "products"."id" IN (1, 2)

用 products.id 来写 where 正是我们想要的行为,完美。。这样虚拟出关联还是相当好玩的,不用 create view 了

has_one :review_summary,
  -> { joins('INNER JOIN reviews ON reviews.product_id = products.id').select('products.id as id, avg(stars) as avg_star')},
  class_name: "Product"

这样?

#5 楼 @flemon1986 应该也行,还差个 foreign_key,只是用 Product 还是用 ReviewSummary 来实例化的问题,反正 avg_star 肯定读的到的

需要 登录 后方可回复, 如果你还没有账号请 注册新账号