mostrar notificaciones a los usuarios cuando alguien comenta en su

debate o responde a su comentario
This commit is contained in:
Julian Herrero
2015-10-06 12:11:20 +02:00
committed by rgarcia
parent ba0ce4e14b
commit d9ba3edc2a
17 changed files with 304 additions and 0 deletions

View File

@@ -116,4 +116,14 @@ class ApplicationController < ActionController::Base
add_notifications_for activity
end
end
def add_notifications_for(activity)
case activity.action
when "debate_comment"
author = activity.trackable.debate.author
when "comment_reply"
author = activity.trackable.parent.author
end
author.notifications.create!(activity: activity) unless activity.made_by? author
end
end

View File

@@ -0,0 +1,9 @@
class NotificationsController < ApplicationController
before_action :authenticate_user!
load_and_authorize_resource class: "User"
def index
@notifications = current_user.notifications.unread.recent.for_render
end
end

View File

@@ -0,0 +1,16 @@
module NotificationsHelper
def notification_text_for(notification)
case notification.activity.action
when "debate_comment"
t("comments.notifications.commented_on_your_debate")
when "comment_reply"
t("comments.notifications.replied_to_your_comment")
end
end
def notifications_class_for(user)
user.notifications.unread.count > 0 ? "with_notifications" : "without_notifications"
end
end

View File

@@ -0,0 +1,12 @@
class Notification < ActiveRecord::Base
belongs_to :user
belongs_to :activity
scope :unread, -> { where(read: false) }
scope :recent, -> { order(id: :desc) }
scope :for_render, -> { includes(activity: [:user, :trackable]) }
def timestamp
activity.trackable.created_at
end
end

View File

@@ -22,6 +22,7 @@ class User < ActiveRecord::Base
has_many :proposals, -> { with_hidden }, foreign_key: :author_id
has_many :comments, -> { with_hidden }
has_many :failed_census_calls
has_many :notifications
validates :username, presence: true, if: :username_required?
validates :username, uniqueness: true, if: :username_required?

View File

@@ -9,6 +9,9 @@
<li>
<%= link_to(t("devise_views.menu.login_items.logout"), destroy_user_session_path, method: :delete) %>
</li>
<li>
<%= link_to 'Notificaciones', notifications_path, class: notifications_class_for(current_user) %>
</li>
<% else %>
<li>
<%= link_to(t("devise_views.menu.login_items.login"), new_user_session_path) %>

View File

@@ -0,0 +1,11 @@
<div class="row">
<ul>
<% @notifications.each do |notification| %>
<li>
<time><%= l notification.timestamp.to_datetime, format: :datetime %></time>&nbsp;&bull;&nbsp;
<%= notification.activity.username %> <%= notification_text_for(notification) %>
<%= link_to notification.activity.trackable.debate.title, notification.activity.trackable.debate %>
</li>
<% end %>
</ul>
</div>

View File

@@ -267,6 +267,9 @@ en:
zero: "No votes"
one: "1 vote"
other: "%{count} votes"
notifications:
commented_on_your_debate: "commented on yout debate"
replied_to_your_comment: "replied to your comment on"
comments_helper:
comment_link: "Comment"
comment_button: "Publish comment"

View File

@@ -267,6 +267,9 @@ es:
zero: "Sin votos"
one: "1 voto"
other: "%{count} votos"
notifications:
commented_on_your_debate: "ha comentado en tu debate"
replied_to_your_comment: "ha respondido a tu comentario en"
comments_helper:
comment_link: "Comentar"
comment_button: "Publicar comentario"

View File

@@ -74,6 +74,9 @@ Rails.application.routes.draw do
resource :account, controller: "account", only: [:show, :update, :delete] do
collection { get :erase }
end
resources :notifications, only: :index
resource :verification, controller: "verification", only: [:show]
scope module: :verification do

View File

@@ -0,0 +1,9 @@
class CreateNotifications < ActiveRecord::Migration
def change
create_table :notifications do |t|
t.belongs_to :user, index: true, foreign_key: true
t.belongs_to :activity, index: true, foreign_key: true
t.boolean :read, default: false
end
end
end

View File

@@ -204,6 +204,17 @@ ActiveRecord::Schema.define(version: 20151215165824) do
add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree
create_table "notifications", force: :cascade do |t|
t.integer "user_id"
t.integer "activity_id"
t.boolean "read", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "notifications", ["activity_id"], name: "index_notifications_on_activity_id", using: :btree
add_index "notifications", ["user_id"], name: "index_notifications_on_user_id", using: :btree
create_table "organizations", force: :cascade do |t|
t.integer "user_id"
t.string "name", limit: 60
@@ -405,5 +416,7 @@ ActiveRecord::Schema.define(version: 20151215165824) do
add_foreign_key "identities", "users"
add_foreign_key "locks", "users"
add_foreign_key "moderators", "users"
add_foreign_key "notifications", "activities"
add_foreign_key "notifications", "users"
add_foreign_key "organizations", "users"
end

View File

@@ -0,0 +1,16 @@
require 'rails_helper'
describe NotificationsController do
describe "#index" do
let(:user) { create :user }
let(:notification) { create :notification, user: user }
it "assigns @notifications" do
sign_in user
get :index, debate: { title: 'A sample debate', description: 'this is a sample debate', terms_of_service: 1 }
expect(assigns(:notifications)).to eq user.notifications.unread.recent.for_render
end
end
end

View File

@@ -290,4 +290,10 @@ FactoryGirl.define do
sequence(:track_id) { |n| "#{n}" }
end
factory :notification do
association :user, factory: :user
association :activity, factory: :activity
read false
end
end

View File

