User, Itemモデルと、両者をつなぐ Bookmark (user, item) 、Follow(user, user)の4モデルがあるのですが、
「過去X時間で最もブックマークされたItem X件」とか、「最近友達がブックマークしたItem X件」といった、ちょっと複雑なリクエストの仕方がイマイチよくわかりません。
こういう風にやれば取得できる!!という定番はあるのでしょうか?
「過去X時間で最もブックマークされたItem X件」は、
bookmarks = Bookmark.group("item_id").select("*, count('item_id') AS cnt").order("cnt DESC")
これで、cntで取り出せます。
ex) p bookmakrs.map(&:cnt)
実は、MySQL/SQLiteなどのSQL DBではタイムライン処理のようなモノが難しいです。
http://labs.cybozu.co.jp/blog/kazuho/archives/2008/06/friends_timeline.php
サーバーはmySQLですかね?そうだったら、mySQL側で、SQLでガリガリ組んで、viewを作ったらどうでしょうかね?
MySQL :: MySQL 5.1 リファレンスマニュアル :: 20.2 CREATE VIEW 構文
Ruby on Railsは知らないんですが、viewだったら、テーブルとして定義されるので、普通にマッピング出来るはず。
それで、肝心のSQLですが、サブクエリで、fromの中にいろいろごにょごにょ入れられるので、それでやってみる事が出来ると思いますよ。
Rubyでも、直接SQLの発行が出来ると思うので、
Ruby on Rails - Rails で、SQL分を直接実行する場合 - gendosuの企画開発室
サブクエリを使ってごにょごにょSQLを組み立てるのもいいかも。
これは僕がcakePHPで作ったサンプルのスクリーンショット。
no title
ORMで複雑なクエリをいろいろすると凄く遅いので、SQLで解決するのが一番パフォーマンスいいと思います。
class User < ActiveRecord::Base has_many :follows, foreign_key: :follower_id end class Item < ActiveRecord::Base has_many :bookmarks scope :bookmarked, select("items.*, COUNT(bookmarks.id) AS bookmark_count"). joins(:bookmarks). group("bookmarks.item_id") scope :recently_bookmarked, proc {|n| t = n.hours.ago bookmarked. where("bookmarks.created_at > ?", t). order("bookmark_count DESC") } scope :recently_bookmarked_by_followee, proc{|n, follower| followee_ids = follower.follows.map{|f| f.followee_id } recently_bookmarked(n). where("bookmarks.user_id IN (?)", followee_ids) } end # 過去 x 時間以内に最もブックマークされた Item を n 件 Item.recently_bookmarked(x).limit(n) # user がフォローしてるユーザに、過去 x 時間以内に最もブックマークされた Item を n 件 Item.recently_bookmarked_by_followee(x, user).limit(n)
定番かどうかわかりませんが、こんな感じでしょうか。ほとんど SQL ですが、scope で抽象化すると使いやすいです。
後者は JOIN のみでも可能ですが、これくらいが複雑になりすぎず好みです。実際には follower.followee_ids みたいなメソッドを作ってキャッシュするなどしてます。
データベースの VIEW を作るのも良いですが、Rails だとテーブルと対応したモデルと別のクラスになってしまうのが悩みどころですね。
うわー、ぱっとやり方が見つからないと思ったら、やっぱりゴニャゴニャと色々やらなきゃならないんですね。 初期のTwitterのfollow上限が1000だったりしたのは、この辺が原因なのかしらですね。
2012/07/13 15:46:57