2013-06-09 2 views
0

В моем запросе ActiveRecord, мне нужно предоставить эту информацию в методе выбора:ActiveRecord выберите добавить счетчик

(SELECT count(*) from likes where likes.spentit_id = spentits.id) as like_count, 
(SELECT count(*) from comments where comments.spentit_id = spentits.id) as comment_count 

Конечно, я прохожу передать эти две строки, как в .select() части, но я интересно, каков правильный/альтернативный способ сделать это?

Вот полный запрос я пытаюсь позвонить:

SELECT DISTINCT 
    spentits.*, 
    username, 
    (SELECT count(*) from likes where likes.spentit_id = spentits.id) as like_count, 
    (SELECT count(*) from comments where comments.spentit_id = spentits.id) as comment_count, 
    (SELECT count(*) from wishlist_items where wishlist_items.spentit_id = spentits.id) as wishlist_count, 
    (case when likes.id is null then 0 else 1 end) as is_liked_by_me, 
    (case when wishlist_items.id is null then 0 else 1 end) as is_wishlisted_by_me, 
    (case when comments.id is null then 0 else 1 end) as is_commented_by_me 
FROM spentits 
LEFT JOIN users ON users.id = spentits.user_id 
LEFT JOIN likes ON likes.user_id = 9 AND likes.spentit_id = spentits.id 
LEFT JOIN wishlist_items ON wishlist_items.user_id = 9 AND wishlist_items.spentit_id = spentits.id 
LEFT JOIN comments ON comments.user_id = 9 AND comments.spentit_id = spentits.id 
WHERE spentits.user_id IN 
    (SELECT follows.following_id 
    FROM follows 
    WHERE follows.follower_id = 9 AND follows.accepted = 1) 
ORDER BY id DESC LIMIT 15 OFFSET 0; 

Все таблицы здесь есть свой соответствующий объект ActiveRecord. Просто очень смутно, как преобразовать этот запрос в «activerecord»/rails с записью наименьшего количества SQL. «9» user_id должен быть параметром.

Update: Ok так вот что я сделал inmean время, это намного лучше, чем необработанного заявление SQL, но она по-прежнему выглядит некрасиво мне:

class Spentit < ActiveRecord::Base 
    belongs_to :user 
    has_many :likes 
    has_many :wishlist_items 
    has_many :comments 

    scope :include_author_info, lambda { 
    joins([:user]). 
    select("username"). 
    select("users.photo_uri as user_photo_uri"). 
    select("spentits.*") 
    } 

    scope :include_counts, lambda { 
    select("(SELECT count(*) from likes where likes.spentit_id = spentits.id) as like_count"). 
    select("(SELECT count(*) from comments where comments.spentit_id = spentits.id) as comment_count"). 
    select("(SELECT count(*) from wishlist_items where wishlist_items.spentit_id = spentits.id) as wishlist_items_count"). 
    select("spentits.*") 
    } 
end 

Используя эти методы, охват, я могу сделать:

Spentit.where(:id => 7520).include_counts.include_author_info.customize_for_user(45) 

Немного о классах. A User имеет много Spentits. A Spentit имеет множество comments, likes и comments.

+0

Эти два выбора должны быть частью одного запроса, или эти два отдельных? «проведенные» - это * один конкретный объект Spentit, не так ли? – kiddorails

+0

@kiddorails Одиночный запрос, и да, потраченные объекты имеют тип Spentit. Я обновил весь свой запрос, чтобы вы знали, что именно я ищу. – 0xSina

+0

Хорошо, можете ли вы описать, с точки зрения связанных объектов, какую информацию вы пытаетесь получить? В принципе - не думайте в терминах SQL вообще, подумайте сначала в терминах «У меня есть куча объектов со ссылками и отношениями» и определите свой запрос с точки зрения этих вместо SQL.SQL - это просто метод сериализации и хранения, а не * вещь *. – Narfanator

ответ

0

Хорошо, вы «делаете это неправильно», немного. Вместо

scope :include_counts, lambda { 
    select("(SELECT count(*) from likes where likes.spentit_id = spentits.id) as like_count"). 
    select("(SELECT count(*) from comments where comments.spentit_id = spentits.id) as comment_count"). 
    select("(SELECT count(*) from wishlist_items where wishlist_items.spentit_id = spentits.id) as wishlist_items_count"). 
    select("spentits.*") 
    } 

сделать

Spentit.find(7520).likes.count 
Spentit.find(7520).wishlist_items.count 
Spentit.find(7520).comments.count 

Вместо

scope :include_author_info, lambda { 
    joins([:user]). 
    select("username"). 
    select("users.photo_uri as user_photo_uri"). 
    select("spentits.*") 
    } 

сделать

Spentit.find(7520).user.username 
Spentit.find(7520).user.photo_uri 

Кроме того, вы можете определить областей в ссылочных моделей, а также использовать эти:

class Follow < ActiveRecord::Base 
    belongs_to :follower, :class_name => "User" 
    belongs_to :following, :class_name => "User" 

    scope :accepted, lambda{ where(:accepted => 1) } 
end 

Spentits.where(:user => Follow.where(:follower => User.find(9)).accepted) 

Теперь, может быть, вы также сделать:

class Spentit 
    def to_hash 
    hash = self.attributes 
    hash[:like_count] = self.like.count 
    # ... 
    end 
end 

, но вам не нужно делать ничего сверхъестественного, чтобы получить эти счетчики «в нормальных условиях», у вас уже есть.

Обратите внимание, что вы, вероятно, также захотите сделать eager loading, который вы, по-видимому, можете сделать как часть default scope, или вы сделаете гораздо больше запросов, чем вам нужно.

+0

Спасибо за подробный ответ. Doing 'Spentit.find (7520) .likes.count', а затем для списка пожеланий, а затем для комментариев выполняет 3 запроса. Вы делаете 3 поездки в дБ. не так уж плохо? Почему бы не использовать JOINS? – 0xSina

+0

Он делает три поездки в БД, если вы не настроите свою загруженную загрузку - см. Ссылку. – Narfanator

+0

Кроме того, если вы возитесь с 'select', вы получаете неизменяемые объекты. Если вы все еще хотите делать JOINS, вы можете использовать '.join (: relation)' и больше (тот же самый документ как область по умолчанию, но другой раздел), но они, похоже, предназначены для использования в операциях '.where'. Если все, что вы делаете, это добавление информации, выглядит как загруженная загрузка (возможно, с 'default_scope' - это путь. – Narfanator

Смежные вопросы