[PROJXP [PATCH] Moving projects over to a RESTful interface.
by Darryl L. Pierce
Added a new resource named :projects.
Renamed ProjectController and ProjectControllerTest to ProjectsController
and ProjectsControllerTest, respectively.
Rewrote the functional tests to make sure they properly vetted the
refactored controller.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/application.rb | 2 +-
app/controllers/product_controller.rb | 2 +-
app/controllers/project_controller.rb | 102 ------------
app/controllers/projects_controller.rb | 142 ++++++++++++++++
app/helpers/application_helper.rb | 56 +++-----
app/models/project.rb | 13 ++-
app/views/layouts/default.html.erb | 2 +-
app/views/product/_details.html.erb | 2 +-
app/views/project/_details.html.erb | 25 ---
app/views/project/_edit.html.erb | 59 -------
app/views/project/_product.html.erb | 19 ---
app/views/project/create.html.erb | 1 -
app/views/project/index.html.erb | 48 ------
app/views/project/modify.html.erb | 1 -
app/views/project/view.html.erb | 59 -------
app/views/projects/_details.html.erb | 25 +++
app/views/projects/_edit.html.erb | 58 +++++++
app/views/projects/_product.html.erb | 19 +++
app/views/projects/edit.html.erb | 1 +
app/views/projects/index.html.erb | 56 +++++++
app/views/projects/new.html.erb | 1 +
app/views/projects/show.html.erb | 59 +++++++
app/views/projects/update.html.erb | 1 +
app/views/tasks/new.html.erb | 4 +-
app/views/user_story/view.html.erb | 4 +-
config/routes.rb | 3 +-
test/functional/product_controller_test.rb | 4 +-
test/functional/project_controller_test.rb | 176 --------------------
test/functional/projects_controller_test.rb | 233 +++++++++++++++++++++++++++
29 files changed, 637 insertions(+), 540 deletions(-)
delete mode 100644 app/controllers/project_controller.rb
create mode 100644 app/controllers/projects_controller.rb
delete mode 100644 app/views/project/_details.html.erb
delete mode 100644 app/views/project/_edit.html.erb
delete mode 100644 app/views/project/_product.html.erb
delete mode 100644 app/views/project/create.html.erb
delete mode 100644 app/views/project/index.html.erb
delete mode 100644 app/views/project/modify.html.erb
delete mode 100644 app/views/project/view.html.erb
create mode 100644 app/views/projects/_details.html.erb
create mode 100644 app/views/projects/_edit.html.erb
create mode 100644 app/views/projects/_product.html.erb
create mode 100644 app/views/projects/edit.html.erb
create mode 100644 app/views/projects/index.html.erb
create mode 100644 app/views/projects/new.html.erb
create mode 100644 app/views/projects/show.html.erb
create mode 100644 app/views/projects/update.html.erb
delete mode 100644 test/functional/project_controller_test.rb
create mode 100644 test/functional/projects_controller_test.rb
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 3378a90..51261a0 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -34,7 +34,7 @@ class ApplicationController < ActionController::Base
if @user == nil
session[:original_uri] = request.request_uri
flash[:message] = 'Please login first.'
- redirect_to(:controller => :home, :action => :login)
+ redirect_to login_url
end
end
diff --git a/app/controllers/product_controller.rb b/app/controllers/product_controller.rb
index f5553d3..43c6425 100644
--- a/app/controllers/product_controller.rb
+++ b/app/controllers/product_controller.rb
@@ -51,7 +51,7 @@ class ProductController < ApplicationController
end
else
flash[:message] = 'You are not authorized to create products.'
- redirect_to :controller => :project, :action => :view, :id => @project.id
+ redirect_to project_path(@project)
end
end
diff --git a/app/controllers/project_controller.rb b/app/controllers/project_controller.rb
deleted file mode 100644
index b122159..0000000
--- a/app/controllers/project_controller.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# project_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +ProjectController+ handles requests relating to the management and viewing of
-# +Product+s.
-#
-class ProjectController < ApplicationController
- before_filter :authenticated, :only => [:create, :modify]
- before_filter :load_project, :except => [:create, :index]
- before_filter :setup_for_edit, :only => [:create, :modify]
-
- # Allows viewing of all defined projects.
- #
- def index
- @projects = Project.paginate :page => params[:page], :per_page => 10
- end
-
- # Shows a single project.
- #
- def view
- @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
- end
-
- # Begins the process of creating a new project.
- #
- def create
- if @user.create_projects?
- if request.post?
- begin
- Project.transaction do
- @project = Project.new(params[:project])
- @project.save!
-
- redirect_to :action => :index
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :create
- end
- else
- @project = Project.new
- end
- else
- flash[:message] = 'That action requires administrative rights.'
- redirect_to :controller => :home, :action => :dashboard
- end
- end
-
- # Modifies an existing project.
- #
- def modify
- if @user.can_modify_project?(@project)
- if request.post?
- begin
- Project.transaction do
- @project.update_attributes(params[:project])
- @project.save!
-
- redirect_to :action => :view, :id => @project
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :modify
- end
- end
- else
- flash[:message] = "You are not authorized to modify that project."
- redirect_to error_path
- end
- end
-
- private
-
- # Loads the requested project.
- #
- def load_project
- @project = Project.find_by_id(params[:id])
-
- unless @project
- flash[:message] = 'Invalid project specified.'
- redirect_to error_path
- end
- end
-
- def setup_for_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
new file mode 100644
index 0000000..6c379b7
--- /dev/null
+++ b/app/controllers/projects_controller.rb
@@ -0,0 +1,142 @@
+# projects_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +ProjectsController+ handles requests relating to the management and viewing
+# of +Product+s.
+#
+class ProjectsController < ApplicationController
+ before_filter :authenticated, :only => [:new, :create, :edit, :update]
+ before_filter :load_project, :except => [:index, :new, :create, :destroy]
+ before_filter :setup_for_edit, :only => [:new, :create, :edit]
+
+ # GET /projects
+ def index
+ @projects = Project.paginate :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /projects/1
+ def show
+ @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # get /projects/new
+ def new
+ if @user.create_projects?
+ @project = Project.new(:owner_id => @user.id)
+ else
+ flash[:error] = "You are not authorized to create projects."
+
+ redirect_to error_url
+ end
+ end
+
+ # GET /projects/1;edit
+ def edit
+ unless @project.user_can_modify?(@user)
+ flash[:message] = "You are not authorized to modify that project."
+ redirect_to project_path(@project)
+ end
+ end
+
+ # POST /projects
+ def create
+ respond_to do |format|
+ if @user.create_projects?
+ @project = Project.new(params[:project])
+
+ begin
+ Project.transaction do
+ if @project.save
+ flash[:message] = "Project '#{(a)project.name}' created!"
+ format.html { redirect_to project_url(@project) }
+ else
+ @project.valid?
+ format.html { render :action => "new" }
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to create projects."
+ format.html { redirect_to error_url }
+ end
+ end
+ end
+
+ # PUT /projects/1
+ def update
+ respond_to do |format|
+ if @project.user_can_modify?(@user)
+ begin
+ Project.transaction do
+ @project.update_attributes(params[:project])
+ if @project.save!
+ flash[:message] = "Project was successfully updated."
+ format.html { redirect_to project_path(@project) }
+ else
+ @project.valid?
+ format.html { render :action => 'edit'}
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to edit this project."
+ format.html { redirect_to project_path(@project) }
+ end
+ end
+ end
+
+ # DELETE /projects/1
+ def destroy
+ respond_to do |format|
+ flash[:message] = "Project deletion is not current supported."
+ format.html { redirect_to error_url}
+ end
+ end
+
+ private
+
+ # Loads the requested project.
+ #
+ def load_project
+ @project = Project.find_by_id(params[:id])
+
+ unless @project
+ flash[:message] = 'Invalid project specified.'
+ respond_to do |format|
+ format.html { redirect_to error_path }
+ end
+ end
+ end
+
+ def setup_for_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 96d59a7..fa755c7 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,19 +1,19 @@
# application_helper.rb
# Copyright (C) 2008, 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/>.
-#
+#
module ApplicationHelper
# Returns the first sentence from the supplied text, up to the first period.
@@ -23,13 +23,13 @@ module ApplicationHelper
if (text == nil) || text.empty?
return " "
end
-
+
return text.slice(/^[^\.]*\.?/)
-
+
end
-
+
# Creates a link to a user.
- #
+ #
def link_to_user(user)
if user
@template.link_to(user.display_name,
@@ -38,9 +38,9 @@ module ApplicationHelper
:id => user)
end
end
-
+
# Creates a link to an owner for a project, product or other item.
- #
+ #
def link_to_owner(owner, args = nil)
if owner
link_to_user(owner)
@@ -49,61 +49,43 @@ module ApplicationHelper
args[:unowned] if args
end
end
-
- # Creates a link to a project.
- #
- def link_to_project(project)
- @template.link_to(project.name,
- :controller => :project,
- :action => :view,
- :id => project)
- end
-
- # Creates a link to a product.
- #
- def link_to_product(product, options = {})
- @template.link_to(options[:text] ? options[:text] : product.name,
- :controller => :product,
- :action => options[:action] ? options[:action] : :view,
- :id => product)
- end
-
+
# Creates a link to a sprint.
- #
+ #
def link_to_sprint(sprint)
@template.link_to(sprint.title,
:controller => :sprint,
:action => :view,
:id => sprint)
end
-
+
# Displays a link and image reporting the health of the sprint
def show_sprint_health(sprint)
@template.image_tag(
sprint.healthy? ? "/images/icons/healthy.png" : "/images/icons/unhealthy.png",
:alt => sprint.healthy? ? "Sprint is healthy" : "Sprint is not healthy")
end
-
+
# Creates a link to a user story.
- #
+ #
def link_to_user_story(user_story, options ={})
@template.link_to((options[:text] ? options[:text] : user_story.title),
:controller => :user_story,
:action => (options[:action] ? options[:action] : :view),
:id => user_story)
end
-
+
# Creates a link to a backlog item.
- #
+ #
def link_to_backlog_item(backlog_item, options = {})
@template.link_to(options[:text] ? options[:text] : backlog_item.title,
:controller => :backlog,
:action => (options[:action] ? options[:action] : :view),
:id => backlog_item.id)
end
-
+
# Shows the hours for a given object as estimated, actual and remaining.
- #
+ #
def show_hours_as_ear(estimated)
format("%0.1f/%0.1f/%0.1f",estimated.estimated_hours, estimated.actual_hours, estimated.remaining_hours)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2bd9541..6a7ce5d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -21,14 +21,25 @@
class Project < ActiveRecord::Base
validates_presence_of :name,
:message => 'A project has to have a name.'
-
+ validates_length_of :name,
+ :minimum => 4,
+ :message => 'The project name must be at least four characters long.'
validates_uniqueness_of :name,
:message => 'This name is used by an existing project.'
+ validates_presence_of :owner_id,
+ :message => 'A project must have an owner.'
+
belongs_to :owner, :class_name => 'User', :foreign_key => :owner_id
has_many :products
+ # Returns whether the user can modify this project.
+ #
+ def user_can_modify?(user)
+ user && (user.privileges.admin_projects || (user.id == owner.id))
+ end
+
# Returns whether the user can create products for this project.
#
def can_create_products?(user)
diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb
index ac4dd6f..e1ac558 100644
--- a/app/views/layouts/default.html.erb
+++ b/app/views/layouts/default.html.erb
@@ -24,7 +24,7 @@
<div id="user-box">
<%= link_to "Home", :controller => :home %>
- <%= link_to "Projects", :controller => :project, :action => :index %>
+ <%= link_to "Projects", projects_path %>
<%= link_to "Reports", :controller => :report, :action => :index %>
<% if @user %>
<%= link_to @user.display_name, dashboard_url %>
diff --git a/app/views/product/_details.html.erb b/app/views/product/_details.html.erb
index d7e1bda..7ea0794 100644
--- a/app/views/product/_details.html.erb
+++ b/app/views/product/_details.html.erb
@@ -14,7 +14,7 @@
<tr>
<td class="label">For Project:</td>
- <td class="value"><%= link_to_project @product.project %></td>
+ <td class="value"><%= link_to @product.project.name, project_path((a)product.project) %></td>
</tr>
<tr>
diff --git a/app/views/project/_details.html.erb b/app/views/project/_details.html.erb
deleted file mode 100644
index 7dfc64c..0000000
--- a/app/views/project/_details.html.erb
+++ /dev/null
@@ -1,25 +0,0 @@
-<table class="details">
- <thead>
- <tr>
- <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td class="label">Owner:</td>
- <td class="value"><%= link_to_user @project.owner %></td>
- </tr>
-
- <tr>
- <td class="label">Homepage:</td>
- <td class="value"><%= link_to_homepage @project %>
- </tr>
-
- <tr>
- <td class="text" colspan="2">
- <%= simple_format @project.description %>
- </td>
- </tr>
- </tbody>
-</table>
diff --git a/app/views/project/_edit.html.erb b/app/views/project/_edit.html.erb
deleted file mode 100644
index d1b02e9..0000000
--- a/app/views/project/_edit.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<% form_for :project do |form| %>
-
- <% unless @project.new_record? %>
- <%= form.hidden_field :id %>
- <% end %>
-
- <table class="edit">
- <tbody>
- <tr>
- <td class="label">Project name</td>
- <td class="value"><%= form.text_field :name %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :name) %>
- </tr>
-
- <tr>
- <td class="label">Website</td>
- <td class="value"><%= form.text_field :url %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :url) %>
- </tr>
-
- <tr>
- <td class="label">Owner</td>
- <td class="value">
- <% if @user.privileges.admin_projects %>
- <%= collection_select :project, :owner_id, @users, :id,
- :display_name, {:include_blank => true} %>
- <% else %>
- <% if @project.owner %>
- <%= h @project.owner.display_name %>
- <% else %>
- UNOWNED
- <% end %>
- <% end %>
- </td>
- <td /><td class="errors"><%= error_message_on(:project, :owner) %>
- </tr>
-
- <tr>
- <td class="label">Description</td>
- <td class="value"><%= form.text_area :description %></td>
- </tr>
-
- <tr>
- <td class="buttons" colspan="2">
- <% if @project.new_record? %>
- <%= submit_tag "Create", :class => 'button' %>
- <% else %>
- <%= submit_tag "Update", :class => 'button' %>
- <% end %>
- </td>
- </tr>
- </tbody>
- </table>
-
-<% end %>
\ No newline at end of file
diff --git a/app/views/project/_product.html.erb b/app/views/project/_product.html.erb
deleted file mode 100644
index 51766eb..0000000
--- a/app/views/project/_product.html.erb
+++ /dev/null
@@ -1,19 +0,0 @@
-<tr>
- <td><%= link_to "#{product.name}",
- {:controller => :product, :action => :view, :id => product} %>
- </td>
- <td>
- <% if product.owner != nil %>
- <%= link_to(product.owner.display_name,
- {:controller => :user,
- :action => :view,
- :id => product.owner})%>
-
- (<%= mail_to(product.owner.email, "email") %>)
- <% else %>
- None (want to become the owner?)
- <% end %>
- </td>
- <td class="description"><%= h product.description %></td>
- <td class="number"><%= product.backlog.size %></td>
-</tr>
diff --git a/app/views/project/create.html.erb b/app/views/project/create.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/create.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/index.html.erb b/app/views/project/index.html.erb
deleted file mode 100644
index d472b78..0000000
--- a/app/views/project/index.html.erb
+++ /dev/null
@@ -1,48 +0,0 @@
-<div class="toolbar">
- <% if @user && @user.create_projects? %>
- <%= link_to "New Project", :action => :create %>
- <% end %>
-</div>
-
-<%= will_paginate @projects %>
-
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
- <thead>
- <tr>
- <th class="title" colspan="5">Projects</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Name</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% if @projects.empty? %>
- <tr><td colspan="5">There are no projects defined.</td></tr>
- <% else %>
- <% @projects.each_with_index do |project, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_project project %></td>
- <td><%= get_first_sentence project.description %></td>
- <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
- <td><%= project.created_at.to_s(:date) %></td>
- </tr>
-
- <% end %>
- <% end %>
- </tbody>
-</table>
diff --git a/app/views/project/modify.html.erb b/app/views/project/modify.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/modify.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/view.html.erb b/app/views/project/view.html.erb
deleted file mode 100644
index c501d67..0000000
--- a/app/views/project/view.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<div style="width: 100%; overflow: auto;">
- <div id="details" style="float: left; width: 33%;">
- <div class="toolbar">
- <% if @user && @user.can_modify_project?(@project) %>
- <%= link_to "Edit project...", :action => :modify, :id => @project %>
- <% end %>
- </div>
- <%= render :partial => 'details', :object => @project %>
- </div>
-
- <div id="products" style="float: right; width: 66%;">
- <div class="toolbar">
- <%= will_paginate @products %>
- <% if @project.can_create_products?(@user) %>
- <%= link_to "Create Product...",
- {
- :controller => :product,
- :action => :create,
- :project => @project
- } %>
- <% end %>
- </div>
- <table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
-
- <thead>
- <tr>
- <th>##</th>
- <th>Product</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% @products.each_with_index do |product, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>"
- onmouseover="this.className='hot'"
- onmouseout="this.className='<%= row_class %>' ">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_product product %></td>
- <td><%= get_first_sentence product.description %></td>
- <td><%= link_to_owner product.owner %></td>
- <td><%= product.created_at.to_s(:date) %></td>
- </tr>
- <% end %>
- </tbody>
- </table>
- </div>
-</div>
diff --git a/app/views/projects/_details.html.erb b/app/views/projects/_details.html.erb
new file mode 100644
index 0000000..9fe7b4b
--- /dev/null
+++ b/app/views/projects/_details.html.erb
@@ -0,0 +1,25 @@
+<table class="details">
+ <thead>
+ <tr>
+ <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td class="label">Owner:</td>
+ <td class="value"><%= link_to_user @project.owner %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Homepage:</td>
+ <td class="value"><%= link_to_homepage @project %>
+ </tr>
+
+ <tr>
+ <td class="text" colspan="2">
+ <%= simple_format @project.description %>
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/app/views/projects/_edit.html.erb b/app/views/projects/_edit.html.erb
new file mode 100644
index 0000000..0b5d409
--- /dev/null
+++ b/app/views/projects/_edit.html.erb
@@ -0,0 +1,58 @@
+<% html = @project.new_record? ? {:method => :post} : {:method => :put} %>
+
+<% form_for :project, @project, :url => project_path(@project), :html => html do |form| %>
+ <table class="edit">
+ <tbody>
+ <tr>
+ <td class="label">Project name</td>
+ <td class="value">
+ <%= form.text_field :name %>
+ <%= error_message_on(:project, :name) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Website</td>
+ <td class="value">
+ <%= form.text_field :url %>
+ <%= error_message_on(:project, :url) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Owner</td>
+ <td class="value">
+ <% if @user.privileges.admin_projects %>
+ <%= collection_select :project, :owner_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <% else %>
+ <% if @project.owner %>
+ <%= h @project.owner.display_name %>
+ <% else %>
+ UNOWNED
+ <% end %>
+ <% end %>
+ <%= error_message_on(:project, :owner) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Description</td>
+ <td class="value">
+ <%= form.text_area :description %>
+ <%= error_message_on(:project, :description) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <% if @project.new_record? %>
+ <%= submit_tag "Create", :class => 'button' %>
+ <% else %>
+ <%= submit_tag "Update", :class => 'button' %>
+ <% end %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<% end %>
\ No newline at end of file
diff --git a/app/views/projects/_product.html.erb b/app/views/projects/_product.html.erb
new file mode 100644
index 0000000..51766eb
--- /dev/null
+++ b/app/views/projects/_product.html.erb
@@ -0,0 +1,19 @@
+<tr>
+ <td><%= link_to "#{product.name}",
+ {:controller => :product, :action => :view, :id => product} %>
+ </td>
+ <td>
+ <% if product.owner != nil %>
+ <%= link_to(product.owner.display_name,
+ {:controller => :user,
+ :action => :view,
+ :id => product.owner})%>
+
+ (<%= mail_to(product.owner.email, "email") %>)
+ <% else %>
+ None (want to become the owner?)
+ <% end %>
+ </td>
+ <td class="description"><%= h product.description %></td>
+ <td class="number"><%= product.backlog.size %></td>
+</tr>
diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/edit.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
new file mode 100644
index 0000000..d4008f5
--- /dev/null
+++ b/app/views/projects/index.html.erb
@@ -0,0 +1,56 @@
+<div class="toolbar">
+ <% if @user && @user.create_projects? %>
+ <%= link_to "New Project", new_project_path %>
+ <% end %>
+</div>
+
+<%= will_paginate @projects %>
+
+<table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ <col class="actions" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th class="title" colspan="6">Projects</th>
+ </tr>
+ <tr>
+ <th>##</th>
+ <th>Name</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% if @projects.empty? %>
+ <tr><td colspan="6">There are no projects defined.</td></tr>
+ <% else %>
+ <% @projects.each_with_index do |project, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to project.name, project_path(project) %></td>
+ <td><%= get_first_sentence project.description %></td>
+ <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
+ <td><%= project.created_at.to_s(:date) %></td>
+ <td>
+ <%= link_to "View", project_path(project) %>
+ <% if project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(project) %>
+ <% end %>
+ </td>
+ </tr>
+
+ <% end %>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/new.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
new file mode 100644
index 0000000..8ab64f6
--- /dev/null
+++ b/app/views/projects/show.html.erb
@@ -0,0 +1,59 @@
+<div style="width: 100%; overflow: auto;">
+ <div id="details" style="float: left; width: 33%;">
+ <div class="toolbar">
+ <% if @project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(@project)%>
+ <% end %>
+ </div>
+ <%= render :partial => 'details', :object => @project %>
+ </div>
+
+ <div id="products" style="float: right; width: 66%;">
+ <div class="toolbar">
+ <%= will_paginate @products %>
+ <% if @project.can_create_products?(@user) %>
+ <%= link_to "Create Product...",
+ {
+ :controller => :product,
+ :action => :create,
+ :project => @project
+ } %>
+ <% end %>
+ </div>
+ <table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ </colgroup>
+
+ <thead>
+ <tr>
+ <th>##</th>
+ <th>Product</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @products.each_with_index do |product, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>"
+ onmouseover="this.className='hot'"
+ onmouseout="this.className='<%= row_class %>' ">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to product.name, :controller => :product, :action => :view, :id => product.id %></td>
+ <td><%= get_first_sentence product.description %></td>
+ <td><%= link_to_owner product.owner %></td>
+ <td><%= product.created_at.to_s(:date) %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/app/views/projects/update.html.erb b/app/views/projects/update.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/update.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb
index ae21641..ee1119f 100644
--- a/app/views/tasks/new.html.erb
+++ b/app/views/tasks/new.html.erb
@@ -1,5 +1,3 @@
<div>
- <% form_for(:task, @task, :url => task_path(@task)) do |form| %>
- <%= render :partial => 'edit', :object => @task %>
- <% end %>
+ <%= render(:partial => 'edit', :object => @project) %>
</div>
diff --git a/app/views/user_story/view.html.erb b/app/views/user_story/view.html.erb
index 6da6d51..d427871 100644
--- a/app/views/user_story/view.html.erb
+++ b/app/views/user_story/view.html.erb
@@ -2,8 +2,8 @@
<%= link_to_user_story @user_story,
:action => :modify,
:text => 'Edit' %>
- <%= link_to_product @user_story.product,
- :text => "Return to #{(a)user_story.product.name}"%>
+ <%= link_to "Return to #{(a)user_story.product.name}",
+ :controller => :product, :action => :view, :id => @user_story.product.id %>
</div>
<table class="details">
diff --git a/config/routes.rb b/config/routes.rb
index 2f66a65..c1f20fc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,11 +16,12 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :projects
map.resources :tasks
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
- map.root :controller => 'project'
+ map.root :controller => 'home'
# Login URL
map.login "/login", :controller => 'home', :action => 'login'
diff --git a/test/functional/product_controller_test.rb b/test/functional/product_controller_test.rb
index acca9e7..f008cbb 100644
--- a/test/functional/product_controller_test.rb
+++ b/test/functional/product_controller_test.rb
@@ -84,7 +84,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => users(:jdonuts).id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that someone with project admin rights but who isn't the project
@@ -95,7 +95,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => @non_project_owner.id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that calling create initially works as expected.
diff --git a/test/functional/project_controller_test.rb b/test/functional/project_controller_test.rb
deleted file mode 100644
index 347e795..0000000
--- a/test/functional/project_controller_test.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-# project_controller_test.rb
-# Copyright (C) 2008, 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'
-
-# +ProjectControllerTest+ performs tests on +ProjectController+ to ensure that
-# it works as expected.
-#
-class ProjectControllerTest < ActionController::TestCase
- fixtures :projects
- fixtures :users
-
- def setup
- @project = projects(:cairo)
- @admin = @project.owner
- @nonadmin = users(:jdonuts)
-
- raise "Admin and non-admin cannot be the same person." if @admin.id == @nonadmin.id
- end
-
- # Ensures that viewing the list of projects works as expected.
- #
- def test_index
- get :index
-
- assert_response :success
- assert assigns['projects']
- end
-
- # Ensures that viewing without a project id shows an error.
- #
- def test_view_without_id
- get :view
-
- assert_redirected_to error_path
- end
-
- # Ensures that viewing a project works as expected.
- #
- def test_view
- get :view, {:id => @project.id}
-
- assert_response :success
- assert_equal 2, assigns['products'].size,
- 'Did not load the set of products.'
- end
-
- # Ensures that an anonymous user cannot create a new project.
- #
- def test_create_for_anonymous_user
- get :create
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who does not have project admin rights cannot create a
- # new project.
- #
- def test_create_for_non_admin
- get :create,
- nil,
- {:user_id => @nonadmin.id}
-
- assert_redirected_to :controller => :home, :action => :dashboard
- end
-
- # Ensures that create works as expected.
- #
- def test_create
- get :create,
- {},
- {:user_id => @admin.id}
-
- assert_response :success
- assert assigns['project']
- assert assigns['users']
- end
-
- # Ensures the controller redisplays the edit form when the project is invalid.
- #
- def test_create_as_save_with_invalid_project
- post :create, {:project => {:owner_id => @admin.id}}, {:user_id => @admin.id}
-
- assert_response :success
- assert_template 'project/create'
- end
-
- # Ensures that saving a project works as expected.
- #
- def test_create_as_save
- project_details = {
- :name => 'project1',
- :owner_id => @admin.id,
- :url => 'http://www.project.org/',
- :description => 'This is a new project'
- }
-
- post :create,
- {:project => project_details},
- {:user_id => @admin.id}
-
- assert_redirected_to :action => :index
-
- result = Project.find_by_name(project_details[:name])
-
- assert result, 'Project was not saved.'
- end
-
- # Ensures that anonymous users cannot modify a project.
- #
- def test_modify_for_anonymous_user
- get :modify
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that modifying an invalid project results in an error.
- #
- def test_modify_for_invalid_project
- post :modify,
- {:id => 9999},
- {:user_id => @admin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that unauthorized users cannot modify a project.
- #
- def test_modify_for_unauthorized_user
- get :modify,
- {:id => @project.id},
- {:user_id => @nonadmin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that starting to modify a project works as expected.
- #
- def test_modify
- get :modify,
- {:id => @project.id},
- {:user_id => @project.owner_id}
-
- assert_response :success
- end
-
- # Ensures that modifying an existing project works as expected.
- #
- def test_modify_as_save
- project_details = {:name => 'newname1'}
-
- post :modify,
- {:id => @project.id, :project => project_details},
- {:user_id => @project.owner_id}
-
- assert_redirected_to :action => :view
-
- result = Project.find_by_id((a)project.id)
-
- assert_equal project_details[:name], result.name, 'Project was not modified.'
- end
-end
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
new file mode 100644
index 0000000..2e3e303
--- /dev/null
+++ b/test/functional/projects_controller_test.rb
@@ -0,0 +1,233 @@
+# projects_controller_test.rb
+# Copyright (C) 2008, 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'
+
+# +ProjectsControllerTest+ performs tests on +ProjectController+ to ensure that
+# it works as expected.
+#
+class ProjectsControllerTest < ActionController::TestCase
+ fixtures :projects
+ fixtures :users
+
+ def setup
+ @project = projects(:cairo)
+ @owner = @project.owner
+ @nonowner = users(:jdonuts)
+ raise "Owner and nonowner cannot be the same user!" if @owner.id == @nonowner.id
+
+ @nonadmin = @nonowner
+ @admin = users(:project_admin)
+ raise "Nonadmin and admin cannot be the same user!" if @nonadmin.id == @admin.id
+ end
+
+ # Ensures that showing the project index works as expected.
+ #
+ def test_index
+ get :index
+
+ assert_response :success
+ assert assigns['projects'], "Didn't load any projects."
+ end
+
+ # Ensures that showing a single project requires an id.
+ #
+ def test_show_without_id
+ get :show
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that showing a project fails on invalid ids.
+ #
+ def test_show_with_invalid_id
+ get :show, {:id => 9999}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures tha show a project works as expected.
+ #
+ def test_show
+ get :show, {:id => @project.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load the project."
+ assert_equal @project.id, assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a new project.
+ #
+ def test_new_as_anonymous
+ get :new
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user without project admin privileges cannot create a
+ # project.
+ #
+ def test_new_as_unauthorized_user
+ get :new, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that starting off a new project works as expected.
+ #
+ def test_new
+ get :new, {}, {:user_id => @admin.id}
+
+ assert_response :success
+ assert assigns['project'], "Did not create a new project."
+ assert_equal @admin.id, assigns['project'].owner_id,
+ "Did not assign the default owner."
+ assert assigns['users'], "Did not load the list of users."
+ end
+
+ # Ensures an anonymous user cannot start editing a project.
+ #
+ def test_edit_as_anonymous
+ get :edit
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that edit fails on an invalid id.
+ #
+ def test_edit_with_invalid_id
+ get :edit,
+ {:id => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonowner cannot edit a project.
+ #
+ def test_edit_as_nonowner
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_edit
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @owner.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load a project."
+ assert_equal @project.id,assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a project.
+ #
+ def test_create_as_anonymous
+ post :create
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a non-admin cannot create a project.
+ #
+ def test_create_as_nonadmin
+ post :create, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an invalid project returns the user to the edit page.
+ #
+ def test_create_with_invalid_project
+ post :create,
+ {:project => {}},
+ {:user_id => @admin.id}
+
+ assert_response :success
+ end
+
+ # Ensures that a project is created when one is submitted.
+ #
+ def test_create
+ post :create,
+ {:project => {:name => 'test', :owner_id => @admin.id}},
+ {:user_id => @admin.id}
+
+ assert_redirected_to project_path(assigns['project'])
+ end
+
+ # Ensures that an anonymous user cannot update a project.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a valid project id must be provided.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @admin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonadmin cannot update a project.
+ #
+ def test_update_by_nonowner
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal @project.name, result.name,
+ "Project should not have been updated!"
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_update
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @owner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal 'newname', result.name,
+ "Project should have been updated!"
+ end
+
+ # Ensures that the destroy method just exists.
+ #
+ def test_destroy
+ delete :destroy
+
+ assert_redirected_to error_url
+ end
+end
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Moving projects over to a RESTful interface.
by Darryl L. Pierce
Added a new resource named :projects.
Renamed ProjectController and ProjectControllerTest to ProjectsController
and ProjectsControllerTest, respectively.
Rewrote the functional tests to make sure they properly vetted the
refactored controller.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/application.rb | 2 +-
app/controllers/product_controller.rb | 2 +-
app/controllers/project_controller.rb | 102 ------------
app/controllers/projects_controller.rb | 142 ++++++++++++++++
app/helpers/application_helper.rb | 57 +++----
app/models/project.rb | 13 ++-
app/views/layouts/default.html.erb | 2 +-
app/views/product/_details.html.erb | 2 +-
app/views/project/_details.html.erb | 25 ---
app/views/project/_edit.html.erb | 59 -------
app/views/project/_product.html.erb | 19 ---
app/views/project/create.html.erb | 1 -
app/views/project/index.html.erb | 48 ------
app/views/project/modify.html.erb | 1 -
app/views/project/view.html.erb | 59 -------
app/views/projects/_details.html.erb | 25 +++
app/views/projects/_edit.html.erb | 58 +++++++
app/views/projects/_product.html.erb | 19 +++
app/views/projects/edit.html.erb | 1 +
app/views/projects/index.html.erb | 56 +++++++
app/views/projects/new.html.erb | 1 +
app/views/projects/show.html.erb | 59 +++++++
app/views/projects/update.html.erb | 1 +
app/views/tasks/new.html.erb | 4 +-
config/routes.rb | 3 +-
test/functional/product_controller_test.rb | 4 +-
test/functional/project_controller_test.rb | 176 --------------------
test/functional/projects_controller_test.rb | 233 +++++++++++++++++++++++++++
28 files changed, 640 insertions(+), 534 deletions(-)
delete mode 100644 app/controllers/project_controller.rb
create mode 100644 app/controllers/projects_controller.rb
delete mode 100644 app/views/project/_details.html.erb
delete mode 100644 app/views/project/_edit.html.erb
delete mode 100644 app/views/project/_product.html.erb
delete mode 100644 app/views/project/create.html.erb
delete mode 100644 app/views/project/index.html.erb
delete mode 100644 app/views/project/modify.html.erb
delete mode 100644 app/views/project/view.html.erb
create mode 100644 app/views/projects/_details.html.erb
create mode 100644 app/views/projects/_edit.html.erb
create mode 100644 app/views/projects/_product.html.erb
create mode 100644 app/views/projects/edit.html.erb
create mode 100644 app/views/projects/index.html.erb
create mode 100644 app/views/projects/new.html.erb
create mode 100644 app/views/projects/show.html.erb
create mode 100644 app/views/projects/update.html.erb
delete mode 100644 test/functional/project_controller_test.rb
create mode 100644 test/functional/projects_controller_test.rb
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 3378a90..51261a0 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -34,7 +34,7 @@ class ApplicationController < ActionController::Base
if @user == nil
session[:original_uri] = request.request_uri
flash[:message] = 'Please login first.'
- redirect_to(:controller => :home, :action => :login)
+ redirect_to login_url
end
end
diff --git a/app/controllers/product_controller.rb b/app/controllers/product_controller.rb
index f5553d3..43c6425 100644
--- a/app/controllers/product_controller.rb
+++ b/app/controllers/product_controller.rb
@@ -51,7 +51,7 @@ class ProductController < ApplicationController
end
else
flash[:message] = 'You are not authorized to create products.'
- redirect_to :controller => :project, :action => :view, :id => @project.id
+ redirect_to project_path(@project)
end
end
diff --git a/app/controllers/project_controller.rb b/app/controllers/project_controller.rb
deleted file mode 100644
index b122159..0000000
--- a/app/controllers/project_controller.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# project_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +ProjectController+ handles requests relating to the management and viewing of
-# +Product+s.
-#
-class ProjectController < ApplicationController
- before_filter :authenticated, :only => [:create, :modify]
- before_filter :load_project, :except => [:create, :index]
- before_filter :setup_for_edit, :only => [:create, :modify]
-
- # Allows viewing of all defined projects.
- #
- def index
- @projects = Project.paginate :page => params[:page], :per_page => 10
- end
-
- # Shows a single project.
- #
- def view
- @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
- end
-
- # Begins the process of creating a new project.
- #
- def create
- if @user.create_projects?
- if request.post?
- begin
- Project.transaction do
- @project = Project.new(params[:project])
- @project.save!
-
- redirect_to :action => :index
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :create
- end
- else
- @project = Project.new
- end
- else
- flash[:message] = 'That action requires administrative rights.'
- redirect_to :controller => :home, :action => :dashboard
- end
- end
-
- # Modifies an existing project.
- #
- def modify
- if @user.can_modify_project?(@project)
- if request.post?
- begin
- Project.transaction do
- @project.update_attributes(params[:project])
- @project.save!
-
- redirect_to :action => :view, :id => @project
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :modify
- end
- end
- else
- flash[:message] = "You are not authorized to modify that project."
- redirect_to error_path
- end
- end
-
- private
-
- # Loads the requested project.
- #
- def load_project
- @project = Project.find_by_id(params[:id])
-
- unless @project
- flash[:message] = 'Invalid project specified.'
- redirect_to error_path
- end
- end
-
- def setup_for_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
new file mode 100644
index 0000000..6c379b7
--- /dev/null
+++ b/app/controllers/projects_controller.rb
@@ -0,0 +1,142 @@
+# projects_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +ProjectsController+ handles requests relating to the management and viewing
+# of +Product+s.
+#
+class ProjectsController < ApplicationController
+ before_filter :authenticated, :only => [:new, :create, :edit, :update]
+ before_filter :load_project, :except => [:index, :new, :create, :destroy]
+ before_filter :setup_for_edit, :only => [:new, :create, :edit]
+
+ # GET /projects
+ def index
+ @projects = Project.paginate :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /projects/1
+ def show
+ @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # get /projects/new
+ def new
+ if @user.create_projects?
+ @project = Project.new(:owner_id => @user.id)
+ else
+ flash[:error] = "You are not authorized to create projects."
+
+ redirect_to error_url
+ end
+ end
+
+ # GET /projects/1;edit
+ def edit
+ unless @project.user_can_modify?(@user)
+ flash[:message] = "You are not authorized to modify that project."
+ redirect_to project_path(@project)
+ end
+ end
+
+ # POST /projects
+ def create
+ respond_to do |format|
+ if @user.create_projects?
+ @project = Project.new(params[:project])
+
+ begin
+ Project.transaction do
+ if @project.save
+ flash[:message] = "Project '#{(a)project.name}' created!"
+ format.html { redirect_to project_url(@project) }
+ else
+ @project.valid?
+ format.html { render :action => "new" }
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to create projects."
+ format.html { redirect_to error_url }
+ end
+ end
+ end
+
+ # PUT /projects/1
+ def update
+ respond_to do |format|
+ if @project.user_can_modify?(@user)
+ begin
+ Project.transaction do
+ @project.update_attributes(params[:project])
+ if @project.save!
+ flash[:message] = "Project was successfully updated."
+ format.html { redirect_to project_path(@project) }
+ else
+ @project.valid?
+ format.html { render :action => 'edit'}
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to edit this project."
+ format.html { redirect_to project_path(@project) }
+ end
+ end
+ end
+
+ # DELETE /projects/1
+ def destroy
+ respond_to do |format|
+ flash[:message] = "Project deletion is not current supported."
+ format.html { redirect_to error_url}
+ end
+ end
+
+ private
+
+ # Loads the requested project.
+ #
+ def load_project
+ @project = Project.find_by_id(params[:id])
+
+ unless @project
+ flash[:message] = 'Invalid project specified.'
+ respond_to do |format|
+ format.html { redirect_to error_path }
+ end
+ end
+ end
+
+ def setup_for_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 96d59a7..048b0c3 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,19 +1,19 @@
# application_helper.rb
# Copyright (C) 2008, 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/>.
-#
+#
module ApplicationHelper
# Returns the first sentence from the supplied text, up to the first period.
@@ -23,13 +23,13 @@ module ApplicationHelper
if (text == nil) || text.empty?
return " "
end
-
+
return text.slice(/^[^\.]*\.?/)
-
+
end
-
+
# Creates a link to a user.
- #
+ #
def link_to_user(user)
if user
@template.link_to(user.display_name,
@@ -38,9 +38,9 @@ module ApplicationHelper
:id => user)
end
end
-
+
# Creates a link to an owner for a project, product or other item.
- #
+ #
def link_to_owner(owner, args = nil)
if owner
link_to_user(owner)
@@ -49,61 +49,52 @@ module ApplicationHelper
args[:unowned] if args
end
end
-
- # Creates a link to a project.
- #
- def link_to_project(project)
- @template.link_to(project.name,
- :controller => :project,
- :action => :view,
- :id => project)
- end
-
+
# Creates a link to a product.
- #
- def link_to_product(product, options = {})
- @template.link_to(options[:text] ? options[:text] : product.name,
+ #
+ def link_to_product(product, args = nil)
+ @template.link_to(args[:text] ? args[:text] : product.name,
:controller => :product,
- :action => options[:action] ? options[:action] : :view,
+ :action => :view,
:id => product)
end
-
+
# Creates a link to a sprint.
- #
+ #
def link_to_sprint(sprint)
@template.link_to(sprint.title,
:controller => :sprint,
:action => :view,
:id => sprint)
end
-
+
# Displays a link and image reporting the health of the sprint
def show_sprint_health(sprint)
@template.image_tag(
sprint.healthy? ? "/images/icons/healthy.png" : "/images/icons/unhealthy.png",
:alt => sprint.healthy? ? "Sprint is healthy" : "Sprint is not healthy")
end
-
+
# Creates a link to a user story.
- #
+ #
def link_to_user_story(user_story, options ={})
@template.link_to((options[:text] ? options[:text] : user_story.title),
:controller => :user_story,
:action => (options[:action] ? options[:action] : :view),
:id => user_story)
end
-
+
# Creates a link to a backlog item.
- #
+ #
def link_to_backlog_item(backlog_item, options = {})
@template.link_to(options[:text] ? options[:text] : backlog_item.title,
:controller => :backlog,
:action => (options[:action] ? options[:action] : :view),
:id => backlog_item.id)
end
-
+
# Shows the hours for a given object as estimated, actual and remaining.
- #
+ #
def show_hours_as_ear(estimated)
format("%0.1f/%0.1f/%0.1f",estimated.estimated_hours, estimated.actual_hours, estimated.remaining_hours)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2bd9541..6a7ce5d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -21,14 +21,25 @@
class Project < ActiveRecord::Base
validates_presence_of :name,
:message => 'A project has to have a name.'
-
+ validates_length_of :name,
+ :minimum => 4,
+ :message => 'The project name must be at least four characters long.'
validates_uniqueness_of :name,
:message => 'This name is used by an existing project.'
+ validates_presence_of :owner_id,
+ :message => 'A project must have an owner.'
+
belongs_to :owner, :class_name => 'User', :foreign_key => :owner_id
has_many :products
+ # Returns whether the user can modify this project.
+ #
+ def user_can_modify?(user)
+ user && (user.privileges.admin_projects || (user.id == owner.id))
+ end
+
# Returns whether the user can create products for this project.
#
def can_create_products?(user)
diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb
index ac4dd6f..e1ac558 100644
--- a/app/views/layouts/default.html.erb
+++ b/app/views/layouts/default.html.erb
@@ -24,7 +24,7 @@
<div id="user-box">
<%= link_to "Home", :controller => :home %>
- <%= link_to "Projects", :controller => :project, :action => :index %>
+ <%= link_to "Projects", projects_path %>
<%= link_to "Reports", :controller => :report, :action => :index %>
<% if @user %>
<%= link_to @user.display_name, dashboard_url %>
diff --git a/app/views/product/_details.html.erb b/app/views/product/_details.html.erb
index d7e1bda..7ea0794 100644
--- a/app/views/product/_details.html.erb
+++ b/app/views/product/_details.html.erb
@@ -14,7 +14,7 @@
<tr>
<td class="label">For Project:</td>
- <td class="value"><%= link_to_project @product.project %></td>
+ <td class="value"><%= link_to @product.project.name, project_path((a)product.project) %></td>
</tr>
<tr>
diff --git a/app/views/project/_details.html.erb b/app/views/project/_details.html.erb
deleted file mode 100644
index 7dfc64c..0000000
--- a/app/views/project/_details.html.erb
+++ /dev/null
@@ -1,25 +0,0 @@
-<table class="details">
- <thead>
- <tr>
- <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td class="label">Owner:</td>
- <td class="value"><%= link_to_user @project.owner %></td>
- </tr>
-
- <tr>
- <td class="label">Homepage:</td>
- <td class="value"><%= link_to_homepage @project %>
- </tr>
-
- <tr>
- <td class="text" colspan="2">
- <%= simple_format @project.description %>
- </td>
- </tr>
- </tbody>
-</table>
diff --git a/app/views/project/_edit.html.erb b/app/views/project/_edit.html.erb
deleted file mode 100644
index d1b02e9..0000000
--- a/app/views/project/_edit.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<% form_for :project do |form| %>
-
- <% unless @project.new_record? %>
- <%= form.hidden_field :id %>
- <% end %>
-
- <table class="edit">
- <tbody>
- <tr>
- <td class="label">Project name</td>
- <td class="value"><%= form.text_field :name %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :name) %>
- </tr>
-
- <tr>
- <td class="label">Website</td>
- <td class="value"><%= form.text_field :url %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :url) %>
- </tr>
-
- <tr>
- <td class="label">Owner</td>
- <td class="value">
- <% if @user.privileges.admin_projects %>
- <%= collection_select :project, :owner_id, @users, :id,
- :display_name, {:include_blank => true} %>
- <% else %>
- <% if @project.owner %>
- <%= h @project.owner.display_name %>
- <% else %>
- UNOWNED
- <% end %>
- <% end %>
- </td>
- <td /><td class="errors"><%= error_message_on(:project, :owner) %>
- </tr>
-
- <tr>
- <td class="label">Description</td>
- <td class="value"><%= form.text_area :description %></td>
- </tr>
-
- <tr>
- <td class="buttons" colspan="2">
- <% if @project.new_record? %>
- <%= submit_tag "Create", :class => 'button' %>
- <% else %>
- <%= submit_tag "Update", :class => 'button' %>
- <% end %>
- </td>
- </tr>
- </tbody>
- </table>
-
-<% end %>
\ No newline at end of file
diff --git a/app/views/project/_product.html.erb b/app/views/project/_product.html.erb
deleted file mode 100644
index 51766eb..0000000
--- a/app/views/project/_product.html.erb
+++ /dev/null
@@ -1,19 +0,0 @@
-<tr>
- <td><%= link_to "#{product.name}",
- {:controller => :product, :action => :view, :id => product} %>
- </td>
- <td>
- <% if product.owner != nil %>
- <%= link_to(product.owner.display_name,
- {:controller => :user,
- :action => :view,
- :id => product.owner})%>
-
- (<%= mail_to(product.owner.email, "email") %>)
- <% else %>
- None (want to become the owner?)
- <% end %>
- </td>
- <td class="description"><%= h product.description %></td>
- <td class="number"><%= product.backlog.size %></td>
-</tr>
diff --git a/app/views/project/create.html.erb b/app/views/project/create.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/create.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/index.html.erb b/app/views/project/index.html.erb
deleted file mode 100644
index d472b78..0000000
--- a/app/views/project/index.html.erb
+++ /dev/null
@@ -1,48 +0,0 @@
-<div class="toolbar">
- <% if @user && @user.create_projects? %>
- <%= link_to "New Project", :action => :create %>
- <% end %>
-</div>
-
-<%= will_paginate @projects %>
-
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
- <thead>
- <tr>
- <th class="title" colspan="5">Projects</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Name</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% if @projects.empty? %>
- <tr><td colspan="5">There are no projects defined.</td></tr>
- <% else %>
- <% @projects.each_with_index do |project, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_project project %></td>
- <td><%= get_first_sentence project.description %></td>
- <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
- <td><%= project.created_at.to_s(:date) %></td>
- </tr>
-
- <% end %>
- <% end %>
- </tbody>
-</table>
diff --git a/app/views/project/modify.html.erb b/app/views/project/modify.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/modify.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/view.html.erb b/app/views/project/view.html.erb
deleted file mode 100644
index c501d67..0000000
--- a/app/views/project/view.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<div style="width: 100%; overflow: auto;">
- <div id="details" style="float: left; width: 33%;">
- <div class="toolbar">
- <% if @user && @user.can_modify_project?(@project) %>
- <%= link_to "Edit project...", :action => :modify, :id => @project %>
- <% end %>
- </div>
- <%= render :partial => 'details', :object => @project %>
- </div>
-
- <div id="products" style="float: right; width: 66%;">
- <div class="toolbar">
- <%= will_paginate @products %>
- <% if @project.can_create_products?(@user) %>
- <%= link_to "Create Product...",
- {
- :controller => :product,
- :action => :create,
- :project => @project
- } %>
- <% end %>
- </div>
- <table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
-
- <thead>
- <tr>
- <th>##</th>
- <th>Product</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% @products.each_with_index do |product, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>"
- onmouseover="this.className='hot'"
- onmouseout="this.className='<%= row_class %>' ">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_product product %></td>
- <td><%= get_first_sentence product.description %></td>
- <td><%= link_to_owner product.owner %></td>
- <td><%= product.created_at.to_s(:date) %></td>
- </tr>
- <% end %>
- </tbody>
- </table>
- </div>
-</div>
diff --git a/app/views/projects/_details.html.erb b/app/views/projects/_details.html.erb
new file mode 100644
index 0000000..9fe7b4b
--- /dev/null
+++ b/app/views/projects/_details.html.erb
@@ -0,0 +1,25 @@
+<table class="details">
+ <thead>
+ <tr>
+ <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td class="label">Owner:</td>
+ <td class="value"><%= link_to_user @project.owner %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Homepage:</td>
+ <td class="value"><%= link_to_homepage @project %>
+ </tr>
+
+ <tr>
+ <td class="text" colspan="2">
+ <%= simple_format @project.description %>
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/app/views/projects/_edit.html.erb b/app/views/projects/_edit.html.erb
new file mode 100644
index 0000000..0b5d409
--- /dev/null
+++ b/app/views/projects/_edit.html.erb
@@ -0,0 +1,58 @@
+<% html = @project.new_record? ? {:method => :post} : {:method => :put} %>
+
+<% form_for :project, @project, :url => project_path(@project), :html => html do |form| %>
+ <table class="edit">
+ <tbody>
+ <tr>
+ <td class="label">Project name</td>
+ <td class="value">
+ <%= form.text_field :name %>
+ <%= error_message_on(:project, :name) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Website</td>
+ <td class="value">
+ <%= form.text_field :url %>
+ <%= error_message_on(:project, :url) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Owner</td>
+ <td class="value">
+ <% if @user.privileges.admin_projects %>
+ <%= collection_select :project, :owner_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <% else %>
+ <% if @project.owner %>
+ <%= h @project.owner.display_name %>
+ <% else %>
+ UNOWNED
+ <% end %>
+ <% end %>
+ <%= error_message_on(:project, :owner) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Description</td>
+ <td class="value">
+ <%= form.text_area :description %>
+ <%= error_message_on(:project, :description) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <% if @project.new_record? %>
+ <%= submit_tag "Create", :class => 'button' %>
+ <% else %>
+ <%= submit_tag "Update", :class => 'button' %>
+ <% end %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<% end %>
\ No newline at end of file
diff --git a/app/views/projects/_product.html.erb b/app/views/projects/_product.html.erb
new file mode 100644
index 0000000..51766eb
--- /dev/null
+++ b/app/views/projects/_product.html.erb
@@ -0,0 +1,19 @@
+<tr>
+ <td><%= link_to "#{product.name}",
+ {:controller => :product, :action => :view, :id => product} %>
+ </td>
+ <td>
+ <% if product.owner != nil %>
+ <%= link_to(product.owner.display_name,
+ {:controller => :user,
+ :action => :view,
+ :id => product.owner})%>
+
+ (<%= mail_to(product.owner.email, "email") %>)
+ <% else %>
+ None (want to become the owner?)
+ <% end %>
+ </td>
+ <td class="description"><%= h product.description %></td>
+ <td class="number"><%= product.backlog.size %></td>
+</tr>
diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/edit.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
new file mode 100644
index 0000000..d4008f5
--- /dev/null
+++ b/app/views/projects/index.html.erb
@@ -0,0 +1,56 @@
+<div class="toolbar">
+ <% if @user && @user.create_projects? %>
+ <%= link_to "New Project", new_project_path %>
+ <% end %>
+</div>
+
+<%= will_paginate @projects %>
+
+<table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ <col class="actions" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th class="title" colspan="6">Projects</th>
+ </tr>
+ <tr>
+ <th>##</th>
+ <th>Name</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% if @projects.empty? %>
+ <tr><td colspan="6">There are no projects defined.</td></tr>
+ <% else %>
+ <% @projects.each_with_index do |project, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to project.name, project_path(project) %></td>
+ <td><%= get_first_sentence project.description %></td>
+ <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
+ <td><%= project.created_at.to_s(:date) %></td>
+ <td>
+ <%= link_to "View", project_path(project) %>
+ <% if project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(project) %>
+ <% end %>
+ </td>
+ </tr>
+
+ <% end %>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/new.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
new file mode 100644
index 0000000..f8fe791
--- /dev/null
+++ b/app/views/projects/show.html.erb
@@ -0,0 +1,59 @@
+<div style="width: 100%; overflow: auto;">
+ <div id="details" style="float: left; width: 33%;">
+ <div class="toolbar">
+ <% if @project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(@project)%>
+ <% end %>
+ </div>
+ <%= render :partial => 'details', :object => @project %>
+ </div>
+
+ <div id="products" style="float: right; width: 66%;">
+ <div class="toolbar">
+ <%= will_paginate @products %>
+ <% if @project.can_create_products?(@user) %>
+ <%= link_to "Create Product...",
+ {
+ :controller => :product,
+ :action => :create,
+ :project => @project
+ } %>
+ <% end %>
+ </div>
+ <table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ </colgroup>
+
+ <thead>
+ <tr>
+ <th>##</th>
+ <th>Product</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @products.each_with_index do |product, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>"
+ onmouseover="this.className='hot'"
+ onmouseout="this.className='<%= row_class %>' ">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to_product product %></td>
+ <td><%= get_first_sentence product.description %></td>
+ <td><%= link_to_owner product.owner %></td>
+ <td><%= product.created_at.to_s(:date) %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/app/views/projects/update.html.erb b/app/views/projects/update.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/update.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb
index ae21641..ee1119f 100644
--- a/app/views/tasks/new.html.erb
+++ b/app/views/tasks/new.html.erb
@@ -1,5 +1,3 @@
<div>
- <% form_for(:task, @task, :url => task_path(@task)) do |form| %>
- <%= render :partial => 'edit', :object => @task %>
- <% end %>
+ <%= render(:partial => 'edit', :object => @project) %>
</div>
diff --git a/config/routes.rb b/config/routes.rb
index 2f66a65..c1f20fc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,11 +16,12 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :projects
map.resources :tasks
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
- map.root :controller => 'project'
+ map.root :controller => 'home'
# Login URL
map.login "/login", :controller => 'home', :action => 'login'
diff --git a/test/functional/product_controller_test.rb b/test/functional/product_controller_test.rb
index acca9e7..f008cbb 100644
--- a/test/functional/product_controller_test.rb
+++ b/test/functional/product_controller_test.rb
@@ -84,7 +84,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => users(:jdonuts).id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that someone with project admin rights but who isn't the project
@@ -95,7 +95,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => @non_project_owner.id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that calling create initially works as expected.
diff --git a/test/functional/project_controller_test.rb b/test/functional/project_controller_test.rb
deleted file mode 100644
index 347e795..0000000
--- a/test/functional/project_controller_test.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-# project_controller_test.rb
-# Copyright (C) 2008, 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'
-
-# +ProjectControllerTest+ performs tests on +ProjectController+ to ensure that
-# it works as expected.
-#
-class ProjectControllerTest < ActionController::TestCase
- fixtures :projects
- fixtures :users
-
- def setup
- @project = projects(:cairo)
- @admin = @project.owner
- @nonadmin = users(:jdonuts)
-
- raise "Admin and non-admin cannot be the same person." if @admin.id == @nonadmin.id
- end
-
- # Ensures that viewing the list of projects works as expected.
- #
- def test_index
- get :index
-
- assert_response :success
- assert assigns['projects']
- end
-
- # Ensures that viewing without a project id shows an error.
- #
- def test_view_without_id
- get :view
-
- assert_redirected_to error_path
- end
-
- # Ensures that viewing a project works as expected.
- #
- def test_view
- get :view, {:id => @project.id}
-
- assert_response :success
- assert_equal 2, assigns['products'].size,
- 'Did not load the set of products.'
- end
-
- # Ensures that an anonymous user cannot create a new project.
- #
- def test_create_for_anonymous_user
- get :create
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who does not have project admin rights cannot create a
- # new project.
- #
- def test_create_for_non_admin
- get :create,
- nil,
- {:user_id => @nonadmin.id}
-
- assert_redirected_to :controller => :home, :action => :dashboard
- end
-
- # Ensures that create works as expected.
- #
- def test_create
- get :create,
- {},
- {:user_id => @admin.id}
-
- assert_response :success
- assert assigns['project']
- assert assigns['users']
- end
-
- # Ensures the controller redisplays the edit form when the project is invalid.
- #
- def test_create_as_save_with_invalid_project
- post :create, {:project => {:owner_id => @admin.id}}, {:user_id => @admin.id}
-
- assert_response :success
- assert_template 'project/create'
- end
-
- # Ensures that saving a project works as expected.
- #
- def test_create_as_save
- project_details = {
- :name => 'project1',
- :owner_id => @admin.id,
- :url => 'http://www.project.org/',
- :description => 'This is a new project'
- }
-
- post :create,
- {:project => project_details},
- {:user_id => @admin.id}
-
- assert_redirected_to :action => :index
-
- result = Project.find_by_name(project_details[:name])
-
- assert result, 'Project was not saved.'
- end
-
- # Ensures that anonymous users cannot modify a project.
- #
- def test_modify_for_anonymous_user
- get :modify
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that modifying an invalid project results in an error.
- #
- def test_modify_for_invalid_project
- post :modify,
- {:id => 9999},
- {:user_id => @admin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that unauthorized users cannot modify a project.
- #
- def test_modify_for_unauthorized_user
- get :modify,
- {:id => @project.id},
- {:user_id => @nonadmin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that starting to modify a project works as expected.
- #
- def test_modify
- get :modify,
- {:id => @project.id},
- {:user_id => @project.owner_id}
-
- assert_response :success
- end
-
- # Ensures that modifying an existing project works as expected.
- #
- def test_modify_as_save
- project_details = {:name => 'newname1'}
-
- post :modify,
- {:id => @project.id, :project => project_details},
- {:user_id => @project.owner_id}
-
- assert_redirected_to :action => :view
-
- result = Project.find_by_id((a)project.id)
-
- assert_equal project_details[:name], result.name, 'Project was not modified.'
- end
-end
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
new file mode 100644
index 0000000..2e3e303
--- /dev/null
+++ b/test/functional/projects_controller_test.rb
@@ -0,0 +1,233 @@
+# projects_controller_test.rb
+# Copyright (C) 2008, 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'
+
+# +ProjectsControllerTest+ performs tests on +ProjectController+ to ensure that
+# it works as expected.
+#
+class ProjectsControllerTest < ActionController::TestCase
+ fixtures :projects
+ fixtures :users
+
+ def setup
+ @project = projects(:cairo)
+ @owner = @project.owner
+ @nonowner = users(:jdonuts)
+ raise "Owner and nonowner cannot be the same user!" if @owner.id == @nonowner.id
+
+ @nonadmin = @nonowner
+ @admin = users(:project_admin)
+ raise "Nonadmin and admin cannot be the same user!" if @nonadmin.id == @admin.id
+ end
+
+ # Ensures that showing the project index works as expected.
+ #
+ def test_index
+ get :index
+
+ assert_response :success
+ assert assigns['projects'], "Didn't load any projects."
+ end
+
+ # Ensures that showing a single project requires an id.
+ #
+ def test_show_without_id
+ get :show
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that showing a project fails on invalid ids.
+ #
+ def test_show_with_invalid_id
+ get :show, {:id => 9999}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures tha show a project works as expected.
+ #
+ def test_show
+ get :show, {:id => @project.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load the project."
+ assert_equal @project.id, assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a new project.
+ #
+ def test_new_as_anonymous
+ get :new
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user without project admin privileges cannot create a
+ # project.
+ #
+ def test_new_as_unauthorized_user
+ get :new, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that starting off a new project works as expected.
+ #
+ def test_new
+ get :new, {}, {:user_id => @admin.id}
+
+ assert_response :success
+ assert assigns['project'], "Did not create a new project."
+ assert_equal @admin.id, assigns['project'].owner_id,
+ "Did not assign the default owner."
+ assert assigns['users'], "Did not load the list of users."
+ end
+
+ # Ensures an anonymous user cannot start editing a project.
+ #
+ def test_edit_as_anonymous
+ get :edit
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that edit fails on an invalid id.
+ #
+ def test_edit_with_invalid_id
+ get :edit,
+ {:id => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonowner cannot edit a project.
+ #
+ def test_edit_as_nonowner
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_edit
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @owner.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load a project."
+ assert_equal @project.id,assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a project.
+ #
+ def test_create_as_anonymous
+ post :create
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a non-admin cannot create a project.
+ #
+ def test_create_as_nonadmin
+ post :create, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an invalid project returns the user to the edit page.
+ #
+ def test_create_with_invalid_project
+ post :create,
+ {:project => {}},
+ {:user_id => @admin.id}
+
+ assert_response :success
+ end
+
+ # Ensures that a project is created when one is submitted.
+ #
+ def test_create
+ post :create,
+ {:project => {:name => 'test', :owner_id => @admin.id}},
+ {:user_id => @admin.id}
+
+ assert_redirected_to project_path(assigns['project'])
+ end
+
+ # Ensures that an anonymous user cannot update a project.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a valid project id must be provided.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @admin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonadmin cannot update a project.
+ #
+ def test_update_by_nonowner
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal @project.name, result.name,
+ "Project should not have been updated!"
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_update
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @owner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal 'newname', result.name,
+ "Project should have been updated!"
+ end
+
+ # Ensures that the destroy method just exists.
+ #
+ def test_destroy
+ delete :destroy
+
+ assert_redirected_to error_url
+ end
+end
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Moving projects over to a RESTful interface.
by Darryl L. Pierce
NOTE: This patch fixes the bug found when viewing a product.
Added a new resource named :projects.
Renamed ProjectController and ProjectControllerTest to ProjectsController
and ProjectsControllerTest, respectively.
Rewrote the functional tests to make sure they properly vetted the
refactored controller.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/application.rb | 2 +-
app/controllers/product_controller.rb | 2 +-
app/controllers/project_controller.rb | 102 ------------
app/controllers/projects_controller.rb | 142 ++++++++++++++++
app/helpers/application_helper.rb | 57 +++----
app/models/project.rb | 13 ++-
app/views/layouts/default.html.erb | 2 +-
app/views/product/_details.html.erb | 2 +-
app/views/project/_details.html.erb | 25 ---
app/views/project/_edit.html.erb | 59 -------
app/views/project/_product.html.erb | 19 ---
app/views/project/create.html.erb | 1 -
app/views/project/index.html.erb | 48 ------
app/views/project/modify.html.erb | 1 -
app/views/project/view.html.erb | 59 -------
app/views/projects/_details.html.erb | 25 +++
app/views/projects/_edit.html.erb | 58 +++++++
app/views/projects/_product.html.erb | 19 +++
app/views/projects/edit.html.erb | 1 +
app/views/projects/index.html.erb | 56 +++++++
app/views/projects/new.html.erb | 1 +
app/views/projects/show.html.erb | 59 +++++++
app/views/projects/update.html.erb | 1 +
app/views/tasks/new.html.erb | 4 +-
config/routes.rb | 3 +-
test/functional/product_controller_test.rb | 4 +-
test/functional/project_controller_test.rb | 176 --------------------
test/functional/projects_controller_test.rb | 233 +++++++++++++++++++++++++++
28 files changed, 640 insertions(+), 534 deletions(-)
delete mode 100644 app/controllers/project_controller.rb
create mode 100644 app/controllers/projects_controller.rb
delete mode 100644 app/views/project/_details.html.erb
delete mode 100644 app/views/project/_edit.html.erb
delete mode 100644 app/views/project/_product.html.erb
delete mode 100644 app/views/project/create.html.erb
delete mode 100644 app/views/project/index.html.erb
delete mode 100644 app/views/project/modify.html.erb
delete mode 100644 app/views/project/view.html.erb
create mode 100644 app/views/projects/_details.html.erb
create mode 100644 app/views/projects/_edit.html.erb
create mode 100644 app/views/projects/_product.html.erb
create mode 100644 app/views/projects/edit.html.erb
create mode 100644 app/views/projects/index.html.erb
create mode 100644 app/views/projects/new.html.erb
create mode 100644 app/views/projects/show.html.erb
create mode 100644 app/views/projects/update.html.erb
delete mode 100644 test/functional/project_controller_test.rb
create mode 100644 test/functional/projects_controller_test.rb
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 3378a90..51261a0 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -34,7 +34,7 @@ class ApplicationController < ActionController::Base
if @user == nil
session[:original_uri] = request.request_uri
flash[:message] = 'Please login first.'
- redirect_to(:controller => :home, :action => :login)
+ redirect_to login_url
end
end
diff --git a/app/controllers/product_controller.rb b/app/controllers/product_controller.rb
index f5553d3..43c6425 100644
--- a/app/controllers/product_controller.rb
+++ b/app/controllers/product_controller.rb
@@ -51,7 +51,7 @@ class ProductController < ApplicationController
end
else
flash[:message] = 'You are not authorized to create products.'
- redirect_to :controller => :project, :action => :view, :id => @project.id
+ redirect_to project_path(@project)
end
end
diff --git a/app/controllers/project_controller.rb b/app/controllers/project_controller.rb
deleted file mode 100644
index b122159..0000000
--- a/app/controllers/project_controller.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# project_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +ProjectController+ handles requests relating to the management and viewing of
-# +Product+s.
-#
-class ProjectController < ApplicationController
- before_filter :authenticated, :only => [:create, :modify]
- before_filter :load_project, :except => [:create, :index]
- before_filter :setup_for_edit, :only => [:create, :modify]
-
- # Allows viewing of all defined projects.
- #
- def index
- @projects = Project.paginate :page => params[:page], :per_page => 10
- end
-
- # Shows a single project.
- #
- def view
- @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
- end
-
- # Begins the process of creating a new project.
- #
- def create
- if @user.create_projects?
- if request.post?
- begin
- Project.transaction do
- @project = Project.new(params[:project])
- @project.save!
-
- redirect_to :action => :index
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :create
- end
- else
- @project = Project.new
- end
- else
- flash[:message] = 'That action requires administrative rights.'
- redirect_to :controller => :home, :action => :dashboard
- end
- end
-
- # Modifies an existing project.
- #
- def modify
- if @user.can_modify_project?(@project)
- if request.post?
- begin
- Project.transaction do
- @project.update_attributes(params[:project])
- @project.save!
-
- redirect_to :action => :view, :id => @project
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :modify
- end
- end
- else
- flash[:message] = "You are not authorized to modify that project."
- redirect_to error_path
- end
- end
-
- private
-
- # Loads the requested project.
- #
- def load_project
- @project = Project.find_by_id(params[:id])
-
- unless @project
- flash[:message] = 'Invalid project specified.'
- redirect_to error_path
- end
- end
-
- def setup_for_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
new file mode 100644
index 0000000..6c379b7
--- /dev/null
+++ b/app/controllers/projects_controller.rb
@@ -0,0 +1,142 @@
+# projects_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +ProjectsController+ handles requests relating to the management and viewing
+# of +Product+s.
+#
+class ProjectsController < ApplicationController
+ before_filter :authenticated, :only => [:new, :create, :edit, :update]
+ before_filter :load_project, :except => [:index, :new, :create, :destroy]
+ before_filter :setup_for_edit, :only => [:new, :create, :edit]
+
+ # GET /projects
+ def index
+ @projects = Project.paginate :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /projects/1
+ def show
+ @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # get /projects/new
+ def new
+ if @user.create_projects?
+ @project = Project.new(:owner_id => @user.id)
+ else
+ flash[:error] = "You are not authorized to create projects."
+
+ redirect_to error_url
+ end
+ end
+
+ # GET /projects/1;edit
+ def edit
+ unless @project.user_can_modify?(@user)
+ flash[:message] = "You are not authorized to modify that project."
+ redirect_to project_path(@project)
+ end
+ end
+
+ # POST /projects
+ def create
+ respond_to do |format|
+ if @user.create_projects?
+ @project = Project.new(params[:project])
+
+ begin
+ Project.transaction do
+ if @project.save
+ flash[:message] = "Project '#{(a)project.name}' created!"
+ format.html { redirect_to project_url(@project) }
+ else
+ @project.valid?
+ format.html { render :action => "new" }
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to create projects."
+ format.html { redirect_to error_url }
+ end
+ end
+ end
+
+ # PUT /projects/1
+ def update
+ respond_to do |format|
+ if @project.user_can_modify?(@user)
+ begin
+ Project.transaction do
+ @project.update_attributes(params[:project])
+ if @project.save!
+ flash[:message] = "Project was successfully updated."
+ format.html { redirect_to project_path(@project) }
+ else
+ @project.valid?
+ format.html { render :action => 'edit'}
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to edit this project."
+ format.html { redirect_to project_path(@project) }
+ end
+ end
+ end
+
+ # DELETE /projects/1
+ def destroy
+ respond_to do |format|
+ flash[:message] = "Project deletion is not current supported."
+ format.html { redirect_to error_url}
+ end
+ end
+
+ private
+
+ # Loads the requested project.
+ #
+ def load_project
+ @project = Project.find_by_id(params[:id])
+
+ unless @project
+ flash[:message] = 'Invalid project specified.'
+ respond_to do |format|
+ format.html { redirect_to error_path }
+ end
+ end
+ end
+
+ def setup_for_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 96d59a7..88cd918 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,19 +1,19 @@
# application_helper.rb
# Copyright (C) 2008, 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/>.
-#
+#
module ApplicationHelper
# Returns the first sentence from the supplied text, up to the first period.
@@ -23,13 +23,13 @@ module ApplicationHelper
if (text == nil) || text.empty?
return " "
end
-
+
return text.slice(/^[^\.]*\.?/)
-
+
end
-
+
# Creates a link to a user.
- #
+ #
def link_to_user(user)
if user
@template.link_to(user.display_name,
@@ -38,9 +38,9 @@ module ApplicationHelper
:id => user)
end
end
-
+
# Creates a link to an owner for a project, product or other item.
- #
+ #
def link_to_owner(owner, args = nil)
if owner
link_to_user(owner)
@@ -49,61 +49,52 @@ module ApplicationHelper
args[:unowned] if args
end
end
-
- # Creates a link to a project.
- #
- def link_to_project(project)
- @template.link_to(project.name,
- :controller => :project,
- :action => :view,
- :id => project)
- end
-
+
# Creates a link to a product.
- #
- def link_to_product(product, options = {})
- @template.link_to(options[:text] ? options[:text] : product.name,
+ #
+ def link_to_product(product)
+ @template.link_to(product.name,
:controller => :product,
- :action => options[:action] ? options[:action] : :view,
+ :action => :view,
:id => product)
end
-
+
# Creates a link to a sprint.
- #
+ #
def link_to_sprint(sprint)
@template.link_to(sprint.title,
:controller => :sprint,
:action => :view,
:id => sprint)
end
-
+
# Displays a link and image reporting the health of the sprint
def show_sprint_health(sprint)
@template.image_tag(
sprint.healthy? ? "/images/icons/healthy.png" : "/images/icons/unhealthy.png",
:alt => sprint.healthy? ? "Sprint is healthy" : "Sprint is not healthy")
end
-
+
# Creates a link to a user story.
- #
+ #
def link_to_user_story(user_story, options ={})
@template.link_to((options[:text] ? options[:text] : user_story.title),
:controller => :user_story,
:action => (options[:action] ? options[:action] : :view),
:id => user_story)
end
-
+
# Creates a link to a backlog item.
- #
+ #
def link_to_backlog_item(backlog_item, options = {})
@template.link_to(options[:text] ? options[:text] : backlog_item.title,
:controller => :backlog,
:action => (options[:action] ? options[:action] : :view),
:id => backlog_item.id)
end
-
+
# Shows the hours for a given object as estimated, actual and remaining.
- #
+ #
def show_hours_as_ear(estimated)
format("%0.1f/%0.1f/%0.1f",estimated.estimated_hours, estimated.actual_hours, estimated.remaining_hours)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2bd9541..6a7ce5d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -21,14 +21,25 @@
class Project < ActiveRecord::Base
validates_presence_of :name,
:message => 'A project has to have a name.'
-
+ validates_length_of :name,
+ :minimum => 4,
+ :message => 'The project name must be at least four characters long.'
validates_uniqueness_of :name,
:message => 'This name is used by an existing project.'
+ validates_presence_of :owner_id,
+ :message => 'A project must have an owner.'
+
belongs_to :owner, :class_name => 'User', :foreign_key => :owner_id
has_many :products
+ # Returns whether the user can modify this project.
+ #
+ def user_can_modify?(user)
+ user && (user.privileges.admin_projects || (user.id == owner.id))
+ end
+
# Returns whether the user can create products for this project.
#
def can_create_products?(user)
diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb
index ac4dd6f..e1ac558 100644
--- a/app/views/layouts/default.html.erb
+++ b/app/views/layouts/default.html.erb
@@ -24,7 +24,7 @@
<div id="user-box">
<%= link_to "Home", :controller => :home %>
- <%= link_to "Projects", :controller => :project, :action => :index %>
+ <%= link_to "Projects", projects_path %>
<%= link_to "Reports", :controller => :report, :action => :index %>
<% if @user %>
<%= link_to @user.display_name, dashboard_url %>
diff --git a/app/views/product/_details.html.erb b/app/views/product/_details.html.erb
index d7e1bda..7ea0794 100644
--- a/app/views/product/_details.html.erb
+++ b/app/views/product/_details.html.erb
@@ -14,7 +14,7 @@
<tr>
<td class="label">For Project:</td>
- <td class="value"><%= link_to_project @product.project %></td>
+ <td class="value"><%= link_to @product.project.name, project_path((a)product.project) %></td>
</tr>
<tr>
diff --git a/app/views/project/_details.html.erb b/app/views/project/_details.html.erb
deleted file mode 100644
index 7dfc64c..0000000
--- a/app/views/project/_details.html.erb
+++ /dev/null
@@ -1,25 +0,0 @@
-<table class="details">
- <thead>
- <tr>
- <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td class="label">Owner:</td>
- <td class="value"><%= link_to_user @project.owner %></td>
- </tr>
-
- <tr>
- <td class="label">Homepage:</td>
- <td class="value"><%= link_to_homepage @project %>
- </tr>
-
- <tr>
- <td class="text" colspan="2">
- <%= simple_format @project.description %>
- </td>
- </tr>
- </tbody>
-</table>
diff --git a/app/views/project/_edit.html.erb b/app/views/project/_edit.html.erb
deleted file mode 100644
index d1b02e9..0000000
--- a/app/views/project/_edit.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<% form_for :project do |form| %>
-
- <% unless @project.new_record? %>
- <%= form.hidden_field :id %>
- <% end %>
-
- <table class="edit">
- <tbody>
- <tr>
- <td class="label">Project name</td>
- <td class="value"><%= form.text_field :name %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :name) %>
- </tr>
-
- <tr>
- <td class="label">Website</td>
- <td class="value"><%= form.text_field :url %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :url) %>
- </tr>
-
- <tr>
- <td class="label">Owner</td>
- <td class="value">
- <% if @user.privileges.admin_projects %>
- <%= collection_select :project, :owner_id, @users, :id,
- :display_name, {:include_blank => true} %>
- <% else %>
- <% if @project.owner %>
- <%= h @project.owner.display_name %>
- <% else %>
- UNOWNED
- <% end %>
- <% end %>
- </td>
- <td /><td class="errors"><%= error_message_on(:project, :owner) %>
- </tr>
-
- <tr>
- <td class="label">Description</td>
- <td class="value"><%= form.text_area :description %></td>
- </tr>
-
- <tr>
- <td class="buttons" colspan="2">
- <% if @project.new_record? %>
- <%= submit_tag "Create", :class => 'button' %>
- <% else %>
- <%= submit_tag "Update", :class => 'button' %>
- <% end %>
- </td>
- </tr>
- </tbody>
- </table>
-
-<% end %>
\ No newline at end of file
diff --git a/app/views/project/_product.html.erb b/app/views/project/_product.html.erb
deleted file mode 100644
index 51766eb..0000000
--- a/app/views/project/_product.html.erb
+++ /dev/null
@@ -1,19 +0,0 @@
-<tr>
- <td><%= link_to "#{product.name}",
- {:controller => :product, :action => :view, :id => product} %>
- </td>
- <td>
- <% if product.owner != nil %>
- <%= link_to(product.owner.display_name,
- {:controller => :user,
- :action => :view,
- :id => product.owner})%>
-
- (<%= mail_to(product.owner.email, "email") %>)
- <% else %>
- None (want to become the owner?)
- <% end %>
- </td>
- <td class="description"><%= h product.description %></td>
- <td class="number"><%= product.backlog.size %></td>
-</tr>
diff --git a/app/views/project/create.html.erb b/app/views/project/create.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/create.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/index.html.erb b/app/views/project/index.html.erb
deleted file mode 100644
index d472b78..0000000
--- a/app/views/project/index.html.erb
+++ /dev/null
@@ -1,48 +0,0 @@
-<div class="toolbar">
- <% if @user && @user.create_projects? %>
- <%= link_to "New Project", :action => :create %>
- <% end %>
-</div>
-
-<%= will_paginate @projects %>
-
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
- <thead>
- <tr>
- <th class="title" colspan="5">Projects</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Name</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% if @projects.empty? %>
- <tr><td colspan="5">There are no projects defined.</td></tr>
- <% else %>
- <% @projects.each_with_index do |project, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_project project %></td>
- <td><%= get_first_sentence project.description %></td>
- <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
- <td><%= project.created_at.to_s(:date) %></td>
- </tr>
-
- <% end %>
- <% end %>
- </tbody>
-</table>
diff --git a/app/views/project/modify.html.erb b/app/views/project/modify.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/modify.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/view.html.erb b/app/views/project/view.html.erb
deleted file mode 100644
index c501d67..0000000
--- a/app/views/project/view.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<div style="width: 100%; overflow: auto;">
- <div id="details" style="float: left; width: 33%;">
- <div class="toolbar">
- <% if @user && @user.can_modify_project?(@project) %>
- <%= link_to "Edit project...", :action => :modify, :id => @project %>
- <% end %>
- </div>
- <%= render :partial => 'details', :object => @project %>
- </div>
-
- <div id="products" style="float: right; width: 66%;">
- <div class="toolbar">
- <%= will_paginate @products %>
- <% if @project.can_create_products?(@user) %>
- <%= link_to "Create Product...",
- {
- :controller => :product,
- :action => :create,
- :project => @project
- } %>
- <% end %>
- </div>
- <table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
-
- <thead>
- <tr>
- <th>##</th>
- <th>Product</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% @products.each_with_index do |product, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>"
- onmouseover="this.className='hot'"
- onmouseout="this.className='<%= row_class %>' ">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_product product %></td>
- <td><%= get_first_sentence product.description %></td>
- <td><%= link_to_owner product.owner %></td>
- <td><%= product.created_at.to_s(:date) %></td>
- </tr>
- <% end %>
- </tbody>
- </table>
- </div>
-</div>
diff --git a/app/views/projects/_details.html.erb b/app/views/projects/_details.html.erb
new file mode 100644
index 0000000..9fe7b4b
--- /dev/null
+++ b/app/views/projects/_details.html.erb
@@ -0,0 +1,25 @@
+<table class="details">
+ <thead>
+ <tr>
+ <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td class="label">Owner:</td>
+ <td class="value"><%= link_to_user @project.owner %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Homepage:</td>
+ <td class="value"><%= link_to_homepage @project %>
+ </tr>
+
+ <tr>
+ <td class="text" colspan="2">
+ <%= simple_format @project.description %>
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/app/views/projects/_edit.html.erb b/app/views/projects/_edit.html.erb
new file mode 100644
index 0000000..0b5d409
--- /dev/null
+++ b/app/views/projects/_edit.html.erb
@@ -0,0 +1,58 @@
+<% html = @project.new_record? ? {:method => :post} : {:method => :put} %>
+
+<% form_for :project, @project, :url => project_path(@project), :html => html do |form| %>
+ <table class="edit">
+ <tbody>
+ <tr>
+ <td class="label">Project name</td>
+ <td class="value">
+ <%= form.text_field :name %>
+ <%= error_message_on(:project, :name) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Website</td>
+ <td class="value">
+ <%= form.text_field :url %>
+ <%= error_message_on(:project, :url) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Owner</td>
+ <td class="value">
+ <% if @user.privileges.admin_projects %>
+ <%= collection_select :project, :owner_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <% else %>
+ <% if @project.owner %>
+ <%= h @project.owner.display_name %>
+ <% else %>
+ UNOWNED
+ <% end %>
+ <% end %>
+ <%= error_message_on(:project, :owner) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Description</td>
+ <td class="value">
+ <%= form.text_area :description %>
+ <%= error_message_on(:project, :description) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <% if @project.new_record? %>
+ <%= submit_tag "Create", :class => 'button' %>
+ <% else %>
+ <%= submit_tag "Update", :class => 'button' %>
+ <% end %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<% end %>
\ No newline at end of file
diff --git a/app/views/projects/_product.html.erb b/app/views/projects/_product.html.erb
new file mode 100644
index 0000000..51766eb
--- /dev/null
+++ b/app/views/projects/_product.html.erb
@@ -0,0 +1,19 @@
+<tr>
+ <td><%= link_to "#{product.name}",
+ {:controller => :product, :action => :view, :id => product} %>
+ </td>
+ <td>
+ <% if product.owner != nil %>
+ <%= link_to(product.owner.display_name,
+ {:controller => :user,
+ :action => :view,
+ :id => product.owner})%>
+
+ (<%= mail_to(product.owner.email, "email") %>)
+ <% else %>
+ None (want to become the owner?)
+ <% end %>
+ </td>
+ <td class="description"><%= h product.description %></td>
+ <td class="number"><%= product.backlog.size %></td>
+</tr>
diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/edit.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
new file mode 100644
index 0000000..d4008f5
--- /dev/null
+++ b/app/views/projects/index.html.erb
@@ -0,0 +1,56 @@
+<div class="toolbar">
+ <% if @user && @user.create_projects? %>
+ <%= link_to "New Project", new_project_path %>
+ <% end %>
+</div>
+
+<%= will_paginate @projects %>
+
+<table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ <col class="actions" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th class="title" colspan="6">Projects</th>
+ </tr>
+ <tr>
+ <th>##</th>
+ <th>Name</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% if @projects.empty? %>
+ <tr><td colspan="6">There are no projects defined.</td></tr>
+ <% else %>
+ <% @projects.each_with_index do |project, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to project.name, project_path(project) %></td>
+ <td><%= get_first_sentence project.description %></td>
+ <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
+ <td><%= project.created_at.to_s(:date) %></td>
+ <td>
+ <%= link_to "View", project_path(project) %>
+ <% if project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(project) %>
+ <% end %>
+ </td>
+ </tr>
+
+ <% end %>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/new.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
new file mode 100644
index 0000000..f8fe791
--- /dev/null
+++ b/app/views/projects/show.html.erb
@@ -0,0 +1,59 @@
+<div style="width: 100%; overflow: auto;">
+ <div id="details" style="float: left; width: 33%;">
+ <div class="toolbar">
+ <% if @project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(@project)%>
+ <% end %>
+ </div>
+ <%= render :partial => 'details', :object => @project %>
+ </div>
+
+ <div id="products" style="float: right; width: 66%;">
+ <div class="toolbar">
+ <%= will_paginate @products %>
+ <% if @project.can_create_products?(@user) %>
+ <%= link_to "Create Product...",
+ {
+ :controller => :product,
+ :action => :create,
+ :project => @project
+ } %>
+ <% end %>
+ </div>
+ <table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ </colgroup>
+
+ <thead>
+ <tr>
+ <th>##</th>
+ <th>Product</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @products.each_with_index do |product, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>"
+ onmouseover="this.className='hot'"
+ onmouseout="this.className='<%= row_class %>' ">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to_product product %></td>
+ <td><%= get_first_sentence product.description %></td>
+ <td><%= link_to_owner product.owner %></td>
+ <td><%= product.created_at.to_s(:date) %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/app/views/projects/update.html.erb b/app/views/projects/update.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/update.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb
index ae21641..ee1119f 100644
--- a/app/views/tasks/new.html.erb
+++ b/app/views/tasks/new.html.erb
@@ -1,5 +1,3 @@
<div>
- <% form_for(:task, @task, :url => task_path(@task)) do |form| %>
- <%= render :partial => 'edit', :object => @task %>
- <% end %>
+ <%= render(:partial => 'edit', :object => @project) %>
</div>
diff --git a/config/routes.rb b/config/routes.rb
index 2f66a65..c1f20fc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,11 +16,12 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :projects
map.resources :tasks
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
- map.root :controller => 'project'
+ map.root :controller => 'home'
# Login URL
map.login "/login", :controller => 'home', :action => 'login'
diff --git a/test/functional/product_controller_test.rb b/test/functional/product_controller_test.rb
index acca9e7..f008cbb 100644
--- a/test/functional/product_controller_test.rb
+++ b/test/functional/product_controller_test.rb
@@ -84,7 +84,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => users(:jdonuts).id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that someone with project admin rights but who isn't the project
@@ -95,7 +95,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => @non_project_owner.id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that calling create initially works as expected.
diff --git a/test/functional/project_controller_test.rb b/test/functional/project_controller_test.rb
deleted file mode 100644
index 347e795..0000000
--- a/test/functional/project_controller_test.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-# project_controller_test.rb
-# Copyright (C) 2008, 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'
-
-# +ProjectControllerTest+ performs tests on +ProjectController+ to ensure that
-# it works as expected.
-#
-class ProjectControllerTest < ActionController::TestCase
- fixtures :projects
- fixtures :users
-
- def setup
- @project = projects(:cairo)
- @admin = @project.owner
- @nonadmin = users(:jdonuts)
-
- raise "Admin and non-admin cannot be the same person." if @admin.id == @nonadmin.id
- end
-
- # Ensures that viewing the list of projects works as expected.
- #
- def test_index
- get :index
-
- assert_response :success
- assert assigns['projects']
- end
-
- # Ensures that viewing without a project id shows an error.
- #
- def test_view_without_id
- get :view
-
- assert_redirected_to error_path
- end
-
- # Ensures that viewing a project works as expected.
- #
- def test_view
- get :view, {:id => @project.id}
-
- assert_response :success
- assert_equal 2, assigns['products'].size,
- 'Did not load the set of products.'
- end
-
- # Ensures that an anonymous user cannot create a new project.
- #
- def test_create_for_anonymous_user
- get :create
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who does not have project admin rights cannot create a
- # new project.
- #
- def test_create_for_non_admin
- get :create,
- nil,
- {:user_id => @nonadmin.id}
-
- assert_redirected_to :controller => :home, :action => :dashboard
- end
-
- # Ensures that create works as expected.
- #
- def test_create
- get :create,
- {},
- {:user_id => @admin.id}
-
- assert_response :success
- assert assigns['project']
- assert assigns['users']
- end
-
- # Ensures the controller redisplays the edit form when the project is invalid.
- #
- def test_create_as_save_with_invalid_project
- post :create, {:project => {:owner_id => @admin.id}}, {:user_id => @admin.id}
-
- assert_response :success
- assert_template 'project/create'
- end
-
- # Ensures that saving a project works as expected.
- #
- def test_create_as_save
- project_details = {
- :name => 'project1',
- :owner_id => @admin.id,
- :url => 'http://www.project.org/',
- :description => 'This is a new project'
- }
-
- post :create,
- {:project => project_details},
- {:user_id => @admin.id}
-
- assert_redirected_to :action => :index
-
- result = Project.find_by_name(project_details[:name])
-
- assert result, 'Project was not saved.'
- end
-
- # Ensures that anonymous users cannot modify a project.
- #
- def test_modify_for_anonymous_user
- get :modify
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that modifying an invalid project results in an error.
- #
- def test_modify_for_invalid_project
- post :modify,
- {:id => 9999},
- {:user_id => @admin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that unauthorized users cannot modify a project.
- #
- def test_modify_for_unauthorized_user
- get :modify,
- {:id => @project.id},
- {:user_id => @nonadmin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that starting to modify a project works as expected.
- #
- def test_modify
- get :modify,
- {:id => @project.id},
- {:user_id => @project.owner_id}
-
- assert_response :success
- end
-
- # Ensures that modifying an existing project works as expected.
- #
- def test_modify_as_save
- project_details = {:name => 'newname1'}
-
- post :modify,
- {:id => @project.id, :project => project_details},
- {:user_id => @project.owner_id}
-
- assert_redirected_to :action => :view
-
- result = Project.find_by_id((a)project.id)
-
- assert_equal project_details[:name], result.name, 'Project was not modified.'
- end
-end
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
new file mode 100644
index 0000000..2e3e303
--- /dev/null
+++ b/test/functional/projects_controller_test.rb
@@ -0,0 +1,233 @@
+# projects_controller_test.rb
+# Copyright (C) 2008, 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'
+
+# +ProjectsControllerTest+ performs tests on +ProjectController+ to ensure that
+# it works as expected.
+#
+class ProjectsControllerTest < ActionController::TestCase
+ fixtures :projects
+ fixtures :users
+
+ def setup
+ @project = projects(:cairo)
+ @owner = @project.owner
+ @nonowner = users(:jdonuts)
+ raise "Owner and nonowner cannot be the same user!" if @owner.id == @nonowner.id
+
+ @nonadmin = @nonowner
+ @admin = users(:project_admin)
+ raise "Nonadmin and admin cannot be the same user!" if @nonadmin.id == @admin.id
+ end
+
+ # Ensures that showing the project index works as expected.
+ #
+ def test_index
+ get :index
+
+ assert_response :success
+ assert assigns['projects'], "Didn't load any projects."
+ end
+
+ # Ensures that showing a single project requires an id.
+ #
+ def test_show_without_id
+ get :show
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that showing a project fails on invalid ids.
+ #
+ def test_show_with_invalid_id
+ get :show, {:id => 9999}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures tha show a project works as expected.
+ #
+ def test_show
+ get :show, {:id => @project.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load the project."
+ assert_equal @project.id, assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a new project.
+ #
+ def test_new_as_anonymous
+ get :new
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user without project admin privileges cannot create a
+ # project.
+ #
+ def test_new_as_unauthorized_user
+ get :new, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that starting off a new project works as expected.
+ #
+ def test_new
+ get :new, {}, {:user_id => @admin.id}
+
+ assert_response :success
+ assert assigns['project'], "Did not create a new project."
+ assert_equal @admin.id, assigns['project'].owner_id,
+ "Did not assign the default owner."
+ assert assigns['users'], "Did not load the list of users."
+ end
+
+ # Ensures an anonymous user cannot start editing a project.
+ #
+ def test_edit_as_anonymous
+ get :edit
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that edit fails on an invalid id.
+ #
+ def test_edit_with_invalid_id
+ get :edit,
+ {:id => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonowner cannot edit a project.
+ #
+ def test_edit_as_nonowner
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_edit
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @owner.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load a project."
+ assert_equal @project.id,assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a project.
+ #
+ def test_create_as_anonymous
+ post :create
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a non-admin cannot create a project.
+ #
+ def test_create_as_nonadmin
+ post :create, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an invalid project returns the user to the edit page.
+ #
+ def test_create_with_invalid_project
+ post :create,
+ {:project => {}},
+ {:user_id => @admin.id}
+
+ assert_response :success
+ end
+
+ # Ensures that a project is created when one is submitted.
+ #
+ def test_create
+ post :create,
+ {:project => {:name => 'test', :owner_id => @admin.id}},
+ {:user_id => @admin.id}
+
+ assert_redirected_to project_path(assigns['project'])
+ end
+
+ # Ensures that an anonymous user cannot update a project.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a valid project id must be provided.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @admin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonadmin cannot update a project.
+ #
+ def test_update_by_nonowner
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal @project.name, result.name,
+ "Project should not have been updated!"
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_update
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @owner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal 'newname', result.name,
+ "Project should have been updated!"
+ end
+
+ # Ensures that the destroy method just exists.
+ #
+ def test_destroy
+ delete :destroy
+
+ assert_redirected_to error_url
+ end
+end
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Moving projects over to a RESTful interface.
by Darryl L. Pierce
Added a new resource named :projects.
Renamed ProjectController and ProjectControllerTest to ProjectsController
and ProjectsControllerTest, respectively.
Rewrote the functional tests to make sure they properly vetted the
refactored controller.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/application.rb | 2 +-
app/controllers/product_controller.rb | 2 +-
app/controllers/project_controller.rb | 102 ------------
app/controllers/projects_controller.rb | 142 ++++++++++++++++
app/helpers/application_helper.rb | 57 +++----
app/models/project.rb | 13 ++-
app/views/layouts/default.html.erb | 2 +-
app/views/project/_details.html.erb | 25 ---
app/views/project/_edit.html.erb | 59 -------
app/views/project/_product.html.erb | 19 ---
app/views/project/create.html.erb | 1 -
app/views/project/index.html.erb | 48 ------
app/views/project/modify.html.erb | 1 -
app/views/project/view.html.erb | 59 -------
app/views/projects/_details.html.erb | 25 +++
app/views/projects/_edit.html.erb | 58 +++++++
app/views/projects/_product.html.erb | 19 +++
app/views/projects/edit.html.erb | 1 +
app/views/projects/index.html.erb | 56 +++++++
app/views/projects/new.html.erb | 1 +
app/views/projects/show.html.erb | 59 +++++++
app/views/projects/update.html.erb | 1 +
app/views/tasks/new.html.erb | 4 +-
config/routes.rb | 3 +-
test/functional/product_controller_test.rb | 4 +-
test/functional/project_controller_test.rb | 176 --------------------
test/functional/projects_controller_test.rb | 233 +++++++++++++++++++++++++++
27 files changed, 639 insertions(+), 533 deletions(-)
delete mode 100644 app/controllers/project_controller.rb
create mode 100644 app/controllers/projects_controller.rb
delete mode 100644 app/views/project/_details.html.erb
delete mode 100644 app/views/project/_edit.html.erb
delete mode 100644 app/views/project/_product.html.erb
delete mode 100644 app/views/project/create.html.erb
delete mode 100644 app/views/project/index.html.erb
delete mode 100644 app/views/project/modify.html.erb
delete mode 100644 app/views/project/view.html.erb
create mode 100644 app/views/projects/_details.html.erb
create mode 100644 app/views/projects/_edit.html.erb
create mode 100644 app/views/projects/_product.html.erb
create mode 100644 app/views/projects/edit.html.erb
create mode 100644 app/views/projects/index.html.erb
create mode 100644 app/views/projects/new.html.erb
create mode 100644 app/views/projects/show.html.erb
create mode 100644 app/views/projects/update.html.erb
delete mode 100644 test/functional/project_controller_test.rb
create mode 100644 test/functional/projects_controller_test.rb
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 3378a90..51261a0 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -34,7 +34,7 @@ class ApplicationController < ActionController::Base
if @user == nil
session[:original_uri] = request.request_uri
flash[:message] = 'Please login first.'
- redirect_to(:controller => :home, :action => :login)
+ redirect_to login_url
end
end
diff --git a/app/controllers/product_controller.rb b/app/controllers/product_controller.rb
index f5553d3..43c6425 100644
--- a/app/controllers/product_controller.rb
+++ b/app/controllers/product_controller.rb
@@ -51,7 +51,7 @@ class ProductController < ApplicationController
end
else
flash[:message] = 'You are not authorized to create products.'
- redirect_to :controller => :project, :action => :view, :id => @project.id
+ redirect_to project_path(@project)
end
end
diff --git a/app/controllers/project_controller.rb b/app/controllers/project_controller.rb
deleted file mode 100644
index b122159..0000000
--- a/app/controllers/project_controller.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# project_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +ProjectController+ handles requests relating to the management and viewing of
-# +Product+s.
-#
-class ProjectController < ApplicationController
- before_filter :authenticated, :only => [:create, :modify]
- before_filter :load_project, :except => [:create, :index]
- before_filter :setup_for_edit, :only => [:create, :modify]
-
- # Allows viewing of all defined projects.
- #
- def index
- @projects = Project.paginate :page => params[:page], :per_page => 10
- end
-
- # Shows a single project.
- #
- def view
- @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
- end
-
- # Begins the process of creating a new project.
- #
- def create
- if @user.create_projects?
- if request.post?
- begin
- Project.transaction do
- @project = Project.new(params[:project])
- @project.save!
-
- redirect_to :action => :index
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :create
- end
- else
- @project = Project.new
- end
- else
- flash[:message] = 'That action requires administrative rights.'
- redirect_to :controller => :home, :action => :dashboard
- end
- end
-
- # Modifies an existing project.
- #
- def modify
- if @user.can_modify_project?(@project)
- if request.post?
- begin
- Project.transaction do
- @project.update_attributes(params[:project])
- @project.save!
-
- redirect_to :action => :view, :id => @project
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :modify
- end
- end
- else
- flash[:message] = "You are not authorized to modify that project."
- redirect_to error_path
- end
- end
-
- private
-
- # Loads the requested project.
- #
- def load_project
- @project = Project.find_by_id(params[:id])
-
- unless @project
- flash[:message] = 'Invalid project specified.'
- redirect_to error_path
- end
- end
-
- def setup_for_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
new file mode 100644
index 0000000..6c379b7
--- /dev/null
+++ b/app/controllers/projects_controller.rb
@@ -0,0 +1,142 @@
+# projects_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +ProjectsController+ handles requests relating to the management and viewing
+# of +Product+s.
+#
+class ProjectsController < ApplicationController
+ before_filter :authenticated, :only => [:new, :create, :edit, :update]
+ before_filter :load_project, :except => [:index, :new, :create, :destroy]
+ before_filter :setup_for_edit, :only => [:new, :create, :edit]
+
+ # GET /projects
+ def index
+ @projects = Project.paginate :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /projects/1
+ def show
+ @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # get /projects/new
+ def new
+ if @user.create_projects?
+ @project = Project.new(:owner_id => @user.id)
+ else
+ flash[:error] = "You are not authorized to create projects."
+
+ redirect_to error_url
+ end
+ end
+
+ # GET /projects/1;edit
+ def edit
+ unless @project.user_can_modify?(@user)
+ flash[:message] = "You are not authorized to modify that project."
+ redirect_to project_path(@project)
+ end
+ end
+
+ # POST /projects
+ def create
+ respond_to do |format|
+ if @user.create_projects?
+ @project = Project.new(params[:project])
+
+ begin
+ Project.transaction do
+ if @project.save
+ flash[:message] = "Project '#{(a)project.name}' created!"
+ format.html { redirect_to project_url(@project) }
+ else
+ @project.valid?
+ format.html { render :action => "new" }
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to create projects."
+ format.html { redirect_to error_url }
+ end
+ end
+ end
+
+ # PUT /projects/1
+ def update
+ respond_to do |format|
+ if @project.user_can_modify?(@user)
+ begin
+ Project.transaction do
+ @project.update_attributes(params[:project])
+ if @project.save!
+ flash[:message] = "Project was successfully updated."
+ format.html { redirect_to project_path(@project) }
+ else
+ @project.valid?
+ format.html { render :action => 'edit'}
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to edit this project."
+ format.html { redirect_to project_path(@project) }
+ end
+ end
+ end
+
+ # DELETE /projects/1
+ def destroy
+ respond_to do |format|
+ flash[:message] = "Project deletion is not current supported."
+ format.html { redirect_to error_url}
+ end
+ end
+
+ private
+
+ # Loads the requested project.
+ #
+ def load_project
+ @project = Project.find_by_id(params[:id])
+
+ unless @project
+ flash[:message] = 'Invalid project specified.'
+ respond_to do |format|
+ format.html { redirect_to error_path }
+ end
+ end
+ end
+
+ def setup_for_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 96d59a7..88cd918 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,19 +1,19 @@
# application_helper.rb
# Copyright (C) 2008, 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/>.
-#
+#
module ApplicationHelper
# Returns the first sentence from the supplied text, up to the first period.
@@ -23,13 +23,13 @@ module ApplicationHelper
if (text == nil) || text.empty?
return " "
end
-
+
return text.slice(/^[^\.]*\.?/)
-
+
end
-
+
# Creates a link to a user.
- #
+ #
def link_to_user(user)
if user
@template.link_to(user.display_name,
@@ -38,9 +38,9 @@ module ApplicationHelper
:id => user)
end
end
-
+
# Creates a link to an owner for a project, product or other item.
- #
+ #
def link_to_owner(owner, args = nil)
if owner
link_to_user(owner)
@@ -49,61 +49,52 @@ module ApplicationHelper
args[:unowned] if args
end
end
-
- # Creates a link to a project.
- #
- def link_to_project(project)
- @template.link_to(project.name,
- :controller => :project,
- :action => :view,
- :id => project)
- end
-
+
# Creates a link to a product.
- #
- def link_to_product(product, options = {})
- @template.link_to(options[:text] ? options[:text] : product.name,
+ #
+ def link_to_product(product)
+ @template.link_to(product.name,
:controller => :product,
- :action => options[:action] ? options[:action] : :view,
+ :action => :view,
:id => product)
end
-
+
# Creates a link to a sprint.
- #
+ #
def link_to_sprint(sprint)
@template.link_to(sprint.title,
:controller => :sprint,
:action => :view,
:id => sprint)
end
-
+
# Displays a link and image reporting the health of the sprint
def show_sprint_health(sprint)
@template.image_tag(
sprint.healthy? ? "/images/icons/healthy.png" : "/images/icons/unhealthy.png",
:alt => sprint.healthy? ? "Sprint is healthy" : "Sprint is not healthy")
end
-
+
# Creates a link to a user story.
- #
+ #
def link_to_user_story(user_story, options ={})
@template.link_to((options[:text] ? options[:text] : user_story.title),
:controller => :user_story,
:action => (options[:action] ? options[:action] : :view),
:id => user_story)
end
-
+
# Creates a link to a backlog item.
- #
+ #
def link_to_backlog_item(backlog_item, options = {})
@template.link_to(options[:text] ? options[:text] : backlog_item.title,
:controller => :backlog,
:action => (options[:action] ? options[:action] : :view),
:id => backlog_item.id)
end
-
+
# Shows the hours for a given object as estimated, actual and remaining.
- #
+ #
def show_hours_as_ear(estimated)
format("%0.1f/%0.1f/%0.1f",estimated.estimated_hours, estimated.actual_hours, estimated.remaining_hours)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2bd9541..6a7ce5d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -21,14 +21,25 @@
class Project < ActiveRecord::Base
validates_presence_of :name,
:message => 'A project has to have a name.'
-
+ validates_length_of :name,
+ :minimum => 4,
+ :message => 'The project name must be at least four characters long.'
validates_uniqueness_of :name,
:message => 'This name is used by an existing project.'
+ validates_presence_of :owner_id,
+ :message => 'A project must have an owner.'
+
belongs_to :owner, :class_name => 'User', :foreign_key => :owner_id
has_many :products
+ # Returns whether the user can modify this project.
+ #
+ def user_can_modify?(user)
+ user && (user.privileges.admin_projects || (user.id == owner.id))
+ end
+
# Returns whether the user can create products for this project.
#
def can_create_products?(user)
diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb
index ac4dd6f..e1ac558 100644
--- a/app/views/layouts/default.html.erb
+++ b/app/views/layouts/default.html.erb
@@ -24,7 +24,7 @@
<div id="user-box">
<%= link_to "Home", :controller => :home %>
- <%= link_to "Projects", :controller => :project, :action => :index %>
+ <%= link_to "Projects", projects_path %>
<%= link_to "Reports", :controller => :report, :action => :index %>
<% if @user %>
<%= link_to @user.display_name, dashboard_url %>
diff --git a/app/views/project/_details.html.erb b/app/views/project/_details.html.erb
deleted file mode 100644
index 7dfc64c..0000000
--- a/app/views/project/_details.html.erb
+++ /dev/null
@@ -1,25 +0,0 @@
-<table class="details">
- <thead>
- <tr>
- <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td class="label">Owner:</td>
- <td class="value"><%= link_to_user @project.owner %></td>
- </tr>
-
- <tr>
- <td class="label">Homepage:</td>
- <td class="value"><%= link_to_homepage @project %>
- </tr>
-
- <tr>
- <td class="text" colspan="2">
- <%= simple_format @project.description %>
- </td>
- </tr>
- </tbody>
-</table>
diff --git a/app/views/project/_edit.html.erb b/app/views/project/_edit.html.erb
deleted file mode 100644
index d1b02e9..0000000
--- a/app/views/project/_edit.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<% form_for :project do |form| %>
-
- <% unless @project.new_record? %>
- <%= form.hidden_field :id %>
- <% end %>
-
- <table class="edit">
- <tbody>
- <tr>
- <td class="label">Project name</td>
- <td class="value"><%= form.text_field :name %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :name) %>
- </tr>
-
- <tr>
- <td class="label">Website</td>
- <td class="value"><%= form.text_field :url %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :url) %>
- </tr>
-
- <tr>
- <td class="label">Owner</td>
- <td class="value">
- <% if @user.privileges.admin_projects %>
- <%= collection_select :project, :owner_id, @users, :id,
- :display_name, {:include_blank => true} %>
- <% else %>
- <% if @project.owner %>
- <%= h @project.owner.display_name %>
- <% else %>
- UNOWNED
- <% end %>
- <% end %>
- </td>
- <td /><td class="errors"><%= error_message_on(:project, :owner) %>
- </tr>
-
- <tr>
- <td class="label">Description</td>
- <td class="value"><%= form.text_area :description %></td>
- </tr>
-
- <tr>
- <td class="buttons" colspan="2">
- <% if @project.new_record? %>
- <%= submit_tag "Create", :class => 'button' %>
- <% else %>
- <%= submit_tag "Update", :class => 'button' %>
- <% end %>
- </td>
- </tr>
- </tbody>
- </table>
-
-<% end %>
\ No newline at end of file
diff --git a/app/views/project/_product.html.erb b/app/views/project/_product.html.erb
deleted file mode 100644
index 51766eb..0000000
--- a/app/views/project/_product.html.erb
+++ /dev/null
@@ -1,19 +0,0 @@
-<tr>
- <td><%= link_to "#{product.name}",
- {:controller => :product, :action => :view, :id => product} %>
- </td>
- <td>
- <% if product.owner != nil %>
- <%= link_to(product.owner.display_name,
- {:controller => :user,
- :action => :view,
- :id => product.owner})%>
-
- (<%= mail_to(product.owner.email, "email") %>)
- <% else %>
- None (want to become the owner?)
- <% end %>
- </td>
- <td class="description"><%= h product.description %></td>
- <td class="number"><%= product.backlog.size %></td>
-</tr>
diff --git a/app/views/project/create.html.erb b/app/views/project/create.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/create.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/index.html.erb b/app/views/project/index.html.erb
deleted file mode 100644
index d472b78..0000000
--- a/app/views/project/index.html.erb
+++ /dev/null
@@ -1,48 +0,0 @@
-<div class="toolbar">
- <% if @user && @user.create_projects? %>
- <%= link_to "New Project", :action => :create %>
- <% end %>
-</div>
-
-<%= will_paginate @projects %>
-
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
- <thead>
- <tr>
- <th class="title" colspan="5">Projects</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Name</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% if @projects.empty? %>
- <tr><td colspan="5">There are no projects defined.</td></tr>
- <% else %>
- <% @projects.each_with_index do |project, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_project project %></td>
- <td><%= get_first_sentence project.description %></td>
- <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
- <td><%= project.created_at.to_s(:date) %></td>
- </tr>
-
- <% end %>
- <% end %>
- </tbody>
-</table>
diff --git a/app/views/project/modify.html.erb b/app/views/project/modify.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/modify.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/view.html.erb b/app/views/project/view.html.erb
deleted file mode 100644
index c501d67..0000000
--- a/app/views/project/view.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<div style="width: 100%; overflow: auto;">
- <div id="details" style="float: left; width: 33%;">
- <div class="toolbar">
- <% if @user && @user.can_modify_project?(@project) %>
- <%= link_to "Edit project...", :action => :modify, :id => @project %>
- <% end %>
- </div>
- <%= render :partial => 'details', :object => @project %>
- </div>
-
- <div id="products" style="float: right; width: 66%;">
- <div class="toolbar">
- <%= will_paginate @products %>
- <% if @project.can_create_products?(@user) %>
- <%= link_to "Create Product...",
- {
- :controller => :product,
- :action => :create,
- :project => @project
- } %>
- <% end %>
- </div>
- <table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
-
- <thead>
- <tr>
- <th>##</th>
- <th>Product</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% @products.each_with_index do |product, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>"
- onmouseover="this.className='hot'"
- onmouseout="this.className='<%= row_class %>' ">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_product product %></td>
- <td><%= get_first_sentence product.description %></td>
- <td><%= link_to_owner product.owner %></td>
- <td><%= product.created_at.to_s(:date) %></td>
- </tr>
- <% end %>
- </tbody>
- </table>
- </div>
-</div>
diff --git a/app/views/projects/_details.html.erb b/app/views/projects/_details.html.erb
new file mode 100644
index 0000000..9fe7b4b
--- /dev/null
+++ b/app/views/projects/_details.html.erb
@@ -0,0 +1,25 @@
+<table class="details">
+ <thead>
+ <tr>
+ <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td class="label">Owner:</td>
+ <td class="value"><%= link_to_user @project.owner %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Homepage:</td>
+ <td class="value"><%= link_to_homepage @project %>
+ </tr>
+
+ <tr>
+ <td class="text" colspan="2">
+ <%= simple_format @project.description %>
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/app/views/projects/_edit.html.erb b/app/views/projects/_edit.html.erb
new file mode 100644
index 0000000..0b5d409
--- /dev/null
+++ b/app/views/projects/_edit.html.erb
@@ -0,0 +1,58 @@
+<% html = @project.new_record? ? {:method => :post} : {:method => :put} %>
+
+<% form_for :project, @project, :url => project_path(@project), :html => html do |form| %>
+ <table class="edit">
+ <tbody>
+ <tr>
+ <td class="label">Project name</td>
+ <td class="value">
+ <%= form.text_field :name %>
+ <%= error_message_on(:project, :name) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Website</td>
+ <td class="value">
+ <%= form.text_field :url %>
+ <%= error_message_on(:project, :url) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Owner</td>
+ <td class="value">
+ <% if @user.privileges.admin_projects %>
+ <%= collection_select :project, :owner_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <% else %>
+ <% if @project.owner %>
+ <%= h @project.owner.display_name %>
+ <% else %>
+ UNOWNED
+ <% end %>
+ <% end %>
+ <%= error_message_on(:project, :owner) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Description</td>
+ <td class="value">
+ <%= form.text_area :description %>
+ <%= error_message_on(:project, :description) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <% if @project.new_record? %>
+ <%= submit_tag "Create", :class => 'button' %>
+ <% else %>
+ <%= submit_tag "Update", :class => 'button' %>
+ <% end %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<% end %>
\ No newline at end of file
diff --git a/app/views/projects/_product.html.erb b/app/views/projects/_product.html.erb
new file mode 100644
index 0000000..51766eb
--- /dev/null
+++ b/app/views/projects/_product.html.erb
@@ -0,0 +1,19 @@
+<tr>
+ <td><%= link_to "#{product.name}",
+ {:controller => :product, :action => :view, :id => product} %>
+ </td>
+ <td>
+ <% if product.owner != nil %>
+ <%= link_to(product.owner.display_name,
+ {:controller => :user,
+ :action => :view,
+ :id => product.owner})%>
+
+ (<%= mail_to(product.owner.email, "email") %>)
+ <% else %>
+ None (want to become the owner?)
+ <% end %>
+ </td>
+ <td class="description"><%= h product.description %></td>
+ <td class="number"><%= product.backlog.size %></td>
+</tr>
diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/edit.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
new file mode 100644
index 0000000..d4008f5
--- /dev/null
+++ b/app/views/projects/index.html.erb
@@ -0,0 +1,56 @@
+<div class="toolbar">
+ <% if @user && @user.create_projects? %>
+ <%= link_to "New Project", new_project_path %>
+ <% end %>
+</div>
+
+<%= will_paginate @projects %>
+
+<table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ <col class="actions" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th class="title" colspan="6">Projects</th>
+ </tr>
+ <tr>
+ <th>##</th>
+ <th>Name</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% if @projects.empty? %>
+ <tr><td colspan="6">There are no projects defined.</td></tr>
+ <% else %>
+ <% @projects.each_with_index do |project, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to project.name, project_path(project) %></td>
+ <td><%= get_first_sentence project.description %></td>
+ <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
+ <td><%= project.created_at.to_s(:date) %></td>
+ <td>
+ <%= link_to "View", project_path(project) %>
+ <% if project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(project) %>
+ <% end %>
+ </td>
+ </tr>
+
+ <% end %>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/new.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
new file mode 100644
index 0000000..f8fe791
--- /dev/null
+++ b/app/views/projects/show.html.erb
@@ -0,0 +1,59 @@
+<div style="width: 100%; overflow: auto;">
+ <div id="details" style="float: left; width: 33%;">
+ <div class="toolbar">
+ <% if @project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(@project)%>
+ <% end %>
+ </div>
+ <%= render :partial => 'details', :object => @project %>
+ </div>
+
+ <div id="products" style="float: right; width: 66%;">
+ <div class="toolbar">
+ <%= will_paginate @products %>
+ <% if @project.can_create_products?(@user) %>
+ <%= link_to "Create Product...",
+ {
+ :controller => :product,
+ :action => :create,
+ :project => @project
+ } %>
+ <% end %>
+ </div>
+ <table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ </colgroup>
+
+ <thead>
+ <tr>
+ <th>##</th>
+ <th>Product</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @products.each_with_index do |product, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>"
+ onmouseover="this.className='hot'"
+ onmouseout="this.className='<%= row_class %>' ">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to_product product %></td>
+ <td><%= get_first_sentence product.description %></td>
+ <td><%= link_to_owner product.owner %></td>
+ <td><%= product.created_at.to_s(:date) %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/app/views/projects/update.html.erb b/app/views/projects/update.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/update.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb
index ae21641..ee1119f 100644
--- a/app/views/tasks/new.html.erb
+++ b/app/views/tasks/new.html.erb
@@ -1,5 +1,3 @@
<div>
- <% form_for(:task, @task, :url => task_path(@task)) do |form| %>
- <%= render :partial => 'edit', :object => @task %>
- <% end %>
+ <%= render(:partial => 'edit', :object => @project) %>
</div>
diff --git a/config/routes.rb b/config/routes.rb
index 2f66a65..c1f20fc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,11 +16,12 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :projects
map.resources :tasks
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
- map.root :controller => 'project'
+ map.root :controller => 'home'
# Login URL
map.login "/login", :controller => 'home', :action => 'login'
diff --git a/test/functional/product_controller_test.rb b/test/functional/product_controller_test.rb
index acca9e7..f008cbb 100644
--- a/test/functional/product_controller_test.rb
+++ b/test/functional/product_controller_test.rb
@@ -84,7 +84,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => users(:jdonuts).id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that someone with project admin rights but who isn't the project
@@ -95,7 +95,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => @non_project_owner.id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that calling create initially works as expected.
diff --git a/test/functional/project_controller_test.rb b/test/functional/project_controller_test.rb
deleted file mode 100644
index 347e795..0000000
--- a/test/functional/project_controller_test.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-# project_controller_test.rb
-# Copyright (C) 2008, 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'
-
-# +ProjectControllerTest+ performs tests on +ProjectController+ to ensure that
-# it works as expected.
-#
-class ProjectControllerTest < ActionController::TestCase
- fixtures :projects
- fixtures :users
-
- def setup
- @project = projects(:cairo)
- @admin = @project.owner
- @nonadmin = users(:jdonuts)
-
- raise "Admin and non-admin cannot be the same person." if @admin.id == @nonadmin.id
- end
-
- # Ensures that viewing the list of projects works as expected.
- #
- def test_index
- get :index
-
- assert_response :success
- assert assigns['projects']
- end
-
- # Ensures that viewing without a project id shows an error.
- #
- def test_view_without_id
- get :view
-
- assert_redirected_to error_path
- end
-
- # Ensures that viewing a project works as expected.
- #
- def test_view
- get :view, {:id => @project.id}
-
- assert_response :success
- assert_equal 2, assigns['products'].size,
- 'Did not load the set of products.'
- end
-
- # Ensures that an anonymous user cannot create a new project.
- #
- def test_create_for_anonymous_user
- get :create
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who does not have project admin rights cannot create a
- # new project.
- #
- def test_create_for_non_admin
- get :create,
- nil,
- {:user_id => @nonadmin.id}
-
- assert_redirected_to :controller => :home, :action => :dashboard
- end
-
- # Ensures that create works as expected.
- #
- def test_create
- get :create,
- {},
- {:user_id => @admin.id}
-
- assert_response :success
- assert assigns['project']
- assert assigns['users']
- end
-
- # Ensures the controller redisplays the edit form when the project is invalid.
- #
- def test_create_as_save_with_invalid_project
- post :create, {:project => {:owner_id => @admin.id}}, {:user_id => @admin.id}
-
- assert_response :success
- assert_template 'project/create'
- end
-
- # Ensures that saving a project works as expected.
- #
- def test_create_as_save
- project_details = {
- :name => 'project1',
- :owner_id => @admin.id,
- :url => 'http://www.project.org/',
- :description => 'This is a new project'
- }
-
- post :create,
- {:project => project_details},
- {:user_id => @admin.id}
-
- assert_redirected_to :action => :index
-
- result = Project.find_by_name(project_details[:name])
-
- assert result, 'Project was not saved.'
- end
-
- # Ensures that anonymous users cannot modify a project.
- #
- def test_modify_for_anonymous_user
- get :modify
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that modifying an invalid project results in an error.
- #
- def test_modify_for_invalid_project
- post :modify,
- {:id => 9999},
- {:user_id => @admin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that unauthorized users cannot modify a project.
- #
- def test_modify_for_unauthorized_user
- get :modify,
- {:id => @project.id},
- {:user_id => @nonadmin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that starting to modify a project works as expected.
- #
- def test_modify
- get :modify,
- {:id => @project.id},
- {:user_id => @project.owner_id}
-
- assert_response :success
- end
-
- # Ensures that modifying an existing project works as expected.
- #
- def test_modify_as_save
- project_details = {:name => 'newname1'}
-
- post :modify,
- {:id => @project.id, :project => project_details},
- {:user_id => @project.owner_id}
-
- assert_redirected_to :action => :view
-
- result = Project.find_by_id((a)project.id)
-
- assert_equal project_details[:name], result.name, 'Project was not modified.'
- end
-end
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
new file mode 100644
index 0000000..2e3e303
--- /dev/null
+++ b/test/functional/projects_controller_test.rb
@@ -0,0 +1,233 @@
+# projects_controller_test.rb
+# Copyright (C) 2008, 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'
+
+# +ProjectsControllerTest+ performs tests on +ProjectController+ to ensure that
+# it works as expected.
+#
+class ProjectsControllerTest < ActionController::TestCase
+ fixtures :projects
+ fixtures :users
+
+ def setup
+ @project = projects(:cairo)
+ @owner = @project.owner
+ @nonowner = users(:jdonuts)
+ raise "Owner and nonowner cannot be the same user!" if @owner.id == @nonowner.id
+
+ @nonadmin = @nonowner
+ @admin = users(:project_admin)
+ raise "Nonadmin and admin cannot be the same user!" if @nonadmin.id == @admin.id
+ end
+
+ # Ensures that showing the project index works as expected.
+ #
+ def test_index
+ get :index
+
+ assert_response :success
+ assert assigns['projects'], "Didn't load any projects."
+ end
+
+ # Ensures that showing a single project requires an id.
+ #
+ def test_show_without_id
+ get :show
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that showing a project fails on invalid ids.
+ #
+ def test_show_with_invalid_id
+ get :show, {:id => 9999}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures tha show a project works as expected.
+ #
+ def test_show
+ get :show, {:id => @project.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load the project."
+ assert_equal @project.id, assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a new project.
+ #
+ def test_new_as_anonymous
+ get :new
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user without project admin privileges cannot create a
+ # project.
+ #
+ def test_new_as_unauthorized_user
+ get :new, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that starting off a new project works as expected.
+ #
+ def test_new
+ get :new, {}, {:user_id => @admin.id}
+
+ assert_response :success
+ assert assigns['project'], "Did not create a new project."
+ assert_equal @admin.id, assigns['project'].owner_id,
+ "Did not assign the default owner."
+ assert assigns['users'], "Did not load the list of users."
+ end
+
+ # Ensures an anonymous user cannot start editing a project.
+ #
+ def test_edit_as_anonymous
+ get :edit
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that edit fails on an invalid id.
+ #
+ def test_edit_with_invalid_id
+ get :edit,
+ {:id => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonowner cannot edit a project.
+ #
+ def test_edit_as_nonowner
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_edit
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @owner.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load a project."
+ assert_equal @project.id,assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a project.
+ #
+ def test_create_as_anonymous
+ post :create
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a non-admin cannot create a project.
+ #
+ def test_create_as_nonadmin
+ post :create, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an invalid project returns the user to the edit page.
+ #
+ def test_create_with_invalid_project
+ post :create,
+ {:project => {}},
+ {:user_id => @admin.id}
+
+ assert_response :success
+ end
+
+ # Ensures that a project is created when one is submitted.
+ #
+ def test_create
+ post :create,
+ {:project => {:name => 'test', :owner_id => @admin.id}},
+ {:user_id => @admin.id}
+
+ assert_redirected_to project_path(assigns['project'])
+ end
+
+ # Ensures that an anonymous user cannot update a project.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a valid project id must be provided.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @admin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonadmin cannot update a project.
+ #
+ def test_update_by_nonowner
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal @project.name, result.name,
+ "Project should not have been updated!"
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_update
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @owner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal 'newname', result.name,
+ "Project should have been updated!"
+ end
+
+ # Ensures that the destroy method just exists.
+ #
+ def test_destroy
+ delete :destroy
+
+ assert_redirected_to error_url
+ end
+end
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Moving projects over to a RESTful interface.
by Darryl L. Pierce
NOTE: This patch fixes the bug found when viewing a product.
Added a new resource named :projects.
Renamed ProjectController and ProjectControllerTest to ProjectsController
and ProjectsControllerTest, respectively.
Rewrote the functional tests to make sure they properly vetted the
refactored controller.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/application.rb | 2 +-
app/controllers/product_controller.rb | 2 +-
app/controllers/project_controller.rb | 102 ------------
app/controllers/projects_controller.rb | 142 ++++++++++++++++
app/helpers/application_helper.rb | 57 +++----
app/models/project.rb | 13 ++-
app/views/layouts/default.html.erb | 2 +-
app/views/product/_details.html.erb | 2 +-
app/views/project/_details.html.erb | 25 ---
app/views/project/_edit.html.erb | 59 -------
app/views/project/_product.html.erb | 19 ---
app/views/project/create.html.erb | 1 -
app/views/project/index.html.erb | 48 ------
app/views/project/modify.html.erb | 1 -
app/views/project/view.html.erb | 59 -------
app/views/projects/_details.html.erb | 25 +++
app/views/projects/_edit.html.erb | 58 +++++++
app/views/projects/_product.html.erb | 19 +++
app/views/projects/edit.html.erb | 1 +
app/views/projects/index.html.erb | 56 +++++++
app/views/projects/new.html.erb | 1 +
app/views/projects/show.html.erb | 59 +++++++
app/views/projects/update.html.erb | 1 +
app/views/tasks/new.html.erb | 4 +-
config/routes.rb | 3 +-
test/functional/product_controller_test.rb | 4 +-
test/functional/project_controller_test.rb | 176 --------------------
test/functional/projects_controller_test.rb | 233 +++++++++++++++++++++++++++
28 files changed, 640 insertions(+), 534 deletions(-)
delete mode 100644 app/controllers/project_controller.rb
create mode 100644 app/controllers/projects_controller.rb
delete mode 100644 app/views/project/_details.html.erb
delete mode 100644 app/views/project/_edit.html.erb
delete mode 100644 app/views/project/_product.html.erb
delete mode 100644 app/views/project/create.html.erb
delete mode 100644 app/views/project/index.html.erb
delete mode 100644 app/views/project/modify.html.erb
delete mode 100644 app/views/project/view.html.erb
create mode 100644 app/views/projects/_details.html.erb
create mode 100644 app/views/projects/_edit.html.erb
create mode 100644 app/views/projects/_product.html.erb
create mode 100644 app/views/projects/edit.html.erb
create mode 100644 app/views/projects/index.html.erb
create mode 100644 app/views/projects/new.html.erb
create mode 100644 app/views/projects/show.html.erb
create mode 100644 app/views/projects/update.html.erb
delete mode 100644 test/functional/project_controller_test.rb
create mode 100644 test/functional/projects_controller_test.rb
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 3378a90..51261a0 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -34,7 +34,7 @@ class ApplicationController < ActionController::Base
if @user == nil
session[:original_uri] = request.request_uri
flash[:message] = 'Please login first.'
- redirect_to(:controller => :home, :action => :login)
+ redirect_to login_url
end
end
diff --git a/app/controllers/product_controller.rb b/app/controllers/product_controller.rb
index f5553d3..43c6425 100644
--- a/app/controllers/product_controller.rb
+++ b/app/controllers/product_controller.rb
@@ -51,7 +51,7 @@ class ProductController < ApplicationController
end
else
flash[:message] = 'You are not authorized to create products.'
- redirect_to :controller => :project, :action => :view, :id => @project.id
+ redirect_to project_path(@project)
end
end
diff --git a/app/controllers/project_controller.rb b/app/controllers/project_controller.rb
deleted file mode 100644
index b122159..0000000
--- a/app/controllers/project_controller.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# project_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +ProjectController+ handles requests relating to the management and viewing of
-# +Product+s.
-#
-class ProjectController < ApplicationController
- before_filter :authenticated, :only => [:create, :modify]
- before_filter :load_project, :except => [:create, :index]
- before_filter :setup_for_edit, :only => [:create, :modify]
-
- # Allows viewing of all defined projects.
- #
- def index
- @projects = Project.paginate :page => params[:page], :per_page => 10
- end
-
- # Shows a single project.
- #
- def view
- @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
- end
-
- # Begins the process of creating a new project.
- #
- def create
- if @user.create_projects?
- if request.post?
- begin
- Project.transaction do
- @project = Project.new(params[:project])
- @project.save!
-
- redirect_to :action => :index
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :create
- end
- else
- @project = Project.new
- end
- else
- flash[:message] = 'That action requires administrative rights.'
- redirect_to :controller => :home, :action => :dashboard
- end
- end
-
- # Modifies an existing project.
- #
- def modify
- if @user.can_modify_project?(@project)
- if request.post?
- begin
- Project.transaction do
- @project.update_attributes(params[:project])
- @project.save!
-
- redirect_to :action => :view, :id => @project
- end
- rescue ActiveRecord::RecordInvalid => error
- @project.valid?
- render :action => :modify
- end
- end
- else
- flash[:message] = "You are not authorized to modify that project."
- redirect_to error_path
- end
- end
-
- private
-
- # Loads the requested project.
- #
- def load_project
- @project = Project.find_by_id(params[:id])
-
- unless @project
- flash[:message] = 'Invalid project specified.'
- redirect_to error_path
- end
- end
-
- def setup_for_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
new file mode 100644
index 0000000..6c379b7
--- /dev/null
+++ b/app/controllers/projects_controller.rb
@@ -0,0 +1,142 @@
+# projects_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +ProjectsController+ handles requests relating to the management and viewing
+# of +Product+s.
+#
+class ProjectsController < ApplicationController
+ before_filter :authenticated, :only => [:new, :create, :edit, :update]
+ before_filter :load_project, :except => [:index, :new, :create, :destroy]
+ before_filter :setup_for_edit, :only => [:new, :create, :edit]
+
+ # GET /projects
+ def index
+ @projects = Project.paginate :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /projects/1
+ def show
+ @products = Product.paginate_by_project_id @project.id, :page => params[:page], :per_page => 10
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # get /projects/new
+ def new
+ if @user.create_projects?
+ @project = Project.new(:owner_id => @user.id)
+ else
+ flash[:error] = "You are not authorized to create projects."
+
+ redirect_to error_url
+ end
+ end
+
+ # GET /projects/1;edit
+ def edit
+ unless @project.user_can_modify?(@user)
+ flash[:message] = "You are not authorized to modify that project."
+ redirect_to project_path(@project)
+ end
+ end
+
+ # POST /projects
+ def create
+ respond_to do |format|
+ if @user.create_projects?
+ @project = Project.new(params[:project])
+
+ begin
+ Project.transaction do
+ if @project.save
+ flash[:message] = "Project '#{(a)project.name}' created!"
+ format.html { redirect_to project_url(@project) }
+ else
+ @project.valid?
+ format.html { render :action => "new" }
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to create projects."
+ format.html { redirect_to error_url }
+ end
+ end
+ end
+
+ # PUT /projects/1
+ def update
+ respond_to do |format|
+ if @project.user_can_modify?(@user)
+ begin
+ Project.transaction do
+ @project.update_attributes(params[:project])
+ if @project.save!
+ flash[:message] = "Project was successfully updated."
+ format.html { redirect_to project_path(@project) }
+ else
+ @project.valid?
+ format.html { render :action => 'edit'}
+ end
+ end
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ format.html { redirect_to error_url }
+ end
+ else
+ flash[:error] = "You are not authorized to edit this project."
+ format.html { redirect_to project_path(@project) }
+ end
+ end
+ end
+
+ # DELETE /projects/1
+ def destroy
+ respond_to do |format|
+ flash[:message] = "Project deletion is not current supported."
+ format.html { redirect_to error_url}
+ end
+ end
+
+ private
+
+ # Loads the requested project.
+ #
+ def load_project
+ @project = Project.find_by_id(params[:id])
+
+ unless @project
+ flash[:message] = 'Invalid project specified.'
+ respond_to do |format|
+ format.html { redirect_to error_path }
+ end
+ end
+ end
+
+ def setup_for_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 96d59a7..88cd918 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,19 +1,19 @@
# application_helper.rb
# Copyright (C) 2008, 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/>.
-#
+#
module ApplicationHelper
# Returns the first sentence from the supplied text, up to the first period.
@@ -23,13 +23,13 @@ module ApplicationHelper
if (text == nil) || text.empty?
return " "
end
-
+
return text.slice(/^[^\.]*\.?/)
-
+
end
-
+
# Creates a link to a user.
- #
+ #
def link_to_user(user)
if user
@template.link_to(user.display_name,
@@ -38,9 +38,9 @@ module ApplicationHelper
:id => user)
end
end
-
+
# Creates a link to an owner for a project, product or other item.
- #
+ #
def link_to_owner(owner, args = nil)
if owner
link_to_user(owner)
@@ -49,61 +49,52 @@ module ApplicationHelper
args[:unowned] if args
end
end
-
- # Creates a link to a project.
- #
- def link_to_project(project)
- @template.link_to(project.name,
- :controller => :project,
- :action => :view,
- :id => project)
- end
-
+
# Creates a link to a product.
- #
- def link_to_product(product, options = {})
- @template.link_to(options[:text] ? options[:text] : product.name,
+ #
+ def link_to_product(product)
+ @template.link_to(product.name,
:controller => :product,
- :action => options[:action] ? options[:action] : :view,
+ :action => :view,
:id => product)
end
-
+
# Creates a link to a sprint.
- #
+ #
def link_to_sprint(sprint)
@template.link_to(sprint.title,
:controller => :sprint,
:action => :view,
:id => sprint)
end
-
+
# Displays a link and image reporting the health of the sprint
def show_sprint_health(sprint)
@template.image_tag(
sprint.healthy? ? "/images/icons/healthy.png" : "/images/icons/unhealthy.png",
:alt => sprint.healthy? ? "Sprint is healthy" : "Sprint is not healthy")
end
-
+
# Creates a link to a user story.
- #
+ #
def link_to_user_story(user_story, options ={})
@template.link_to((options[:text] ? options[:text] : user_story.title),
:controller => :user_story,
:action => (options[:action] ? options[:action] : :view),
:id => user_story)
end
-
+
# Creates a link to a backlog item.
- #
+ #
def link_to_backlog_item(backlog_item, options = {})
@template.link_to(options[:text] ? options[:text] : backlog_item.title,
:controller => :backlog,
:action => (options[:action] ? options[:action] : :view),
:id => backlog_item.id)
end
-
+
# Shows the hours for a given object as estimated, actual and remaining.
- #
+ #
def show_hours_as_ear(estimated)
format("%0.1f/%0.1f/%0.1f",estimated.estimated_hours, estimated.actual_hours, estimated.remaining_hours)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2bd9541..6a7ce5d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -21,14 +21,25 @@
class Project < ActiveRecord::Base
validates_presence_of :name,
:message => 'A project has to have a name.'
-
+ validates_length_of :name,
+ :minimum => 4,
+ :message => 'The project name must be at least four characters long.'
validates_uniqueness_of :name,
:message => 'This name is used by an existing project.'
+ validates_presence_of :owner_id,
+ :message => 'A project must have an owner.'
+
belongs_to :owner, :class_name => 'User', :foreign_key => :owner_id
has_many :products
+ # Returns whether the user can modify this project.
+ #
+ def user_can_modify?(user)
+ user && (user.privileges.admin_projects || (user.id == owner.id))
+ end
+
# Returns whether the user can create products for this project.
#
def can_create_products?(user)
diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb
index ac4dd6f..e1ac558 100644
--- a/app/views/layouts/default.html.erb
+++ b/app/views/layouts/default.html.erb
@@ -24,7 +24,7 @@
<div id="user-box">
<%= link_to "Home", :controller => :home %>
- <%= link_to "Projects", :controller => :project, :action => :index %>
+ <%= link_to "Projects", projects_path %>
<%= link_to "Reports", :controller => :report, :action => :index %>
<% if @user %>
<%= link_to @user.display_name, dashboard_url %>
diff --git a/app/views/product/_details.html.erb b/app/views/product/_details.html.erb
index d7e1bda..7ea0794 100644
--- a/app/views/product/_details.html.erb
+++ b/app/views/product/_details.html.erb
@@ -14,7 +14,7 @@
<tr>
<td class="label">For Project:</td>
- <td class="value"><%= link_to_project @product.project %></td>
+ <td class="value"><%= link_to @product.project.name, project_path((a)product.project) %></td>
</tr>
<tr>
diff --git a/app/views/project/_details.html.erb b/app/views/project/_details.html.erb
deleted file mode 100644
index 7dfc64c..0000000
--- a/app/views/project/_details.html.erb
+++ /dev/null
@@ -1,25 +0,0 @@
-<table class="details">
- <thead>
- <tr>
- <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
- </tr>
- </thead>
-
- <tbody>
- <tr>
- <td class="label">Owner:</td>
- <td class="value"><%= link_to_user @project.owner %></td>
- </tr>
-
- <tr>
- <td class="label">Homepage:</td>
- <td class="value"><%= link_to_homepage @project %>
- </tr>
-
- <tr>
- <td class="text" colspan="2">
- <%= simple_format @project.description %>
- </td>
- </tr>
- </tbody>
-</table>
diff --git a/app/views/project/_edit.html.erb b/app/views/project/_edit.html.erb
deleted file mode 100644
index d1b02e9..0000000
--- a/app/views/project/_edit.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<% form_for :project do |form| %>
-
- <% unless @project.new_record? %>
- <%= form.hidden_field :id %>
- <% end %>
-
- <table class="edit">
- <tbody>
- <tr>
- <td class="label">Project name</td>
- <td class="value"><%= form.text_field :name %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :name) %>
- </tr>
-
- <tr>
- <td class="label">Website</td>
- <td class="value"><%= form.text_field :url %></td>
- </tr>
- <tr>
- <td /><td class="errors"><%= error_message_on(:project, :url) %>
- </tr>
-
- <tr>
- <td class="label">Owner</td>
- <td class="value">
- <% if @user.privileges.admin_projects %>
- <%= collection_select :project, :owner_id, @users, :id,
- :display_name, {:include_blank => true} %>
- <% else %>
- <% if @project.owner %>
- <%= h @project.owner.display_name %>
- <% else %>
- UNOWNED
- <% end %>
- <% end %>
- </td>
- <td /><td class="errors"><%= error_message_on(:project, :owner) %>
- </tr>
-
- <tr>
- <td class="label">Description</td>
- <td class="value"><%= form.text_area :description %></td>
- </tr>
-
- <tr>
- <td class="buttons" colspan="2">
- <% if @project.new_record? %>
- <%= submit_tag "Create", :class => 'button' %>
- <% else %>
- <%= submit_tag "Update", :class => 'button' %>
- <% end %>
- </td>
- </tr>
- </tbody>
- </table>
-
-<% end %>
\ No newline at end of file
diff --git a/app/views/project/_product.html.erb b/app/views/project/_product.html.erb
deleted file mode 100644
index 51766eb..0000000
--- a/app/views/project/_product.html.erb
+++ /dev/null
@@ -1,19 +0,0 @@
-<tr>
- <td><%= link_to "#{product.name}",
- {:controller => :product, :action => :view, :id => product} %>
- </td>
- <td>
- <% if product.owner != nil %>
- <%= link_to(product.owner.display_name,
- {:controller => :user,
- :action => :view,
- :id => product.owner})%>
-
- (<%= mail_to(product.owner.email, "email") %>)
- <% else %>
- None (want to become the owner?)
- <% end %>
- </td>
- <td class="description"><%= h product.description %></td>
- <td class="number"><%= product.backlog.size %></td>
-</tr>
diff --git a/app/views/project/create.html.erb b/app/views/project/create.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/create.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/index.html.erb b/app/views/project/index.html.erb
deleted file mode 100644
index d472b78..0000000
--- a/app/views/project/index.html.erb
+++ /dev/null
@@ -1,48 +0,0 @@
-<div class="toolbar">
- <% if @user && @user.create_projects? %>
- <%= link_to "New Project", :action => :create %>
- <% end %>
-</div>
-
-<%= will_paginate @projects %>
-
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
- <thead>
- <tr>
- <th class="title" colspan="5">Projects</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Name</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% if @projects.empty? %>
- <tr><td colspan="5">There are no projects defined.</td></tr>
- <% else %>
- <% @projects.each_with_index do |project, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_project project %></td>
- <td><%= get_first_sentence project.description %></td>
- <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
- <td><%= project.created_at.to_s(:date) %></td>
- </tr>
-
- <% end %>
- <% end %>
- </tbody>
-</table>
diff --git a/app/views/project/modify.html.erb b/app/views/project/modify.html.erb
deleted file mode 100644
index 0ae005d..0000000
--- a/app/views/project/modify.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/project/view.html.erb b/app/views/project/view.html.erb
deleted file mode 100644
index c501d67..0000000
--- a/app/views/project/view.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-<div style="width: 100%; overflow: auto;">
- <div id="details" style="float: left; width: 33%;">
- <div class="toolbar">
- <% if @user && @user.can_modify_project?(@project) %>
- <%= link_to "Edit project...", :action => :modify, :id => @project %>
- <% end %>
- </div>
- <%= render :partial => 'details', :object => @project %>
- </div>
-
- <div id="products" style="float: right; width: 66%;">
- <div class="toolbar">
- <%= will_paginate @products %>
- <% if @project.can_create_products?(@user) %>
- <%= link_to "Create Product...",
- {
- :controller => :product,
- :action => :create,
- :project => @project
- } %>
- <% end %>
- </div>
- <table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="name" />
- <col class="description" />
- <col class="user" />
- <col class="date" />
- </colgroup>
-
- <thead>
- <tr>
- <th>##</th>
- <th>Product</th>
- <th>Description</th>
- <th>Owner</th>
- <th>Created</th>
- </tr>
- </thead>
-
- <tbody>
- <% @products.each_with_index do |product, i| %>
- <% row_class = i%2 == 0 ? 'even' : 'odd' %>
-
- <tr class="<%= row_class %>"
- onmouseover="this.className='hot'"
- onmouseout="this.className='<%= row_class %>' ">
- <td class="row_id"><%= i + 1 %></td>
- <td><%= link_to_product product %></td>
- <td><%= get_first_sentence product.description %></td>
- <td><%= link_to_owner product.owner %></td>
- <td><%= product.created_at.to_s(:date) %></td>
- </tr>
- <% end %>
- </tbody>
- </table>
- </div>
-</div>
diff --git a/app/views/projects/_details.html.erb b/app/views/projects/_details.html.erb
new file mode 100644
index 0000000..9fe7b4b
--- /dev/null
+++ b/app/views/projects/_details.html.erb
@@ -0,0 +1,25 @@
+<table class="details">
+ <thead>
+ <tr>
+ <th class="title" colspan="2"><%= "Details For #{(a)project.name}" %></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td class="label">Owner:</td>
+ <td class="value"><%= link_to_user @project.owner %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Homepage:</td>
+ <td class="value"><%= link_to_homepage @project %>
+ </tr>
+
+ <tr>
+ <td class="text" colspan="2">
+ <%= simple_format @project.description %>
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/app/views/projects/_edit.html.erb b/app/views/projects/_edit.html.erb
new file mode 100644
index 0000000..0b5d409
--- /dev/null
+++ b/app/views/projects/_edit.html.erb
@@ -0,0 +1,58 @@
+<% html = @project.new_record? ? {:method => :post} : {:method => :put} %>
+
+<% form_for :project, @project, :url => project_path(@project), :html => html do |form| %>
+ <table class="edit">
+ <tbody>
+ <tr>
+ <td class="label">Project name</td>
+ <td class="value">
+ <%= form.text_field :name %>
+ <%= error_message_on(:project, :name) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Website</td>
+ <td class="value">
+ <%= form.text_field :url %>
+ <%= error_message_on(:project, :url) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Owner</td>
+ <td class="value">
+ <% if @user.privileges.admin_projects %>
+ <%= collection_select :project, :owner_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <% else %>
+ <% if @project.owner %>
+ <%= h @project.owner.display_name %>
+ <% else %>
+ UNOWNED
+ <% end %>
+ <% end %>
+ <%= error_message_on(:project, :owner) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Description</td>
+ <td class="value">
+ <%= form.text_area :description %>
+ <%= error_message_on(:project, :description) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <% if @project.new_record? %>
+ <%= submit_tag "Create", :class => 'button' %>
+ <% else %>
+ <%= submit_tag "Update", :class => 'button' %>
+ <% end %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<% end %>
\ No newline at end of file
diff --git a/app/views/projects/_product.html.erb b/app/views/projects/_product.html.erb
new file mode 100644
index 0000000..51766eb
--- /dev/null
+++ b/app/views/projects/_product.html.erb
@@ -0,0 +1,19 @@
+<tr>
+ <td><%= link_to "#{product.name}",
+ {:controller => :product, :action => :view, :id => product} %>
+ </td>
+ <td>
+ <% if product.owner != nil %>
+ <%= link_to(product.owner.display_name,
+ {:controller => :user,
+ :action => :view,
+ :id => product.owner})%>
+
+ (<%= mail_to(product.owner.email, "email") %>)
+ <% else %>
+ None (want to become the owner?)
+ <% end %>
+ </td>
+ <td class="description"><%= h product.description %></td>
+ <td class="number"><%= product.backlog.size %></td>
+</tr>
diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/edit.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
new file mode 100644
index 0000000..d4008f5
--- /dev/null
+++ b/app/views/projects/index.html.erb
@@ -0,0 +1,56 @@
+<div class="toolbar">
+ <% if @user && @user.create_projects? %>
+ <%= link_to "New Project", new_project_path %>
+ <% end %>
+</div>
+
+<%= will_paginate @projects %>
+
+<table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ <col class="actions" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th class="title" colspan="6">Projects</th>
+ </tr>
+ <tr>
+ <th>##</th>
+ <th>Name</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% if @projects.empty? %>
+ <tr><td colspan="6">There are no projects defined.</td></tr>
+ <% else %>
+ <% @projects.each_with_index do |project, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to project.name, project_path(project) %></td>
+ <td><%= get_first_sentence project.description %></td>
+ <td><%= link_to_owner project.owner, 'Current Unowned' %></td>
+ <td><%= project.created_at.to_s(:date) %></td>
+ <td>
+ <%= link_to "View", project_path(project) %>
+ <% if project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(project) %>
+ <% end %>
+ </td>
+ </tr>
+
+ <% end %>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/new.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
new file mode 100644
index 0000000..f8fe791
--- /dev/null
+++ b/app/views/projects/show.html.erb
@@ -0,0 +1,59 @@
+<div style="width: 100%; overflow: auto;">
+ <div id="details" style="float: left; width: 33%;">
+ <div class="toolbar">
+ <% if @project.user_can_modify?(@user) %>
+ <%= link_to "Edit", edit_project_path(@project)%>
+ <% end %>
+ </div>
+ <%= render :partial => 'details', :object => @project %>
+ </div>
+
+ <div id="products" style="float: right; width: 66%;">
+ <div class="toolbar">
+ <%= will_paginate @products %>
+ <% if @project.can_create_products?(@user) %>
+ <%= link_to "Create Product...",
+ {
+ :controller => :product,
+ :action => :create,
+ :project => @project
+ } %>
+ <% end %>
+ </div>
+ <table class="list">
+ <colgroup>
+ <col class="row_id" />
+ <col class="name" />
+ <col class="description" />
+ <col class="user" />
+ <col class="date" />
+ </colgroup>
+
+ <thead>
+ <tr>
+ <th>##</th>
+ <th>Product</th>
+ <th>Description</th>
+ <th>Owner</th>
+ <th>Created</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @products.each_with_index do |product, i| %>
+ <% row_class = i%2 == 0 ? 'even' : 'odd' %>
+
+ <tr class="<%= row_class %>"
+ onmouseover="this.className='hot'"
+ onmouseout="this.className='<%= row_class %>' ">
+ <td class="row_id"><%= i + 1 %></td>
+ <td><%= link_to_product product %></td>
+ <td><%= get_first_sentence product.description %></td>
+ <td><%= link_to_owner product.owner %></td>
+ <td><%= product.created_at.to_s(:date) %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/app/views/projects/update.html.erb b/app/views/projects/update.html.erb
new file mode 100644
index 0000000..0ae005d
--- /dev/null
+++ b/app/views/projects/update.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'edit', :object => @project) %>
diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb
index ae21641..ee1119f 100644
--- a/app/views/tasks/new.html.erb
+++ b/app/views/tasks/new.html.erb
@@ -1,5 +1,3 @@
<div>
- <% form_for(:task, @task, :url => task_path(@task)) do |form| %>
- <%= render :partial => 'edit', :object => @task %>
- <% end %>
+ <%= render(:partial => 'edit', :object => @project) %>
</div>
diff --git a/config/routes.rb b/config/routes.rb
index 2f66a65..c1f20fc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,11 +16,12 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :projects
map.resources :tasks
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
- map.root :controller => 'project'
+ map.root :controller => 'home'
# Login URL
map.login "/login", :controller => 'home', :action => 'login'
diff --git a/test/functional/product_controller_test.rb b/test/functional/product_controller_test.rb
index acca9e7..f008cbb 100644
--- a/test/functional/product_controller_test.rb
+++ b/test/functional/product_controller_test.rb
@@ -84,7 +84,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => users(:jdonuts).id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that someone with project admin rights but who isn't the project
@@ -95,7 +95,7 @@ class ProductControllerTest < ActionController::TestCase
{:project => @project.id},
{:user_id => @non_project_owner.id}
- assert_redirected_to :controller => :project, :action => :view, :id => @project.id
+ assert_redirected_to project_url(@project)
end
# Ensures that calling create initially works as expected.
diff --git a/test/functional/project_controller_test.rb b/test/functional/project_controller_test.rb
deleted file mode 100644
index 347e795..0000000
--- a/test/functional/project_controller_test.rb
+++ /dev/null
@@ -1,176 +0,0 @@
-# project_controller_test.rb
-# Copyright (C) 2008, 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'
-
-# +ProjectControllerTest+ performs tests on +ProjectController+ to ensure that
-# it works as expected.
-#
-class ProjectControllerTest < ActionController::TestCase
- fixtures :projects
- fixtures :users
-
- def setup
- @project = projects(:cairo)
- @admin = @project.owner
- @nonadmin = users(:jdonuts)
-
- raise "Admin and non-admin cannot be the same person." if @admin.id == @nonadmin.id
- end
-
- # Ensures that viewing the list of projects works as expected.
- #
- def test_index
- get :index
-
- assert_response :success
- assert assigns['projects']
- end
-
- # Ensures that viewing without a project id shows an error.
- #
- def test_view_without_id
- get :view
-
- assert_redirected_to error_path
- end
-
- # Ensures that viewing a project works as expected.
- #
- def test_view
- get :view, {:id => @project.id}
-
- assert_response :success
- assert_equal 2, assigns['products'].size,
- 'Did not load the set of products.'
- end
-
- # Ensures that an anonymous user cannot create a new project.
- #
- def test_create_for_anonymous_user
- get :create
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who does not have project admin rights cannot create a
- # new project.
- #
- def test_create_for_non_admin
- get :create,
- nil,
- {:user_id => @nonadmin.id}
-
- assert_redirected_to :controller => :home, :action => :dashboard
- end
-
- # Ensures that create works as expected.
- #
- def test_create
- get :create,
- {},
- {:user_id => @admin.id}
-
- assert_response :success
- assert assigns['project']
- assert assigns['users']
- end
-
- # Ensures the controller redisplays the edit form when the project is invalid.
- #
- def test_create_as_save_with_invalid_project
- post :create, {:project => {:owner_id => @admin.id}}, {:user_id => @admin.id}
-
- assert_response :success
- assert_template 'project/create'
- end
-
- # Ensures that saving a project works as expected.
- #
- def test_create_as_save
- project_details = {
- :name => 'project1',
- :owner_id => @admin.id,
- :url => 'http://www.project.org/',
- :description => 'This is a new project'
- }
-
- post :create,
- {:project => project_details},
- {:user_id => @admin.id}
-
- assert_redirected_to :action => :index
-
- result = Project.find_by_name(project_details[:name])
-
- assert result, 'Project was not saved.'
- end
-
- # Ensures that anonymous users cannot modify a project.
- #
- def test_modify_for_anonymous_user
- get :modify
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that modifying an invalid project results in an error.
- #
- def test_modify_for_invalid_project
- post :modify,
- {:id => 9999},
- {:user_id => @admin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that unauthorized users cannot modify a project.
- #
- def test_modify_for_unauthorized_user
- get :modify,
- {:id => @project.id},
- {:user_id => @nonadmin.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that starting to modify a project works as expected.
- #
- def test_modify
- get :modify,
- {:id => @project.id},
- {:user_id => @project.owner_id}
-
- assert_response :success
- end
-
- # Ensures that modifying an existing project works as expected.
- #
- def test_modify_as_save
- project_details = {:name => 'newname1'}
-
- post :modify,
- {:id => @project.id, :project => project_details},
- {:user_id => @project.owner_id}
-
- assert_redirected_to :action => :view
-
- result = Project.find_by_id((a)project.id)
-
- assert_equal project_details[:name], result.name, 'Project was not modified.'
- end
-end
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
new file mode 100644
index 0000000..2e3e303
--- /dev/null
+++ b/test/functional/projects_controller_test.rb
@@ -0,0 +1,233 @@
+# projects_controller_test.rb
+# Copyright (C) 2008, 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'
+
+# +ProjectsControllerTest+ performs tests on +ProjectController+ to ensure that
+# it works as expected.
+#
+class ProjectsControllerTest < ActionController::TestCase
+ fixtures :projects
+ fixtures :users
+
+ def setup
+ @project = projects(:cairo)
+ @owner = @project.owner
+ @nonowner = users(:jdonuts)
+ raise "Owner and nonowner cannot be the same user!" if @owner.id == @nonowner.id
+
+ @nonadmin = @nonowner
+ @admin = users(:project_admin)
+ raise "Nonadmin and admin cannot be the same user!" if @nonadmin.id == @admin.id
+ end
+
+ # Ensures that showing the project index works as expected.
+ #
+ def test_index
+ get :index
+
+ assert_response :success
+ assert assigns['projects'], "Didn't load any projects."
+ end
+
+ # Ensures that showing a single project requires an id.
+ #
+ def test_show_without_id
+ get :show
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that showing a project fails on invalid ids.
+ #
+ def test_show_with_invalid_id
+ get :show, {:id => 9999}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures tha show a project works as expected.
+ #
+ def test_show
+ get :show, {:id => @project.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load the project."
+ assert_equal @project.id, assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a new project.
+ #
+ def test_new_as_anonymous
+ get :new
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user without project admin privileges cannot create a
+ # project.
+ #
+ def test_new_as_unauthorized_user
+ get :new, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that starting off a new project works as expected.
+ #
+ def test_new
+ get :new, {}, {:user_id => @admin.id}
+
+ assert_response :success
+ assert assigns['project'], "Did not create a new project."
+ assert_equal @admin.id, assigns['project'].owner_id,
+ "Did not assign the default owner."
+ assert assigns['users'], "Did not load the list of users."
+ end
+
+ # Ensures an anonymous user cannot start editing a project.
+ #
+ def test_edit_as_anonymous
+ get :edit
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that edit fails on an invalid id.
+ #
+ def test_edit_with_invalid_id
+ get :edit,
+ {:id => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonowner cannot edit a project.
+ #
+ def test_edit_as_nonowner
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_edit
+ get :edit,
+ {:id => @project.id},
+ {:user_id => @owner.id}
+
+ assert_response :success
+ assert assigns['project'], "Failed to load a project."
+ assert_equal @project.id,assigns['project'].id,
+ "Failed to load the correct project."
+ end
+
+ # Ensures that an anonymous user cannot create a project.
+ #
+ def test_create_as_anonymous
+ post :create
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a non-admin cannot create a project.
+ #
+ def test_create_as_nonadmin
+ post :create, {}, {:user_id => @nonadmin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an invalid project returns the user to the edit page.
+ #
+ def test_create_with_invalid_project
+ post :create,
+ {:project => {}},
+ {:user_id => @admin.id}
+
+ assert_response :success
+ end
+
+ # Ensures that a project is created when one is submitted.
+ #
+ def test_create
+ post :create,
+ {:project => {:name => 'test', :owner_id => @admin.id}},
+ {:user_id => @admin.id}
+
+ assert_redirected_to project_path(assigns['project'])
+ end
+
+ # Ensures that an anonymous user cannot update a project.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that a valid project id must be provided.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @admin.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a nonadmin cannot update a project.
+ #
+ def test_update_by_nonowner
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal @project.name, result.name,
+ "Project should not have been updated!"
+ end
+
+ # Ensures that the owner can edit a project.
+ #
+ def test_update
+ put :update,
+ {:id => @project.id,
+ :project => {:name => 'newname'}},
+ {:user_id => @owner.id}
+
+ assert_redirected_to project_path(@project)
+ result = Project.find_by_id((a)project.id)
+ assert_equal 'newname', result.name,
+ "Project should have been updated!"
+ end
+
+ # Ensures that the destroy method just exists.
+ #
+ def test_destroy
+ delete :destroy
+
+ assert_redirected_to error_url
+ end
+end
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Refactored the tasks controller to be RESTful.
by Darryl L. Pierce
Renamed TaskController and TaskControllerTest to TasksController and
TasksControllerTest. Added a new resource to routes.rb to provide
RESTful methods for working with Task objects. Updated all of the unit
and functional tests and renamed and refactored the actions in the
controller.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/backlog_controller.rb | 1 +
app/controllers/task_controller.rb | 127 ------------
app/controllers/tasks_controller.rb | 167 ++++++++++++++++
app/helpers/application_helper.rb | 9 -
app/models/task.rb | 71 +++++--
app/views/backlog/view.html.erb | 66 +------
app/views/task/_edit.html.erb | 47 -----
app/views/task/create.html.erb | 3 -
app/views/task/modify.html.erb | 1 -
app/views/tasks/_edit.html.erb | 66 +++++++
app/views/tasks/_list.html.erb | 34 ++++
app/views/tasks/edit.html.erb | 1 +
app/views/tasks/index.html.erb | 1 +
app/views/tasks/new.html.erb | 3 +
app/views/tasks/show.html.erb | 35 ++++
config/routes.rb | 5 +
public/stylesheets/forms.css | 5 +
test/functional/task_controller_test.rb | 233 ----------------------
test/functional/tasks_controller_test.rb | 313 ++++++++++++++++++++++++++++++
test/unit/product_test.rb | 3 +-
test/unit/task_test.rb | 121 +++++++++++-
21 files changed, 806 insertions(+), 506 deletions(-)
delete mode 100644 app/controllers/task_controller.rb
create mode 100644 app/controllers/tasks_controller.rb
delete mode 100644 app/views/task/_edit.html.erb
delete mode 100644 app/views/task/create.html.erb
delete mode 100644 app/views/task/modify.html.erb
create mode 100644 app/views/tasks/_edit.html.erb
create mode 100644 app/views/tasks/_list.html.erb
create mode 100644 app/views/tasks/edit.html.erb
create mode 100644 app/views/tasks/index.html.erb
create mode 100644 app/views/tasks/new.html.erb
create mode 100644 app/views/tasks/show.html.erb
delete mode 100644 test/functional/task_controller_test.rb
create mode 100644 test/functional/tasks_controller_test.rb
diff --git a/app/controllers/backlog_controller.rb b/app/controllers/backlog_controller.rb
index 3e6ca56..6011264 100644
--- a/app/controllers/backlog_controller.rb
+++ b/app/controllers/backlog_controller.rb
@@ -28,6 +28,7 @@ class BacklogController < ApplicationController
@task = Task.new
@task.primary = @user if @user
@users = User.find(:all, :conditions => "id in (select user_id from product_roles where product_id = #{(a)product.id})")
+ @tasks = @backlog_item.tasks
end
def modify
diff --git a/app/controllers/task_controller.rb b/app/controllers/task_controller.rb
deleted file mode 100644
index 6c6e42b..0000000
--- a/app/controllers/task_controller.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# task_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +TaskController+ allows the user to create and modify tasks for a backlog
-# item.
-#
-class TaskController < ApplicationController
- before_filter :authenticated, :except => [:view]
- before_filter :load_backlog_item, :only => [:create]
- before_filter :load_task, :only => [:modify, :delete]
- before_filter :authorized_to_create, :only => [:create]
- before_filter :authorized_to_manage, :only => [:modify, :delete]
- before_filter :before_edit, :only => [:create, :modify]
- before_filter :ensure_sprint_is_active, :only => [:create]
-
- def create
- if request.post?
- begin
- Task.transaction do
- @task = Task.create(params[:task])
- @task.backlog_item = @backlog_item
- @backlog_item.tasks << @task
- @backlog_item.save!
- @task.save!
-
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
- end
-
- rescue ActiveRecord::RecordInvalid => error
- flash[:message] = error.message
- @task.valid?
- render :action => :create
- end
- else
- @task = Task.new(:backlog_item => @backlog_item, :primary => @user)
- end
- end
-
- def modify
- if request.post?
- @task.update_attributes(params[:task])
-
- @task.save
-
- redirect_to :controller => :backlog, :action => :view, :id => @task.backlog_item.id
- end
- end
-
- def delete
- if request.post?
- begin
- Task.transaction do
- @task.destroy
- end
-
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
-
- rescue Exception => error
- flash[:message] = error.message
-
- redirect_to error_path
- end
- else
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-
- private
-
- def load_backlog_item
- @backlog_item = BacklogItem.find_by_id(params[:backlog_item])
-
- unless @backlog_item
- flash[:message] = 'Invalid user id.'
- redirect_to error_path
- end
- end
-
- def load_task
- @task = Task.find_by_id(params[:id])
- @backlog_item = @task.backlog_item if @task
-
- unless @task
- flash[:message] = 'Invalid task id.'
- redirect_to error_path
- end
- end
-
- def authorized_to_create
- unless TaskHelper.can_create_task(@user,@backlog_item)
- flash[:message] = 'You are not authorized to work on tasks for this user story.'
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-
- def authorized_to_manage
- unless TaskHelper.can_manage_task(@user,@task)
- flash[:message] = 'You are not authorized to modify this task.'
- redirect_to :controller => :backlog, :action => :view, :id => @task.backlog_item.id
- end
- end
-
- def before_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-
- def ensure_sprint_is_active
- unless @backlog_item.sprint.status == Sprint::STATUS_ACTIVE
- flash[:message] = 'The sprint must be active for that action.'
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-end
diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb
new file mode 100644
index 0000000..af3aec0
--- /dev/null
+++ b/app/controllers/tasks_controller.rb
@@ -0,0 +1,167 @@
+# tasks_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +TasksController+ allows the user to create and modify tasks for a backlog
+# item.
+#
+class TasksController < ApplicationController
+ before_filter :authenticated, :except => [:index, :show]
+ before_filter :load_backlog_item, :only => [:index, :new, :create]
+ before_filter :load_task, :only => [:show, :edit, :update, :destroy]
+ before_filter :before_edit, :only => [:create, :update]
+ before_filter :ensure_sprint_is_active, :only => [:create]
+
+ # GET /tasks
+ def index
+ @tasks = Task.find_all_by_backlog_item_id((a)backlog_item.id)
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /tasks/1
+ def show
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /tasks/new
+ def new
+ if @backlog_item.user_can_add_tasks?(@user)
+ @task = Task.new(
+ :backlog_item_id => @backlog_item.id,
+ :primary_id => @user.id
+ )
+ @users = User.find(:all, :order => 'display_name ASC')
+ else
+ flash[:error] = 'You are not allowed to add tasks to this backlog item.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ # GET /tasks/1;edit
+ def edit
+ unless @task.user_can_edit?(@user)
+ flash[:error] = "You are not allowd to edit this task."
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ # POST /tasks
+ def create
+ if @backlog_item.user_can_add_tasks?(@user)
+ Task.transaction do
+ @task = Task.new(params[:task])
+ @task.backlog_item = @backlog_item
+
+ respond_to do |format|
+ if @task.save
+ flash[:message] = "Recorded #{(a)task.hours} against this backlog item."
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ else
+ format.html { render :action => 'new' }
+ end
+ end
+ end
+ else
+ flash[:error] = "You are not allowed to add tasks to this backlog item."
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ # PUT /tasks/1
+ def update
+ if @task.user_can_edit?(@user)
+ begin
+ Task.transaction do
+ @task.update_attributes(params[:task])
+ @task.save!
+ end
+
+ flash[:message] = "Task updated."
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ end
+
+ respond_to do |format|
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ end
+ else
+ flash[:error] = "You are not allowed to update that task."
+
+ @task.valid?
+
+ respond_to do |format|
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ end
+ end
+ end
+
+ # DELETE /tasks/1
+ def destroy
+ if @task.user_can_delete?(@user)
+ begin
+ Task.transaction do
+ @task.destroy
+ end
+
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ redirect_to error_url
+ end
+ else
+ flash[:error] = 'You are not allowed to delete this task.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ private
+
+ def load_backlog_item
+ @backlog_item = BacklogItem.find_by_id(params[:backlog_item])
+
+ unless @backlog_item
+ flash[:message] = 'Invalid user id.'
+ redirect_to error_path
+ end
+ end
+
+ def load_task
+ @task = Task.find_by_id(params[:id])
+ @backlog_item = @task.backlog_item if @task
+
+ unless @task
+ flash[:message] = 'Invalid task id.'
+ redirect_to error_path
+ end
+ end
+
+ def before_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ def ensure_sprint_is_active
+ unless @backlog_item.sprint.status == Sprint::STATUS_ACTIVE
+ flash[:message] = 'The sprint must be active for that action.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f20df88..96d59a7 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -102,15 +102,6 @@ module ApplicationHelper
:id => backlog_item.id)
end
- # Creates a link to a task.
- #
- def link_to_task(task, options = {})
- @template.link_to(options[:text] ? options[:text] : task.id,
- :controller => :task,
- :action => options[:action] ? options[:action] : :view,
- :id => task.id)
- end
-
# Shows the hours for a given object as estimated, actual and remaining.
#
def show_hours_as_ear(estimated)
diff --git a/app/models/task.rb b/app/models/task.rb
index 89af73d..7785b4d 100644
--- a/app/models/task.rb
+++ b/app/models/task.rb
@@ -1,39 +1,74 @@
# task.rb
# Copyright (C) 2008, 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 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.
+# 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/>.
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
#
# A +Task+ represents a single piece of work associated with a +UserStory+.
-# Tasks have a primary developer, and an optional backup developer.
+# Tasks have a primary developer, and an optional backup developer.
class Task < ActiveRecord::Base
validates_presence_of :backlog_item_id
belongs_to :primary, :class_name => 'User', :foreign_key => 'primary_id'
belongs_to :backup, :class_name => 'User', :foreign_key => 'backup_id'
+ validates_presence_of :primary_id,
+ :message => 'A primary developer must be defined.'
+
validates_presence_of :description,
- :message => 'You need to describe this task.'
+ :message => 'You need to describe this task.'
validates_length_of :description,
- :maximum => 1000,
- :too_long => 'Please shorten the description.'
+ :maximum => 1000,
+ :too_long => 'Please shorten the description.'
- validates_presence_of :hours,
- :message => 'You must report the hours worked on this task.'
+ validates_presence_of :hours,
+ :message => 'You must report the hours worked on this task.'
validates_numericality_of :hours,
- :greater_than => 0,
- :message => 'Hours must be numeric and greater than 0.'
+ :greater_than => 0,
+ :message => 'Hours must be numeric and greater than 0.'
+
+ validates_presence_of :when,
+ :message => 'You must specify when the task was performed.'
belongs_to :backlog_item
+
+ # Returns whether the user can edit the task.
+ #
+ def user_can_edit?(user)
+ user &&
+ backlog_item.sprint.active? &&
+ (primary && (primary.id == user.id))
+ end
+
+ # Returns whether the user can delete the task or not.
+ #
+ def user_can_delete?(user)
+ user &&
+ (backlog_item.owner && (backlog_item.owner.id == user.id)) &&
+ backlog_item.sprint.active?
+ end
+
+ protected
+
+ def validate
+ if primary && backup && (primary.id == backup.id)
+ errors.add('backup_id',"The backup developer cannot also be #{primary.display_name}.")
+ end
+ if backlog_item
+ unless (backlog_item.sprint.start...(backlog_item.sprint.start + backlog_item.sprint.duration)).include? self.when
+ errors.add('when', 'Task must be done during the sprint.')
+ end
+ end
+ end
end
diff --git a/app/views/backlog/view.html.erb b/app/views/backlog/view.html.erb
index c02f27e..8ee0333 100644
--- a/app/views/backlog/view.html.erb
+++ b/app/views/backlog/view.html.erb
@@ -63,67 +63,5 @@
</tbody>
</table>
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="description" />
- <col class="user" />
- <col class="user" />
- <col class="hours" />
- <col class="date" />
- <col class="actions" />
- </colgroup>
-
- <thead>
- <tr>
- <th colspan="7" class="title">Tasks</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Description</th>
- <th>Primary</th>
- <th>Backup</th>
- <th>Hours</th>
- <th>Date</th>
- <th>Action</th>
- </tr>
- </thead>
-
- <tbody>
-
- <% if @backlog_item.user_can_add_tasks?(@user) %>
- <% form_for :task, :url => {:controller => :task, :action => :create} do |form| %>
- <%= hidden_field_tag :backlog_item, @backlog_item.id %>
- <tr>
- <td></td>
- <td><%= form.text_field :description %></td>
- <td><%= collection_select :task, :primary_id, @users, :id,
- :display_name, {:include_blank => true} %></td>
- <td><%= collection_select :task, :backup_id, @users, :id,
- :display_name, {:include_blank => true} %></td>
- <td><%= form.text_field :hours %></td>
- <td><%= form.date_select :when %></td>
- <td><%= submit_tag "Create" %></td>
- </tr>
- <% end %>
- <% end %>
-
- <% @backlog_item.tasks.each_with_index do |task,i| %>
- <tr class="<% row_class = i%2 == 0 ? 'even' : 'odd' %>">
-
- <td><%= link_to_task task, :action => :modify %></td>
- <td><%= simple_format task.description %></td>
- <td><%= link_to_user task.primary %></td>
- <td><%= link_to_user task.backup %></td>
- <td><%= task.hours %></td>
- <td><%= task.when.to_s(:date) %></td>
- <td>
- <% form_for :task, :url => {:controller => :task, :action => :delete} do |f| %>
- <%= hidden_field_tag :id, task.id %>
- <%= submit_tag "Delete" %>
- <% end %>
- </td>
- </tr>
- <% end %>
- </tbody>
-</table>
+<%= link_to "Add Task", new_task_path(:backlog_item => @backlog_item) %>
+<%= render(:partial => 'tasks/list', :object => @backlog_item.tasks) %>
diff --git a/app/views/task/_edit.html.erb b/app/views/task/_edit.html.erb
deleted file mode 100644
index 4195c45..0000000
--- a/app/views/task/_edit.html.erb
+++ /dev/null
@@ -1,47 +0,0 @@
-<div>
- <% form_for :task do |form| %>
-
- <%= hidden_field_tag :backlog_item, @task.backlog_item.id %>
-
- <table class="edit">
- <tbody>
- <tr>
- <td class="label">Primary:</td>
- <td class="value">
- <%= collection_select :task, :primary_id, @users, :id,
- :display_name, {:include_blank => true} %>
- </td>
- </tr>
-
- <tr>
- <td class="label">Backup:</td>
- <td class="value">
- <%= collection_select :task, :backup_id, @users, :id,
- :display_name, {:include_blank => true} %>
- </td>
- </tr>
-
- <tr>
- <td class="label">Hours:</td>
- <td class="value"><%= form.text_field :hours %></td>
- </tr>
-
- <tr>
- <td class="label">Description:</td>
- <td class="value"><%= form.text_area :description %></td>
- </tr>
-
- <tr>
- <td class="buttons" colspan="2">
- <% if @task.new_record? %>
- <%= submit_tag "Create", :class => 'button' %>
- <% else %>
- <%= submit_tag "Update", :class => 'button' %>
- <% end %>
- </td>
- </tr>
- </tbody>
- </table>
-
- <% end %>
-</div>
\ No newline at end of file
diff --git a/app/views/task/create.html.erb b/app/views/task/create.html.erb
deleted file mode 100644
index ad1a69b..0000000
--- a/app/views/task/create.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<div>
- <%= render :partial => 'edit', :object => @task %>
-</div>
\ No newline at end of file
diff --git a/app/views/task/modify.html.erb b/app/views/task/modify.html.erb
deleted file mode 100644
index e536180..0000000
--- a/app/views/task/modify.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render :partial => "edit", :object => @task %>
diff --git a/app/views/tasks/_edit.html.erb b/app/views/tasks/_edit.html.erb
new file mode 100644
index 0000000..5fe80e8
--- /dev/null
+++ b/app/views/tasks/_edit.html.erb
@@ -0,0 +1,66 @@
+<div>
+ <% form_for(:task, @task, :url => tasks_path) do |form| %>
+
+ <%= hidden_field_tag :backlog_item, @task.backlog_item.id %>
+
+ <table class="edit">
+ <tbody>
+ <tr>
+ <td class="label">When</td>
+ <td class="value">
+ <%= form.date_select :when %>
+ <%= error_message_on(:task, :when) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Primary</td>
+ <td class="value">
+ <%= collection_select :task, :primary_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <%= error_message_on(:task, :primary_id) %>
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Backup</td>
+ <td class="value">
+ <%= collection_select :task, :backup_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <%= error_message_on(:task, :backup_id) %>
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Hours</td>
+ <td class="value">
+ <%= form.text_field :hours %>
+ <%= error_message_on(:task, :hours) %>
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Description</td>
+ <td class="value">
+ <%= form.text_area :description %>
+ <%= error_message_on(:task, :description) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <% if @task.new_record? %>
+ <%= submit_tag "Create", :class => 'button' %>
+ <% else %>
+ <%= submit_tag "Update", :class => 'button' %>
+ <% end %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <% end %>
+</div>
diff --git a/app/views/tasks/_list.html.erb b/app/views/tasks/_list.html.erb
new file mode 100644
index 0000000..273296e
--- /dev/null
+++ b/app/views/tasks/_list.html.erb
@@ -0,0 +1,34 @@
+<table class="list">
+ <thead>
+ <tr>
+ <th>Date</th>
+ <th>Description</th>
+ <th>Primary</th>
+ <th>Backup</th>
+ <th>Hours</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @tasks.each_with_index do |task, index| %>
+ <% row_class = index%2 == 0 ? 'even' : 'odd' %>
+ <tr class="<%= row_class %>">
+ <td><%= task.when %></td>
+ <td><%= simple_format task.description %></td>
+ <td><%= link_to_user task.primary %></td>
+ <td><%= link_to_user task.backup %></td>
+ <td><%= format "%0.2f", task.hours %>
+ <td>
+ <%= link_to "View", task_path(task) %>
+ <% if task.user_can_edit?(@user) %>
+ <%= link_to "Edit", edit_task_path(task) %>
+ <% end %>
+ <% if task.user_can_delete?(@user) %>
+ <%= link_to "Delete", task_path(task), :confirm => "Are you sure?", :method => :delete %>
+ <% end %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/tasks/edit.html.erb b/app/views/tasks/edit.html.erb
new file mode 100644
index 0000000..e536180
--- /dev/null
+++ b/app/views/tasks/edit.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "edit", :object => @task %>
diff --git a/app/views/tasks/index.html.erb b/app/views/tasks/index.html.erb
new file mode 100644
index 0000000..0a9c778
--- /dev/null
+++ b/app/views/tasks/index.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'list') %>
diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb
new file mode 100644
index 0000000..51d818d
--- /dev/null
+++ b/app/views/tasks/new.html.erb
@@ -0,0 +1,3 @@
+<div>
+ <%= render :partial => 'edit', :object => @task %>
+</div>
diff --git a/app/views/tasks/show.html.erb b/app/views/tasks/show.html.erb
new file mode 100644
index 0000000..fb0cfb9
--- /dev/null
+++ b/app/views/tasks/show.html.erb
@@ -0,0 +1,35 @@
+<table class="details">
+ <tr>
+ <td class="label">Description:</td>
+ <td class="value"><%= simple_format @task.description %></td>
+ </tr>
+
+ <tr>
+ <td class="label">When:</td>
+ <td class="value"><%= @task.when %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Hours:</td>
+ <td class="value"><%= format "%0.2f", @task.hours %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Primary:</td>
+ <td class="value"><%= link_to_user @task.primary %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Backup:</td>
+ <td class="label"><%= link_to_user @task.backup %></td>
+ </tr>
+
+</table>
+
+<%= link_to "Back", :controller => :backlog, :action => :view, :id => @task.backlog_item %>
+<% if @task.user_can_edit?(@user) %>
+ <% link_to "Edit", edit_task_path(@task) %>
+<% end %>
+<% if @task.user_can_delete?(@user) %>
+ <%= link_to "Delete", task_path(@task), :confirm => "Are you sure?", :method => :delete %>
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 874f8e9..2f66a65 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,10 +16,15 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :tasks
+
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
map.root :controller => 'project'
+ # Login URL
+ map.login "/login", :controller => 'home', :action => 'login'
+
# Error handling
map.error "/error", :controller => 'home', :action => 'error'
diff --git a/public/stylesheets/forms.css b/public/stylesheets/forms.css
index 99073f4..1809889 100644
--- a/public/stylesheets/forms.css
+++ b/public/stylesheets/forms.css
@@ -1,3 +1,8 @@
+.formError {
+ color: #ff0000;
+ font-weight: bold;
+}
+
table.edit:before {
color: #ff0000;
content: "<p>* indicates a required field.</p>"
diff --git a/test/functional/task_controller_test.rb b/test/functional/task_controller_test.rb
deleted file mode 100644
index 92551da..0000000
--- a/test/functional/task_controller_test.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-# task_controller_test.rb
-# Copyright (C) 2008, 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 TaskControllerTest < ActionController::TestCase
- fixtures :backlog_items
- fixtures :users
-
- def setup
- @backlog_item = backlog_items(:owned_backlog_item)
- @task = @backlog_item.tasks.first
- @owner = @backlog_item.owner
- @non_owner = users(:jdonuts)
- @inactive_sprint_item = backlog_items(:owned_inactive_sprint_backlog_item)
-
- flunk "FIXTURES ARE BROKEN" if @owner.id == @non_owner.id
-
- @details = {
- :primary_id => @backlog_item.owner.id,
- :description => 'This is a new task.',
- :hours => '2.5',
- :when => Date.today
- }
- end
-
- # Ensures that an invalid user story causes create to fail.
- #
- def test_create_with_invalid_user_story
- get :create,
- nil,
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
-
- get :create,
- {:user_story => 9999},
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that an anonymous user cannot create a task.
- #
- def test_create_for_anonymous
- get :create,
- {:backlog_item => @backlog_item.id}
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who doesn't have authorization aren't able to create
- # tasks.
- #
- def test_create_for_unauthorized_user
- get :create, {:backlog_item => @backlog_item.id}, {:user_id => users(:jdonuts).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that tasks cannot be created for a backlog item on an non-active
- # sprint.
- #
- def test_create_for_nonactive_sprint
- count = @inactive_sprint_item.tasks.size
-
- get :create,
- {:backlog_item => @inactive_sprint_item.id},
- {:user_id => @inactive_sprint_item.owner.id}
-
- assert_redirected_to :controller => :backlog, :action => :view, :id => @inactive_sprint_item.id
- result = BacklogItem.find_by_id((a)inactive_sprint_item.id)
- assert_equal count, result.tasks.size, 'Tasks should not have been updated'
- end
-
- # Ensures that creating a task works as expected.
- #
- def test_create
- get :create,
- {
- :backlog_item => @backlog_item.id,
- :task => @details
- },
- {:user_id => users(:mcpierce).id}
-
- assert_response :success
- assert assigns['task'], 'Did not create a new task.'
- assert assigns['users'], 'Did not load users.'
- end
-
- # Ensures that tasks are saved corrected.
- #
- def test_create_as_save
- post :create,
- {
- :backlog_item => @backlog_item.id,
- :task => @details
- },
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that attempting to modify an invalid task fails.
- #
- def test_modify_with_invalid_task
- get :modify, nil, {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
-
- get :modify, {:id => 9999}
-
- assert_redirected_to error_path
- end
-
- # Ensures that anonymous users cannot modify tasks.
- #
- def test_modify_for_anonymous
- get :modify, {:id => tasks(:created_login_view).id}
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who's not the task owner or backup cannot modify the
- # task.
- #
- def test_modify_for_non_developer
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => users(:project_admin).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that the owner of the user story is allowed to edit a task against
- # that user story.
- #
- def test_modify_for_user_story_owner
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => users(:mcpierce).id}
-
- assert_response :success
- end
-
- # Ensures that modifying works as expected.
- #
- def test_modify
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => tasks(:created_login_view).primary_id}
-
- assert_response :success
- assert assigns['task'], 'Did not load the task.'
- assert_equal tasks(:created_login_view).id, assigns['task'].id, 'Did not load the right task.'
- assert assigns['users'], 'Did not load the series.'
- end
-
- # Ensures that saving a task after modifying it works as expected.
- #
- def test_modify_as_save
- task = tasks(:created_login_view).attributes
-
- post :modify,
- {:id => tasks(:created_login_view), :task => task},
- {:user_id => tasks(:created_login_view).primary_id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that anonymous users can't delete tasks
- #
- def test_delete_as_anonymous
- get :delete
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that deletion requires a valid task id.
- #
- def test_delete_with_invalid_id
- post :delete,
- {:id => 9999},
- {:user_id => @owner.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that someone who is not the backlog item owner can't add tasks to
- # it.
- #
- def test_delete_as_non_owner
- post :delete,
- {
- :id => @task.id
- },
- {
- :user_id => @non_owner.id
- }
-
- assert Task.find_by_id((a)task.id), 'Task should not have been deleted.'
- end
-
- # Ensures that the primary can delete a task.
- #
- def test_delete
- post :delete,
- {
- :id => @task.id
- },
- {:user_id => @owner.id}
-
- assert_redirected_to(
- :controller => :backlog,
- :action => :view,
- :id => @task.backlog_item.id)
- assert_equal nil, Task.find_by_id((a)task.id), 'Did not delete the task.'
- end
-end
diff --git a/test/functional/tasks_controller_test.rb b/test/functional/tasks_controller_test.rb
new file mode 100644
index 0000000..270b308
--- /dev/null
+++ b/test/functional/tasks_controller_test.rb
@@ -0,0 +1,313 @@
+# tasks_controller_test.rb
+# Copyright (C) 2008, 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 TasksControllerTest < ActionController::TestCase
+ fixtures :backlog_items
+ fixtures :users
+
+ def setup
+ @backlog_item = backlog_items(:owned_backlog_item)
+ @task = @backlog_item.tasks.first
+ @owner = @backlog_item.owner
+ @nonowner = users(:jdonuts)
+ raise "Owner and nonowner cannot be the same person!" if @owner.id == @nonowner.id
+
+ @inactive_sprint_item = backlog_items(:owned_inactive_sprint_backlog_item)
+
+ @details = {
+ :primary_id => @backlog_item.owner.id,
+ :description => 'This is a new task.',
+ :hours => '2.5',
+ :when => Date.today
+ }
+ end
+
+ # Ensures that viewing tasks when no backlog item is specified fails.
+ #
+ def test_index_without_backlog_item
+ get :index
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that getting the list of tasks for a backlog item works as expected.
+ #
+ def test_index
+ get :index, {:backlog_item => @backlog_item.id}
+
+ assert_response :success
+ assert assigns['backlog_item'], "Failed to load the backlog item."
+ assert_equal @backlog_item.id, assigns['backlog_item'].id,
+ "Did not load the right backlog item."
+ assert assigns['tasks'], "Failed to load the tasks."
+ end
+
+ # Ensures that an error is show if no task id is present.
+ #
+ def test_show_without_task_id
+ get :show
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that creating a task requires the user to be logged in.
+ #
+ def test_new_for_anonymous
+ get :new
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that creating a task without a backlog item fails.
+ #
+ def test_new_without_backlog_item
+ get :new, nil, {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a non-owner cannot create tasks for a backlog item.
+ #
+ def test_new_for_nonowner
+ get :new,
+ {:backlog_item => @backlog_item.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item.id
+ end
+
+ # Ensures that creating a task works as expected.
+ #
+ def test_new
+ get :new,
+ {:backlog_item => @backlog_item.id},
+ {:user_id => @owner.id}
+
+ assert_response :success
+ assert assigns['task'], "Didn't create a task."
+ assert assigns['users'], "Didn't load the list of users."
+ assert_equal @backlog_item.id, assigns['task'].backlog_item_id,
+ "Didn't set the backlog item correctly."
+ assert_equal @owner.id, assigns['task'].primary_id,
+ "Didn't set the primary developer correctly."
+ end
+
+ # Ensures that attempting to modify an invalid task fails.
+ #
+ def test_edit_with_invalid_task
+ get :edit, nil, {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+
+ get :edit, {:id => 9999}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that anonymous users cannot modify tasks.
+ #
+ def test_edit_for_anonymous
+ get :edit, {:id => tasks(:created_login_view).id}
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user who's not the task owner or backup cannot modify the
+ # task.
+ #
+ def test_edit_for_non_developer
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => users(:project_admin).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view
+ end
+
+ # Ensures that the owner of the user story is allowed to edit a task against
+ # that user story.
+ #
+ def test_edit_for_user_story_owner
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => users(:mcpierce).id}
+
+ assert_response :success
+ end
+
+ # Ensures that modifying works as expected.
+ #
+ def test_edit
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => tasks(:created_login_view).primary_id}
+
+ assert_response :success
+ assert assigns['task'], 'Did not load the task.'
+ assert_equal tasks(:created_login_view).id, assigns['task'].id, 'Did not load the right task.'
+ assert assigns['users'], 'Did not load the series.'
+ end
+
+ # Ensures that an invalid user story causes create to fail.
+ #
+ def test_create_with_invalid_user_story
+ get :create,
+ nil,
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+
+ get :create,
+ {:user_story => 9999},
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that an anonymous user cannot create a task.
+ #
+ def test_create_for_anonymous
+ get :create,
+ {:backlog_item => @backlog_item.id}
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user who doesn't have authorization aren't able to create
+ # tasks.
+ #
+ def test_create_for_unauthorized_user
+ get :create, {:backlog_item => @backlog_item.id}, {:user_id => users(:jdonuts).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+
+ # Ensures that tasks cannot be created for a backlog item on an non-active
+ # sprint.
+ #
+ def test_create_for_nonactive_sprint
+ count = @inactive_sprint_item.tasks.size
+
+ get :create,
+ {:backlog_item => @inactive_sprint_item.id},
+ {:user_id => @inactive_sprint_item.owner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @inactive_sprint_item.id
+ result = BacklogItem.find_by_id((a)inactive_sprint_item.id)
+ assert_equal count, result.tasks.size, 'Tasks should not have been updated'
+ end
+
+ # Ensures that tasks are saved corrected.
+ #
+ def test_create
+ post :create,
+ {
+ :backlog_item => @backlog_item.id,
+ :task => @details
+ },
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+
+ # Ensures that anonymous users can't delete tasks
+ #
+ def test_destroy_as_anonymous
+ get :destroy
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that deletion requires a valid task id.
+ #
+ def test_destroy_with_invalid_id
+ post :destroy,
+ {:id => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that someone who is not the backlog item owner can't add tasks to
+ # it.
+ #
+ def test_destroy_as_non_owner
+ post :destroy,
+ {
+ :id => @task.id
+ },
+ {
+ :user_id => @nonowner.id
+ }
+
+ assert Task.find_by_id((a)task.id), 'Task should not have been deleted.'
+ end
+
+ # Ensures that the primary can delete a task.
+ #
+ def test_destroy
+ post :destroy,
+ {
+ :id => @task.id
+ },
+ {:user_id => @owner.id}
+
+ assert_redirected_to(
+ :controller => :backlog,
+ :action => :view,
+ :id => @task.backlog_item.id)
+ assert_equal nil, Task.find_by_id((a)task.id), 'Did not delete the task.'
+ end
+
+ # Ensures that an update requires the user be loggeg in.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that an update fails without a task id.
+ #
+ def test_update_without_id
+ put :update, {}, {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an update fails with an invalid id.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @owner.id}
+ end
+
+ # Ensures that a non-owner cannot update a task.
+ #
+ def test_update_as_nonowner
+ put :update,
+ {:id => @task.id, :task => @task.attributes},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ result = Task.find_by_id((a)task.id)
+ assert_equal @task.updated_at, result.updated_at,
+ "Task should not have been updated."
+ end
+end
diff --git a/test/unit/product_test.rb b/test/unit/product_test.rb
index a0df74d..62647db 100644
--- a/test/unit/product_test.rb
+++ b/test/unit/product_test.rb
@@ -25,7 +25,8 @@ class ProductTest < ActiveSupport::TestCase
def setup
@new_product = Product.new(
:project_id => projects(:cairo).id,
- :owner_id => users(:mcpierce).id)
+ :owner_id => users(:mcpierce).id,
+ :name => "Test Product")
@product = products(:cairo_web)
@owner = @product.owner
diff --git a/test/unit/task_test.rb b/test/unit/task_test.rb
index b524127..7cf486d 100644
--- a/test/unit/task_test.rb
+++ b/test/unit/task_test.rb
@@ -1,8 +1,123 @@
+# task_test.rb
+# Copyright (C) 2008, 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 TaskTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- def test_truth
- assert true
+ fixtures :backlog_items
+ fixtures :sprints
+ fixtures :users
+
+ def setup
+ @backlog_item = backlog_items(:owned_backlog_item)
+ @new_task = Task.new(
+ :primary_id => @backlog_item.owner_id,
+ :description => 'This is a task',
+ :hours => 5,
+ :when => @backlog_item.sprint.start + (@backlog_item.sprint.duration / 2))
+ @new_task.backlog_item = @backlog_item
+ @backup = users(:jdonuts)
+
+ raise "Backup cannot be the owner!" if @backup.id == @backlog_item.owner.id
+ end
+
+ # Ensures that at least a primary must be defined.
+ #
+ def test_valid_fails_without_primary
+ @new_task.primary = nil
+
+ flunk "A primary developer must be set." if @new_task.valid?
+ end
+
+ # Ensures that the primary and backup developer cannot be the same person.
+ #
+ def test_valid_fails_if_primary_is_backup
+ @new_task.backup = @new_task.primary
+
+ flunk "The primary developer cannot be the backup." if @new_task.valid?
+ end
+
+ # Ensures that a backlog item is required.
+ #
+ def test_valid_fails_without_backlog_item
+ @new_task.backlog_item = nil
+
+ flunk "A backlog item must be defined." if @new_task.valid?
+ end
+
+ # Ensures that the description must be present.
+ #
+ def test_valid_fails_without_description
+ @new_task.description = ""
+
+ flunk "A description must be provided." if @new_task.valid?
+ end
+
+ # Ensures that hours must be provided.
+ #
+ def test_valid_fails_without_hours
+ @new_task.hours = nil
+
+ flunk "Hours must be specified." if @new_task.valid?
+ end
+
+ # Ensures that hours cannot be negative.
+ #
+ def test_valid_fails_with_negative_hours
+ @new_task.hours = -3
+
+ flunk "Hours must be a positive value." if @new_task.valid?
+ end
+
+ # Ensures that hours cannot be zero.
+ #
+ def test_valid_fails_with_zero_for_hours
+ @new_task.hours = 0
+
+ flunk "Hours cannot be zero." if @new_task.valid?
+ end
+
+ # Ensures that a date must be present.
+ #
+ def test_valid_fails_without_date
+ @new_task.when = nil
+
+ flunk "Date must be set." if @new_task.valid?
+ end
+
+ # Ensures that the date for a task can't be before the sprint starts.
+ #
+ def test_valid_fails_when_date_preceeds_sprint
+ @new_task.when = @backlog_item.sprint.start - 1
+
+ flunk "Date cannot be before the sprint starts." if @new_task.valid?
+ end
+
+ # Ensures that the date for a task can't be after the sprint ends.
+ #
+ def test_valid_fail_when_date_exceeds_sprint
+ @new_task.when = @backlog_item.sprint.start + @backlog_item.sprint.duration
+
+ flunk "Date cannot be after the sprint ends." if @new_task.valid?
+ end
+
+ # Ensures that a well-formed task if valid.
+ #
+ def test_valid
+ flunk "Something is wrong with validation." unless @new_task.valid?
end
end
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Refactored the tasks controller to be RESTful.
by Darryl L. Pierce
Renamed TaskController and TaskControllerTest to TasksController and
TasksControllerTest. Added a new resource to routes.rb to provide
RESTful methods for working with Task objects. Updated all of the unit
and functional tests and renamed and refactored the actions in the
controller.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/backlog_controller.rb | 1 +
app/controllers/task_controller.rb | 127 ------------
app/controllers/tasks_controller.rb | 167 ++++++++++++++++
app/helpers/application_helper.rb | 9 -
app/models/task.rb | 71 +++++--
app/views/backlog/view.html.erb | 66 +------
app/views/task/_edit.html.erb | 47 -----
app/views/task/create.html.erb | 3 -
app/views/task/modify.html.erb | 1 -
app/views/tasks/_edit.html.erb | 65 ++++++
app/views/tasks/_list.html.erb | 34 ++++
app/views/tasks/edit.html.erb | 1 +
app/views/tasks/index.html.erb | 1 +
app/views/tasks/new.html.erb | 5 +
app/views/tasks/show.html.erb | 35 ++++
config/routes.rb | 5 +
public/stylesheets/forms.css | 5 +
test/functional/task_controller_test.rb | 233 ----------------------
test/functional/tasks_controller_test.rb | 313 ++++++++++++++++++++++++++++++
test/unit/product_test.rb | 3 +-
test/unit/task_test.rb | 121 +++++++++++-
21 files changed, 807 insertions(+), 506 deletions(-)
delete mode 100644 app/controllers/task_controller.rb
create mode 100644 app/controllers/tasks_controller.rb
delete mode 100644 app/views/task/_edit.html.erb
delete mode 100644 app/views/task/create.html.erb
delete mode 100644 app/views/task/modify.html.erb
create mode 100644 app/views/tasks/_edit.html.erb
create mode 100644 app/views/tasks/_list.html.erb
create mode 100644 app/views/tasks/edit.html.erb
create mode 100644 app/views/tasks/index.html.erb
create mode 100644 app/views/tasks/new.html.erb
create mode 100644 app/views/tasks/show.html.erb
delete mode 100644 test/functional/task_controller_test.rb
create mode 100644 test/functional/tasks_controller_test.rb
diff --git a/app/controllers/backlog_controller.rb b/app/controllers/backlog_controller.rb
index 3e6ca56..6011264 100644
--- a/app/controllers/backlog_controller.rb
+++ b/app/controllers/backlog_controller.rb
@@ -28,6 +28,7 @@ class BacklogController < ApplicationController
@task = Task.new
@task.primary = @user if @user
@users = User.find(:all, :conditions => "id in (select user_id from product_roles where product_id = #{(a)product.id})")
+ @tasks = @backlog_item.tasks
end
def modify
diff --git a/app/controllers/task_controller.rb b/app/controllers/task_controller.rb
deleted file mode 100644
index 6c6e42b..0000000
--- a/app/controllers/task_controller.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# task_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +TaskController+ allows the user to create and modify tasks for a backlog
-# item.
-#
-class TaskController < ApplicationController
- before_filter :authenticated, :except => [:view]
- before_filter :load_backlog_item, :only => [:create]
- before_filter :load_task, :only => [:modify, :delete]
- before_filter :authorized_to_create, :only => [:create]
- before_filter :authorized_to_manage, :only => [:modify, :delete]
- before_filter :before_edit, :only => [:create, :modify]
- before_filter :ensure_sprint_is_active, :only => [:create]
-
- def create
- if request.post?
- begin
- Task.transaction do
- @task = Task.create(params[:task])
- @task.backlog_item = @backlog_item
- @backlog_item.tasks << @task
- @backlog_item.save!
- @task.save!
-
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
- end
-
- rescue ActiveRecord::RecordInvalid => error
- flash[:message] = error.message
- @task.valid?
- render :action => :create
- end
- else
- @task = Task.new(:backlog_item => @backlog_item, :primary => @user)
- end
- end
-
- def modify
- if request.post?
- @task.update_attributes(params[:task])
-
- @task.save
-
- redirect_to :controller => :backlog, :action => :view, :id => @task.backlog_item.id
- end
- end
-
- def delete
- if request.post?
- begin
- Task.transaction do
- @task.destroy
- end
-
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
-
- rescue Exception => error
- flash[:message] = error.message
-
- redirect_to error_path
- end
- else
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-
- private
-
- def load_backlog_item
- @backlog_item = BacklogItem.find_by_id(params[:backlog_item])
-
- unless @backlog_item
- flash[:message] = 'Invalid user id.'
- redirect_to error_path
- end
- end
-
- def load_task
- @task = Task.find_by_id(params[:id])
- @backlog_item = @task.backlog_item if @task
-
- unless @task
- flash[:message] = 'Invalid task id.'
- redirect_to error_path
- end
- end
-
- def authorized_to_create
- unless TaskHelper.can_create_task(@user,@backlog_item)
- flash[:message] = 'You are not authorized to work on tasks for this user story.'
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-
- def authorized_to_manage
- unless TaskHelper.can_manage_task(@user,@task)
- flash[:message] = 'You are not authorized to modify this task.'
- redirect_to :controller => :backlog, :action => :view, :id => @task.backlog_item.id
- end
- end
-
- def before_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-
- def ensure_sprint_is_active
- unless @backlog_item.sprint.status == Sprint::STATUS_ACTIVE
- flash[:message] = 'The sprint must be active for that action.'
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-end
diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb
new file mode 100644
index 0000000..af3aec0
--- /dev/null
+++ b/app/controllers/tasks_controller.rb
@@ -0,0 +1,167 @@
+# tasks_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +TasksController+ allows the user to create and modify tasks for a backlog
+# item.
+#
+class TasksController < ApplicationController
+ before_filter :authenticated, :except => [:index, :show]
+ before_filter :load_backlog_item, :only => [:index, :new, :create]
+ before_filter :load_task, :only => [:show, :edit, :update, :destroy]
+ before_filter :before_edit, :only => [:create, :update]
+ before_filter :ensure_sprint_is_active, :only => [:create]
+
+ # GET /tasks
+ def index
+ @tasks = Task.find_all_by_backlog_item_id((a)backlog_item.id)
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /tasks/1
+ def show
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /tasks/new
+ def new
+ if @backlog_item.user_can_add_tasks?(@user)
+ @task = Task.new(
+ :backlog_item_id => @backlog_item.id,
+ :primary_id => @user.id
+ )
+ @users = User.find(:all, :order => 'display_name ASC')
+ else
+ flash[:error] = 'You are not allowed to add tasks to this backlog item.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ # GET /tasks/1;edit
+ def edit
+ unless @task.user_can_edit?(@user)
+ flash[:error] = "You are not allowd to edit this task."
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ # POST /tasks
+ def create
+ if @backlog_item.user_can_add_tasks?(@user)
+ Task.transaction do
+ @task = Task.new(params[:task])
+ @task.backlog_item = @backlog_item
+
+ respond_to do |format|
+ if @task.save
+ flash[:message] = "Recorded #{(a)task.hours} against this backlog item."
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ else
+ format.html { render :action => 'new' }
+ end
+ end
+ end
+ else
+ flash[:error] = "You are not allowed to add tasks to this backlog item."
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ # PUT /tasks/1
+ def update
+ if @task.user_can_edit?(@user)
+ begin
+ Task.transaction do
+ @task.update_attributes(params[:task])
+ @task.save!
+ end
+
+ flash[:message] = "Task updated."
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ end
+
+ respond_to do |format|
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ end
+ else
+ flash[:error] = "You are not allowed to update that task."
+
+ @task.valid?
+
+ respond_to do |format|
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ end
+ end
+ end
+
+ # DELETE /tasks/1
+ def destroy
+ if @task.user_can_delete?(@user)
+ begin
+ Task.transaction do
+ @task.destroy
+ end
+
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ redirect_to error_url
+ end
+ else
+ flash[:error] = 'You are not allowed to delete this task.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ private
+
+ def load_backlog_item
+ @backlog_item = BacklogItem.find_by_id(params[:backlog_item])
+
+ unless @backlog_item
+ flash[:message] = 'Invalid user id.'
+ redirect_to error_path
+ end
+ end
+
+ def load_task
+ @task = Task.find_by_id(params[:id])
+ @backlog_item = @task.backlog_item if @task
+
+ unless @task
+ flash[:message] = 'Invalid task id.'
+ redirect_to error_path
+ end
+ end
+
+ def before_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ def ensure_sprint_is_active
+ unless @backlog_item.sprint.status == Sprint::STATUS_ACTIVE
+ flash[:message] = 'The sprint must be active for that action.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f20df88..96d59a7 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -102,15 +102,6 @@ module ApplicationHelper
:id => backlog_item.id)
end
- # Creates a link to a task.
- #
- def link_to_task(task, options = {})
- @template.link_to(options[:text] ? options[:text] : task.id,
- :controller => :task,
- :action => options[:action] ? options[:action] : :view,
- :id => task.id)
- end
-
# Shows the hours for a given object as estimated, actual and remaining.
#
def show_hours_as_ear(estimated)
diff --git a/app/models/task.rb b/app/models/task.rb
index 89af73d..7785b4d 100644
--- a/app/models/task.rb
+++ b/app/models/task.rb
@@ -1,39 +1,74 @@
# task.rb
# Copyright (C) 2008, 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 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.
+# 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/>.
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
#
# A +Task+ represents a single piece of work associated with a +UserStory+.
-# Tasks have a primary developer, and an optional backup developer.
+# Tasks have a primary developer, and an optional backup developer.
class Task < ActiveRecord::Base
validates_presence_of :backlog_item_id
belongs_to :primary, :class_name => 'User', :foreign_key => 'primary_id'
belongs_to :backup, :class_name => 'User', :foreign_key => 'backup_id'
+ validates_presence_of :primary_id,
+ :message => 'A primary developer must be defined.'
+
validates_presence_of :description,
- :message => 'You need to describe this task.'
+ :message => 'You need to describe this task.'
validates_length_of :description,
- :maximum => 1000,
- :too_long => 'Please shorten the description.'
+ :maximum => 1000,
+ :too_long => 'Please shorten the description.'
- validates_presence_of :hours,
- :message => 'You must report the hours worked on this task.'
+ validates_presence_of :hours,
+ :message => 'You must report the hours worked on this task.'
validates_numericality_of :hours,
- :greater_than => 0,
- :message => 'Hours must be numeric and greater than 0.'
+ :greater_than => 0,
+ :message => 'Hours must be numeric and greater than 0.'
+
+ validates_presence_of :when,
+ :message => 'You must specify when the task was performed.'
belongs_to :backlog_item
+
+ # Returns whether the user can edit the task.
+ #
+ def user_can_edit?(user)
+ user &&
+ backlog_item.sprint.active? &&
+ (primary && (primary.id == user.id))
+ end
+
+ # Returns whether the user can delete the task or not.
+ #
+ def user_can_delete?(user)
+ user &&
+ (backlog_item.owner && (backlog_item.owner.id == user.id)) &&
+ backlog_item.sprint.active?
+ end
+
+ protected
+
+ def validate
+ if primary && backup && (primary.id == backup.id)
+ errors.add('backup_id',"The backup developer cannot also be #{primary.display_name}.")
+ end
+ if backlog_item
+ unless (backlog_item.sprint.start...(backlog_item.sprint.start + backlog_item.sprint.duration)).include? self.when
+ errors.add('when', 'Task must be done during the sprint.')
+ end
+ end
+ end
end
diff --git a/app/views/backlog/view.html.erb b/app/views/backlog/view.html.erb
index c02f27e..8ee0333 100644
--- a/app/views/backlog/view.html.erb
+++ b/app/views/backlog/view.html.erb
@@ -63,67 +63,5 @@
</tbody>
</table>
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="description" />
- <col class="user" />
- <col class="user" />
- <col class="hours" />
- <col class="date" />
- <col class="actions" />
- </colgroup>
-
- <thead>
- <tr>
- <th colspan="7" class="title">Tasks</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Description</th>
- <th>Primary</th>
- <th>Backup</th>
- <th>Hours</th>
- <th>Date</th>
- <th>Action</th>
- </tr>
- </thead>
-
- <tbody>
-
- <% if @backlog_item.user_can_add_tasks?(@user) %>
- <% form_for :task, :url => {:controller => :task, :action => :create} do |form| %>
- <%= hidden_field_tag :backlog_item, @backlog_item.id %>
- <tr>
- <td></td>
- <td><%= form.text_field :description %></td>
- <td><%= collection_select :task, :primary_id, @users, :id,
- :display_name, {:include_blank => true} %></td>
- <td><%= collection_select :task, :backup_id, @users, :id,
- :display_name, {:include_blank => true} %></td>
- <td><%= form.text_field :hours %></td>
- <td><%= form.date_select :when %></td>
- <td><%= submit_tag "Create" %></td>
- </tr>
- <% end %>
- <% end %>
-
- <% @backlog_item.tasks.each_with_index do |task,i| %>
- <tr class="<% row_class = i%2 == 0 ? 'even' : 'odd' %>">
-
- <td><%= link_to_task task, :action => :modify %></td>
- <td><%= simple_format task.description %></td>
- <td><%= link_to_user task.primary %></td>
- <td><%= link_to_user task.backup %></td>
- <td><%= task.hours %></td>
- <td><%= task.when.to_s(:date) %></td>
- <td>
- <% form_for :task, :url => {:controller => :task, :action => :delete} do |f| %>
- <%= hidden_field_tag :id, task.id %>
- <%= submit_tag "Delete" %>
- <% end %>
- </td>
- </tr>
- <% end %>
- </tbody>
-</table>
+<%= link_to "Add Task", new_task_path(:backlog_item => @backlog_item) %>
+<%= render(:partial => 'tasks/list', :object => @backlog_item.tasks) %>
diff --git a/app/views/task/_edit.html.erb b/app/views/task/_edit.html.erb
deleted file mode 100644
index 4195c45..0000000
--- a/app/views/task/_edit.html.erb
+++ /dev/null
@@ -1,47 +0,0 @@
-<div>
- <% form_for :task do |form| %>
-
- <%= hidden_field_tag :backlog_item, @task.backlog_item.id %>
-
- <table class="edit">
- <tbody>
- <tr>
- <td class="label">Primary:</td>
- <td class="value">
- <%= collection_select :task, :primary_id, @users, :id,
- :display_name, {:include_blank => true} %>
- </td>
- </tr>
-
- <tr>
- <td class="label">Backup:</td>
- <td class="value">
- <%= collection_select :task, :backup_id, @users, :id,
- :display_name, {:include_blank => true} %>
- </td>
- </tr>
-
- <tr>
- <td class="label">Hours:</td>
- <td class="value"><%= form.text_field :hours %></td>
- </tr>
-
- <tr>
- <td class="label">Description:</td>
- <td class="value"><%= form.text_area :description %></td>
- </tr>
-
- <tr>
- <td class="buttons" colspan="2">
- <% if @task.new_record? %>
- <%= submit_tag "Create", :class => 'button' %>
- <% else %>
- <%= submit_tag "Update", :class => 'button' %>
- <% end %>
- </td>
- </tr>
- </tbody>
- </table>
-
- <% end %>
-</div>
\ No newline at end of file
diff --git a/app/views/task/create.html.erb b/app/views/task/create.html.erb
deleted file mode 100644
index ad1a69b..0000000
--- a/app/views/task/create.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<div>
- <%= render :partial => 'edit', :object => @task %>
-</div>
\ No newline at end of file
diff --git a/app/views/task/modify.html.erb b/app/views/task/modify.html.erb
deleted file mode 100644
index e536180..0000000
--- a/app/views/task/modify.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render :partial => "edit", :object => @task %>
diff --git a/app/views/tasks/_edit.html.erb b/app/views/tasks/_edit.html.erb
new file mode 100644
index 0000000..98b1c54
--- /dev/null
+++ b/app/views/tasks/_edit.html.erb
@@ -0,0 +1,65 @@
+<% html = @task.new_record? ? {:method => :post} : {:method => :put} %>
+
+<% form_for(:task, @task, :url => task_path(@task), :html => html) do |form| %>
+
+ <%= hidden_field_tag :backlog_item, @task.backlog_item.id %>
+
+ <table class="edit">
+ <tbody>
+ <tr>
+ <td class="label">When</td>
+ <td class="value">
+ <%= form.date_select :when %>
+ <%= error_message_on(:task, :when) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Primary</td>
+ <td class="value">
+ <%= collection_select :task, :primary_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <%= error_message_on(:task, :primary_id) %>
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Backup</td>
+ <td class="value">
+ <%= collection_select :task, :backup_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ <%= error_message_on(:task, :backup_id) %>
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Hours</td>
+ <td class="value">
+ <%= form.text_field :hours %>
+ <%= error_message_on(:task, :hours) %>
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Description</td>
+ <td class="value">
+ <%= form.text_area :description %>
+ <%= error_message_on(:task, :description) %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <% if @task.new_record? %>
+ <%= submit_tag "Create", :class => 'button' %>
+ <% else %>
+ <%= submit_tag "Update", :class => 'button' %>
+ <% end %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<% end %>
diff --git a/app/views/tasks/_list.html.erb b/app/views/tasks/_list.html.erb
new file mode 100644
index 0000000..273296e
--- /dev/null
+++ b/app/views/tasks/_list.html.erb
@@ -0,0 +1,34 @@
+<table class="list">
+ <thead>
+ <tr>
+ <th>Date</th>
+ <th>Description</th>
+ <th>Primary</th>
+ <th>Backup</th>
+ <th>Hours</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @tasks.each_with_index do |task, index| %>
+ <% row_class = index%2 == 0 ? 'even' : 'odd' %>
+ <tr class="<%= row_class %>">
+ <td><%= task.when %></td>
+ <td><%= simple_format task.description %></td>
+ <td><%= link_to_user task.primary %></td>
+ <td><%= link_to_user task.backup %></td>
+ <td><%= format "%0.2f", task.hours %>
+ <td>
+ <%= link_to "View", task_path(task) %>
+ <% if task.user_can_edit?(@user) %>
+ <%= link_to "Edit", edit_task_path(task) %>
+ <% end %>
+ <% if task.user_can_delete?(@user) %>
+ <%= link_to "Delete", task_path(task), :confirm => "Are you sure?", :method => :delete %>
+ <% end %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/tasks/edit.html.erb b/app/views/tasks/edit.html.erb
new file mode 100644
index 0000000..e536180
--- /dev/null
+++ b/app/views/tasks/edit.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "edit", :object => @task %>
diff --git a/app/views/tasks/index.html.erb b/app/views/tasks/index.html.erb
new file mode 100644
index 0000000..0a9c778
--- /dev/null
+++ b/app/views/tasks/index.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'list') %>
diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb
new file mode 100644
index 0000000..ae21641
--- /dev/null
+++ b/app/views/tasks/new.html.erb
@@ -0,0 +1,5 @@
+<div>
+ <% form_for(:task, @task, :url => task_path(@task)) do |form| %>
+ <%= render :partial => 'edit', :object => @task %>
+ <% end %>
+</div>
diff --git a/app/views/tasks/show.html.erb b/app/views/tasks/show.html.erb
new file mode 100644
index 0000000..fb0cfb9
--- /dev/null
+++ b/app/views/tasks/show.html.erb
@@ -0,0 +1,35 @@
+<table class="details">
+ <tr>
+ <td class="label">Description:</td>
+ <td class="value"><%= simple_format @task.description %></td>
+ </tr>
+
+ <tr>
+ <td class="label">When:</td>
+ <td class="value"><%= @task.when %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Hours:</td>
+ <td class="value"><%= format "%0.2f", @task.hours %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Primary:</td>
+ <td class="value"><%= link_to_user @task.primary %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Backup:</td>
+ <td class="label"><%= link_to_user @task.backup %></td>
+ </tr>
+
+</table>
+
+<%= link_to "Back", :controller => :backlog, :action => :view, :id => @task.backlog_item %>
+<% if @task.user_can_edit?(@user) %>
+ <% link_to "Edit", edit_task_path(@task) %>
+<% end %>
+<% if @task.user_can_delete?(@user) %>
+ <%= link_to "Delete", task_path(@task), :confirm => "Are you sure?", :method => :delete %>
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 874f8e9..2f66a65 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,10 +16,15 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :tasks
+
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
map.root :controller => 'project'
+ # Login URL
+ map.login "/login", :controller => 'home', :action => 'login'
+
# Error handling
map.error "/error", :controller => 'home', :action => 'error'
diff --git a/public/stylesheets/forms.css b/public/stylesheets/forms.css
index 99073f4..1809889 100644
--- a/public/stylesheets/forms.css
+++ b/public/stylesheets/forms.css
@@ -1,3 +1,8 @@
+.formError {
+ color: #ff0000;
+ font-weight: bold;
+}
+
table.edit:before {
color: #ff0000;
content: "<p>* indicates a required field.</p>"
diff --git a/test/functional/task_controller_test.rb b/test/functional/task_controller_test.rb
deleted file mode 100644
index 92551da..0000000
--- a/test/functional/task_controller_test.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-# task_controller_test.rb
-# Copyright (C) 2008, 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 TaskControllerTest < ActionController::TestCase
- fixtures :backlog_items
- fixtures :users
-
- def setup
- @backlog_item = backlog_items(:owned_backlog_item)
- @task = @backlog_item.tasks.first
- @owner = @backlog_item.owner
- @non_owner = users(:jdonuts)
- @inactive_sprint_item = backlog_items(:owned_inactive_sprint_backlog_item)
-
- flunk "FIXTURES ARE BROKEN" if @owner.id == @non_owner.id
-
- @details = {
- :primary_id => @backlog_item.owner.id,
- :description => 'This is a new task.',
- :hours => '2.5',
- :when => Date.today
- }
- end
-
- # Ensures that an invalid user story causes create to fail.
- #
- def test_create_with_invalid_user_story
- get :create,
- nil,
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
-
- get :create,
- {:user_story => 9999},
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that an anonymous user cannot create a task.
- #
- def test_create_for_anonymous
- get :create,
- {:backlog_item => @backlog_item.id}
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who doesn't have authorization aren't able to create
- # tasks.
- #
- def test_create_for_unauthorized_user
- get :create, {:backlog_item => @backlog_item.id}, {:user_id => users(:jdonuts).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that tasks cannot be created for a backlog item on an non-active
- # sprint.
- #
- def test_create_for_nonactive_sprint
- count = @inactive_sprint_item.tasks.size
-
- get :create,
- {:backlog_item => @inactive_sprint_item.id},
- {:user_id => @inactive_sprint_item.owner.id}
-
- assert_redirected_to :controller => :backlog, :action => :view, :id => @inactive_sprint_item.id
- result = BacklogItem.find_by_id((a)inactive_sprint_item.id)
- assert_equal count, result.tasks.size, 'Tasks should not have been updated'
- end
-
- # Ensures that creating a task works as expected.
- #
- def test_create
- get :create,
- {
- :backlog_item => @backlog_item.id,
- :task => @details
- },
- {:user_id => users(:mcpierce).id}
-
- assert_response :success
- assert assigns['task'], 'Did not create a new task.'
- assert assigns['users'], 'Did not load users.'
- end
-
- # Ensures that tasks are saved corrected.
- #
- def test_create_as_save
- post :create,
- {
- :backlog_item => @backlog_item.id,
- :task => @details
- },
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that attempting to modify an invalid task fails.
- #
- def test_modify_with_invalid_task
- get :modify, nil, {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
-
- get :modify, {:id => 9999}
-
- assert_redirected_to error_path
- end
-
- # Ensures that anonymous users cannot modify tasks.
- #
- def test_modify_for_anonymous
- get :modify, {:id => tasks(:created_login_view).id}
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who's not the task owner or backup cannot modify the
- # task.
- #
- def test_modify_for_non_developer
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => users(:project_admin).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that the owner of the user story is allowed to edit a task against
- # that user story.
- #
- def test_modify_for_user_story_owner
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => users(:mcpierce).id}
-
- assert_response :success
- end
-
- # Ensures that modifying works as expected.
- #
- def test_modify
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => tasks(:created_login_view).primary_id}
-
- assert_response :success
- assert assigns['task'], 'Did not load the task.'
- assert_equal tasks(:created_login_view).id, assigns['task'].id, 'Did not load the right task.'
- assert assigns['users'], 'Did not load the series.'
- end
-
- # Ensures that saving a task after modifying it works as expected.
- #
- def test_modify_as_save
- task = tasks(:created_login_view).attributes
-
- post :modify,
- {:id => tasks(:created_login_view), :task => task},
- {:user_id => tasks(:created_login_view).primary_id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that anonymous users can't delete tasks
- #
- def test_delete_as_anonymous
- get :delete
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that deletion requires a valid task id.
- #
- def test_delete_with_invalid_id
- post :delete,
- {:id => 9999},
- {:user_id => @owner.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that someone who is not the backlog item owner can't add tasks to
- # it.
- #
- def test_delete_as_non_owner
- post :delete,
- {
- :id => @task.id
- },
- {
- :user_id => @non_owner.id
- }
-
- assert Task.find_by_id((a)task.id), 'Task should not have been deleted.'
- end
-
- # Ensures that the primary can delete a task.
- #
- def test_delete
- post :delete,
- {
- :id => @task.id
- },
- {:user_id => @owner.id}
-
- assert_redirected_to(
- :controller => :backlog,
- :action => :view,
- :id => @task.backlog_item.id)
- assert_equal nil, Task.find_by_id((a)task.id), 'Did not delete the task.'
- end
-end
diff --git a/test/functional/tasks_controller_test.rb b/test/functional/tasks_controller_test.rb
new file mode 100644
index 0000000..270b308
--- /dev/null
+++ b/test/functional/tasks_controller_test.rb
@@ -0,0 +1,313 @@
+# tasks_controller_test.rb
+# Copyright (C) 2008, 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 TasksControllerTest < ActionController::TestCase
+ fixtures :backlog_items
+ fixtures :users
+
+ def setup
+ @backlog_item = backlog_items(:owned_backlog_item)
+ @task = @backlog_item.tasks.first
+ @owner = @backlog_item.owner
+ @nonowner = users(:jdonuts)
+ raise "Owner and nonowner cannot be the same person!" if @owner.id == @nonowner.id
+
+ @inactive_sprint_item = backlog_items(:owned_inactive_sprint_backlog_item)
+
+ @details = {
+ :primary_id => @backlog_item.owner.id,
+ :description => 'This is a new task.',
+ :hours => '2.5',
+ :when => Date.today
+ }
+ end
+
+ # Ensures that viewing tasks when no backlog item is specified fails.
+ #
+ def test_index_without_backlog_item
+ get :index
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that getting the list of tasks for a backlog item works as expected.
+ #
+ def test_index
+ get :index, {:backlog_item => @backlog_item.id}
+
+ assert_response :success
+ assert assigns['backlog_item'], "Failed to load the backlog item."
+ assert_equal @backlog_item.id, assigns['backlog_item'].id,
+ "Did not load the right backlog item."
+ assert assigns['tasks'], "Failed to load the tasks."
+ end
+
+ # Ensures that an error is show if no task id is present.
+ #
+ def test_show_without_task_id
+ get :show
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that creating a task requires the user to be logged in.
+ #
+ def test_new_for_anonymous
+ get :new
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that creating a task without a backlog item fails.
+ #
+ def test_new_without_backlog_item
+ get :new, nil, {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a non-owner cannot create tasks for a backlog item.
+ #
+ def test_new_for_nonowner
+ get :new,
+ {:backlog_item => @backlog_item.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item.id
+ end
+
+ # Ensures that creating a task works as expected.
+ #
+ def test_new
+ get :new,
+ {:backlog_item => @backlog_item.id},
+ {:user_id => @owner.id}
+
+ assert_response :success
+ assert assigns['task'], "Didn't create a task."
+ assert assigns['users'], "Didn't load the list of users."
+ assert_equal @backlog_item.id, assigns['task'].backlog_item_id,
+ "Didn't set the backlog item correctly."
+ assert_equal @owner.id, assigns['task'].primary_id,
+ "Didn't set the primary developer correctly."
+ end
+
+ # Ensures that attempting to modify an invalid task fails.
+ #
+ def test_edit_with_invalid_task
+ get :edit, nil, {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+
+ get :edit, {:id => 9999}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that anonymous users cannot modify tasks.
+ #
+ def test_edit_for_anonymous
+ get :edit, {:id => tasks(:created_login_view).id}
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user who's not the task owner or backup cannot modify the
+ # task.
+ #
+ def test_edit_for_non_developer
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => users(:project_admin).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view
+ end
+
+ # Ensures that the owner of the user story is allowed to edit a task against
+ # that user story.
+ #
+ def test_edit_for_user_story_owner
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => users(:mcpierce).id}
+
+ assert_response :success
+ end
+
+ # Ensures that modifying works as expected.
+ #
+ def test_edit
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => tasks(:created_login_view).primary_id}
+
+ assert_response :success
+ assert assigns['task'], 'Did not load the task.'
+ assert_equal tasks(:created_login_view).id, assigns['task'].id, 'Did not load the right task.'
+ assert assigns['users'], 'Did not load the series.'
+ end
+
+ # Ensures that an invalid user story causes create to fail.
+ #
+ def test_create_with_invalid_user_story
+ get :create,
+ nil,
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+
+ get :create,
+ {:user_story => 9999},
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that an anonymous user cannot create a task.
+ #
+ def test_create_for_anonymous
+ get :create,
+ {:backlog_item => @backlog_item.id}
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user who doesn't have authorization aren't able to create
+ # tasks.
+ #
+ def test_create_for_unauthorized_user
+ get :create, {:backlog_item => @backlog_item.id}, {:user_id => users(:jdonuts).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+
+ # Ensures that tasks cannot be created for a backlog item on an non-active
+ # sprint.
+ #
+ def test_create_for_nonactive_sprint
+ count = @inactive_sprint_item.tasks.size
+
+ get :create,
+ {:backlog_item => @inactive_sprint_item.id},
+ {:user_id => @inactive_sprint_item.owner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @inactive_sprint_item.id
+ result = BacklogItem.find_by_id((a)inactive_sprint_item.id)
+ assert_equal count, result.tasks.size, 'Tasks should not have been updated'
+ end
+
+ # Ensures that tasks are saved corrected.
+ #
+ def test_create
+ post :create,
+ {
+ :backlog_item => @backlog_item.id,
+ :task => @details
+ },
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+
+ # Ensures that anonymous users can't delete tasks
+ #
+ def test_destroy_as_anonymous
+ get :destroy
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that deletion requires a valid task id.
+ #
+ def test_destroy_with_invalid_id
+ post :destroy,
+ {:id => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that someone who is not the backlog item owner can't add tasks to
+ # it.
+ #
+ def test_destroy_as_non_owner
+ post :destroy,
+ {
+ :id => @task.id
+ },
+ {
+ :user_id => @nonowner.id
+ }
+
+ assert Task.find_by_id((a)task.id), 'Task should not have been deleted.'
+ end
+
+ # Ensures that the primary can delete a task.
+ #
+ def test_destroy
+ post :destroy,
+ {
+ :id => @task.id
+ },
+ {:user_id => @owner.id}
+
+ assert_redirected_to(
+ :controller => :backlog,
+ :action => :view,
+ :id => @task.backlog_item.id)
+ assert_equal nil, Task.find_by_id((a)task.id), 'Did not delete the task.'
+ end
+
+ # Ensures that an update requires the user be loggeg in.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that an update fails without a task id.
+ #
+ def test_update_without_id
+ put :update, {}, {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an update fails with an invalid id.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @owner.id}
+ end
+
+ # Ensures that a non-owner cannot update a task.
+ #
+ def test_update_as_nonowner
+ put :update,
+ {:id => @task.id, :task => @task.attributes},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ result = Task.find_by_id((a)task.id)
+ assert_equal @task.updated_at, result.updated_at,
+ "Task should not have been updated."
+ end
+end
diff --git a/test/unit/product_test.rb b/test/unit/product_test.rb
index a0df74d..62647db 100644
--- a/test/unit/product_test.rb
+++ b/test/unit/product_test.rb
@@ -25,7 +25,8 @@ class ProductTest < ActiveSupport::TestCase
def setup
@new_product = Product.new(
:project_id => projects(:cairo).id,
- :owner_id => users(:mcpierce).id)
+ :owner_id => users(:mcpierce).id,
+ :name => "Test Product")
@product = products(:cairo_web)
@owner = @product.owner
diff --git a/test/unit/task_test.rb b/test/unit/task_test.rb
index b524127..7cf486d 100644
--- a/test/unit/task_test.rb
+++ b/test/unit/task_test.rb
@@ -1,8 +1,123 @@
+# task_test.rb
+# Copyright (C) 2008, 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 TaskTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- def test_truth
- assert true
+ fixtures :backlog_items
+ fixtures :sprints
+ fixtures :users
+
+ def setup
+ @backlog_item = backlog_items(:owned_backlog_item)
+ @new_task = Task.new(
+ :primary_id => @backlog_item.owner_id,
+ :description => 'This is a task',
+ :hours => 5,
+ :when => @backlog_item.sprint.start + (@backlog_item.sprint.duration / 2))
+ @new_task.backlog_item = @backlog_item
+ @backup = users(:jdonuts)
+
+ raise "Backup cannot be the owner!" if @backup.id == @backlog_item.owner.id
+ end
+
+ # Ensures that at least a primary must be defined.
+ #
+ def test_valid_fails_without_primary
+ @new_task.primary = nil
+
+ flunk "A primary developer must be set." if @new_task.valid?
+ end
+
+ # Ensures that the primary and backup developer cannot be the same person.
+ #
+ def test_valid_fails_if_primary_is_backup
+ @new_task.backup = @new_task.primary
+
+ flunk "The primary developer cannot be the backup." if @new_task.valid?
+ end
+
+ # Ensures that a backlog item is required.
+ #
+ def test_valid_fails_without_backlog_item
+ @new_task.backlog_item = nil
+
+ flunk "A backlog item must be defined." if @new_task.valid?
+ end
+
+ # Ensures that the description must be present.
+ #
+ def test_valid_fails_without_description
+ @new_task.description = ""
+
+ flunk "A description must be provided." if @new_task.valid?
+ end
+
+ # Ensures that hours must be provided.
+ #
+ def test_valid_fails_without_hours
+ @new_task.hours = nil
+
+ flunk "Hours must be specified." if @new_task.valid?
+ end
+
+ # Ensures that hours cannot be negative.
+ #
+ def test_valid_fails_with_negative_hours
+ @new_task.hours = -3
+
+ flunk "Hours must be a positive value." if @new_task.valid?
+ end
+
+ # Ensures that hours cannot be zero.
+ #
+ def test_valid_fails_with_zero_for_hours
+ @new_task.hours = 0
+
+ flunk "Hours cannot be zero." if @new_task.valid?
+ end
+
+ # Ensures that a date must be present.
+ #
+ def test_valid_fails_without_date
+ @new_task.when = nil
+
+ flunk "Date must be set." if @new_task.valid?
+ end
+
+ # Ensures that the date for a task can't be before the sprint starts.
+ #
+ def test_valid_fails_when_date_preceeds_sprint
+ @new_task.when = @backlog_item.sprint.start - 1
+
+ flunk "Date cannot be before the sprint starts." if @new_task.valid?
+ end
+
+ # Ensures that the date for a task can't be after the sprint ends.
+ #
+ def test_valid_fail_when_date_exceeds_sprint
+ @new_task.when = @backlog_item.sprint.start + @backlog_item.sprint.duration
+
+ flunk "Date cannot be after the sprint ends." if @new_task.valid?
+ end
+
+ # Ensures that a well-formed task if valid.
+ #
+ def test_valid
+ flunk "Something is wrong with validation." unless @new_task.valid?
end
end
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Refactored the tasks controller to be RESTful.
by Darryl L. Pierce
Renamed TaskController and TaskControllerTest to TasksController and
TasksControllerTest. Added a new resource to routes.rb to provide
RESTful methods for working with Task objects. Updated all of the unit
tests and renamed and refactored the actions in the controller.
Signed-off-by: Darryl L. Pierce <dpierce(a)redhat.com>
---
app/controllers/backlog_controller.rb | 1 +
app/controllers/task_controller.rb | 127 -------
app/controllers/tasks_controller.rb | 167 +++++++++
app/helpers/application_helper.rb | 9 -
app/models/task.rb | 50 ++-
app/views/backlog/view.html.erb | 66 +----
app/views/{task => tasks}/_edit.html.erb | 31 +-
app/views/tasks/_list.html.erb | 34 ++
.../{task/modify.html.erb => tasks/edit.html.erb} | 0
app/views/tasks/index.html.erb | 1 +
.../{task/create.html.erb => tasks/new.html.erb} | 0
app/views/tasks/show.html.erb | 35 ++
config/routes.rb | 5 +
...controller_test.rb => tasks_controller_test.rb} | 364 ++++++++++++--------
test/unit/product_test.rb | 3 +-
15 files changed, 520 insertions(+), 373 deletions(-)
delete mode 100644 app/controllers/task_controller.rb
create mode 100644 app/controllers/tasks_controller.rb
rename app/views/{task => tasks}/_edit.html.erb (73%)
create mode 100644 app/views/tasks/_list.html.erb
rename app/views/{task/modify.html.erb => tasks/edit.html.erb} (100%)
create mode 100644 app/views/tasks/index.html.erb
rename app/views/{task/create.html.erb => tasks/new.html.erb} (100%)
create mode 100644 app/views/tasks/show.html.erb
rename test/functional/{task_controller_test.rb => tasks_controller_test.rb} (56%)
diff --git a/app/controllers/backlog_controller.rb b/app/controllers/backlog_controller.rb
index 3e6ca56..6011264 100644
--- a/app/controllers/backlog_controller.rb
+++ b/app/controllers/backlog_controller.rb
@@ -28,6 +28,7 @@ class BacklogController < ApplicationController
@task = Task.new
@task.primary = @user if @user
@users = User.find(:all, :conditions => "id in (select user_id from product_roles where product_id = #{(a)product.id})")
+ @tasks = @backlog_item.tasks
end
def modify
diff --git a/app/controllers/task_controller.rb b/app/controllers/task_controller.rb
deleted file mode 100644
index 6c6e42b..0000000
--- a/app/controllers/task_controller.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# task_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +TaskController+ allows the user to create and modify tasks for a backlog
-# item.
-#
-class TaskController < ApplicationController
- before_filter :authenticated, :except => [:view]
- before_filter :load_backlog_item, :only => [:create]
- before_filter :load_task, :only => [:modify, :delete]
- before_filter :authorized_to_create, :only => [:create]
- before_filter :authorized_to_manage, :only => [:modify, :delete]
- before_filter :before_edit, :only => [:create, :modify]
- before_filter :ensure_sprint_is_active, :only => [:create]
-
- def create
- if request.post?
- begin
- Task.transaction do
- @task = Task.create(params[:task])
- @task.backlog_item = @backlog_item
- @backlog_item.tasks << @task
- @backlog_item.save!
- @task.save!
-
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
- end
-
- rescue ActiveRecord::RecordInvalid => error
- flash[:message] = error.message
- @task.valid?
- render :action => :create
- end
- else
- @task = Task.new(:backlog_item => @backlog_item, :primary => @user)
- end
- end
-
- def modify
- if request.post?
- @task.update_attributes(params[:task])
-
- @task.save
-
- redirect_to :controller => :backlog, :action => :view, :id => @task.backlog_item.id
- end
- end
-
- def delete
- if request.post?
- begin
- Task.transaction do
- @task.destroy
- end
-
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
-
- rescue Exception => error
- flash[:message] = error.message
-
- redirect_to error_path
- end
- else
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-
- private
-
- def load_backlog_item
- @backlog_item = BacklogItem.find_by_id(params[:backlog_item])
-
- unless @backlog_item
- flash[:message] = 'Invalid user id.'
- redirect_to error_path
- end
- end
-
- def load_task
- @task = Task.find_by_id(params[:id])
- @backlog_item = @task.backlog_item if @task
-
- unless @task
- flash[:message] = 'Invalid task id.'
- redirect_to error_path
- end
- end
-
- def authorized_to_create
- unless TaskHelper.can_create_task(@user,@backlog_item)
- flash[:message] = 'You are not authorized to work on tasks for this user story.'
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-
- def authorized_to_manage
- unless TaskHelper.can_manage_task(@user,@task)
- flash[:message] = 'You are not authorized to modify this task.'
- redirect_to :controller => :backlog, :action => :view, :id => @task.backlog_item.id
- end
- end
-
- def before_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-
- def ensure_sprint_is_active
- unless @backlog_item.sprint.status == Sprint::STATUS_ACTIVE
- flash[:message] = 'The sprint must be active for that action.'
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-end
diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb
new file mode 100644
index 0000000..af3aec0
--- /dev/null
+++ b/app/controllers/tasks_controller.rb
@@ -0,0 +1,167 @@
+# tasks_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +TasksController+ allows the user to create and modify tasks for a backlog
+# item.
+#
+class TasksController < ApplicationController
+ before_filter :authenticated, :except => [:index, :show]
+ before_filter :load_backlog_item, :only => [:index, :new, :create]
+ before_filter :load_task, :only => [:show, :edit, :update, :destroy]
+ before_filter :before_edit, :only => [:create, :update]
+ before_filter :ensure_sprint_is_active, :only => [:create]
+
+ # GET /tasks
+ def index
+ @tasks = Task.find_all_by_backlog_item_id((a)backlog_item.id)
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /tasks/1
+ def show
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /tasks/new
+ def new
+ if @backlog_item.user_can_add_tasks?(@user)
+ @task = Task.new(
+ :backlog_item_id => @backlog_item.id,
+ :primary_id => @user.id
+ )
+ @users = User.find(:all, :order => 'display_name ASC')
+ else
+ flash[:error] = 'You are not allowed to add tasks to this backlog item.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ # GET /tasks/1;edit
+ def edit
+ unless @task.user_can_edit?(@user)
+ flash[:error] = "You are not allowd to edit this task."
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ # POST /tasks
+ def create
+ if @backlog_item.user_can_add_tasks?(@user)
+ Task.transaction do
+ @task = Task.new(params[:task])
+ @task.backlog_item = @backlog_item
+
+ respond_to do |format|
+ if @task.save
+ flash[:message] = "Recorded #{(a)task.hours} against this backlog item."
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ else
+ format.html { render :action => 'new' }
+ end
+ end
+ end
+ else
+ flash[:error] = "You are not allowed to add tasks to this backlog item."
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ # PUT /tasks/1
+ def update
+ if @task.user_can_edit?(@user)
+ begin
+ Task.transaction do
+ @task.update_attributes(params[:task])
+ @task.save!
+ end
+
+ flash[:message] = "Task updated."
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ end
+
+ respond_to do |format|
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ end
+ else
+ flash[:error] = "You are not allowed to update that task."
+
+ @task.valid?
+
+ respond_to do |format|
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ end
+ end
+ end
+
+ # DELETE /tasks/1
+ def destroy
+ if @task.user_can_delete?(@user)
+ begin
+ Task.transaction do
+ @task.destroy
+ end
+
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ redirect_to error_url
+ end
+ else
+ flash[:error] = 'You are not allowed to delete this task.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ private
+
+ def load_backlog_item
+ @backlog_item = BacklogItem.find_by_id(params[:backlog_item])
+
+ unless @backlog_item
+ flash[:message] = 'Invalid user id.'
+ redirect_to error_path
+ end
+ end
+
+ def load_task
+ @task = Task.find_by_id(params[:id])
+ @backlog_item = @task.backlog_item if @task
+
+ unless @task
+ flash[:message] = 'Invalid task id.'
+ redirect_to error_path
+ end
+ end
+
+ def before_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ def ensure_sprint_is_active
+ unless @backlog_item.sprint.status == Sprint::STATUS_ACTIVE
+ flash[:message] = 'The sprint must be active for that action.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f20df88..96d59a7 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -102,15 +102,6 @@ module ApplicationHelper
:id => backlog_item.id)
end
- # Creates a link to a task.
- #
- def link_to_task(task, options = {})
- @template.link_to(options[:text] ? options[:text] : task.id,
- :controller => :task,
- :action => options[:action] ? options[:action] : :view,
- :id => task.id)
- end
-
# Shows the hours for a given object as estimated, actual and remaining.
#
def show_hours_as_ear(estimated)
diff --git a/app/models/task.rb b/app/models/task.rb
index 89af73d..9173f64 100644
--- a/app/models/task.rb
+++ b/app/models/task.rb
@@ -1,22 +1,22 @@
# task.rb
# Copyright (C) 2008, 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 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.
+# 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/>.
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
#
# A +Task+ represents a single piece of work associated with a +UserStory+.
-# Tasks have a primary developer, and an optional backup developer.
+# Tasks have a primary developer, and an optional backup developer.
class Task < ActiveRecord::Base
validates_presence_of :backlog_item_id
@@ -24,16 +24,32 @@ class Task < ActiveRecord::Base
belongs_to :backup, :class_name => 'User', :foreign_key => 'backup_id'
validates_presence_of :description,
- :message => 'You need to describe this task.'
+ :message => 'You need to describe this task.'
validates_length_of :description,
- :maximum => 1000,
- :too_long => 'Please shorten the description.'
+ :maximum => 1000,
+ :too_long => 'Please shorten the description.'
validates_presence_of :hours,
- :message => 'You must report the hours worked on this task.'
+ :message => 'You must report the hours worked on this task.'
validates_numericality_of :hours,
- :greater_than => 0,
- :message => 'Hours must be numeric and greater than 0.'
+ :greater_than => 0,
+ :message => 'Hours must be numeric and greater than 0.'
belongs_to :backlog_item
+
+ # Returns whether the user can edit the task.
+ #
+ def user_can_edit?(user)
+ user &&
+ backlog_item.sprint.active? &&
+ (primary && (primary.id == user.id))
+ end
+
+ # Returns whether the user can delete the task or not.
+ #
+ def user_can_delete?(user)
+ user &&
+ (backlog_item.owner && (backlog_item.owner.id == user.id)) &&
+ backlog_item.sprint.active?
+ end
end
diff --git a/app/views/backlog/view.html.erb b/app/views/backlog/view.html.erb
index c02f27e..8ee0333 100644
--- a/app/views/backlog/view.html.erb
+++ b/app/views/backlog/view.html.erb
@@ -63,67 +63,5 @@
</tbody>
</table>
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="description" />
- <col class="user" />
- <col class="user" />
- <col class="hours" />
- <col class="date" />
- <col class="actions" />
- </colgroup>
-
- <thead>
- <tr>
- <th colspan="7" class="title">Tasks</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Description</th>
- <th>Primary</th>
- <th>Backup</th>
- <th>Hours</th>
- <th>Date</th>
- <th>Action</th>
- </tr>
- </thead>
-
- <tbody>
-
- <% if @backlog_item.user_can_add_tasks?(@user) %>
- <% form_for :task, :url => {:controller => :task, :action => :create} do |form| %>
- <%= hidden_field_tag :backlog_item, @backlog_item.id %>
- <tr>
- <td></td>
- <td><%= form.text_field :description %></td>
- <td><%= collection_select :task, :primary_id, @users, :id,
- :display_name, {:include_blank => true} %></td>
- <td><%= collection_select :task, :backup_id, @users, :id,
- :display_name, {:include_blank => true} %></td>
- <td><%= form.text_field :hours %></td>
- <td><%= form.date_select :when %></td>
- <td><%= submit_tag "Create" %></td>
- </tr>
- <% end %>
- <% end %>
-
- <% @backlog_item.tasks.each_with_index do |task,i| %>
- <tr class="<% row_class = i%2 == 0 ? 'even' : 'odd' %>">
-
- <td><%= link_to_task task, :action => :modify %></td>
- <td><%= simple_format task.description %></td>
- <td><%= link_to_user task.primary %></td>
- <td><%= link_to_user task.backup %></td>
- <td><%= task.hours %></td>
- <td><%= task.when.to_s(:date) %></td>
- <td>
- <% form_for :task, :url => {:controller => :task, :action => :delete} do |f| %>
- <%= hidden_field_tag :id, task.id %>
- <%= submit_tag "Delete" %>
- <% end %>
- </td>
- </tr>
- <% end %>
- </tbody>
-</table>
+<%= link_to "Add Task", new_task_path(:backlog_item => @backlog_item) %>
+<%= render(:partial => 'tasks/list', :object => @backlog_item.tasks) %>
diff --git a/app/views/task/_edit.html.erb b/app/views/tasks/_edit.html.erb
similarity index 73%
rename from app/views/task/_edit.html.erb
rename to app/views/tasks/_edit.html.erb
index 4195c45..8fa5ad1 100644
--- a/app/views/task/_edit.html.erb
+++ b/app/views/tasks/_edit.html.erb
@@ -1,36 +1,41 @@
<div>
- <% form_for :task do |form| %>
-
+ <% form_for(:task, @task, :url => tasks_path) do |form| %>
+
<%= hidden_field_tag :backlog_item, @task.backlog_item.id %>
-
+
<table class="edit">
<tbody>
<tr>
- <td class="label">Primary:</td>
+ <td class="label">When</td>
+ <td class="value"><%= form.date_select :when %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Primary</td>
<td class="value">
<%= collection_select :task, :primary_id, @users, :id,
:display_name, {:include_blank => true} %>
</td>
</tr>
-
+
<tr>
- <td class="label">Backup:</td>
+ <td class="label">Backup</td>
<td class="value">
<%= collection_select :task, :backup_id, @users, :id,
:display_name, {:include_blank => true} %>
</td>
</tr>
-
+
<tr>
- <td class="label">Hours:</td>
+ <td class="label">Hours</td>
<td class="value"><%= form.text_field :hours %></td>
</tr>
-
+
<tr>
- <td class="label">Description:</td>
+ <td class="label">Description</td>
<td class="value"><%= form.text_area :description %></td>
</tr>
-
+
<tr>
<td class="buttons" colspan="2">
<% if @task.new_record? %>
@@ -42,6 +47,6 @@
</tr>
</tbody>
</table>
-
+
<% end %>
-</div>
\ No newline at end of file
+</div>
diff --git a/app/views/tasks/_list.html.erb b/app/views/tasks/_list.html.erb
new file mode 100644
index 0000000..273296e
--- /dev/null
+++ b/app/views/tasks/_list.html.erb
@@ -0,0 +1,34 @@
+<table class="list">
+ <thead>
+ <tr>
+ <th>Date</th>
+ <th>Description</th>
+ <th>Primary</th>
+ <th>Backup</th>
+ <th>Hours</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @tasks.each_with_index do |task, index| %>
+ <% row_class = index%2 == 0 ? 'even' : 'odd' %>
+ <tr class="<%= row_class %>">
+ <td><%= task.when %></td>
+ <td><%= simple_format task.description %></td>
+ <td><%= link_to_user task.primary %></td>
+ <td><%= link_to_user task.backup %></td>
+ <td><%= format "%0.2f", task.hours %>
+ <td>
+ <%= link_to "View", task_path(task) %>
+ <% if task.user_can_edit?(@user) %>
+ <%= link_to "Edit", edit_task_path(task) %>
+ <% end %>
+ <% if task.user_can_delete?(@user) %>
+ <%= link_to "Delete", task_path(task), :confirm => "Are you sure?", :method => :delete %>
+ <% end %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/task/modify.html.erb b/app/views/tasks/edit.html.erb
similarity index 100%
rename from app/views/task/modify.html.erb
rename to app/views/tasks/edit.html.erb
diff --git a/app/views/tasks/index.html.erb b/app/views/tasks/index.html.erb
new file mode 100644
index 0000000..0a9c778
--- /dev/null
+++ b/app/views/tasks/index.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'list') %>
diff --git a/app/views/task/create.html.erb b/app/views/tasks/new.html.erb
similarity index 100%
rename from app/views/task/create.html.erb
rename to app/views/tasks/new.html.erb
diff --git a/app/views/tasks/show.html.erb b/app/views/tasks/show.html.erb
new file mode 100644
index 0000000..fb0cfb9
--- /dev/null
+++ b/app/views/tasks/show.html.erb
@@ -0,0 +1,35 @@
+<table class="details">
+ <tr>
+ <td class="label">Description:</td>
+ <td class="value"><%= simple_format @task.description %></td>
+ </tr>
+
+ <tr>
+ <td class="label">When:</td>
+ <td class="value"><%= @task.when %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Hours:</td>
+ <td class="value"><%= format "%0.2f", @task.hours %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Primary:</td>
+ <td class="value"><%= link_to_user @task.primary %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Backup:</td>
+ <td class="label"><%= link_to_user @task.backup %></td>
+ </tr>
+
+</table>
+
+<%= link_to "Back", :controller => :backlog, :action => :view, :id => @task.backlog_item %>
+<% if @task.user_can_edit?(@user) %>
+ <% link_to "Edit", edit_task_path(@task) %>
+<% end %>
+<% if @task.user_can_delete?(@user) %>
+ <%= link_to "Delete", task_path(@task), :confirm => "Are you sure?", :method => :delete %>
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 874f8e9..2f66a65 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,10 +16,15 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :tasks
+
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
map.root :controller => 'project'
+ # Login URL
+ map.login "/login", :controller => 'home', :action => 'login'
+
# Error handling
map.error "/error", :controller => 'home', :action => 'error'
diff --git a/test/functional/task_controller_test.rb b/test/functional/tasks_controller_test.rb
similarity index 56%
rename from test/functional/task_controller_test.rb
rename to test/functional/tasks_controller_test.rb
index 92551da..270b308 100644
--- a/test/functional/task_controller_test.rb
+++ b/test/functional/tasks_controller_test.rb
@@ -1,224 +1,267 @@
-# task_controller_test.rb
+# tasks_controller_test.rb
# Copyright (C) 2008, 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 TaskControllerTest < ActionController::TestCase
+class TasksControllerTest < ActionController::TestCase
fixtures :backlog_items
fixtures :users
-
+
def setup
@backlog_item = backlog_items(:owned_backlog_item)
@task = @backlog_item.tasks.first
@owner = @backlog_item.owner
- @non_owner = users(:jdonuts)
+ @nonowner = users(:jdonuts)
+ raise "Owner and nonowner cannot be the same person!" if @owner.id == @nonowner.id
+
@inactive_sprint_item = backlog_items(:owned_inactive_sprint_backlog_item)
-
- flunk "FIXTURES ARE BROKEN" if @owner.id == @non_owner.id
-
+
@details = {
:primary_id => @backlog_item.owner.id,
:description => 'This is a new task.',
:hours => '2.5',
:when => Date.today
- }
+ }
end
-
- # Ensures that an invalid user story causes create to fail.
- #
- def test_create_with_invalid_user_story
- get :create,
- nil,
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
-
- get :create,
- {:user_story => 9999},
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
+
+ # Ensures that viewing tasks when no backlog item is specified fails.
+ #
+ def test_index_without_backlog_item
+ get :index
+
+ assert_redirected_to error_url
end
-
- # Ensures that an anonymous user cannot create a task.
- #
- def test_create_for_anonymous
- get :create,
- {:backlog_item => @backlog_item.id}
-
- assert_redirected_to :controller => :home, :action => :login
+
+ # Ensures that getting the list of tasks for a backlog item works as expected.
+ #
+ def test_index
+ get :index, {:backlog_item => @backlog_item.id}
+
+ assert_response :success
+ assert assigns['backlog_item'], "Failed to load the backlog item."
+ assert_equal @backlog_item.id, assigns['backlog_item'].id,
+ "Did not load the right backlog item."
+ assert assigns['tasks'], "Failed to load the tasks."
end
-
- # Ensures that a user who doesn't have authorization aren't able to create
- # tasks.
- #
- def test_create_for_unauthorized_user
- get :create, {:backlog_item => @backlog_item.id}, {:user_id => users(:jdonuts).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
+
+ # Ensures that an error is show if no task id is present.
+ #
+ def test_show_without_task_id
+ get :show
+
+ assert_redirected_to error_url
end
-
- # Ensures that tasks cannot be created for a backlog item on an non-active
- # sprint.
+
+ # Ensures that creating a task requires the user to be logged in.
#
- def test_create_for_nonactive_sprint
- count = @inactive_sprint_item.tasks.size
-
- get :create,
- {:backlog_item => @inactive_sprint_item.id},
- {:user_id => @inactive_sprint_item.owner.id}
-
- assert_redirected_to :controller => :backlog, :action => :view, :id => @inactive_sprint_item.id
- result = BacklogItem.find_by_id((a)inactive_sprint_item.id)
- assert_equal count, result.tasks.size, 'Tasks should not have been updated'
+ def test_new_for_anonymous
+ get :new
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that creating a task without a backlog item fails.
+ #
+ def test_new_without_backlog_item
+ get :new, nil, {:user_id => @owner.id}
+
+ assert_redirected_to error_url
end
-
+
+ # Ensures that a non-owner cannot create tasks for a backlog item.
+ #
+ def test_new_for_nonowner
+ get :new,
+ {:backlog_item => @backlog_item.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item.id
+ end
+
# Ensures that creating a task works as expected.
- #
- def test_create
- get :create,
- {
- :backlog_item => @backlog_item.id,
- :task => @details
- },
- {:user_id => users(:mcpierce).id}
-
+ #
+ def test_new
+ get :new,
+ {:backlog_item => @backlog_item.id},
+ {:user_id => @owner.id}
+
assert_response :success
- assert assigns['task'], 'Did not create a new task.'
- assert assigns['users'], 'Did not load users.'
+ assert assigns['task'], "Didn't create a task."
+ assert assigns['users'], "Didn't load the list of users."
+ assert_equal @backlog_item.id, assigns['task'].backlog_item_id,
+ "Didn't set the backlog item correctly."
+ assert_equal @owner.id, assigns['task'].primary_id,
+ "Didn't set the primary developer correctly."
end
-
- # Ensures that tasks are saved corrected.
- #
- def test_create_as_save
- post :create,
- {
- :backlog_item => @backlog_item.id,
- :task => @details
- },
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
+
# Ensures that attempting to modify an invalid task fails.
- #
- def test_modify_with_invalid_task
- get :modify, nil, {:user_id => users(:mcpierce).id}
-
+ #
+ def test_edit_with_invalid_task
+ get :edit, nil, {:user_id => users(:mcpierce).id}
+
assert_redirected_to error_path
-
- get :modify, {:id => 9999}
-
+
+ get :edit, {:id => 9999}
+
assert_redirected_to error_path
end
-
+
# Ensures that anonymous users cannot modify tasks.
- #
- def test_modify_for_anonymous
- get :modify, {:id => tasks(:created_login_view).id}
-
+ #
+ def test_edit_for_anonymous
+ get :edit, {:id => tasks(:created_login_view).id}
+
assert_redirected_to :controller => :home, :action => :login
end
-
+
# Ensures that a user who's not the task owner or backup cannot modify the
# task.
- #
- def test_modify_for_non_developer
- get :modify,
+ #
+ def test_edit_for_non_developer
+ get :edit,
{:id => tasks(:created_login_view).id},
{:user_id => users(:project_admin).id}
-
+
assert_redirected_to :controller => :backlog, :action => :view
end
-
+
# Ensures that the owner of the user story is allowed to edit a task against
# that user story.
- #
- def test_modify_for_user_story_owner
- get :modify,
+ #
+ def test_edit_for_user_story_owner
+ get :edit,
{:id => tasks(:created_login_view).id},
{:user_id => users(:mcpierce).id}
-
+
assert_response :success
end
-
+
# Ensures that modifying works as expected.
- #
- def test_modify
- get :modify,
+ #
+ def test_edit
+ get :edit,
{:id => tasks(:created_login_view).id},
{:user_id => tasks(:created_login_view).primary_id}
-
- assert_response :success
+
+ assert_response :success
assert assigns['task'], 'Did not load the task.'
assert_equal tasks(:created_login_view).id, assigns['task'].id, 'Did not load the right task.'
assert assigns['users'], 'Did not load the series.'
end
-
- # Ensures that saving a task after modifying it works as expected.
- #
- def test_modify_as_save
- task = tasks(:created_login_view).attributes
-
- post :modify,
- {:id => tasks(:created_login_view), :task => task},
- {:user_id => tasks(:created_login_view).primary_id}
-
- assert_redirected_to :controller => :backlog, :action => :view
+
+ # Ensures that an invalid user story causes create to fail.
+ #
+ def test_create_with_invalid_user_story
+ get :create,
+ nil,
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+
+ get :create,
+ {:user_story => 9999},
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that an anonymous user cannot create a task.
+ #
+ def test_create_for_anonymous
+ get :create,
+ {:backlog_item => @backlog_item.id}
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user who doesn't have authorization aren't able to create
+ # tasks.
+ #
+ def test_create_for_unauthorized_user
+ get :create, {:backlog_item => @backlog_item.id}, {:user_id => users(:jdonuts).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+
+ # Ensures that tasks cannot be created for a backlog item on an non-active
+ # sprint.
+ #
+ def test_create_for_nonactive_sprint
+ count = @inactive_sprint_item.tasks.size
+
+ get :create,
+ {:backlog_item => @inactive_sprint_item.id},
+ {:user_id => @inactive_sprint_item.owner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @inactive_sprint_item.id
+ result = BacklogItem.find_by_id((a)inactive_sprint_item.id)
+ assert_equal count, result.tasks.size, 'Tasks should not have been updated'
end
-
+
+ # Ensures that tasks are saved corrected.
+ #
+ def test_create
+ post :create,
+ {
+ :backlog_item => @backlog_item.id,
+ :task => @details
+ },
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+
# Ensures that anonymous users can't delete tasks
- #
- def test_delete_as_anonymous
- get :delete
-
+ #
+ def test_destroy_as_anonymous
+ get :destroy
+
assert_redirected_to :controller => :home, :action => :login
end
-
+
# Ensures that deletion requires a valid task id.
- #
- def test_delete_with_invalid_id
- post :delete,
+ #
+ def test_destroy_with_invalid_id
+ post :destroy,
{:id => 9999},
{:user_id => @owner.id}
-
+
assert_redirected_to error_path
end
-
+
# Ensures that someone who is not the backlog item owner can't add tasks to
# it.
- #
- def test_delete_as_non_owner
- post :delete,
+ #
+ def test_destroy_as_non_owner
+ post :destroy,
{
:id => @task.id
},
{
- :user_id => @non_owner.id
+ :user_id => @nonowner.id
}
-
+
assert Task.find_by_id((a)task.id), 'Task should not have been deleted.'
end
-
+
# Ensures that the primary can delete a task.
- #
- def test_delete
- post :delete,
+ #
+ def test_destroy
+ post :destroy,
{
:id => @task.id
},
@@ -226,8 +269,45 @@ class TaskControllerTest < ActionController::TestCase
assert_redirected_to(
:controller => :backlog,
- :action => :view,
+ :action => :view,
:id => @task.backlog_item.id)
assert_equal nil, Task.find_by_id((a)task.id), 'Did not delete the task.'
end
+
+ # Ensures that an update requires the user be loggeg in.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that an update fails without a task id.
+ #
+ def test_update_without_id
+ put :update, {}, {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an update fails with an invalid id.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @owner.id}
+ end
+
+ # Ensures that a non-owner cannot update a task.
+ #
+ def test_update_as_nonowner
+ put :update,
+ {:id => @task.id, :task => @task.attributes},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ result = Task.find_by_id((a)task.id)
+ assert_equal @task.updated_at, result.updated_at,
+ "Task should not have been updated."
+ end
end
diff --git a/test/unit/product_test.rb b/test/unit/product_test.rb
index a0df74d..62647db 100644
--- a/test/unit/product_test.rb
+++ b/test/unit/product_test.rb
@@ -25,7 +25,8 @@ class ProductTest < ActiveSupport::TestCase
def setup
@new_product = Product.new(
:project_id => projects(:cairo).id,
- :owner_id => users(:mcpierce).id)
+ :owner_id => users(:mcpierce).id,
+ :name => "Test Product")
@product = products(:cairo_web)
@owner = @product.owner
--
1.5.6.5
15 years, 5 months
[PROJXP [PATCH] Refactored the tasks controller to be RESTful.
by Darryl L. Pierce
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/backlog_controller.rb | 1 +
app/controllers/task_controller.rb | 127 ------------
app/controllers/tasks_controller.rb | 167 ++++++++++++++++
app/helpers/application_helper.rb | 9 -
app/models/task.rb | 51 ++++--
app/views/backlog/view.html.erb | 66 +------
app/views/task/_edit.html.erb | 47 -----
app/views/task/create.html.erb | 3 -
app/views/task/modify.html.erb | 1 -
app/views/tasks/_edit.html.erb | 52 +++++
app/views/tasks/_list.html.erb | 34 ++++
app/views/tasks/index.html.erb | 1 +
app/views/tasks/modify.html.erb | 1 +
app/views/tasks/new.html.erb | 3 +
app/views/tasks/show.html.erb | 35 ++++
config/routes.rb | 5 +
test/functional/task_controller_test.rb | 233 ----------------------
test/functional/tasks_controller_test.rb | 313 ++++++++++++++++++++++++++++++
test/unit/product_test.rb | 3 +-
19 files changed, 650 insertions(+), 502 deletions(-)
delete mode 100644 app/controllers/task_controller.rb
create mode 100644 app/controllers/tasks_controller.rb
delete mode 100644 app/views/task/_edit.html.erb
delete mode 100644 app/views/task/create.html.erb
delete mode 100644 app/views/task/modify.html.erb
create mode 100644 app/views/tasks/_edit.html.erb
create mode 100644 app/views/tasks/_list.html.erb
create mode 100644 app/views/tasks/index.html.erb
create mode 100644 app/views/tasks/modify.html.erb
create mode 100644 app/views/tasks/new.html.erb
create mode 100644 app/views/tasks/show.html.erb
delete mode 100644 test/functional/task_controller_test.rb
create mode 100644 test/functional/tasks_controller_test.rb
diff --git a/app/controllers/backlog_controller.rb b/app/controllers/backlog_controller.rb
index 3e6ca56..6011264 100644
--- a/app/controllers/backlog_controller.rb
+++ b/app/controllers/backlog_controller.rb
@@ -28,6 +28,7 @@ class BacklogController < ApplicationController
@task = Task.new
@task.primary = @user if @user
@users = User.find(:all, :conditions => "id in (select user_id from product_roles where product_id = #{(a)product.id})")
+ @tasks = @backlog_item.tasks
end
def modify
diff --git a/app/controllers/task_controller.rb b/app/controllers/task_controller.rb
deleted file mode 100644
index 6c6e42b..0000000
--- a/app/controllers/task_controller.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-# task_controller.rb
-# Copyright (C) 2008, 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/>.
-#
-
-# +TaskController+ allows the user to create and modify tasks for a backlog
-# item.
-#
-class TaskController < ApplicationController
- before_filter :authenticated, :except => [:view]
- before_filter :load_backlog_item, :only => [:create]
- before_filter :load_task, :only => [:modify, :delete]
- before_filter :authorized_to_create, :only => [:create]
- before_filter :authorized_to_manage, :only => [:modify, :delete]
- before_filter :before_edit, :only => [:create, :modify]
- before_filter :ensure_sprint_is_active, :only => [:create]
-
- def create
- if request.post?
- begin
- Task.transaction do
- @task = Task.create(params[:task])
- @task.backlog_item = @backlog_item
- @backlog_item.tasks << @task
- @backlog_item.save!
- @task.save!
-
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
- end
-
- rescue ActiveRecord::RecordInvalid => error
- flash[:message] = error.message
- @task.valid?
- render :action => :create
- end
- else
- @task = Task.new(:backlog_item => @backlog_item, :primary => @user)
- end
- end
-
- def modify
- if request.post?
- @task.update_attributes(params[:task])
-
- @task.save
-
- redirect_to :controller => :backlog, :action => :view, :id => @task.backlog_item.id
- end
- end
-
- def delete
- if request.post?
- begin
- Task.transaction do
- @task.destroy
- end
-
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
-
- rescue Exception => error
- flash[:message] = error.message
-
- redirect_to error_path
- end
- else
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-
- private
-
- def load_backlog_item
- @backlog_item = BacklogItem.find_by_id(params[:backlog_item])
-
- unless @backlog_item
- flash[:message] = 'Invalid user id.'
- redirect_to error_path
- end
- end
-
- def load_task
- @task = Task.find_by_id(params[:id])
- @backlog_item = @task.backlog_item if @task
-
- unless @task
- flash[:message] = 'Invalid task id.'
- redirect_to error_path
- end
- end
-
- def authorized_to_create
- unless TaskHelper.can_create_task(@user,@backlog_item)
- flash[:message] = 'You are not authorized to work on tasks for this user story.'
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-
- def authorized_to_manage
- unless TaskHelper.can_manage_task(@user,@task)
- flash[:message] = 'You are not authorized to modify this task.'
- redirect_to :controller => :backlog, :action => :view, :id => @task.backlog_item.id
- end
- end
-
- def before_edit
- @users = User.find(:all, :order => 'display_name ASC')
- end
-
- def ensure_sprint_is_active
- unless @backlog_item.sprint.status == Sprint::STATUS_ACTIVE
- flash[:message] = 'The sprint must be active for that action.'
- redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
- end
- end
-end
diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb
new file mode 100644
index 0000000..dda739c
--- /dev/null
+++ b/app/controllers/tasks_controller.rb
@@ -0,0 +1,167 @@
+# tasks_controller.rb
+# Copyright (C) 2008, 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/>.
+#
+
+# +TasksController+ allows the user to create and modify tasks for a backlog
+# item.
+#
+class TasksController < ApplicationController
+ before_filter :authenticated, :except => [:index, :show]
+ before_filter :load_backlog_item, :only => [:index, :new, :create]
+ before_filter :load_task, :only => [:show, :edit, :update, :delete]
+ before_filter :before_edit, :only => [:create, :modify]
+ before_filter :ensure_sprint_is_active, :only => [:create]
+
+ # GET /tasks
+ def index
+ @tasks = Task.find_all_by_backlog_item_id((a)backlog_item.id)
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /tasks/1
+ def show
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ # GET /tasks/new
+ def new
+ if @backlog_item.user_can_add_tasks?(@user)
+ @task = Task.new(
+ :backlog_item_id => @backlog_item.id,
+ :primary_id => @user.id
+ )
+ @users = User.find(:all, :order => 'display_name ASC')
+ else
+ flash[:error] = 'You are not allowed to add tasks to this backlog item.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ # GET /tasks/1;edit
+ def edit
+ unless @task.user_can_edit?(@user)
+ flash[:error] = "You are not allowd to edit this task."
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ # POST /tasks
+ def create
+ if @backlog_item.user_can_add_tasks?(@user)
+ Task.transaction do
+ @task = Task.new(params[:task])
+ @task.backlog_item = @backlog_item
+
+ respond_to do |format|
+ if @task.save
+ flash[:message] = "Recorded #{(a)task.hours} against this backlog item."
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item }
+ else
+ format.html { render :action => 'new' }
+ end
+ end
+ end
+ else
+ flash[:error] = "You are not allowed to add tasks to this backlog item."
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ # PUT /tasks/1
+ def update
+ if @task.user_can_edit?(@user)
+ begin
+ Task.transaction do
+ @task.update_attributes(params[:task])
+ @task.save!
+ end
+
+ flash[:message] = "Task updated."
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ end
+
+ respond_to do |format|
+ format.html { redirect_to :controller => :backlog, :action => :view, :id => @backlog_item}
+ end
+ else
+ flash[:error] = "You are not allowed to update that task."
+
+ @task.valid?
+
+ respond_to do |format|
+ format.html { render :action => :edit }
+ end
+ end
+ end
+
+ # DELETE /tasks/1
+ def delete
+ if @task.user_can_delete?(@user)
+ begin
+ Task.transaction do
+ @task.destroy
+ end
+
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ rescue Exception => error
+ flash[:error] = "ERROR: #{error.message}"
+ redirect_to error_url
+ end
+ else
+ flash[:error] = 'You are not allowed to delete this task.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+ end
+
+ private
+
+ def load_backlog_item
+ @backlog_item = BacklogItem.find_by_id(params[:backlog_item])
+
+ unless @backlog_item
+ flash[:message] = 'Invalid user id.'
+ redirect_to error_path
+ end
+ end
+
+ def load_task
+ @task = Task.find_by_id(params[:id])
+ @backlog_item = @task.backlog_item if @task
+
+ unless @task
+ flash[:message] = 'Invalid task id.'
+ redirect_to error_path
+ end
+ end
+
+ def before_edit
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ def ensure_sprint_is_active
+ unless @backlog_item.sprint.status == Sprint::STATUS_ACTIVE
+ flash[:message] = 'The sprint must be active for that action.'
+ redirect_to :controller => :backlog, :action => :view, :id => @backlog_item.id
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f20df88..96d59a7 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -102,15 +102,6 @@ module ApplicationHelper
:id => backlog_item.id)
end
- # Creates a link to a task.
- #
- def link_to_task(task, options = {})
- @template.link_to(options[:text] ? options[:text] : task.id,
- :controller => :task,
- :action => options[:action] ? options[:action] : :view,
- :id => task.id)
- end
-
# Shows the hours for a given object as estimated, actual and remaining.
#
def show_hours_as_ear(estimated)
diff --git a/app/models/task.rb b/app/models/task.rb
index 89af73d..b518364 100644
--- a/app/models/task.rb
+++ b/app/models/task.rb
@@ -1,22 +1,22 @@
# task.rb
# Copyright (C) 2008, 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 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.
+# 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/>.
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
#
# A +Task+ represents a single piece of work associated with a +UserStory+.
-# Tasks have a primary developer, and an optional backup developer.
+# Tasks have a primary developer, and an optional backup developer.
class Task < ActiveRecord::Base
validates_presence_of :backlog_item_id
@@ -24,16 +24,33 @@ class Task < ActiveRecord::Base
belongs_to :backup, :class_name => 'User', :foreign_key => 'backup_id'
validates_presence_of :description,
- :message => 'You need to describe this task.'
+ :message => 'You need to describe this task.'
validates_length_of :description,
- :maximum => 1000,
- :too_long => 'Please shorten the description.'
+ :maximum => 1000,
+ :too_long => 'Please shorten the description.'
validates_presence_of :hours,
- :message => 'You must report the hours worked on this task.'
+ :message => 'You must report the hours worked on this task.'
validates_numericality_of :hours,
- :greater_than => 0,
- :message => 'Hours must be numeric and greater than 0.'
+ :greater_than => 0,
+ :message => 'Hours must be numeric and greater than 0.'
belongs_to :backlog_item
+
+ # Returns whether the user can edit the task.
+ #
+ def user_can_edit?(user)
+ user &&
+ backlog_item.sprint.active? &&
+ ((primary && (primary.id == user.id)) ||
+ (backup && backup.id == user.id))
+ end
+
+ # Returns whether the user can delete the task or not.
+ #
+ def user_can_delete?(user)
+ user &&
+ (backlog_item.owner && (backlog_item.owner.id == user.id)) &&
+ backlog_item.sprint.active?
+ end
end
diff --git a/app/views/backlog/view.html.erb b/app/views/backlog/view.html.erb
index c02f27e..8ee0333 100644
--- a/app/views/backlog/view.html.erb
+++ b/app/views/backlog/view.html.erb
@@ -63,67 +63,5 @@
</tbody>
</table>
-<table class="list">
- <colgroup>
- <col class="row_id" />
- <col class="description" />
- <col class="user" />
- <col class="user" />
- <col class="hours" />
- <col class="date" />
- <col class="actions" />
- </colgroup>
-
- <thead>
- <tr>
- <th colspan="7" class="title">Tasks</th>
- </tr>
- <tr>
- <th>##</th>
- <th>Description</th>
- <th>Primary</th>
- <th>Backup</th>
- <th>Hours</th>
- <th>Date</th>
- <th>Action</th>
- </tr>
- </thead>
-
- <tbody>
-
- <% if @backlog_item.user_can_add_tasks?(@user) %>
- <% form_for :task, :url => {:controller => :task, :action => :create} do |form| %>
- <%= hidden_field_tag :backlog_item, @backlog_item.id %>
- <tr>
- <td></td>
- <td><%= form.text_field :description %></td>
- <td><%= collection_select :task, :primary_id, @users, :id,
- :display_name, {:include_blank => true} %></td>
- <td><%= collection_select :task, :backup_id, @users, :id,
- :display_name, {:include_blank => true} %></td>
- <td><%= form.text_field :hours %></td>
- <td><%= form.date_select :when %></td>
- <td><%= submit_tag "Create" %></td>
- </tr>
- <% end %>
- <% end %>
-
- <% @backlog_item.tasks.each_with_index do |task,i| %>
- <tr class="<% row_class = i%2 == 0 ? 'even' : 'odd' %>">
-
- <td><%= link_to_task task, :action => :modify %></td>
- <td><%= simple_format task.description %></td>
- <td><%= link_to_user task.primary %></td>
- <td><%= link_to_user task.backup %></td>
- <td><%= task.hours %></td>
- <td><%= task.when.to_s(:date) %></td>
- <td>
- <% form_for :task, :url => {:controller => :task, :action => :delete} do |f| %>
- <%= hidden_field_tag :id, task.id %>
- <%= submit_tag "Delete" %>
- <% end %>
- </td>
- </tr>
- <% end %>
- </tbody>
-</table>
+<%= link_to "Add Task", new_task_path(:backlog_item => @backlog_item) %>
+<%= render(:partial => 'tasks/list', :object => @backlog_item.tasks) %>
diff --git a/app/views/task/_edit.html.erb b/app/views/task/_edit.html.erb
deleted file mode 100644
index 4195c45..0000000
--- a/app/views/task/_edit.html.erb
+++ /dev/null
@@ -1,47 +0,0 @@
-<div>
- <% form_for :task do |form| %>
-
- <%= hidden_field_tag :backlog_item, @task.backlog_item.id %>
-
- <table class="edit">
- <tbody>
- <tr>
- <td class="label">Primary:</td>
- <td class="value">
- <%= collection_select :task, :primary_id, @users, :id,
- :display_name, {:include_blank => true} %>
- </td>
- </tr>
-
- <tr>
- <td class="label">Backup:</td>
- <td class="value">
- <%= collection_select :task, :backup_id, @users, :id,
- :display_name, {:include_blank => true} %>
- </td>
- </tr>
-
- <tr>
- <td class="label">Hours:</td>
- <td class="value"><%= form.text_field :hours %></td>
- </tr>
-
- <tr>
- <td class="label">Description:</td>
- <td class="value"><%= form.text_area :description %></td>
- </tr>
-
- <tr>
- <td class="buttons" colspan="2">
- <% if @task.new_record? %>
- <%= submit_tag "Create", :class => 'button' %>
- <% else %>
- <%= submit_tag "Update", :class => 'button' %>
- <% end %>
- </td>
- </tr>
- </tbody>
- </table>
-
- <% end %>
-</div>
\ No newline at end of file
diff --git a/app/views/task/create.html.erb b/app/views/task/create.html.erb
deleted file mode 100644
index ad1a69b..0000000
--- a/app/views/task/create.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<div>
- <%= render :partial => 'edit', :object => @task %>
-</div>
\ No newline at end of file
diff --git a/app/views/task/modify.html.erb b/app/views/task/modify.html.erb
deleted file mode 100644
index e536180..0000000
--- a/app/views/task/modify.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render :partial => "edit", :object => @task %>
diff --git a/app/views/tasks/_edit.html.erb b/app/views/tasks/_edit.html.erb
new file mode 100644
index 0000000..8fa5ad1
--- /dev/null
+++ b/app/views/tasks/_edit.html.erb
@@ -0,0 +1,52 @@
+<div>
+ <% form_for(:task, @task, :url => tasks_path) do |form| %>
+
+ <%= hidden_field_tag :backlog_item, @task.backlog_item.id %>
+
+ <table class="edit">
+ <tbody>
+ <tr>
+ <td class="label">When</td>
+ <td class="value"><%= form.date_select :when %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Primary</td>
+ <td class="value">
+ <%= collection_select :task, :primary_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Backup</td>
+ <td class="value">
+ <%= collection_select :task, :backup_id, @users, :id,
+ :display_name, {:include_blank => true} %>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">Hours</td>
+ <td class="value"><%= form.text_field :hours %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Description</td>
+ <td class="value"><%= form.text_area :description %></td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="2">
+ <% if @task.new_record? %>
+ <%= submit_tag "Create", :class => 'button' %>
+ <% else %>
+ <%= submit_tag "Update", :class => 'button' %>
+ <% end %>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <% end %>
+</div>
diff --git a/app/views/tasks/_list.html.erb b/app/views/tasks/_list.html.erb
new file mode 100644
index 0000000..3571b09
--- /dev/null
+++ b/app/views/tasks/_list.html.erb
@@ -0,0 +1,34 @@
+<table class="list">
+ <thead>
+ <tr>
+ <th>Date</th>
+ <th>Description</th>
+ <th>Primary</th>
+ <th>Backup</th>
+ <th>Hours</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @tasks.each_with_index do |task, index| %>
+ <% row_class = index%2 == 0 ? 'even' : 'odd' %>
+ <tr class="<%= row_class %>">
+ <td><%= task.when %></td>
+ <td><%= simple_format task.description %></td>
+ <td><%= link_to_user task.primary %></td>
+ <td><%= link_to_user task.backup %></td>
+ <td><%= format "%0.2f", task.hours %>
+ <td>
+ <%= link_to "View", task_path(task) %>
+ <% if task.user_can_edit?(@user) %>
+ <%= link_to "Edit", edit_task_path(task) %>
+ <% end %>
+ <% if task.user_can_delete?(@user) %>
+ <%= link_to "Delete", task_path(task), :config => "Are you sure?", :method => :delete %>
+ <% end %>
+ </td>
+ </tr>
+ <% end %>
+ </tbody>
+</table>
diff --git a/app/views/tasks/index.html.erb b/app/views/tasks/index.html.erb
new file mode 100644
index 0000000..0a9c778
--- /dev/null
+++ b/app/views/tasks/index.html.erb
@@ -0,0 +1 @@
+<%= render(:partial => 'list') %>
diff --git a/app/views/tasks/modify.html.erb b/app/views/tasks/modify.html.erb
new file mode 100644
index 0000000..e536180
--- /dev/null
+++ b/app/views/tasks/modify.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "edit", :object => @task %>
diff --git a/app/views/tasks/new.html.erb b/app/views/tasks/new.html.erb
new file mode 100644
index 0000000..ad1a69b
--- /dev/null
+++ b/app/views/tasks/new.html.erb
@@ -0,0 +1,3 @@
+<div>
+ <%= render :partial => 'edit', :object => @task %>
+</div>
\ No newline at end of file
diff --git a/app/views/tasks/show.html.erb b/app/views/tasks/show.html.erb
new file mode 100644
index 0000000..fb0cfb9
--- /dev/null
+++ b/app/views/tasks/show.html.erb
@@ -0,0 +1,35 @@
+<table class="details">
+ <tr>
+ <td class="label">Description:</td>
+ <td class="value"><%= simple_format @task.description %></td>
+ </tr>
+
+ <tr>
+ <td class="label">When:</td>
+ <td class="value"><%= @task.when %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Hours:</td>
+ <td class="value"><%= format "%0.2f", @task.hours %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Primary:</td>
+ <td class="value"><%= link_to_user @task.primary %></td>
+ </tr>
+
+ <tr>
+ <td class="label">Backup:</td>
+ <td class="label"><%= link_to_user @task.backup %></td>
+ </tr>
+
+</table>
+
+<%= link_to "Back", :controller => :backlog, :action => :view, :id => @task.backlog_item %>
+<% if @task.user_can_edit?(@user) %>
+ <% link_to "Edit", edit_task_path(@task) %>
+<% end %>
+<% if @task.user_can_delete?(@user) %>
+ <%= link_to "Delete", task_path(@task), :confirm => "Are you sure?", :method => :delete %>
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 874f8e9..2f66a65 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,10 +16,15 @@
#
ActionController::Routing::Routes.draw do |map|
+ map.resources :tasks
+
# You can have the root of your site routed with map.root -- just remember to
# delete public/index.html.
map.root :controller => 'project'
+ # Login URL
+ map.login "/login", :controller => 'home', :action => 'login'
+
# Error handling
map.error "/error", :controller => 'home', :action => 'error'
diff --git a/test/functional/task_controller_test.rb b/test/functional/task_controller_test.rb
deleted file mode 100644
index 92551da..0000000
--- a/test/functional/task_controller_test.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-# task_controller_test.rb
-# Copyright (C) 2008, 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 TaskControllerTest < ActionController::TestCase
- fixtures :backlog_items
- fixtures :users
-
- def setup
- @backlog_item = backlog_items(:owned_backlog_item)
- @task = @backlog_item.tasks.first
- @owner = @backlog_item.owner
- @non_owner = users(:jdonuts)
- @inactive_sprint_item = backlog_items(:owned_inactive_sprint_backlog_item)
-
- flunk "FIXTURES ARE BROKEN" if @owner.id == @non_owner.id
-
- @details = {
- :primary_id => @backlog_item.owner.id,
- :description => 'This is a new task.',
- :hours => '2.5',
- :when => Date.today
- }
- end
-
- # Ensures that an invalid user story causes create to fail.
- #
- def test_create_with_invalid_user_story
- get :create,
- nil,
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
-
- get :create,
- {:user_story => 9999},
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that an anonymous user cannot create a task.
- #
- def test_create_for_anonymous
- get :create,
- {:backlog_item => @backlog_item.id}
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who doesn't have authorization aren't able to create
- # tasks.
- #
- def test_create_for_unauthorized_user
- get :create, {:backlog_item => @backlog_item.id}, {:user_id => users(:jdonuts).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that tasks cannot be created for a backlog item on an non-active
- # sprint.
- #
- def test_create_for_nonactive_sprint
- count = @inactive_sprint_item.tasks.size
-
- get :create,
- {:backlog_item => @inactive_sprint_item.id},
- {:user_id => @inactive_sprint_item.owner.id}
-
- assert_redirected_to :controller => :backlog, :action => :view, :id => @inactive_sprint_item.id
- result = BacklogItem.find_by_id((a)inactive_sprint_item.id)
- assert_equal count, result.tasks.size, 'Tasks should not have been updated'
- end
-
- # Ensures that creating a task works as expected.
- #
- def test_create
- get :create,
- {
- :backlog_item => @backlog_item.id,
- :task => @details
- },
- {:user_id => users(:mcpierce).id}
-
- assert_response :success
- assert assigns['task'], 'Did not create a new task.'
- assert assigns['users'], 'Did not load users.'
- end
-
- # Ensures that tasks are saved corrected.
- #
- def test_create_as_save
- post :create,
- {
- :backlog_item => @backlog_item.id,
- :task => @details
- },
- {:user_id => users(:mcpierce).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that attempting to modify an invalid task fails.
- #
- def test_modify_with_invalid_task
- get :modify, nil, {:user_id => users(:mcpierce).id}
-
- assert_redirected_to error_path
-
- get :modify, {:id => 9999}
-
- assert_redirected_to error_path
- end
-
- # Ensures that anonymous users cannot modify tasks.
- #
- def test_modify_for_anonymous
- get :modify, {:id => tasks(:created_login_view).id}
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that a user who's not the task owner or backup cannot modify the
- # task.
- #
- def test_modify_for_non_developer
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => users(:project_admin).id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that the owner of the user story is allowed to edit a task against
- # that user story.
- #
- def test_modify_for_user_story_owner
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => users(:mcpierce).id}
-
- assert_response :success
- end
-
- # Ensures that modifying works as expected.
- #
- def test_modify
- get :modify,
- {:id => tasks(:created_login_view).id},
- {:user_id => tasks(:created_login_view).primary_id}
-
- assert_response :success
- assert assigns['task'], 'Did not load the task.'
- assert_equal tasks(:created_login_view).id, assigns['task'].id, 'Did not load the right task.'
- assert assigns['users'], 'Did not load the series.'
- end
-
- # Ensures that saving a task after modifying it works as expected.
- #
- def test_modify_as_save
- task = tasks(:created_login_view).attributes
-
- post :modify,
- {:id => tasks(:created_login_view), :task => task},
- {:user_id => tasks(:created_login_view).primary_id}
-
- assert_redirected_to :controller => :backlog, :action => :view
- end
-
- # Ensures that anonymous users can't delete tasks
- #
- def test_delete_as_anonymous
- get :delete
-
- assert_redirected_to :controller => :home, :action => :login
- end
-
- # Ensures that deletion requires a valid task id.
- #
- def test_delete_with_invalid_id
- post :delete,
- {:id => 9999},
- {:user_id => @owner.id}
-
- assert_redirected_to error_path
- end
-
- # Ensures that someone who is not the backlog item owner can't add tasks to
- # it.
- #
- def test_delete_as_non_owner
- post :delete,
- {
- :id => @task.id
- },
- {
- :user_id => @non_owner.id
- }
-
- assert Task.find_by_id((a)task.id), 'Task should not have been deleted.'
- end
-
- # Ensures that the primary can delete a task.
- #
- def test_delete
- post :delete,
- {
- :id => @task.id
- },
- {:user_id => @owner.id}
-
- assert_redirected_to(
- :controller => :backlog,
- :action => :view,
- :id => @task.backlog_item.id)
- assert_equal nil, Task.find_by_id((a)task.id), 'Did not delete the task.'
- end
-end
diff --git a/test/functional/tasks_controller_test.rb b/test/functional/tasks_controller_test.rb
new file mode 100644
index 0000000..7e0360d
--- /dev/null
+++ b/test/functional/tasks_controller_test.rb
@@ -0,0 +1,313 @@
+# tasks_controller_test.rb
+# Copyright (C) 2008, 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 TasksControllerTest < ActionController::TestCase
+ fixtures :backlog_items
+ fixtures :users
+
+ def setup
+ @backlog_item = backlog_items(:owned_backlog_item)
+ @task = @backlog_item.tasks.first
+ @owner = @backlog_item.owner
+ @nonowner = users(:jdonuts)
+ @inactive_sprint_item = backlog_items(:owned_inactive_sprint_backlog_item)
+
+ flunk "FIXTURES ARE BROKEN" if @owner.id == @nonowner.id
+
+ @details = {
+ :primary_id => @backlog_item.owner.id,
+ :description => 'This is a new task.',
+ :hours => '2.5',
+ :when => Date.today
+ }
+ end
+
+ # Ensures that viewing tasks when no backlog item is specified fails.
+ #
+ def test_index_without_backlog_item
+ get :index
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that getting the list of tasks for a backlog item works as expected.
+ #
+ def test_index
+ get :index, {:backlog_item => @backlog_item.id}
+
+ assert_response :success
+ assert assigns['backlog_item'], "Failed to load the backlog item."
+ assert_equal @backlog_item.id, assigns['backlog_item'].id,
+ "Did not load the right backlog item."
+ assert assigns['tasks'], "Failed to load the tasks."
+ end
+
+ # Ensures that an error is show if no task id is present.
+ #
+ def test_show_without_task_id
+ get :show
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that creating a task requires the user to be logged in.
+ #
+ def test_new_for_anonymous
+ get :new
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that creating a task without a backlog item fails.
+ #
+ def test_new_without_backlog_item
+ get :new, nil, {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a non-owner cannot create tasks for a backlog item.
+ #
+ def test_new_for_nonowner
+ get :new,
+ {:backlog_item => @backlog_item.id},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item.id
+ end
+
+ # Ensures that creating a task works as expected.
+ #
+ def test_new
+ get :new,
+ {:backlog_item => @backlog_item.id},
+ {:user_id => @owner.id}
+
+ assert_response :success
+ assert assigns['task'], "Didn't create a task."
+ assert assigns['users'], "Didn't load the list of users."
+ assert_equal @backlog_item.id, assigns['task'].backlog_item_id,
+ "Didn't set the backlog item correctly."
+ assert_equal @owner.id, assigns['task'].primary_id,
+ "Didn't set the primary developer correctly."
+ end
+
+ # Ensures that attempting to modify an invalid task fails.
+ #
+ def test_edit_with_invalid_task
+ get :edit, nil, {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+
+ get :edit, {:id => 9999}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that anonymous users cannot modify tasks.
+ #
+ def test_edit_for_anonymous
+ get :edit, {:id => tasks(:created_login_view).id}
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user who's not the task owner or backup cannot modify the
+ # task.
+ #
+ def test_edit_for_non_developer
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => users(:project_admin).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view
+ end
+
+ # Ensures that the owner of the user story is allowed to edit a task against
+ # that user story.
+ #
+ def test_edit_for_user_story_owner
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => users(:mcpierce).id}
+
+ assert_response :success
+ end
+
+ # Ensures that modifying works as expected.
+ #
+ def test_edit
+ get :edit,
+ {:id => tasks(:created_login_view).id},
+ {:user_id => tasks(:created_login_view).primary_id}
+
+ assert_response :success
+ assert assigns['task'], 'Did not load the task.'
+ assert_equal tasks(:created_login_view).id, assigns['task'].id, 'Did not load the right task.'
+ assert assigns['users'], 'Did not load the series.'
+ end
+
+ # Ensures that an invalid user story causes create to fail.
+ #
+ def test_create_with_invalid_user_story
+ get :create,
+ nil,
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+
+ get :create,
+ {:user_story => 9999},
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that an anonymous user cannot create a task.
+ #
+ def test_create_for_anonymous
+ get :create,
+ {:backlog_item => @backlog_item.id}
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that a user who doesn't have authorization aren't able to create
+ # tasks.
+ #
+ def test_create_for_unauthorized_user
+ get :create, {:backlog_item => @backlog_item.id}, {:user_id => users(:jdonuts).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+
+ # Ensures that tasks cannot be created for a backlog item on an non-active
+ # sprint.
+ #
+ def test_create_for_nonactive_sprint
+ count = @inactive_sprint_item.tasks.size
+
+ get :create,
+ {:backlog_item => @inactive_sprint_item.id},
+ {:user_id => @inactive_sprint_item.owner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @inactive_sprint_item.id
+ result = BacklogItem.find_by_id((a)inactive_sprint_item.id)
+ assert_equal count, result.tasks.size, 'Tasks should not have been updated'
+ end
+
+ # Ensures that tasks are saved corrected.
+ #
+ def test_create
+ post :create,
+ {
+ :backlog_item => @backlog_item.id,
+ :task => @details
+ },
+ {:user_id => users(:mcpierce).id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ end
+
+ # Ensures that anonymous users can't delete tasks
+ #
+ def test_delete_as_anonymous
+ get :delete
+
+ assert_redirected_to :controller => :home, :action => :login
+ end
+
+ # Ensures that deletion requires a valid task id.
+ #
+ def test_delete_with_invalid_id
+ post :delete,
+ {:id => 9999},
+ {:user_id => @owner.id}
+
+ assert_redirected_to error_path
+ end
+
+ # Ensures that someone who is not the backlog item owner can't add tasks to
+ # it.
+ #
+ def test_delete_as_non_owner
+ post :delete,
+ {
+ :id => @task.id
+ },
+ {
+ :user_id => @nonowner.id
+ }
+
+ assert Task.find_by_id((a)task.id), 'Task should not have been deleted.'
+ end
+
+ # Ensures that the primary can delete a task.
+ #
+ def test_delete
+ post :delete,
+ {
+ :id => @task.id
+ },
+ {:user_id => @owner.id}
+
+ assert_redirected_to(
+ :controller => :backlog,
+ :action => :view,
+ :id => @task.backlog_item.id)
+ assert_equal nil, Task.find_by_id((a)task.id), 'Did not delete the task.'
+ end
+
+ # Ensures that an update requires the user be loggeg in.
+ #
+ def test_update_as_anonymous
+ put :update
+
+ assert_redirected_to login_url
+ end
+
+ # Ensures that an update fails without a task id.
+ #
+ def test_update_without_id
+ put :update, {}, {:user_id => @owner.id}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that an update fails with an invalid id.
+ #
+ def test_update_with_invalid_id
+ put :update,
+ {:id => 9999},
+ {:user_id => @owner.id}
+ end
+
+ # Ensures that a non-owner cannot update a task.
+ #
+ def test_update_as_nonowner
+ put :update,
+ {:id => @task.id, :task => @task.attributes},
+ {:user_id => @nonowner.id}
+
+ assert_redirected_to :controller => :backlog, :action => :view, :id => @backlog_item
+ result = Task.find_by_id((a)task.id)
+ assert_equal @task.updated_at, result.updated_at,
+ "Task should not have been updated."
+ end
+end
diff --git a/test/unit/product_test.rb b/test/unit/product_test.rb
index a0df74d..62647db 100644
--- a/test/unit/product_test.rb
+++ b/test/unit/product_test.rb
@@ -25,7 +25,8 @@ class ProductTest < ActiveSupport::TestCase
def setup
@new_product = Product.new(
:project_id => projects(:cairo).id,
- :owner_id => users(:mcpierce).id)
+ :owner_id => users(:mcpierce).id,
+ :name => "Test Product")
@product = products(:cairo_web)
@owner = @product.owner
--
1.6.0.2
15 years, 5 months
[PROJXP [PATCH] Users can view a effort report. #12
by Darryl L. Pierce
If no date range is given, then all efforts for currently active
sprints are shown.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
app/controllers/report_controller.rb | 30 ++++++++++++++++-
app/views/report/effort.html.erb | 51 +++++++++++++++++++++++++++++
app/views/report/index.html.erb | 16 +++++++++
app/views/user/view.html.erb | 1 +
config/routes.rb | 24 +++++++-------
db/schema.rb | 20 +++++++++++-
test/functional/report_controller_test.rb | 27 +++++++++++++++
7 files changed, 155 insertions(+), 14 deletions(-)
create mode 100644 app/views/report/effort.html.erb
create mode 100644 app/views/report/index.html.erb
diff --git a/app/controllers/report_controller.rb b/app/controllers/report_controller.rb
index c3ca4a1..caebe81 100644
--- a/app/controllers/report_controller.rb
+++ b/app/controllers/report_controller.rb
@@ -18,7 +18,26 @@
# +ReportController+ allows users to view various reports.
#
class ReportController < ApplicationController
- before_filter :load_sprint
+ before_filter :load_sprint, :except => [:index, :effort]
+ before_filter :load_user, :only => [:effort]
+
+ def index
+ @users = User.find(:all, :order => 'display_name ASC')
+ end
+
+ def burndown
+
+ end
+
+ def effort
+ @tasks = Task.find(
+ :all,
+ :conditions => ['(primary_id = ? or backup_id = ?) and
+ backlog_item_id in
+ (select bi.id from backlog_items bi where bi.sprint_id in (select id from sprints where status = ?))',
+ @this_user.id, @this_user.id, Sprint::STATUS_ACTIVE],
+ :order => 'backlog_item_id')
+ end
# Generates a burndown report.
#
@@ -53,4 +72,13 @@ class ReportController < ApplicationController
redirect_to error_url
end
end
+
+ def load_user
+ @this_user = User.find_by_id(params[:id])
+
+ unless @this_user
+ flash[:error] = "Invalid or unspecified user."
+ redirect_to error_url
+ end
+ end
end
diff --git a/app/views/report/effort.html.erb b/app/views/report/effort.html.erb
new file mode 100644
index 0000000..2ac22cd
--- /dev/null
+++ b/app/views/report/effort.html.erb
@@ -0,0 +1,51 @@
+<table class="list">
+<colgroup>
+ <col class="row_id" />
+ <col class="date" />
+ <col class="title" />
+ <col class="user" />
+ <col class="user" />
+ <col class="hours" />
+</colgroup>
+<thead>
+ <tr class="title">
+ <th colspan="6"><%= "Effort Report For #{(a)this_user.display_name}" %></th>
+ </tr>
+ <tr>
+ <th>##</th>
+ <th>Date</th>
+ <th>Description</th>
+ <th>Primary</th>
+ <th>Backup</th>
+ <th>Hours</th>
+ </tr>
+</thead>
+
+<tbody>
+ <% backlog_item_id = -1 %>
+ <% total_hours = 0 %>
+ <% @tasks.each_with_index do |task, index| %>
+ <% row_class = index%2 == 0 ? 'even' : 'odd' %>
+ <% if backlog_item_id != task.backlog_item.id %>
+ <tr class="separator">
+ <td colspan="6"><%= "For item: #{task.backlog_item.user_story.title}" %></td>
+ <% backlog_item_id = task.backlog_item_id %>
+ </tr>
+ <% end %>
+ <tr class="<%= row_class %>">
+ <td><%= index + 1 %></td>
+ <td><%= task.when %></td>
+ <td><%= task.description %></td>
+ <td><%= link_to_user(task.primary) %></td>
+ <td><%= link_to_user(task.backup) %></td>
+ <td><%= task.hours %><% total_hours += task.hours %></td>
+ </tr>
+ </tr>
+ <% end %>
+
+ <tr>
+ <td colspan="5"></td>
+ <td><%= format("%0.1f", total_hours) %></td>
+ </tr>
+</tbody>
+</table>
diff --git a/app/views/report/index.html.erb b/app/views/report/index.html.erb
new file mode 100644
index 0000000..4436714
--- /dev/null
+++ b/app/views/report/index.html.erb
@@ -0,0 +1,16 @@
+
+<table>
+
+ <% form_tag(:action => :effort) do %>
+ <tr>
+ <td>Effort report for: </td>
+ <td>
+ <%= select_tag "id",
+ options_from_collection_for_select(@users, :id, :display_name,
+ (@owner ? @backlog_item.owner.id : nil)) %>
+ </td>
+ <td><%= submit_tag "View" %></td>
+ </tr>
+ <% end %>
+
+</table>
diff --git a/app/views/user/view.html.erb b/app/views/user/view.html.erb
index 69a0f0a..3430325 100644
--- a/app/views/user/view.html.erb
+++ b/app/views/user/view.html.erb
@@ -3,6 +3,7 @@
<%= link_to "Edit", :action => :modify, :id => @this_user.id %>
<% end %>
<%= link_to "View my backlog", :action => :backlog, :id => @this_user.id %>
+ <%= link_to "Effort report", :controller => :report, :action => :effort, :id => @this_user.id %>
</div>
<table class="edit">
diff --git a/config/routes.rb b/config/routes.rb
index 7f843f0..874f8e9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,18 +1,18 @@
-# routers.rb
-# Copyright (C) 2008, Darryl L. Pierce <mcpierce(a)gmail.com>
+# routers.rb
+# Copyright (C) 2008, 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 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.
+# 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/>.
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
ActionController::Routing::Routes.draw do |map|
diff --git a/db/schema.rb b/db/schema.rb
index 63db227..74e9110 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -23,6 +23,7 @@ ActiveRecord::Schema.define(:version => 16) do
end
add_index "backlog_items", ["sprint_id", "user_story_id"], :name => "index_backlog_items_on_sprint_id_and_user_story_id", :unique => true
+ add_index "backlog_items", ["user_story_id"], :name => "fk_backlog_items_user_story"
create_table "product_roles", :force => true do |t|
t.integer "user_id", :null => false
@@ -32,7 +33,9 @@ ActiveRecord::Schema.define(:version => 16) do
t.datetime "updated_at"
end
- add_index "product_roles", ["product_id", "user_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true
+ add_index "product_roles", ["user_id", "product_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true
+ add_index "product_roles", ["product_id"], :name => "fk_user_role_product"
+ add_index "product_roles", ["role_id"], :name => "fk_user_role_role"
create_table "products", :force => true do |t|
t.integer "project_id"
@@ -44,6 +47,7 @@ ActiveRecord::Schema.define(:version => 16) do
end
add_index "products", ["name"], :name => "index_products_on_name", :unique => true
+ add_index "products", ["project_id"], :name => "fk_products_project"
create_table "projects", :force => true do |t|
t.integer "owner_id", :null => false
@@ -55,6 +59,7 @@ ActiveRecord::Schema.define(:version => 16) do
end
add_index "projects", ["name"], :name => "index_projects_on_name", :unique => true
+ add_index "projects", ["owner_id"], :name => "fk_product_owner"
create_table "remaining_hours_estimates", :force => true do |t|
t.integer "backlog_item_id", :null => false
@@ -65,6 +70,9 @@ ActiveRecord::Schema.define(:version => 16) do
t.datetime "estimated_on", :null => false
end
+ add_index "remaining_hours_estimates", ["backlog_item_id"], :name => "fk_remaining_hours_item"
+ add_index "remaining_hours_estimates", ["user_id"], :name => "fk_remaining_hours_user"
+
create_table "roles", :force => true do |t|
t.string "name", :null => false
t.boolean "can_manage_backlog_items", :default => false, :null => false
@@ -96,6 +104,8 @@ ActiveRecord::Schema.define(:version => 16) do
t.integer "status", :default => 0, :null => false
end
+ add_index "sprints", ["product_id"], :name => "fk_sprint_product"
+
create_table "tasks", :force => true do |t|
t.integer "backlog_item_id", :null => false
t.integer "primary_id", :null => false
@@ -107,6 +117,10 @@ ActiveRecord::Schema.define(:version => 16) do
t.datetime "updated_at"
end
+ add_index "tasks", ["backlog_item_id"], :name => "fk_task_backlog_item"
+ add_index "tasks", ["primary_id"], :name => "fk_task_primary"
+ add_index "tasks", ["backup_id"], :name => "fk_task_backup"
+
create_table "user_privileges", :force => true do |t|
t.integer "user_id", :null => false
t.boolean "admin_projects", :default => false, :null => false
@@ -115,6 +129,8 @@ ActiveRecord::Schema.define(:version => 16) do
t.datetime "updated_at"
end
+ add_index "user_privileges", ["user_id"], :name => "fk_privilege_user"
+
create_table "user_stories", :force => true do |t|
t.integer "product_id"
t.integer "priority"
@@ -125,6 +141,8 @@ ActiveRecord::Schema.define(:version => 16) do
t.datetime "updated_at"
end
+ add_index "user_stories", ["product_id"], :name => "fk_user_story_product"
+
create_table "user_verifications", :force => true do |t|
t.integer "user_id", :null => false
t.string "token", :limit => 16, :null => false
diff --git a/test/functional/report_controller_test.rb b/test/functional/report_controller_test.rb
index 6957fe5..902c457 100644
--- a/test/functional/report_controller_test.rb
+++ b/test/functional/report_controller_test.rb
@@ -19,10 +19,12 @@ require File.dirname(__FILE__) + '/../test_helper'
class ReportControllerTest < ActionController::TestCase
fixtures :sprints
+ fixtures :users
def setup
@pending_sprint = sprints(:inactive_sprint)
@sprint = sprints(:initial_development_sprint)
+ @user = users(:mcpierce)
end
# Ensures that an error occurs when no sprint id was provided.
@@ -49,4 +51,29 @@ class ReportControllerTest < ActionController::TestCase
assert_response :success
assert assigns['data'], "Didn't generate the data needed for a report."
end
+
+ # Ensures that an effort report requires a user id.
+ #
+ def test_effort_without_user_id
+ get :effort
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that the user id must be valid for an effort report.
+ #
+ def test_effort_with_invalid_user_id
+ get :effort, {:id => 9999}
+
+ assert_redirected_to error_url
+ end
+
+ # Ensures that a well formed request works.
+ #
+ def test_effort
+ get :effort, {:id => @user.id}
+
+ assert_response :success
+ assert assigns['tasks'], 'Failed to load the set of tasks for the given user.'
+ end
end
--
1.6.0.2
15 years, 5 months