2015-06-16 10 views
6

Mam trzy modele: Użytkownik, Komentarz i Awans. User-to-Comment ma relację jeden-do-wielu, Comment-to-Upvote ma relację jeden-do-wielu, a User-to-Upvote ma relację jeden-do-wielu.Zapobieganie pobraniu modelu z każdego komentarza

Chcę zrobić coś podobnego do upvotingu na Stackoverflow. Kiedy więc awansujesz/pójdziesz w dół, strzałka będzie podświetlać i pozostanie wyróżniona, nawet jeśli odświeżysz stronę lub powrócisz na stronę dni/tygodnie później.

Obecnie robie to:

<% if Upvote.voted?(@user.id, comment.id) %> 
    <%= link_to '^', ... style: 'color: orange;'%> 
<% else %> 
    <%= link_to '^', ... style: 'color:black;'%> 
<% end %> 

gdzie metoda voted? wygląda następująco:

def self.voted?(user_id, comment_id) 
    find_by(comment_id: comment_id, user_id: user_id).present? 
    end 

Więc jeśli mam 10 komentarzy na stronie, to będzie załadować upvote z mojej bazy danych 10 razy, żeby sprawdzić, czy istnieje!

Musi być lepszy sposób na zrobienie tego, ale myślę, że mój mózg przestał działać, więc nie mogę myśleć o żadnym.

+1

Prawdopodobnie nie powinno być wywołanie głosował na 'upvote 'klasa. Jeśli myślisz o akcji głosowania, nie jest to coś, co robi "komentarz", ale raczej "użytkownik". Chciałbym przenieść tę metodę do klasy 'User'. – kobaltz

+0

Na marginesie, zamiast robić 'find_by(). Present?', Może lepiej byłoby zrobić 'exists?()' –

Odpowiedz

7

Zakładając masz poprawnie ustawione stosunki

# user.rb 
class User 
    has_many :upvotes 
end 

możemy załadować komentarze, bieżącego użytkownika i jego upvotes:

# comments_controller.rb 
def index 
    @comments = Comment.limit(10) 
    @user = current_user 
    user_upvotes_for_comments = current_user.upvotes.where(comment_id: @comments.map(&:id)) 
    @upvoted_comments_ids = user_upvotes_for_comments.pluck(:comment_id) 
end 

a następnie zmienić if warunek w widoku:

# index.html.erb 
<% if @upvoted_comments_ids.include?(comment.id) %> 
    <%= link_to '^', ... style: 'color: orange;'%> 
<% else %> 
    <%= link_to '^', ... style: 'color:black;'%> 
<% end %> 

Będzie wymagać tylko 2 zapytań DB. Mam nadzieję, że to pomoże.

+0

Jeśli używasz również powiązania "has_many through" i zdefiniować user -> upvotes - -> komentarze, możesz wykonać to samo z połączeniem i pojedynczym trafieniem do bazy danych. –

+0

Można również zapisać sobie mapę i zmniejszyć rozmiar kwerendy bazy danych, wykonując '@comments = Comment.limit (10) .pluck (: id)' – williamcodes

+0

@williamcodes komentarze obiektów są wymagane do wyświetlenia na stronie, więc nie możemy użyć 'wyrwać' – hedgesky

2

Jeśli jesteś ograniczony do N Komentarze na stronie to prawdopodobnie można to zrobić w dwóch zapytań z wykorzystaniem metod i offsetlimit zwrotu 1, 2, ... ith zestaw komentarzy N na stronie ith coś jak (składnia może być wyłączony, Ruby nie jest mój język podstawowy)

comment_ids = 
    Comments.select("comment_id") 
      .where(user_id: user_id) 
      .order(post_date/comment_id/whatever) 
      .offset(per_page * (page_number - 1)) // assumes 1-based page index 
      .limit(per_page) 

daje listę comment_ids które można wykorzystać do kwerendy upvote:

upvoted_comments = 
    Upvotes.select("comment_id") 
      .where(user_id: user_id, comment_id: comment_ids) 

Jeśli sortujesz comment_ids przez kolumnę, która istnieje również w Upvote (np. jeśli sortujesz według comment_id), możesz zastąpić zapytanie o zestawie Upvote kwerendą zakresu.

Umieść upvoted_comments w haszy i dobrze ci pójść - jeśli comment_id jest w haszowaniu, to został wznowiony, inaczej nie.

0

nie jestem pewien, że to pozwoli uniknąć nadmiaru zapytań w tym stanie, ale może mógłby zawierać upvotes kiedy pobrać komentarzy:

@comments = Comment.includes(:upvotes).where(foo: 'bar').limit(10) 

Następnie w widoku:

<%= 
    link_color = comment.upvotes.map(&:user_id).include?(@user.id) ? 'orange' : 'red' 
    link_to '^', ...style: "color: #{link_color}" 
%> 
+0

Byłbym w tej samej sytuacji uderzając w moje DB 10 razy, pytając o istnienie obiektu. –

+0

Masz rację. Przepraszam! Źle zrozumiałem. Zmienię moją sugestię .. –

+0

Podobała mi się twoja zmiana w widoku, to jest czystsze niż to, co miałem. –

4

Możemy zrób to w następujący sposób, jeśli chcesz, aby było obsługiwane przez jedno zapytanie.

Pozwala upewnić się, że relacje są prawidłowe

# user.rb 
class User < ActiveRecord::Base 
    has_many :comments 
    has_many :upvotes 
end 

# comment.rb 
class Comment < ActiveRecord::Base 
    belongs_to :user 
    has_many :upvotes 
end 

# upvote.rb 
class Upvote < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :comment 
end 

Następnie w sterowniku

def index 
    current_user = User.first # current_user may come from devise or any authentication logic you have. 
    @comments = Comment.select('comments.*, upvotes.id as upvote').joins("LEFT OUTER JOIN upvotes ON comments.id = upvotes.comment_id AND upvotes.user_id = #{current_user.id}") 
end 

iw świetle

# index.html.erb 
<% @comment.each do |comment| %> 
    <% link_color = comment.upvote ? 'orange' : 'black' %> 
    <%= link_to '^', ...style: "color: #{link_color}" %> 
<% end %> 
# And all of your logics ;) 
Powiązane problemy