Defined a new controller, MessagesController, to handle controller
replies.
On the message view page, the user can click to enter a reply. They are
then taken to a reply page where they can enter their message and save
it.
On the item details page, a link to reply to the message is shown. If
the user clicks the link they are taken to the reply page. After posting
their reply they're returned to the item page.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/messages_controller.rb | 69 ++++++++++++++++++
app/helpers/messages_helper.rb | 2 +
app/models/message.rb | 3 +
app/views/items/show.html.erb | 6 ++
app/views/messages/_message.html.erb | 9 +++
app/views/messages/reply.html.erb | 43 +++++++++++
app/views/messages/show.html.erb | 36 +++++++++
config/routes.rb | 13 ++++
test/fixtures/messages.yml | 7 ++-
test/functional/messages_controller_test.rb | 105 +++++++++++++++++++++++++++
10 files changed, 292 insertions(+), 1 deletions(-)
create mode 100644 app/controllers/messages_controller.rb
create mode 100644 app/helpers/messages_helper.rb
create mode 100644 app/views/messages/_message.html.erb
create mode 100644 app/views/messages/reply.html.erb
create mode 100644 app/views/messages/show.html.erb
create mode 100644 test/functional/messages_controller_test.rb
diff --git a/app/controllers/messages_controller.rb
b/app/controllers/messages_controller.rb
new file mode 100644
index 0000000..7d02297
--- /dev/null
+++ b/app/controllers/messages_controller.rb
@@ -0,0 +1,69 @@
+# messages_controller.rb
+# Copyright (C) 2009, Darryl L. Pierce <mcpierce(a)gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <
http://www.gnu.org/licenses/>.
+#
+
+# +MessageController+ enforces business rules on instances of +Message+.
+class MessagesController < ApplicationController
+ before_filter :authenticated, :except => [:show]
+ before_filter :load_message
+ before_filter :get_landing_page, :except => [:show]
+
+ # GET /messages/1
+ def show
+
+ end
+
+ # GET /message/1/reply
+ def reply
+ @reply = Message.new
+ end
+
+ # POST /messages/1
+ def post_reply
+ Message.transaction do
+ @reply = Message.new(params[:reply])
+ @reply.posted = Time.current
+ @reply.author = @user
+ @reply.reference = @message
+ @message.replies << @reply
+
+ respond_to do |format|
+ if @message.save
+ flash[:message] = "Reply posted."
+ if @landing_page
+ format.html {redirect_to @landing_page}
+ else
+ format.html {redirect_to message_path(@message)}
+ end
+ else
+ @reply.valid?
+ format.html {render :action => :reply}
+ end
+ end
+ end
+ end
+
+ private
+
+ def load_message
+ @message = Message.find_by_id(params[:id])
+
+ report_error "Invalid or missing message." unless @message
+ end
+
+ def get_landing_page
+ @landing_page = params[:dest]
+ end
+end
diff --git a/app/helpers/messages_helper.rb b/app/helpers/messages_helper.rb
new file mode 100644
index 0000000..f1bca9f
--- /dev/null
+++ b/app/helpers/messages_helper.rb
@@ -0,0 +1,2 @@
+module MessagesHelper
+end
diff --git a/app/models/message.rb b/app/models/message.rb
index 023b3e6..58966f4 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -29,6 +29,9 @@ class Message < ActiveRecord::Base
belongs_to :author, :class_name => 'User', :foreign_key => :author_id
+ has_many :replies, :class_name => 'Message', :foreign_key =>
:reference_id
+ has_one :reference, :class_name => 'Message', :foreign_key =>
:reference_id
+
named_scope :blocker_messages, lambda {|backlog_item|
{
:conditions => ['id in (select message_id from backlog_items_messages where
backlog_item_id = ?)', backlog_item.id],
diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb
index 30c2da1..6ba5f99 100644
--- a/app/views/items/show.html.erb
+++ b/app/views/items/show.html.erb
@@ -17,6 +17,12 @@
<b>By <%= link_to @backlog_item.blocker_message.author.display_name,
user_path((a)backlog_item.blocker_message.author) %>,
Posted <%= show_date((a)backlog_item.blocker_message.posted, true)
%>:</b></p>
<p><%= RedCloth.new((a)backlog_item.blocker_message.body).to_html
%></p>
+ <% unless @backlog_item.blocker_message.replies.empty? %>
+ <p>There are <%= link_to "#{(a)backlog_item.blocker_message.replies.size}
replies", message_path((a)backlog_item.blocker_message) %> currently.</p>
+ <% end %>
+ <% if @user %>
+ <p><%= link_to "Reply to this message...",
reply_message_path((a)backlog_item.blocker_message, :dest => request.request_uri)
%></p>
+ <% end %>
</dd>
<% end %>
</dl>
diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb
new file mode 100644
index 0000000..a4b637b
--- /dev/null
+++ b/app/views/messages/_message.html.erb
@@ -0,0 +1,9 @@
+<div class="message">
+ <div class="heading">
+ <div class="date">Posted <%= show_date((a)message.posted, true)
%></div>
+ <div class="author">Posted by <%= link_to
@message.author.display_name, user_path((a)message.author) %></div>
+ </div>
+ <div class="body">
+ <%= RedCloth.new((a)message.body).to_html %>
+ </div>
+</div>
diff --git a/app/views/messages/reply.html.erb b/app/views/messages/reply.html.erb
new file mode 100644
index 0000000..e621df5
--- /dev/null
+++ b/app/views/messages/reply.html.erb
@@ -0,0 +1,43 @@
+<div id="content-no-sidebar">
+ <% form_for :reply, @reply,:url => post_reply_message_path(@message) do |form|
%>
+
+ <% if @landing_page %>
+ <%= hidden_field_tag :dest, @landing_page %>
+ <% end %>
+
+ <table class="edit">
+ <caption>Post a Reply To:</caption>
+ <tr>
+ <td class="label">Posted:</td>
+ <td class="value"><%= show_date((a)message.posted, true)
%></td>
+ </tr>
+
+ <tr>
+ <td class="label">Author:</td>
+ <td class="value"><%= link_to @message.author.display_name,
user_path((a)message.author) %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Message:</td>
+ <td class="value">
+ <%= RedCloth.new((a)message.body).to_html %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label-required">Your comments:<br />(you can use
wiki markup)</td>
+ <td class="value">
+ <%= form.text_area :body, :cols => 100, :rows => 10 %>
+ <%= error_message_on(:reply, :body) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <%= submit_tag "Post Reply" %>
+ </td>
+ </tr>
+ </table>
+
+ <% end %>
+</div>
diff --git a/app/views/messages/show.html.erb b/app/views/messages/show.html.erb
new file mode 100644
index 0000000..f5f9035
--- /dev/null
+++ b/app/views/messages/show.html.erb
@@ -0,0 +1,36 @@
+<div id="content-no-sidebar">
+ <%= render :partial => 'message', :locals => {:message => @message}
%>
+
+ <% if @user %>
+ [ <%= link_to "Reply To This Message", reply_message_path(@message) %>
]
+ <% end %>
+
+ <table class="main-list">
+ <caption>Replies</caption>
+ <thead>
+ <th scope="col">#</th>
+ <th scope="col" class="name">Content</th>
+ <th scope="col">Posted</th>
+ <th scope="col">Replies</th>
+ </thead>
+
+ <tbody>
+ <% @message.replies.each do |reply| %>
+
+ <tr class="<%= cycle('odd', 'even') %>">
+ <td><%= link_to "#{reply.id}", message_path(reply) %></td>
+ <td class="name">
+ <%= link_to reply.author.display_name, user_path(reply.author) %>
+ <%= RedCloth.new(get_first_sentence(reply.body)).to_html %>
+ <% if reply.reference %>
+ <%= link_to "In reply to #{reply.reference_id}",
message_path(reply.reference) %>
+ <% end %>
+ </td>
+ <td><%= show_date(reply.posted, true) %></td>
+ <td><%= reply.replies %></td>
+ </tr>
+
+ <% end %>
+ </tbody>
+ </table>
+</div>
diff --git a/config/routes.rb b/config/routes.rb
index b0040f0..e52c84e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -17,6 +17,7 @@
ActionController::Routing::Routes.draw do |map|
map.admin "/admin", :controller => 'admin', :action =>
'index'
+
map.resource :admin do |admin|
admin.resource :server, :controller => 'ServerSettings'
end
@@ -26,20 +27,25 @@ ActionController::Routing::Routes.draw do |map|
:approve => :put,
:reject => :put
}
+
map.resources :epics, :member =>
{
:close => :put,
:reopen => :put
}
+
map.resources :projects, :member =>
{
:approve => :put,
:reject => :put
}
+
map.resources :stories
+
map.resources :products do |product|
product.resources :roles
end
+
map.resources :sprints, :member =>
{
:plan => :get,
@@ -47,6 +53,7 @@ ActionController::Routing::Routes.draw do |map|
:status => :put,
:members => :get
}
+
map.resources :items, :member =>
{
:accept => :get,
@@ -57,6 +64,7 @@ ActionController::Routing::Routes.draw do |map|
:reopen => :get,
:estimate => :post
}
+
map.resources :users, :member =>
{
:backlog => :get,
@@ -68,6 +76,11 @@ ActionController::Routing::Routes.draw do |map|
map.resources :tasks
+ map.resources :messages, :member =>
+ {
+ :reply => :get,
+ :post_reply => :post
+ }
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml
index 7e54d3a..01c1baf 100644
--- a/test/fixtures/messages.yml
+++ b/test/fixtures/messages.yml
@@ -1,4 +1,9 @@
blocker_message:
author_id: <%= Fixtures.identify(:mcpierce) %>
- posted: <%= Date.today %>
+ posted: <%= Time.current %>
body: This is a blocked item.
+
+blocker_message_reply:
+ author_id: <%= Fixtures.identify(:jdonuts) %>
+ posted: <%= Time.current %>
+ body: This is a reply.
diff --git a/test/functional/messages_controller_test.rb
b/test/functional/messages_controller_test.rb
new file mode 100644
index 0000000..a051dcd
--- /dev/null
+++ b/test/functional/messages_controller_test.rb
@@ -0,0 +1,105 @@
+# Copyright (C) 2009, Darryl L. Pierce <mcpierce(a)gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <
http://www.gnu.org/licenses/>.
+#
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class MessagesControllerTest < ActionController::TestCase
+ def setup
+ @user = users(:mcpierce)
+ @message = messages(:blocker_message)
+ @landing_page = dashboard_path
+
+ @new_reply = {:body => 'This is a reply', :reference_id =>
@message.id}
+ end
+
+ # Ensures that a valid id is require to view a message.
+ def test_show_with_invalid_id
+ get :show
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that showing a message works as expected.
+ def test_show
+ get :show, {:id => @message.id}
+
+ assert_response :success
+ assert assigns['message'], "Failed to load a message."
+ assert_equal @message.id, assigns['message'].id, "Failed to load the
correct message."
+ end
+
+ # Ensures that entering a reply requires the user log in.
+ def test_reply_as_anonymous
+ get :reply
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that entering a reply requires a valid id.
+ def test_reply_with_invalid_id
+ get :reply, { }, {:user_id => @user.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that editing a reply works as expected.
+ def test_reply
+ get :reply, {:id => @message.id}, {:user_id => @user.id}
+
+ assert_response :success
+ assert assigns['reply'], "Failed to create a reply object."
+ end
+
+ # Ensures that anonymous users cannot post replies.
+ def test_post_reply_as_anonymous
+ post :post_reply
+
+ assert_redirected_to login_path
+ end
+
+ # Ensures that posting a reply requires a valid message id.
+ def test_post_reply_with_invalid_message
+ post :post_reply, { }, {:user_id => @user.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that, if the message is malformed, then the user is redirected to the src
page.
+ def test_post_reply_with_malformed_message
+ post :post_reply, {:id => @message.id, :dest => @landing_page, :reply =>
{}}, {:user_id => @user.id}
+
+ assert_template 'messages/reply'
+ end
+
+ # Ensures that a reply is posted as expected, and the user redirected to the landing
page.
+ def test_post_reply_with_landing_page
+ post :post_reply, {:id => @message.id, :dest => @landing_page, :reply =>
@new_reply}, {:user_id => @user.id}
+
+ assert_redirected_to @landing_page
+ result = Message.find_by_id((a)message.id).replies.last
+ assert result, "Did not save the reply."
+ assert_equal @new_reply[:body], result.body, "Did not save the message."
+ end
+
+ # Ensures that a landing page is provided.
+ def test_post_reply
+ post :post_reply, {:id => @message.id, :reply => @new_reply}, {:user_id =>
@user.id}
+
+ assert_redirected_to message_path(@message)
+ result = Message.find_by_id((a)message.id).replies.last
+ assert result, "Did not save the reply."
+ assert_equal @new_reply[:body], result.body, "Did not save the message."
+ end
+end
--
1.6.0.6