سؤال

لقد ضربت كتلة طفيفة مع الجديد scope الطرق (Arel 0.4.0 ، Rails 3.0.0.RC)

في الأساس لدي:

أ topics النموذج ، الذي has_many :comments, ، و comments نموذج (مع أ 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 ، ولكن بناء الجملة 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_at و topic_id عمود في comments الطاولة.

الحل 2

أضف 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. تتمثل إحدى الطرق في الحصول على قائمة التعليقات الخمسة الأخيرة التي تم تجميعها بواسطة Topic_ID. ثم احصل على التعليقات.

أنا جديد جدًا على أريل ولكن شيء من هذا القبيل يمكن أن يعمل

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

لقد استخدمت البذور التالية. 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 > 

يعد SQL الذي تم إنشاؤه لـ SQLite (إعادة تنسيق) بسيطًا للغاية وآمل أن يقدم Arel SQL مختلفًا للمحركات الأخرى لأن هذا سيفشل بالتأكيد في العديد من محركات DB لأن الأعمدة الموجودة في الموضوع ليست في "المجموعة حسب القائمة". إذا كان هذا يمثل مشكلة ، فربما يمكنك التغلب عليها من خلال الحد من الأعمدة المحددة على التعليقات فقط. 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

نظرًا لأن السؤال كان حول Arel ، اعتقدت أنني سأضيف هذا ، منذ أن أضيف Rails 3.2.1 uniq إلى QueryMethods:

اذا اضفت .uniq إلى أريل يضيف DISTINCT إلى select بيان.

على سبيل المثال 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