質問

新しいものでわずかなブロックを打った scope 方法(Arel 0.4.0、Rails 3.0.0.RC)

基本的に私は持っています:

a topics モデル、それ has_many :comments, 、a comments モデル(aで topic_id 列)どちら belongs_to :topics.

私は「ホットトピック」のコレクションを取得しようとしています。つまり、最近コメントされたトピックです。現在のコードは次のとおりです。

# models/comment.rb
scope :recent, order("comments.created_at DESC")

# models/topic.rb
scope :hot, joins(:comments) & Comment.recent & limit(5)

実行した場合 Topic.hot.to_sql, 、次のクエリが起動されます。

SELECT "topics".* FROM "topics" INNER JOIN "comments"
ON "comments"."topic_id" = "topics"."id"
ORDER BY comments.created_at DESC LIMIT 5

これは正常に機能しますが、重複するトピックを返す可能性があります。トピック#3が最近数回コメントされた場合、何度か返されます。

私の質問

私はまだアクセスする必要があることを念頭に置いて、明確な一連のトピックを返却するにはどうすればよいですか comments.created_at フィールド、最後の投稿はどれくらい前に表示されましたか?私はその線に沿って何かを想像するでしょう distinct また group_by, 、しかし、私はそれをどのように行うのが最善かわからない。

どんなアドバイス /提案も大歓迎です - 私はすぐにエレガントなソリューションに来ることを期待して、100の担当者の賞金を追加しました。

役に立ちましたか?

解決

解決策1

これはARELを使用しませんが、Rails 2.x構文:

Topic.all(:select => "topics.*, C.id AS last_comment_id, 
                       C.created_at AS last_comment_at",
          :joins => "JOINS (
             SELECT DISTINCT A.id, A.topic_id, B.created_at
             FROM   messages A,
             (
               SELECT   topic_id, max(created_at) AS created_at
               FROM     comments
               GROUP BY topic_id
               ORDER BY created_at
               LIMIT 5
             ) B
             WHERE  A.user_id    = B.user_id AND 
                    A.created_at = B.created_at
           ) AS C ON topics.id = C.topic_id
          "
).each do |topic|
  p "topic id: #{topic.id}"
  p "last comment id: #{topic.last_comment_id}"
  p "last comment at: #{topic.last_comment_at}"
end

必ずインデックスを付けてください created_attopic_id の列 comments テーブル。

解決策2

Aを追加します last_comment_id あなたの列 Topic モデル。更新します last_comment_id コメントを作成した後。このアプローチは、複雑なSQLを使用して最後のコメントを決定するよりもはるかに高速です。

例えば:

class Topic < ActiveRecord::Base
  has_many :comments
  belongs_to :last_comment, :class_name => "Comment"
  scope :hot, joins(:last_comment).order("comments.created_at DESC").limit(5)
end

class  Comment
  belongs_to :topic

  after_create :update_topic

  def update_topic
    topic.last_comment = self
    topic.save
    # OR better still
    # topic.update_attribute(:last_comment_id, id)
  end
end

これは、複雑なSQLクエリを実行してホットトピックを決定するよりもはるかに効率的です。

他のヒント

これは、ほとんどのSQL実装ではそれほどエレガントではありません。 1つの方法は、最初にTopic_IDによってグループ化された5つの最新のコメントのリストを取得することです。次に、in句を使用してsubを選択してcomments.created_atを取得します。

私はアレルにとても新しいですが、このようなものはうまくいくかもしれません

recent_unique_comments = Comment.group(c[:topic_id]) \
                                .order('comments.created_at DESC') \
                                .limit(5) \
                                .project(comments[:topic_id]
recent_topics = Topic.where(t[:topic_id].in(recent_unique_comments))

# Another experiment (there has to be another way...)

recent_comments = Comment.join(Topic) \
                         .on(Comment[:topic_id].eq(Topic[:topic_id])) \ 
                         .where(t[:topic_id].in(recent_unique_comments)) \
                         .order('comments.topic_id, comments.created_at DESC') \
                         .group_by(&:topic_id).to_a.map{|hsh| hsh[1][0]}

これを達成するには、 GROUP BY 各トピックの最新のコメントを取得します。その後、このスコープを注文できます created_at トピックについて最新のコメントを取得するため。

SQLiteを使用して、以下の機能が機能します

class Comment < ActiveRecord::Base

  belongs_to :topic

  scope :recent, order("comments.created_at DESC")
  scope :latest_by_topic, group("comments.topic_id").order("comments.created_at DESC")
end


class Topic < ActiveRecord::Base
  has_many :comments

  scope :hot, joins(:comments) & Comment.latest_by_topic & limit(5)
end

次のSeeds.RBを使用してテストデータを生成しました

(1..10).each do |t|
  topic = Topic.new
  (1..10).each do |c|
    topic.comments.build(:subject => "Comment #{c} for topic #{t}")
  end
  topic.save
end

そして、以下はテスト結果です

ruby-1.9.2-p0 > Topic.hot.map(&:id)
 => [10, 9, 8, 7, 6] 
ruby-1.9.2-p0 > Topic.first.comments.create(:subject => 'Topic 1 - New comment')
 => #<Comment id: 101, subject: "Topic 1 - New comment", topic_id: 1, content: nil, created_at: "2010-08-26 10:53:34", updated_at: "2010-08-26 10:53:34"> 
ruby-1.9.2-p0 > Topic.hot.map(&:id)
 => [1, 10, 9, 8, 7] 
ruby-1.9.2-p0 > 

SQLite用に生成されたSQL(Reforatted)は非常に簡単であり、トピック内の列が「グループごと」にないため、多くのDBエンジンで確かに失敗するため、Arelが他のエンジンのSQLを異なることを願っています。これが問題を提示した場合、選択した列を単なるコメントに制限することで、おそらくそれを克服することができます。topic_id

puts Topic.hot.to_sql
SELECT     "topics".* 
FROM       "topics" 
INNER JOIN "comments" ON "comments"."topic_id" = "topics"."id" 
GROUP BY  comments.topic_id 
ORDER BY  comments.created_at DESC LIMIT 5

質問はアレルに関するものだったので、Rails 3.2.1が追加するので、私はこれを追加すると思いました uniq QueryMethodsへ:

追加する場合 .uniq アレルに追加します DISTINCTselect 声明。

例えば Topic.hot.uniq

範囲でも動作します:

例えば scope :hot, joins(:comments).order("comments.created_at DESC").limit(5).uniq

だから私はそれを想定します

scope :hot, joins(:comments) & Comment.recent & limit(5) & uniq

おそらく機能するはずです。

見る http://apidock.com/rails/activerecord/querymethods/uniq

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top