Merge pull request #369 from AyuntamientoMadrid/caching_debates

Caches debates and comments
This commit is contained in:
Enrique García
2015-09-06 00:02:07 +02:00
16 changed files with 315 additions and 202 deletions

View File

@@ -21,8 +21,8 @@ class DebatesController < ApplicationController
@root_comments = @debate.comments.roots.recent.page(params[:page]).per(10).for_render
@comments = @root_comments.inject([]){|all, root| all + root.descendants}
all_visible_comments = @root_comments + @comments
set_comment_flags(all_visible_comments)
@all_visible_comments = @root_comments + @comments
set_comment_flags(@all_visible_comments)
end
def new

View File

@@ -12,6 +12,8 @@ module CacheKeysHelper
user_status += ":signed"
user_status += ":verified" if current_user.verified_at.present?
user_status += ":org" if current_user.organization?
user_status += ":admin" if current_user.administrator?
user_status += ":moderator" if current_user.moderator?
else
user_status += ":visitor"
end

View File

@@ -1,5 +1,5 @@
class Administrator < ActiveRecord::Base
belongs_to :user
belongs_to :user, touch: true
delegate :name, :email, to: :user
validates :user_id, presence: true, uniqueness: true

View File

@@ -13,7 +13,7 @@ class Comment < ActiveRecord::Base
belongs_to :commentable, -> { with_hidden }, polymorphic: true, counter_cache: true
belongs_to :user, -> { with_hidden }
has_many :flags, :as => :flaggable
has_many :flags, as: :flaggable
scope :recent, -> { order(id: :desc) }

View File

@@ -9,8 +9,8 @@ class Debate < ActiveRecord::Base
include ActsAsParanoidAliases
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
has_many :flags, :as => :flaggable
has_many :comments, as: :commentable
has_many :flags, as: :flaggable
validates :title, presence: true
validates :description, presence: true

View File

@@ -22,6 +22,7 @@ class Flag < ActiveRecord::Base
end
def self.flagged?(user, flaggable)
return false unless user
!! by_user_and_flaggable(user, flaggable).try(:first)
end

View File

@@ -1,5 +1,5 @@
class Moderator < ActiveRecord::Base
belongs_to :user
belongs_to :user, touch: true
delegate :name, :email, to: :user
validates :user_id, presence: true, uniqueness: true

View File

@@ -1,5 +1,5 @@
class Organization < ActiveRecord::Base
belongs_to :user
belongs_to :user, touch: true
validates :name, presence: true

View File

@@ -36,6 +36,17 @@ class User < ActiveRecord::Base
scope :officials, -> { where("official_level > 0") }
scope :for_render, -> { includes(:organization) }
after_update :touch_debates, :touch_comments
after_touch :touch_debates, :touch_comments
def touch_debates
debates.map(&:touch)
end
def touch_comments
comments.map(&:touch)
end
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)

View File

