Commit e64d04b5 by tady

Merge pull request #105 from tadyjp/feat/fix_notify

通知系のリファクタリング
parents ed2d1770 174b1847
...@@ -21,3 +21,5 @@ ...@@ -21,3 +21,5 @@
_.mixin(_.string.exports()); _.mixin(_.string.exports());
// $('[data-toggle="tooltip"]').tooltip();
// $('[data-toggle="popover"]').popover();
...@@ -26,3 +26,7 @@ a .text-link { ...@@ -26,3 +26,7 @@ a .text-link {
a:visited .text-link { a:visited .text-link {
color: #609; color: #609;
} }
.popover {
max-width: 400px;
}
...@@ -8,7 +8,6 @@ class PostsController < ApplicationController ...@@ -8,7 +8,6 @@ class PostsController < ApplicationController
# GET /posts/1 # GET /posts/1
# GET /posts/1.json # GET /posts/1.json
def show def show
current_user.visit_post!(@post) current_user.visit_post!(@post)
@post.tags.each do |_tag| @post.tags.each do |_tag|
......
...@@ -4,5 +4,4 @@ class UserDecorator < Draper::Decorator ...@@ -4,5 +4,4 @@ class UserDecorator < Draper::Decorator
def draft_count def draft_count
model.posts.where(is_draft: true).count model.posts.where(is_draft: true).count
end end
end end
...@@ -11,11 +11,14 @@ ...@@ -11,11 +11,14 @@
# #
class Comment < ActiveRecord::Base class Comment < ActiveRecord::Base
######################################################################
# Associations
######################################################################
belongs_to :author, class_name: 'User' belongs_to :author, class_name: 'User'
belongs_to :post belongs_to :post
###################################################################### ######################################################################
# validations # Validations
###################################################################### ######################################################################
validates :author_id, presence: true validates :author_id, presence: true
validates :post_id, presence: true validates :post_id, presence: true
...@@ -24,14 +27,23 @@ class Comment < ActiveRecord::Base ...@@ -24,14 +27,23 @@ class Comment < ActiveRecord::Base
###################################################################### ######################################################################
# Callback # Callback
###################################################################### ######################################################################
after_save :notify_author after_save :set_watcher!
after_save :notify_watchers!
###################################################################### ######################################################################
# Instance method # Instance method
###################################################################### ######################################################################
private private
def notify_author def notify_watchers!
post.author.push_notification(post.decorate.show_path, "#{author.name}さんがあなたの投稿にコメントしました") post.watchers.each do |watcher|
next if watcher == author
watcher.push_notification(post.decorate.show_path, "#{author.name}さんが「#{post.title}」にコメントしました。")
end
end
def set_watcher!
author.watch!(post: post)
end end
end end
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
require 'date' require 'date'
class Post < ActiveRecord::Base class Post < ActiveRecord::Base
######################################################################
# Associations
######################################################################
has_many :post_tags has_many :post_tags
has_many :tags, through: :post_tags has_many :tags, through: :post_tags
belongs_to :author, class_name: 'User' belongs_to :author, class_name: 'User'
...@@ -34,7 +37,8 @@ class Post < ActiveRecord::Base ...@@ -34,7 +37,8 @@ class Post < ActiveRecord::Base
###################################################################### ######################################################################
# Callback # Callback
###################################################################### ######################################################################
after_save :notify_watchers after_save :set_watcher!
after_save :notify_watchers!
###################################################################### ######################################################################
# Named scope # Named scope
...@@ -107,11 +111,24 @@ class Post < ActiveRecord::Base ...@@ -107,11 +111,24 @@ class Post < ActiveRecord::Base
footprints.select(:user_id).uniq.count footprints.select(:user_id).uniq.count
end end
# FIXME:
# has_many :watchers, :through => :watches
# 正常に動作しないため動作しないため一時的にメソッドを作成
# def watchers
# watches.map { |watch| watch.watcher }
# end
private private
def notify_watchers def notify_watchers!
watchers.each do |watcher| watchers.each do |watcher|
next if watcher == author
watcher.push_notification(decorate.show_path, "#{author.name}さんが「#{title}」を編集しました") watcher.push_notification(decorate.show_path, "#{author.name}さんが「#{title}」を編集しました")
end end
end end
def set_watcher!
author.watch!(post: self)
end
end end
...@@ -15,9 +15,6 @@ class Tag < ActiveRecord::Base ...@@ -15,9 +15,6 @@ class Tag < ActiveRecord::Base
has_many :post_tags has_many :post_tags
has_many :posts, through: :post_tags has_many :posts, through: :post_tags
has_many :watches, :as => :watchable, :dependent => :destroy
has_many :watchers, :through => :watches
# for tree structure # for tree structure
has_ancestry has_ancestry
......
...@@ -40,12 +40,8 @@ class User < ActiveRecord::Base ...@@ -40,12 +40,8 @@ class User < ActiveRecord::Base
has_many :notifications has_many :notifications
has_many :footprints has_many :footprints
has_many :watches, :as => :watchable, :dependent => :destroy
has_many :watchers, :through => :watches
has_many :watchings, class_name: 'Watch', foreign_key: 'watcher_id' has_many :watchings, class_name: 'Watch', foreign_key: 'watcher_id'
has_many :watching_posts, :through => :watchings, :source => :watchable, :source_type => "Post" has_many :watching_posts, :through => :watchings, :source => :watchable, :source_type => "Post"
# has_many :watchings, :as => :resource
###################################################################### ######################################################################
# scope # scope
......
...@@ -24,6 +24,11 @@ nav.navbar.navbar-default.navbar-fixed-top role="navigation" ...@@ -24,6 +24,11 @@ nav.navbar.navbar-default.navbar-fixed-top role="navigation"
ul.nav.navbar-nav.navbar-right ul.nav.navbar-nav.navbar-right
li li
a#notifications data-container="body" data-toggle="popover" data-placement="bottom"
span.glyphicon.glyphicon-flag
- if current_user.notifications.unread.any?
span.badge = current_user.notifications.unread.count
li
form form
a.btn.btn-primary.navbar-btn href=new_post_path a.btn.btn-primary.navbar-btn href=new_post_path
| New Post&nbsp;&nbsp; | New Post&nbsp;&nbsp;
...@@ -46,21 +51,24 @@ nav.navbar.navbar-default.navbar-fixed-top role="navigation" ...@@ -46,21 +51,24 @@ nav.navbar.navbar-default.navbar-fixed-top role="navigation"
li.divider li.divider
li li
a href=destroy_user_session_path data-method="delete" rel="nofollow" SignOut a href=destroy_user_session_path data-method="delete" rel="nofollow" SignOut
li.dropdown
a.dropdown-toggle data-toggle="dropdown" script#notification-content type="text/template"
| 通知
- if current_user.notifications.unread.any?
span.badge = current_user.notifications.unread.count
b.caret
ul.dropdown-menu
- if current_user.notifications.unread.any? - if current_user.notifications.unread.any?
h4 通知一覧
.list-group
- current_user.notifications.unread.each do |notification| - current_user.notifications.unread.each do |notification|
li a.list-group-item href=notification_bridge_path(notification.id)
a href=notification_bridge_path(notification.id) spen.small
| [#{notification.created_at.strftime('%m/%d %H:%M')}]&nbsp;
= notification.body = notification.body
- else - else
li h4 通知はありません
a 通知はありません
- content_for :footer_js do
javascript:
$('#notifications').popover({
html: true,
content: $('#notification-content').html()
});
...@@ -6,7 +6,7 @@ describe RV::Mailer do ...@@ -6,7 +6,7 @@ describe RV::Mailer do
let(:klass) { DummyClass.new.extend(RV::Mailer) } let(:klass) { DummyClass.new.extend(RV::Mailer) }
let(:alice) { FactoryGirl.create(:alice) } let(:alice) { FactoryGirl.create(:alice) }
let(:post) { Post.create title: 'ruby rspec', body: 'This is first espec test: ruby' } let(:post) { Post.create title: 'ruby rspec', body: 'This is first espec test: ruby', author: create(:author) }
it 'valid' do it 'valid' do
expect { klass.compose_mail(post, user: alice, to: 'dummy@example.com') }.not_to raise_error expect { klass.compose_mail(post, user: alice, to: 'dummy@example.com') }.not_to raise_error
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
FactoryGirl.define do FactoryGirl.define do
factory :post do factory :post do
association :author, factory: :author
title 'sample title' title 'sample title'
body 'sample body' body 'sample body'
specified_date Date.new(2014, 4, 1) specified_date Date.new(2014, 4, 1)
......
...@@ -40,6 +40,14 @@ FactoryGirl.define do ...@@ -40,6 +40,14 @@ FactoryGirl.define do
google_token_expires_at Time.now - 1.hour google_token_expires_at Time.now - 1.hour
end end
factory :author, class: User do
name 'Author'
email 'author@mail.com'
nickname 'author'
password Devise.friendly_token[0, 20]
google_token_expires_at Time.now - 30.hour
end
factory :login_user_1, class: User do factory :login_user_1, class: User do
name 'Test User' name 'Test User'
email 'example@example.com' email 'example@example.com'
......
...@@ -15,5 +15,59 @@ ...@@ -15,5 +15,59 @@
require 'rails_helper' require 'rails_helper'
describe Notification do describe Notification do
pending "add some examples to (or delete) #{__FILE__}" describe 'Instance method' do
before :each do
@alice = create(:alice)
@bob = create(:bob)
@post = create(:post)
end
it "notifies on post edited" do
@bob.watch!(post: @post)
expect(@bob.watching?(post: @post)).to be_truthy
@post.reload
expect(@post.watchers).to include(@bob)
@post.update!(title: @post.title + ' [New!]')
expect(@bob.notifications.size).to eq(1)
end
it "not notifies on post edited by him" do
@bob.watch!(post: @post)
@post.reload
@post.update!(title: @post.title + ' [New!]', author: @bob)
expect(@bob.notifications.size).to eq(0)
end
it "notifies on post commented" do
@bob.watch!(post: @post)
expect(@bob.watching?(post: @post)).to be_truthy
@post.reload
expect(@post.watchers).to include(@bob)
@post.comments.create!(author: @alice, body: 'new comment')
expect(@bob.notifications.size).to eq(1)
end
it "not notifies on post commented by him" do
@bob.watch!(post: @post)
@post.reload
@post.comments.create!(author: @bob, body: 'new comment')
expect(@bob.notifications.size).to eq(0)
end
it "set watch on user create a new post" do
new_post = Post.create!(author: @bob, title: 'title', body: 'body')
expect(@bob.watching?(post: new_post)).to be_truthy
end
it "set watch on user edit a post" do
@post.update!(author: @bob, title: 'new title')
expect(@bob.watching?(post: @post)).to be_truthy
end
it "set watch on user comment a post" do
@post.comments.create!(author: @bob, body: 'new comment')
expect(@bob.watching?(post: @post)).to be_truthy
end
end
end end
...@@ -20,17 +20,13 @@ describe Post do ...@@ -20,17 +20,13 @@ describe Post do
describe 'Instance method' do describe 'Instance method' do
before :each do before :each do
@post = create(:post) @post = create(:post)
@alice = create(:alice) @bob = create(:bob)
@new_post = @post.generate_fork(@alice) @new_post = @post.generate_fork(@bob)
@new_post.save @new_post.save
end end
describe 'Fork' do describe 'Fork' do
# subject {
# @post.generate_fork(@alice).save
# }
it 'duplicated' do it 'duplicated' do
expect(@new_post.id).not_to eq(@post.id) expect(@new_post.id).not_to eq(@post.id)
end end
...@@ -44,7 +40,7 @@ describe Post do ...@@ -44,7 +40,7 @@ describe Post do
end end
it 'valid user' do it 'valid user' do
expect(@new_post.author).to eq(@alice) expect(@new_post.author).to eq(@bob)
end end
it 'valid user' do it 'valid user' do
...@@ -61,10 +57,11 @@ describe Post do ...@@ -61,10 +57,11 @@ describe Post do
describe 'scope :search' do describe 'scope :search' do
before :each do before :each do
@alice = create(:alice) @alice = create(:alice)
@post1 = Post.create id: 1001, title: 'ruby rspec', body: 'This is first espec test: ruby' @author = create(:author)
@post2 = Post.create id: 1002, title: 'php test', body: 'PHP is very easy', author_id: @alice.id @post1 = Post.create id: 1001, author: @alice, title: 'ruby rspec', body: 'This is first espec test: ruby'
@post3 = Post.create id: 1003, title: 'java java...', body: 'Java is not ruby...', updated_at: Time.new(1989, 2, 25, 5, 30, 0) @post2 = Post.create id: 1002, author: @alice, title: 'php test', body: 'PHP is very easy'
@post4 = Post.create id: 1004, title: 'about ruby TDD', body: 'test is the best ....', is_draft: true @post3 = Post.create id: 1003, author: @author, title: 'java java...', body: 'Java is not ruby...', updated_at: Time.new(1989, 2, 25, 5, 30, 0)
@post4 = Post.create id: 1004, author: @author, title: 'about ruby TDD', body: 'test is the best ....', is_draft: true
@tag_java = Tag.create(name: 'java') @tag_java = Tag.create(name: 'java')
@post3.tags << @tag_java @post3.tags << @tag_java
end end
...@@ -85,7 +82,7 @@ describe Post do ...@@ -85,7 +82,7 @@ describe Post do
end end
it 'by @<author_name>' do it 'by @<author_name>' do
expect(Post.search('@Alice').size).to eq(1) expect(Post.search('@Alice').size).to eq(2)
expect(Post.search('@Alice')).to include(@post2) expect(Post.search('@Alice')).to include(@post2)
end end
...@@ -120,10 +117,11 @@ describe Post do ...@@ -120,10 +117,11 @@ describe Post do
it do it do
@post.watchers << @bob @post.watchers << @bob
@post.reload
expect(@bob.watching_posts.size).to eq(1) expect(@bob.watching_posts.size).to eq(1)
expect(@bob.notifications.size).to eq(0) expect(@bob.notifications.size).to eq(0)
@post.update!(title: @post.title + '+') @post.update!(title: @post.title + '+')
@bob.reload
expect(@bob.notifications.size).to eq(1) expect(@bob.notifications.size).to eq(1)
end end
end end
......
...@@ -18,9 +18,10 @@ describe Tag do ...@@ -18,9 +18,10 @@ describe Tag do
before :each do before :each do
@tag_ruby = Tag.create(name: 'ruby') @tag_ruby = Tag.create(name: 'ruby')
@tag_java = Tag.create(name: 'java') @tag_java = Tag.create(name: 'java')
@post1 = Post.create id: 1001, title: 'ruby rspec', body: 'hoge', tags: [@tag_ruby] @author = create(:author)
@post2 = Post.create id: 1002, title: 'ruby is better than java', body: 'hoge', tags: [@tag_ruby, @tag_java] @post1 = Post.create id: 1001, author: @author, title: 'ruby rspec', body: 'hoge', tags: [@tag_ruby]
@post3 = Post.create id: 1003, title: 'java java...', body: 'hoge', tags: [@tag_java] @post2 = Post.create id: 1002, author: @author, title: 'ruby is better than java', body: 'hoge', tags: [@tag_ruby, @tag_java]
@post3 = Post.create id: 1003, author: @author, title: 'java java...', body: 'hoge', tags: [@tag_java]
end end
it 'successfully moved' do it 'successfully moved' do
......
...@@ -29,8 +29,8 @@ describe User do ...@@ -29,8 +29,8 @@ describe User do
describe 'Instance method' do describe 'Instance method' do
let(:alice) { create(:alice) }
let(:bob) { create(:bob) } let(:bob) { create(:bob) }
let(:alice) { create(:alice) }
let(:post) { create(:post) } let(:post) { create(:post) }
describe '#google_oauth_token_expired?' do describe '#google_oauth_token_expired?' do
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment