Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'ruby_china/ruby_china'

Conflicts:
	app/models/reply.rb
	spec/models/reply_spec.rb
  • Loading branch information...
commit 069289f3bf500a971cb989e8ec7663e8b6590046 2 parents b48de4c + 7de3c36
@HungYuHei HungYuHei authored
View
5 app/assets/stylesheets/application.scss
@@ -25,18 +25,19 @@ code { background:none; }
.breadcrumb { color:#999;}
+.container { width:960px; }
.container-fluid { padding:0; }
.container-fluid > .sidebar {
position:absolute;
left:auto;
right:0;
top:0;
- width: 220px;
+ width: 240px;
}
.container-fluid > .content {
margin-left:0;
- margin-right: 240px;
+ margin-right: 260px;
}
fieldset { margin-bottom:0;}
View
2  app/assets/stylesheets/gadget.scss
@@ -1,4 +1,4 @@
-$feed_gadget_width: 198px;
+$feed_gadget_width: 218px;
#feedGadget {
margin-top: 0px;
View
2  app/assets/stylesheets/home.scss
@@ -14,7 +14,7 @@
.home_suggest_topics {
position:relative;
.box {
- width:440px;
+ width:450px;
ol {
margin:0;
list-style-position: inside;
View
11 app/controllers/topics_controller.rb
@@ -57,9 +57,12 @@ def show
@node = @topic.node
@replies = @topic.replies.without_body.asc(:_id).all.includes(:user).reject { |r| r.user.blank? }
if current_user
- current_user.read_topic(@topic)
- # TODO: 此处导致每次查看帖子都会执行 update 需要改进
- current_user.notifications.where(:reply_id.in => @replies.map(&:id), :read => false).update_all(:read => true)
+ unless current_user.topic_read?(@topic)
+ current_user.notifications.unread.any_of({:mentionable_type => 'Topic', :mentionable_id => @topic.id},
+ {:mentionable_type => 'Reply', :mentionable_id.in => @replies.map(&:id)},
+ {:reply_id.in => @replies.map(&:id)}).update_all(:read => true)
+ current_user.read_topic(@topic)
+ end
end
set_seo_meta("#{@topic.title} » #{t("menu.topics")}")
drop_breadcrumb("#{@node.name}", node_topics_path(@node.id))
@@ -129,7 +132,7 @@ def destroy
@topic.destroy_by(current_user)
redirect_to(topics_path, :notice => t("topics.delete_topic_success"))
end
-
+
def favorite
if params[:type] == "unfavorite"
current_user.unfavorite_topic(params[:id])
View
46 app/models/mongoid/mentionable.rb
@@ -0,0 +1,46 @@
+module Mongoid
+ module Mentionable
+ extend ActiveSupport::Concern
+ included do
+ field :mentioned_user_ids, :type => Array, :default => []
+ before_save :extract_mentioned_users
+ after_create :send_mention_notification
+ after_destroy :delete_notifiaction_mentions
+ has_many :notification_mentions, :as => :mentionable, :class_name => 'Notification::Mention'
+ end
+
+ # Wait for https://github.com/mongoid/mongoid/commit/2f94b5fab018b22a9e84ac2e988d4a3cf97e7f2e
+ def delete_notifiaction_mentions
+ Notification::Mention.where(:mentionable_id => self.id, :mentionable_type => self.class.name).delete_all
+ end
+
+ def mentioned_users
+ User.where(:_id.in => mentioned_user_ids)
+ end
+
+ def mentioned_user_logins
+ # 用于作为缓存 key
+ ids_md5 = Digest::MD5.hexdigest(self.mentioned_user_ids.to_s)
+ Rails.cache.fetch("#{self.class.name.downcase}:#{self.id}:mentioned_user_logins:#{ids_md5}") do
+ User.where(:_id.in => self.mentioned_user_ids).only(:login).map(&:login)
+ end
+ end
+
+ def extract_mentioned_users
+ logins = body.scan(/@([#{User::LOGIN_FORMATTING}]{3,20})/).flatten
+ if logins.any?
+ self.mentioned_user_ids = User.where(:login => /^(#{logins.join('|')})$/i, :_id.ne => user.id).limit(5).only(:_id).map(&:_id).to_a
+ end
+ end
+
+ def no_mention_users
+ [user]
+ end
+
+ def send_mention_notification
+ (mentioned_users - no_mention_users).each do |user|
+ Notification::Mention.create :user => user, :mentionable => self
+ end
+ end
+ end
+end
View
2  app/models/notification/base.rb
@@ -7,6 +7,8 @@ class Notification::Base
field :read, :default => false
+ index :read
+
scope :unread, where(:read => false)
belongs_to :user
View
2  app/models/notification/mention.rb
@@ -1,3 +1,3 @@
class Notification::Mention < Notification::Base
- belongs_to :reply
+ belongs_to :mentionable, :polymorphic => true
end
View
27 app/models/reply.rb
@@ -7,12 +7,12 @@ class Reply
include Mongoid::CounterCache
include Mongoid::SoftDelete
include Mongoid::MarkdownBody
+ include Mongoid::Mentionable
field :body
field :body_html
field :source
field :message_id
- field :mentioned_user_ids, :type => Array, :default => []
belongs_to :user, :inverse_of => :replies
belongs_to :topic, :inverse_of => :replies
@@ -41,29 +41,7 @@ def update_parent_topic_updated_at
end
end
- before_save :extract_mentioned_users
- def extract_mentioned_users
- logins = body.scan(/@([#{User::LOGIN_FORMATTING}]{3,20})/).flatten
- if logins.any?
- self.mentioned_user_ids = User.where(:login => /^(#{logins.join('|')})$/i, :_id.ne => user.id).limit(5).only(:_id).map(&:_id).to_a
- end
- end
-
- def mentioned_user_logins
- # 用于作为缓存 key
- ids_md5 = Digest::MD5.hexdigest(self.mentioned_user_ids.to_s)
- Rails.cache.fetch("reply:#{self.id}:mentioned_user_logins:#{ids_md5}") do
- User.where(:_id.in => self.mentioned_user_ids).only(:login).map(&:login)
- end
- end
-
- after_create :send_mention_notification, :send_topic_reply_notification
- def send_mention_notification
- self.mentioned_user_ids.each do |user_id|
- Notification::Mention.create :user_id => user_id, :reply => self
- end
- end
-
+ after_create :send_topic_reply_notification
def send_topic_reply_notification
if self.user != topic.user && !mentioned_user_ids.include?(topic.user_id)
Notification::TopicReply.create :user => topic.user, :reply => self
@@ -73,5 +51,6 @@ def send_topic_reply_notification
def destroy
super
notifications.delete_all
+ delete_notifiaction_mentions
end
end
View
8 app/models/topic.rb
@@ -9,6 +9,7 @@ class Topic
include Mongoid::MarkdownBody
include Redis::Objects
include Sunspot::Mongoid
+ include Mongoid::Mentionable
field :title
field :body
@@ -96,11 +97,16 @@ def update_last_reply(reply)
def self.find_by_message_id(message_id)
where(:message_id => message_id).first
end
-
+
# 删除并记录删除人
def destroy_by(user)
return false if user.blank?
self.update_attribute(:who_deleted,user.login)
self.destroy
end
+
+ def destroy
+ super
+ delete_notifiaction_mentions
+ end
end
View
76 app/views/notifications/notification/_mention.html.erb
@@ -1,25 +1,55 @@
<%
- reply = notification.reply
- topic = reply.topic
+ case notification.mentionable
+ when Reply
%>
-<td class="face">
- <%= user_avatar_tag(reply.user, :normal) %>
-</td>
-<td>
- <h3>
- <span class="user">
- <%= user_name_tag(reply.user) %>
- </span>
- 在
- <%= render_topic_title(topic) %>
- 提及你:
- <% if !notification.read? %>
- <span class="new label warning">新</span>
- <% end %>
- </h3>
- <%- if reply.present? -%>
- <div class="body md_body">
- <%= raw reply.body_html %>
- </div>
- <%- end -%>
-</td>
+ <%
+ reply = notification.mentionable
+ topic = reply.topic
+ %>
+ <td class="face">
+ <%= user_avatar_tag(reply.user, :normal) %>
+ </td>
+ <td>
+ <h3>
+ <span class="user">
+ <%= user_name_tag(reply.user) %>
+ </span>
+ 在
+ <%= render_topic_title(topic) %>
+ 提及你:
+ <% if !notification.read? %>
+ <span class="new label warning">新</span>
+ <% end %>
+ </h3>
+ <%- if reply.present? -%>
+ <div class="body md_body">
+ <%= raw reply.body_html %>
+ </div>
+ <%- end -%>
+ </td>
+<% when Topic %>
+ <%
+ topic = notification.mentionable
+ %>
+ <td class="face">
+ <%= user_avatar_tag(topic.user, :normal) %>
+ </td>
+ <td>
+ <h3>
+ <span class="user">
+ <%= user_name_tag(topic.user) %>
+ </span>
+ 在
+ <%= render_topic_title(topic) %>
+ 提及你:
+ <% if !notification.read? %>
+ <span class="new label warning">新</span>
+ <% end %>
+ </h3>
+ <%- if topic.present? -%>
+ <div class="body md_body">
+ <%= raw topic.body_html %>
+ </div>
+ <%- end -%>
+ </td>
+<% end %>
View
1  config/deploy.rb
@@ -43,6 +43,7 @@
task :link_shared_files, :roles => :web do
run "ln -sf #{deploy_to}/shared/config/*.yml #{deploy_to}/current/config/"
run "ln -sf #{deploy_to}/shared/config/unicorn.rb #{deploy_to}/current/config/"
+ run "ln -sf #{deploy_to}/shared/config/initializers/secret_token.rb #{deploy_to}/current/config/initializers"
run "ln -s #{deploy_to}/shared/assets #{deploy_to}/current/public/assets"
end
View
1  config/routes.rb
@@ -62,7 +62,6 @@
resources :likes
match "/search" => "search#index", :as => :search
- match "/search/topics" => "search#topics", :as => :search_topics
match "/search/wiki" => "search#wiki", :as => :search_wiki
namespace :cpanel do
View
19 db/migrate/20120422061501_reply_mention_to_mentionable.rb
@@ -0,0 +1,19 @@
+class ReplyMentionToMentionable < Mongoid::Migration
+ def self.up
+ Notification::Mention.where(:reply_id.ne => nil).each do |mention|
+ mention[:mentionable_id] = mention["reply_id"]
+ mention[:mentionable_type] = 'Reply'
+ mention.save
+ mention.unset :reply_id
+ end
+ end
+
+ def self.down
+ Notification::Mention.where(:reply_id => nil).each do |mention|
+ mention["reply_id"] = mention["mentionable_id"]
+ mention.save
+ mention.unset :mentionable_id
+ mention.unset :mentionable_type
+ end
+ end
+end
View
37 lib/api.rb
@@ -70,25 +70,46 @@ class API < Grape::API
end
end
+ # Mark a topic as favorite for current authenticated user
+ # Example
+ # /api/user/favorite/qichunren/8.json?token=232332233223:1
+ resource :user do
+ put "favorite/:user/:topic" do
+ authenticate!
+ current_user.favorite_topic(params[:topic])
+ end
+ end
+
resource :users do
- # Get hot users, a recent topic embed with a user
+ # Get top 20 hot users
# Example
# /api/users.json
get do
- @users = User.hot.limit(page_size)
+ @users = User.hot.limit(20)
present @users, :with => APIEntities::DetailUser
end
- # Get topic detail
+ # Get a single user
# Example
- # /api/topics/1.json
- get ":id" do
- @user = User.where(:login => /^#{params[:id]}$/i).first
- present @user, :topics_limit => 10, :with => APIEntities::DetailUser
+ # /api/users/qichunren.json
+ get ":user" do
+ @user = User.where(:login => /^#{params[:user]}$/i).first
+ present @user, :topics_limit => 5, :with => APIEntities::DetailUser
end
- get ":id/topics" do
+ # List topics for a user
+ get ":user/topics" do
+ @user = User.where(:login => /^#{params[:user]}$/i).first
+ @topics = @user.topics.recent.limit(page_size)
+ present @topics, :with => APIEntities::UserTopic
+ end
+
+ # List favorite topics for a user
+ get ":user/topics/favorite" do
+ @user = User.where(:login => /^#{params[:user]}$/i).first
+ @topics = Topic.find(@user.favorite_topic_ids)
+ present @topics, :with => APIEntities::Topic
end
end
View
8 lib/api/entities.rb
@@ -12,11 +12,17 @@ class DetailUser < Grape::Entity
expose :_id, :name, :login, :location, :website, :bio, :tagline, :github_url
expose(:gravatar_hash) { |model, opts| Digest::MD5.hexdigest(model.email || "") }
expose(:avatar_url) { |model, opts| model.avatar? ? model.avatar.url(:normal) : "" }
- expose(:topics) do |model, opts|
+ expose(:topics, :unless => { :collection => true }) do |model, opts|
model.topics.recent.limit(opts[:topics_limit] ||= 1).as_json(:only => [:title, :created_at, :node_name, :replies_count])
end
end
+ class UserTopic < Grape::Entity
+ expose :_id, :title, :body, :body_html, :created_at, :updated_at, :replied_at, :replies_count, :node_name, :node_id, :last_reply_user_login
+ end
+
+
+
class Reply < Grape::Entity
expose :_id, :body, :body_html, :message_id, :created_at, :updated_at
expose :user, :using => APIEntities::User
View
6 spec/controllers/notifications_controller_spec.rb
@@ -5,7 +5,7 @@
describe "#index" do
it "should show notifications" do
sign_in user
- Factory :notification_mention, :user => user
+ Factory :notification_mention, :user => user, :mentionable => Factory(:reply)
Factory :notification_topic_reply, :user => user
get :index
response.should render_template(:index)
@@ -15,7 +15,7 @@
describe "#destroy" do
it "should destroy notification" do
sign_in user
- notification = Factory :notification_mention, :user => user
+ notification = Factory :notification_mention, :user => user, :mentionable => Factory(:reply)
lambda do
delete :destroy, :id => notification
@@ -26,7 +26,7 @@
describe "#mark_all_as_read" do
it "should mark all as read" do
sign_in user
- 3.times{ Factory :notification_mention, :user => user }
+ 3.times{ Factory :notification_mention, :user => user, :mentionable => Factory(:reply) }
put :mark_all_as_read
user.notifications.unread.count.should == 0
View
10 spec/controllers/topics_controller_spec.rb
@@ -89,11 +89,13 @@
describe "#show" do
it "should clear user mention notification when show topic" do
- notification = Factory :notification_mention
- sign_in notification.user
+ user = Factory :user
+ topic = Factory :topic, :body => "@#{user.login}"
+ Factory :reply, :body => "@#{user.login}", :topic => topic
+ sign_in user
lambda do
- get :show, :id => notification.reply.topic
- end.should change(notification.user.notifications.unread, :count)
+ get :show, :id => topic
+ end.should change(user.notifications.unread, :count).by(-2)
end
context "user deletes her own account" do
View
1  spec/factories/notification_mentions.rb
@@ -1,5 +1,4 @@
FactoryGirl.define do
factory :notification_mention, :class => Notification::Mention, :parent => :notification_base do
- association :reply
end
end
View
6 spec/factories/users.rb
@@ -2,9 +2,9 @@
FactoryGirl.define do
factory :user do
- sequence(:name){|n| "中文Ac2_#{n}" }
- sequence(:login){|n| "中文ABcd12_#{n}" }
- sequence(:email){|n| "email#{n}@ruby-chine.org" }
+ sequence(:name){ |n| "中文發Ac2_#{n}" }
+ sequence(:login){ |n| "中文發Bd12_#{n}" }
+ sequence(:email){ |n| "email#{n}@ruby-chine.org" }
password 'password'
password_confirmation 'password'
location "China"
View
53 spec/models/mongoid/mentionable_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+class TestDocument
+ include Mongoid::Document
+ include Mongoid::Mentionable
+
+ belongs_to :user
+ field :body
+end
+
+describe Mongoid::Mentionable do
+ it "should extract mentioned user ids" do
+ user = Factory :user
+ doc = TestDocument.create :body => "@#{user.login}", :user => Factory(:user)
+ doc.mentioned_user_ids.should == [user.id]
+ doc.mentioned_user_logins.should == [user.login]
+ end
+
+ it "limit 5 mentioned user" do
+ logins = ""
+ 6.times { logins << " @#{Factory(:user).login}" }
+ doc = TestDocument.create :body => logins, :user => Factory(:user)
+ doc.mentioned_user_ids.count.should == 5
+ end
+
+ it "except self user" do
+ user = Factory :user
+ doc = TestDocument.create :body => "@#{user.login}", :user => user
+ doc.mentioned_user_ids.count.should == 0
+ end
+
+ it "should get mentioned user logins" do
+ user1 = Factory :user
+ user2 = Factory :user
+ doc = TestDocument.create :body => "@#{user1.login} @#{user2.login}", :user => Factory(:user)
+ doc.mentioned_user_logins.should =~ [user1.login, user2.login]
+ end
+
+ it "should send mention notification" do
+ user = Factory :user
+ lambda do
+ TestDocument.create :body => "@#{user.login}", :user => Factory(:user)
+ end.should change(user.notifications.unread, :count)
+
+ lambda do
+ TestDocument.create(:body => "@#{user.login}", :user => user)
+ end.should_not change(user.notifications.unread, :count)
+
+ lambda do
+ TestDocument.create(:body => "@#{user.login}", :user => Factory(:user)).destroy
+ end.should_not change(user.notifications.unread, :count)
+ end
+end
View
37 spec/models/reply_spec.rb
@@ -2,42 +2,11 @@
require 'spec_helper'
describe Reply do
- describe "extract mention" do
- it "should extract mentioned user ids" do
- user = Factory :user, :login => '中文ABcd12_'
- reply = Factory :reply, :body => "@#{user.login}"
- reply.mentioned_user_ids.should == [user.id]
- reply.mentioned_user_logins.should == [user.login]
- end
-
- it "limit 5 mentioned user" do
- logins = ""
- 6.times { logins << " @#{Factory(:user).login}" }
- reply = Factory :reply, :body => logins
- reply.mentioned_user_ids.count.should == 5
- end
-
- it "except self user" do
- user = Factory :user
- reply = Factory :reply, :body => "@#{user.login}", :user => user
- reply.mentioned_user_ids.count.should == 0
- end
-
- it "should ge mentioned user logins" do
- user1 = Factory :user
- user2 = Factory :user
- reply = Factory :reply, :mentioned_user_ids => [user1.id, user2.id]
- reply.mentioned_user_logins.should =~ [user1.login, user2.login]
- end
-
- it "should send mention notification" do
+ describe "notifications" do
+ it "should delete mention notification after destroy" do
user = Factory :user
lambda do
- Factory :reply, :mentioned_user_ids => [user.id]
- end.should change(user.notifications.unread, :count)
-
- lambda do
- Factory(:reply, :mentioned_user_ids => [user.id]).destroy
+ Factory(:reply, :body => "@#{user.login}").destroy
end.should_not change(user.notifications.unread, :count)
end
Please sign in to comment.
Something went wrong with that request. Please try again.