@@ -1,101 +1,104 @@
<div class="row">
<div id="<%= dom_id(comment) %>" class="comment small-12 column">
<% cache [locale_and_user_status, comment, @commentable, Flag.flagged?(current_user, comment)] do %>
<div class="row">
<div id="<%= dom_id(comment) %>" class="comment small-12 column">
<% if comment.hidden? || comment.user.hidden? %>
<% if select_children(@comments, comment).size > 0 %>
<div class="is-deleted">
<p><%= t("debates.comment.deleted") %></p>
</div>
<% end %>
<% else %>
<% if comment.as_administrator? %>
<%= image_tag("admin_avatar.png", size: 32, class: "admin-avatar left") %>
<% elsif comment.as_moderator? %>
<%= image_tag("moderator_avatar.png", size: 32, class: "moderator-avatar left") %>
<% if comment.hidden? || comment.user.hidden? %>
<% if select_children(@comments, comment).size > 0 %>
<div class="is-deleted">
<p><%= t("debates.comment.deleted") %></p>
</div>
<% end %>
<% else %>
<% if comment.user.organization? %>
<%= image_tag("collective_avatar.png", size: 32, class: "avatar left") %>
<% if comment.as_administrator? %>
<%= image_tag("admin_avatar.png", size: 32, class: "admin-avatar left") %>
<% elsif comment.as_moderator? %>
<%= image_tag("moderator_avatar.png", size: 32, class: "moderator-avatar left") %>
<% else %>
<%= avatar_image(comment.user, seed: comment.user_id, size: 32, class: "left") %>
<% end %>
<% if comment.user.hidden? %>
<i class="icon-deleted user-deleted"></i>
<% end %>
<% end %>
<div class="comment-body">
<div class="comment-info">
<% if comment.as_administrator? %>
<span class="user-name"><%= t("debates.comment.admin") %> #<%= comment.administrator_id%></span>
<% elsif comment.as_moderator? %>
<span class="user-name"><%= t("debates.comment.moderator") %> #<%= comment.moderator_id%></span>
<% if comment.user.organization? %>
<%= image_tag("collective_avatar.png", size: 32, class: "avatar left") %>
<% else %>
<%= avatar_image(comment.user, seed: comment.user_id, size: 32, class: "left") %>
<% end %>
<% if comment.user.hidden? %>
<i class="icon-deleted user-deleted"></i>
<% end %>
<% end %>
<% if comment.user.hidden? %>
<span class="user-name"><%= t("debates.comment.user_deleted") %></span>
<div class="comment-body">
<div class="comment-info">
<% if comment.as_administrator? %>
<span class="user-name"><%= t("debates.comment.admin") %> #<%= comment.administrator_id%></span>
<% elsif comment.as_moderator? %>
<span class="user-name"><%= t("debates.comment.moderator") %> #<%= comment.moderator_id%></span>
<% else %>
<span class="user-name"><%= comment.user.name %></span>
<% if comment.user.official? %>
<% if comment.user.hidden? %>
<span class="user-name"><%= t("debates.comment.user_deleted") %></span>
<% else %>
<span class="user-name"><%= comment.user.name %></span>
<% if comment.user.official? %>
&nbsp;&bullet;&nbsp;
<span class="label round level-<%= comment.user.official_level %>">
<%= comment.user.official_position %>
</span>
<% end %>
<% end %>
<% if comment.user.verified_organization? %>
&nbsp;&bullet;&nbsp;
<span class="label round level-<%= comment.user.official_level %>">
<%= comment.user.official_position %>
<span class="label round is-association">
<%= t("shared.collective") %>
</span>
<% end %>
<% end %>
<% if comment.user.verified_organization? %>
&nbsp;&bullet;&nbsp;
<span class="label round is-association">
<%= t("shared.collective") %>
</span>
<% end %>
<% if comment.user_id == @commentable.author_id %>
&nbsp;&bullet;&nbsp;
<span class="label round is-author">
<%= t("debates.comment.author") %>
</span>
<% if comment.user_id == @commentable.author_id %>
&nbsp;&bullet;&nbsp;
<span class="label round is-author">
<%= t("debates.comment.author") %>
</span>
<% end %>
<% end %>
&nbsp;&bullet;&nbsp;<%= time_ago_in_words(comment.created_at) %>
</div>
<% if comment.as_administrator? %>
<p class="comment-user is-admin"><%= comment.body %></p>
<% elsif comment.as_moderator? %>
<p class="comment-user is-moderator"><%= comment.body %></p>
<% elsif comment.user.official? && comment.user_id == @commentable.author_id %>
<p class="comment-user level-<%= comment.user.official_level %> is-author"><%= comment.body %></p>
<% elsif comment.user.official? %>
<p class="comment-user level-<%= comment.user.official_level %>"><%= comment.body %></p>
<% elsif comment.user_id == @commentable.author_id %>
<p class="comment-user is-author"><%= comment.body %></p>
<% else %>
<p class="comment-user"><%= comment.body %></p>
<% end %>
<span id="<%= dom_id(comment) %>_votes" class="comment-votes right">
<%= render 'comments/votes', comment: comment %>
</span>
&nbsp;&bullet;&nbsp;<%= time_ago_in_words(comment.created_at) %>
<div class="reply">
<%= t("debates.comment.responses", count: select_children(@comments, comment).size) %>
<% if user_signed_in? %>
<span class="divider">&nbsp;|&nbsp;</span>
<%= link_to(comment_link_text(comment), "",
class: "js-add-comment-link", data: {'id': dom_id(comment)}) %>
<%= render 'comments/actions', comment: comment %>
<%= render 'comments/form', {commentable: @commentable, parent_id: comment.id, toggeable: true} %>
<% end %>
</div>
</div>
<% if comment.as_administrator? %>
<p class="comment-user is-admin"><%= comment.body %></p>
<% elsif comment.as_moderator? %>
<p class="comment-user is-moderator"><%= comment.body %></p>
<% elsif comment.user.official? && comment.user_id == @commentable.author_id %>
<p class="comment-user level-<%= comment.user.official_level %> is-author"><%= comment.body %></p>
<% elsif comment.user.official? %>
<p class="comment-user level-<%= comment.user.official_level %>"><%= comment.body %></p>
<% elsif comment.user_id == @commentable.author_id %>
<p class="comment-user is-author"><%= comment.body %></p>
<% else %>
<p class="comment-user"><%= comment.body %></p>
<% end %>
<span id="<%= dom_id(comment) %>_votes" class="comment-votes right">
<%= render 'comments/votes', comment: comment %>
</span>
<div class="reply">
<%= t("debates.comment.responses", count: select_children(@comments, comment).size) %>
<% if user_signed_in? %>
<span class="divider">&nbsp;|&nbsp;</span>
<%= link_to(comment_link_text(comment), "",
class: "js-add-comment-link", data: {'id': dom_id(comment)}) %>
<%= render 'comments/actions', comment: comment %>
<%= render 'comments/form', {commentable: @commentable, parent_id: comment.id, toggeable: true} %>
<% end %>
</div>
</div>
<% end %>
<% end %>
<% end %>
<div class="comment-children">
<%= render select_children(@comments, comment) if @comments.present? %>
<% select_children(@comments, comment).each do |child| %>
<%= render 'comments/comment', comment: child %>
<% end %>
</div>
</div>
</div>
</div>

