Rails Arael Выбирает различные колонны
-
30-09-2019 - |
Вопрос
Я ударил небольшой блок с новым 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 Rep Bounty в надежде прийти на элегантное решение в ближайшее время.
Решение
Решение 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. Затем получите комментарии .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 >
SQL, генерируемый для SQLite (Reformatted), чрезвычайно прост, и я надеюсь, что 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
к запросам:
Если вы добавите .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.