@@ -0,0 +1,97 @@
require 'rails_helper'
feature "Notifications" do
let(:author) { create :user }
let(:user) { create :user }
let(:debate) { create :debate, author: author }
scenario "User commented on my debate", :js do
login_as user
visit debate_path debate
fill_in "comment-body-debate_#{debate.id}", with: "I commented on your debate"
click_button "Publish comment"
within "#comments" do
expect(page).to have_content "I commented on your debate"
end
logout
login_as author
visit root_path
expect(page).to have_xpath "//a[@class='with_notifications' and @href='#{notifications_path}' and text()='Notificaciones']"
click_link "Notificaciones"
expect(page).to have_content user.username
expect(page).to have_content I18n.t("comments.notifications.commented_on_your_debate")
expect(page).to_not have_content I18n.t("comments.notifications.replied_to_your_comment")
expect(page).to have_link debate.title, href: debate_path(debate)
end
scenario "User replied to my comment", :js do
comment = create :comment, commentable: debate, user: author
login_as user
visit debate_path debate
click_link "Reply"
within "#js-comment-form-comment_#{comment.id}" do
fill_in "comment-body-comment_#{comment.id}", with: "I replied to your comment"
click_button "Publish reply"
end
within "#comment_#{comment.id}" do
expect(page).to have_content "I replied to your comment"
end
logout
login_as author
visit root_path
expect(page).to have_xpath "//a[@class='with_notifications' and @href='#{notifications_path}' and text()='Notificaciones']"
visit notifications_path
expect(page).to have_content user.username
expect(page).to have_content I18n.t("comments.notifications.replied_to_your_comment")
expect(page).to_not have_content I18n.t("comments.notifications.commented_on_your_debate")
expect(page).to have_link debate.title, href: debate_path(debate)
end
scenario "Author commented on his own debate", :js do
login_as author
visit debate_path debate
fill_in "comment-body-debate_#{debate.id}", with: "I commented on my own debate"
click_button "Publish comment"
within "#comments" do
expect(page).to have_content "I commented on my own debate"
end
expect(page).to have_xpath "//a[@class='without_notifications' and @href='#{notifications_path}' and text()='Notificaciones']"
click_link "Notificaciones"
expect(page).to_not have_content user.username
expect(page).to_not have_content I18n.t("comments.notifications.commented_on_your_debate")
expect(page).to_not have_content I18n.t("comments.notifications.replied_to_your_comment")
expect(page).to_not have_link debate.title, href: debate_path(debate)
end
scenario "Author replied to his own comment", :js do
comment = create :comment, commentable: debate, user: author
login_as author
visit debate_path debate
click_link "Reply"
within "#js-comment-form-comment_#{comment.id}" do
fill_in "comment-body-comment_#{comment.id}", with: "I replied to my own comment"
click_button "Publish reply"
end
within "#comment_#{comment.id}" do
expect(page).to have_content "I replied to my own comment"
end
expect(page).to have_xpath "//a[@class='without_notifications' and @href='#{notifications_path}' and text()='Notificaciones']"
visit notifications_path
expect(page).to_not have_content user.username
expect(page).to_not have_content I18n.t("comments.notifications.replied_to_your_comment")
expect(page).to_not have_content I18n.t("comments.notifications.commented_on_your_debate")
expect(page).to_not have_link debate.title, href: debate_path(debate)
end
end

View File

@@ -0,0 +1,48 @@
require 'rails_helper'
describe NotificationsHelper do
describe "#notification_text_for" do
let(:comment_activity) { create :activity, action: "debate_comment" }
let(:reply_activity) { create :activity, action: "comment_reply" }
context "when action was comment on a debate" do
it "returns 'commented_on_your_debate' locale text" do
notification = create :notification, activity: comment_activity
expect(notification_text_for(notification)).to eq t("comments.notifications.commented_on_your_debate")
end
end
context "when action was comment on a debate" do
it "returns 'replied_to_your_comment' locale text" do
notification = create :notification, activity: reply_activity
expect(notification_text_for(notification)).to eq t("comments.notifications.replied_to_your_comment")
end
end
end
describe "#notifications_class_for" do
let(:user) { create :user }
context "when user doesn't have any notification" do
it "returns class 'without_notifications'" do
expect(notifications_class_for(user)).to eq "without_notifications"
end
end
context "when user doesn't have unread notifications" do
it "returns class 'without_notifications'" do
notification = create :notification, user: user, read: true
expect(notifications_class_for(user)).to eq "without_notifications"
end
end
context "when user has unread notifications" do
it "returns class 'with_notifications'" do
notification = create :notification, user: user
expect(notifications_class_for(user)).to eq "with_notifications"
end
end
end
end

View File

@@ -0,0 +1,44 @@
require 'rails_helper'
describe Notification do
describe "#unread (scope)" do
it "returns only unread notifications" do
unread_notification = create :notification
read_notification = create :notification, read: true
unread_notifications = Notification.unread
expect(unread_notifications.size).to be 1
expect(unread_notifications.first).to eq unread_notification
end
end
describe "#recent (scope)" do
it "returns notifications sorted by id descendant" do
old_notification = create :notification
new_notification = create :notification
sorted_notifications = Notification.recent
expect(sorted_notifications.size).to be 2
expect(sorted_notifications.first).to eq new_notification
expect(sorted_notifications.last).to eq old_notification
end
end
describe "#for_render (scope)" do
it "returns notifications with including activity, user and trackable info" do
expect(Notification).to receive(:includes).with(activity: [:user, :trackable]).exactly(:once)
Notification.for_render
end
end
describe "#timestamp" do
it "returns the timestamp of the trackable object" do
comment = create :comment
activity = create :activity, trackable: comment
notification = create :notification, activity: activity
expect(notification.timestamp).to eq comment.created_at
end
end
end