View File

@@ -1,25 +1,28 @@
<% css_id = parent_or_commentable_dom_id(parent_id, commentable) %>
<div id="js-comment-form-<%= css_id %>" <%= "style='display:none'".html_safe if toggeable %>>
<%= form_for [commentable, Comment.new], remote: true do |f| %>
<%= label_tag "comment-body-#{css_id}", t("comments.form.leave_comment") %>
<%= f.text_area :body, id: "comment-body-#{css_id}", label: false %>
<%= f.hidden_field :commentable_type, value: commentable.class.name %>
<%= f.hidden_field :commentable_id, value: commentable.id %>
<%= f.hidden_field :parent_id, value: parent_id %>
<% cache [locale_and_user_status, css_id] do %>
<div id="js-comment-form-<%= css_id %>" <%= "style='display:none'".html_safe if toggeable %>>
<%= form_for [commentable, Comment.new], remote: true do |f| %>
<%= label_tag "comment-body-#{css_id}", t("comments.form.leave_comment") %>
<%= f.text_area :body, id: "comment-body-#{css_id}", label: false %>
<%= f.hidden_field :commentable_type, value: commentable.class.name %>
<%= f.hidden_field :commentable_id, value: commentable.id %>
<%= f.hidden_field :parent_id, value: parent_id %>
<%= f.submit comment_button_text(parent_id), class: "button radius small inline-block" %>
<%= f.submit comment_button_text(parent_id), class: "button radius small inline-block" %>
<% if can? :comment_as_moderator, commentable %>º
<div class="right">
<%= f.check_box :as_moderator, id: "comment-as-moderator-#{css_id}", label: false %>
<%= label_tag "comment-as-moderator-#{css_id}", t("comments.form.comment_as_moderator"), class: "checkbox" %>
</div>
<% end %>
<% if can? :comment_as_administrator, commentable %>
<div class="right">
<%= f.check_box :as_administrator, id: "comment-as-administrator-#{css_id}",label: false %>
<%= label_tag "comment-as-administrator-#{css_id}", t("comments.form.comment_as_admin"), class: "checkbox" %>
</div>
<% end %>
<% if can? :comment_as_moderator, commentable %>º
<div class="right">
<%= f.check_box :as_moderator, id: "comment-as-moderator-#{css_id}", label: false %>
<%= label_tag "comment-as-moderator-#{css_id}", t("comments.form.comment_as_moderator"), class: "checkbox" %>
</div>
<% end %>
<% if can? :comment_as_administrator, commentable %>
<div class="right">
<%= f.check_box :as_administrator, id: "comment-as-administrator-#{css_id}",label: false %>
<%= label_tag "comment-as-administrator-#{css_id}", t("comments.form.comment_as_admin"), class: "checkbox" %>
</div>
<% end %>
<% end %>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,30 @@
<% cache [locale_and_user_status, @all_visible_comments.map(&:cache_key), @debate.comments_count, @comment_flags.to_a] do %>
<section class="row-full comments">
<div class="row">
<div id="comments" class="small-12 column">
<h2>
<%= t("debates.show.comments_title") %>
<span>(<%= @debate.comments_count %>)</span>
</h2>
<% if user_signed_in? %>
<%= render 'comments/form', {commentable: @debate, parent_id: nil, toggeable: false} %>
<% else %>
<br>
<div class="alert-box radius info">
<%= t("debates.show.login_to_comment",
signin: link_to(t("votes.signin"), new_user_session_path),
signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %>
</div>
<% end %>
<% @root_comments.each do |comment| %>
<%= render 'comments/comment', comment: comment %>
<% end %>
<%= paginate @root_comments %>
</div>
</div>
</section>
<% end %>

View File

@@ -1,99 +1,81 @@
<section class="debate-show">
<div id="<%= dom_id(@debate) %>" class="row">
<div class="small-12 medium-9 column">
<i class="icon-angle-left left"></i>&nbsp;<%= link_to t("debates.show.back_link"), debates_path, class: 'left back' %>
<% cache [locale_and_user_status, @debate, Flag.flagged?(current_user, @debate)] do %>
<section class="debate-show">
<div id="<%= dom_id(@debate) %>" class="row">
<div class="small-12 medium-9 column">
<i class="icon-angle-left left"></i>&nbsp;
<%= link_to t("debates.show.back_link"), debates_path, class: 'left back' %>
<% if current_user && @debate.editable_by?(current_user) %>
<%= link_to edit_debate_path(@debate), :class => 'edit-debate button success tiny radius right' do %>
<i class="icon-edit"></i>
<%= t("debates.show.edit_debate_link") %>
<% end %>
<% end %>
<h1><%= @debate.title %></h1>
<div class="debate-info">
<%= avatar_image(@debate.author, seed: @debate.author_id, size: 32, class: 'author-photo') %>
<% if @debate.author.hidden? %>
<i class="icon-deleted author-deleted"></i>
<span class="author">
<%= t("debates.show.author_deleted") %>
</span>
<% else %>
<span class="author">
<%= @debate.author.name %>
</span>
<% if @debate.author.official? %>
&nbsp;&bullet;&nbsp;
<span class="label round level-<%= @debate.author.official_level %>">
<%= @debate.author.official_position %>
</span>
<% if current_user && @debate.editable_by?(current_user) %>
<%= link_to edit_debate_path(@debate), class: 'edit-debate button success tiny radius right' do %>
<i class="icon-edit"></i>
<%= t("debates.show.edit_debate_link") %>
<% end %>
<% end %>
<% if @debate.author.verified_organization? %>
&nbsp;&bullet;&nbsp;
<span class="label round is-association">
<%= t("shared.collective") %>
<h1><%= @debate.title %></h1>
<div class="debate-info">
<%= avatar_image(@debate.author, seed: @debate.author_id, size: 32, class: 'author-photo') %>
<% if @debate.author.hidden? %>
<i class="icon-deleted author-deleted"></i>
<span class="author">
<%= t("debates.show.author_deleted") %>
</span>
<% else %>
<span class="author">
<%= @debate.author.name %>
</span>
<% if @debate.author.official? %>
&nbsp;&bullet;&nbsp;
<span class="label round level-<%= @debate.author.official_level %>">
<%= @debate.author.official_position %>
</span>
<% end %>
<% end %>
<% if @debate.author.verified_organization? %>
&nbsp;&bullet;&nbsp;
<span class="label round is-association">
<%= t("shared.collective") %>
</span>
<% end %>
<span class="bullet">&nbsp;&bullet;&nbsp;</span>
<%= l @debate.created_at.to_date %>
<span class="bullet">&nbsp;&bullet;&nbsp;</span>
<i class="icon-comments"></i>&nbsp;
<%= link_to t("debates.show.comments", count: @debate.comments_count), "#comments" %>
<span class="bullet">&nbsp;&bullet;&nbsp;</span>
<span class="js-flag-actions">
<%= render 'debates/flag_actions', debate: @debate %>
</span>
</div>
<%= @debate.description %>
<%= render 'shared/tags', debate: @debate %>
<% if moderator? %>
<div class='js-moderator-debate-actions'>
<%= render 'actions', debate: @debate %>
</div>
<% end %>
<span class="bullet">&nbsp;&bullet;&nbsp;</span>
<%= l @debate.created_at.to_date %>
<span class="bullet">&nbsp;&bullet;&nbsp;</span>
<i class="icon-comments"></i>&nbsp;
<%= link_to t("debates.show.comments", count: @debate.comments_count), "#comments" %>
<span class="bullet">&nbsp;&bullet;&nbsp;</span>
<span class="js-flag-actions">
<%= render 'debates/flag_actions', debate: @debate %>
</span>
</div>
<%= @debate.description %>
<%= render 'shared/tags', debate: @debate %>
<% if moderator? %>
<div class='js-moderator-debate-actions'>
<%= render 'actions', debate: @debate %>
<aside class="small-12 medium-3 column">
<div class="sidebar-divider"></div>
<h3><%= t("votes.supports") %></h3>
<div class="text-center">
<div id="<%= dom_id(@debate) %>_votes">
<%= render 'debates/votes', debate: @debate %>
</div>
</div>
<% end %>
<div class="sidebar-divider"></div>
<h3><%= t("debates.show.share") %></h3>
<%= social_share_button_tag(@debate.title) %>
</aside>
</div>
<aside class="small-12 medium-3 column">
<div class="sidebar-divider"></div>
<h3><%= t("votes.supports") %></h3>
<div class="text-center">
<div id="<%= dom_id(@debate) %>_votes">
<%= render 'debates/votes', debate: @debate %>
</div>
</div>
<div class="sidebar-divider"></div>
<h3><%= t("debates.show.share") %></h3>
<%= social_share_button_tag(@debate.title) %>
</aside>
</div>
</section>
<section class="row-full comments">
<div class="row">
<div id="comments" class="small-12 column">
<h2>
<%= t("debates.show.comments_title") %>
<span>(<%= @debate.comments_count %>)</span>
</h2>
<% if user_signed_in? %>
<%= render 'comments/form', {commentable: @debate, parent_id: nil, toggeable: false} %>
<% else %>
<br>
<div class="alert-box radius info">
<%= t("debates.show.login_to_comment",
signin: link_to(t("votes.signin"), new_user_session_path),
signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %>
</div>
<% end %>
<%= render @root_comments %>
<%= paginate @root_comments %>
</div>
</div>
</section>
</section>
<% end %>
<%= render "comments" %>

View File

@@ -36,4 +36,34 @@ describe Comment do
expect(comment).to be_as_moderator
end
end
describe "cache" do
let(:comment) { create(:comment) }
it "should expire cache when it has a new vote" do
expect { create(:vote, votable: comment) }
.to change { comment.updated_at }
end
it "should expire cache when hidden" do
expect { comment.hide }
.to change { comment.updated_at }
end
it "should expire cache when the author is hidden" do
expect { comment.user.hide }
.to change { comment.reload.updated_at }
end
it "should expire cache when the author changes" do
expect { comment.user.update(username: "Isabel") }
.to change { comment.reload.updated_at }
end
it "should expire cache when the author's organization get verified" do
create(:organization, user: comment.user)
expect { comment.user.organization.verify }
.to change { comment.reload.updated_at}
end
end
end

View File

@@ -268,4 +268,34 @@ describe Debate do
end
end
describe "cache" do
let(:debate) { create(:debate) }
it "should expire cache when it has a new comment" do
expect { create(:comment, commentable: debate) }
.to change { debate.updated_at }
end
it "should expire cache when it has a new vote" do
expect { create(:vote, votable: debate) }
.to change { debate.updated_at }
end
it "should expire cache when it has a new tag" do
expect { debate.update(tag_list: "new tag") }
.to change { debate.updated_at }
end
it "should expire cache when its author changes" do
expect { debate.author.update(username: "Eva") }
.to change { debate.reload.updated_at }
end
it "should expire cache when the author's organization get verified" do
create(:organization, user: debate.author)
expect { debate.author.organization.verify }
.to change { debate.reload.updated_at}
end
end
end

View File

@@ -299,4 +299,25 @@ describe User do
end
end
describe "cache" do
let(:user) { create(:user) }
it "should expire cache with becoming a moderator" do
expect { create(:moderator, user: user) }
.to change { user.updated_at}
end
it "should expire cache with becoming an admin" do
expect { create(:administrator, user: user) }
.to change { user.updated_at}
end
it "should expire cache with becoming a veridied organization" do
create(:organization, user: user)
expect { user.organization.verify }
.to change { user.reload.updated_at}
end
end
end