aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWei Xie <xieconnect@gmail.com>2010-06-06 11:02:38 +0800
committerHans de Graaff <hans@degraaff.org>2010-07-28 20:01:54 +0200
commit2898476c00c98c7c5f8a0243778c29f4e1268490 (patch)
treecb843b70373abf14b7b589ce352a227655b180ea
parentRename User Role guest to user (diff)
downloadcouncil-webapp-2898476c00c98c7c5f8a0243778c29f4e1268490.tar.gz
council-webapp-2898476c00c98c7c5f8a0243778c29f4e1268490.tar.bz2
council-webapp-2898476c00c98c7c5f8a0243778c29f4e1268490.zip
AgendaItem implemented and tested
-rw-r--r--app/controllers/agenda_items_controller.rb6
-rw-r--r--app/models/agenda.rb1
-rw-r--r--app/models/agenda_item.rb54
-rw-r--r--app/viewhints/agenda_hints.rb3
-rw-r--r--app/views/agenda_items/show.dryml5
-rw-r--r--app/views/taglibs/application.dryml31
-rw-r--r--app/views/taglibs/auto/rapid/cards.dryml14
-rw-r--r--app/views/taglibs/auto/rapid/forms.dryml34
-rw-r--r--app/views/taglibs/auto/rapid/pages.dryml180
-rw-r--r--db/migrate/20100606031333_create_agenda_items.rb23
-rw-r--r--db/seeds.rb11
-rw-r--r--features/agenda_items.feature30
-rw-r--r--features/step_definitions/agenda_item_step.rb5
-rw-r--r--spec/fixtures/agenda_items.yml11
-rw-r--r--spec/models/agenda_item_spec.rb32
15 files changed, 440 insertions, 0 deletions
diff --git a/app/controllers/agenda_items_controller.rb b/app/controllers/agenda_items_controller.rb
new file mode 100644
index 0000000..2852414
--- /dev/null
+++ b/app/controllers/agenda_items_controller.rb
@@ -0,0 +1,6 @@
+class AgendaItemsController < ApplicationController
+ hobo_model_controller
+
+ auto_actions :all
+
+end
diff --git a/app/models/agenda.rb b/app/models/agenda.rb
index 057fc7e..b806a7a 100644
--- a/app/models/agenda.rb
+++ b/app/models/agenda.rb
@@ -11,6 +11,7 @@ class Agenda < ActiveRecord::Base
end
belongs_to :owner, :class_name => 'User', :creator => true
+ has_many :agenda_items, :dependent => :destroy
validates_presence_of :owner
diff --git a/app/models/agenda_item.rb b/app/models/agenda_item.rb
new file mode 100644
index 0000000..2d26905
--- /dev/null
+++ b/app/models/agenda_item.rb
@@ -0,0 +1,54 @@
+class AgendaItem < ActiveRecord::Base
+
+ hobo_model # Don't put anything above this
+
+ fields do
+ name :string, :null => false
+ description :text, :null => false
+ reason_of_rejection :text
+ timestamps
+ end
+
+ belongs_to :owner, :class_name => 'User', :creator => true
+ belongs_to :approved_by, :class_name => 'User'
+ belongs_to :agenda
+
+ validates_presence_of :owner
+ # should provide reason of rejection when rejecting items
+ validates_presence_of :reason_of_rejection,
+ :if => Proc.new {|agenda_item| agenda_item.state.to_sym == :rejected}
+
+ lifecycle do
+ state :pending, :default => true
+ state :approved, :rejected
+ transition :approve, {:pending => :approved}, :available_to => 'User',
+ :if => 'permitted_for_roles(:admin, :council_member)'
+ transition :reject, {:pending => :rejected}, :params => [:reason_of_rejection], :available_to => 'User',
+ :if => 'permitted_for_roles(:admin, :council_member)'
+ end
+
+ # --- Permissions --- #
+ attr_protected :approved_by, :owner
+
+ def create_permitted?
+ acting_user.signed_up?
+ end
+
+ # never show reason of rejection in create/edit forms
+ def reason_of_rejection_edit_permitted?
+ false
+ end
+
+ def update_permitted?
+ (owner_is?(acting_user) and only_changed?(:name, :description)) or
+ permitted_for_roles(:council_member, :admin)
+ end
+
+ def destroy_permitted?
+ permitted_for_roles(:admin, :council_member)
+ end
+
+ def view_permitted?(field)
+ true
+ end
+end
diff --git a/app/viewhints/agenda_hints.rb b/app/viewhints/agenda_hints.rb
new file mode 100644
index 0000000..75bcb5e
--- /dev/null
+++ b/app/viewhints/agenda_hints.rb
@@ -0,0 +1,3 @@
+class AgendaHints < Hobo::ViewHints
+ children :agenda_items
+end
diff --git a/app/views/agenda_items/show.dryml b/app/views/agenda_items/show.dryml
new file mode 100644
index 0000000..16ee2b4
--- /dev/null
+++ b/app/views/agenda_items/show.dryml
@@ -0,0 +1,5 @@
+<show-page>
+ <append-content-header:>
+ <transition-buttons/>
+ </append-content-header:>
+</show-page>
diff --git a/app/views/taglibs/application.dryml b/app/views/taglibs/application.dryml
index 4575ca3..692a258 100644
--- a/app/views/taglibs/application.dryml
+++ b/app/views/taglibs/application.dryml
@@ -21,3 +21,34 @@
</label:>
</old-field-list>
</extend>
+
+<!--
+ The transition-button tag is originally authored by the Hobo dev team.
+ URL: http://cookbook.hobocentral.net/api_tag_defs/transition-button
+ Wei Xie (XieConnect) added html class "button" to transition-button
+-->
+
+<def attrs='transition, update, label' tag='transition-button'><%=
+ if transition.is_a?(String)
+ transition = this.lifecycle.find_transition(transition, current_user)
+ end
+ transition_name = transition.name
+ has_params = !transition.options[:params].blank?
+ ajax_attributes, html_attributes = attributes.partition_hash(Hobo::RapidHelper::AJAX_ATTRS)
+
+ html_attributes[:method] ||= has_params ? :get : :put
+ add_classes!(html_attributes, "button transition-button #{transition_name}-button")
+ label = ht("#{this.class.name.tableize}.actions.#{transition_name}", :default => (label || transition_name.to_s.titleize))
+ url = object_url(this, transition_name, :method => html_attributes[:method])
+
+ if (update || !ajax_attributes.empty?) && !has_params
+ ajax_attributes[:message] ||= label
+ ajax_attributes[:method] = html_attributes[:method]
+ func = ajax_updater(url, update, ajax_attributes)
+ html_attributes.update(:onclick => "var e = this; " + func, :type =>'button', :value => label)
+ element(:input, html_attributes, nil, true, true)
+ else
+ button_to(label, url, html_attributes)
+ end
+ %>
+</def>
diff --git a/app/views/taglibs/auto/rapid/cards.dryml b/app/views/taglibs/auto/rapid/cards.dryml
index 4186cdc..055d135 100644
--- a/app/views/taglibs/auto/rapid/cards.dryml
+++ b/app/views/taglibs/auto/rapid/cards.dryml
@@ -7,6 +7,20 @@
</header:>
<body: param>
<a:owner param="creator-link"/>
+ <ht key="agenda_items.collection.count" count="&this.agenda_items.size">
+ <count:agenda_items param/>
+ </ht>
+ </body:>
+ </card>
+</def>
+
+<def tag="card" for="AgendaItem">
+ <card class="agenda-item" param="default" merge>
+ <header: param>
+ <h4 param="heading"><a><name/></a></h4>
+ </header:>
+ <body: param>
+ <a:owner param="creator-link"/>
</body:>
</card>
</def>
diff --git a/app/views/taglibs/auto/rapid/forms.dryml b/app/views/taglibs/auto/rapid/forms.dryml
index c57f7b6..3bd585d 100644
--- a/app/views/taglibs/auto/rapid/forms.dryml
+++ b/app/views/taglibs/auto/rapid/forms.dryml
@@ -1,5 +1,39 @@
<!-- AUTOMATICALLY GENERATED FILE - DO NOT EDIT -->
+<def tag="form" for="AgendaItem">
+ <form merge param="default">
+ <error-messages param/>
+ <field-list fields="name, description, reason_of_rejection, state, agenda, approved_by, owner" param/>
+ <div param="actions">
+ <submit label="#{ht 'agenda_items.actions.save', :default=>['Save']}" param/><or-cancel param="cancel"/>
+ </div>
+ </form>
+</def>
+
+
+<def tag="approve-form" polymorphic/>
+<def tag="approve-form" for="AgendaItem">
+ <form lifecycle="approve" merge param="default">
+ <error-messages param/>
+ <input type="hidden" name="key" value="&this.lifecycle.provided_key" if="&this.lifecycle.provided_key"/>
+ <field-list fields="" param/>
+ <div param="actions">
+ <submit label="#{ht 'agenda_items.actions.approve', :default=>['Approve']}" param/><or-cancel param="cancel"/>
+ </div>
+ </form>
+</def>
+<def tag="reject-form" polymorphic/>
+<def tag="reject-form" for="AgendaItem">
+ <form lifecycle="reject" merge param="default">
+ <error-messages param/>
+ <input type="hidden" name="key" value="&this.lifecycle.provided_key" if="&this.lifecycle.provided_key"/>
+ <field-list fields="reason_of_rejection" param/>
+ <div param="actions">
+ <submit label="#{ht 'agenda_items.actions.reject', :default=>['Reject']}" param/><or-cancel param="cancel"/>
+ </div>
+ </form>
+</def>
+
<def tag="form" for="Agenda">
<form merge param="default">
<error-messages param/>
diff --git a/app/views/taglibs/auto/rapid/pages.dryml b/app/views/taglibs/auto/rapid/pages.dryml
index 5b4a00a..c8636ea 100644
--- a/app/views/taglibs/auto/rapid/pages.dryml
+++ b/app/views/taglibs/auto/rapid/pages.dryml
@@ -6,6 +6,7 @@
<navigation class="main-nav" merge-attrs param="default">
<nav-item href="#{base_url}/">Home</nav-item>
<nav-item with="&Agenda"><ht key="agendas.nav_item">Agendas</ht></nav-item>
+ <nav-item with="&AgendaItem"><ht key="agenda_items.nav_item">Agenda Items</ht></nav-item>
<nav-item with="&Question"><ht key="questions.nav_item">Questions</ht></nav-item>
</navigation>
</def>
@@ -13,6 +14,176 @@
+<!-- ====== AgendaItem Pages ====== -->
+
+<def tag="index-page" for="AgendaItem">
+ <page merge title="#{ht 'agenda_items.index.title', :default=>['Agenda Items'] }">
+ <body: class="index-page agenda-item" param/>
+
+ <content: param>
+ <header param="content-header">
+ <h2 param="heading">
+ <ht key="agenda_items.index.heading">
+ Agenda Items
+ </ht>
+ </h2>
+
+ <p param="count" if>
+ <ht key="agenda_items.collection.count" count="&this.size">
+ There <count prefix="are"/>
+ </ht>
+ </p>
+ </header>
+
+ <section param="content-body">
+ <a action="new" to="&model" param="new-link">
+ <ht key="agenda_items.actions.new">New Agenda Item</ht>
+ </a>
+
+ <page-nav param="top-page-nav"/>
+
+ <collection param/>
+
+ <page-nav param="bottom-page-nav"/>
+
+
+ </section>
+ </content:>
+ </page>
+</def>
+
+
+<def tag="new-page" for="AgendaItem">
+ <page merge title="#{ht 'agenda_items.new.title', :default=>[' New Agenda Item'] }">
+ <body: class="new-page agenda-item" param/>
+
+ <content: param>
+ <section param="content-header">
+ <h2 param="heading">
+ <ht key="agenda_items.new.heading">
+ New Agenda Item
+ </ht>
+ </h2>
+ </section>
+
+ <section param="content-body">
+ <form param>
+ <submit: label="#{ht 'agenda_items.actions.create', :default=>['Create Agenda Item']}"/>
+ </form>
+ </section>
+ </content:>
+ </page>
+</def>
+
+
+<def tag="show-page" for="AgendaItem">
+ <page merge title="#{ht 'agenda_items.show.title', :default=>['Agenda Item'] }">
+
+ <body: class="show-page agenda-item" param/>
+
+ <content: param>
+ <header param="content-header">
+ <a:agenda param="parent-link">&laquo; <ht key="agenda_items.actions.back" to="agenda"><name/></ht></a:agenda>
+ <h2 param="heading">
+ <ht key="agenda_items.show.heading" name="&this.respond_to?(:name) ? this.name : ''">
+ <name/>
+ </ht>
+ </h2>
+
+ <record-flags fields="" param/>
+
+ <a:owner param="creator-link"/>
+
+ <a action="edit" if="&can_edit?" param="edit-link">
+ <ht key="agenda_items.actions.edit" name="&this.respond_to?(:name) ? this.name : ''">
+ Edit Agenda Item
+ </ht>
+ </a>
+ </header>
+
+ <section param="content-body">
+ <view:description param="description"/>
+ <field-list fields="reason_of_rejection, state, approved_by" param/>
+ </section>
+ </content:>
+
+ </page>
+</def>
+
+
+<def tag="edit-page" for="AgendaItem">
+ <page merge title="#{ht 'agenda_items.edit.title', :default=>['Edit Agenda Item'] }">
+
+ <body: class="edit-page agenda-item" param/>
+
+ <content:>
+ <section param="content-header">
+ <h2 param="heading">
+ <ht key="agenda_items.edit.heading" name="&this.respond_to?(:name) ? this.name : ''">
+ Edit <type-name/>
+ </ht>
+ </h2>
+ <delete-button label="#{ht 'agenda_items.actions.delete', :default=>['Remove This Agenda Item']}" param/>
+ </section>
+
+ <section param="content-body">
+ <form param/>
+ </section>
+ </content:>
+
+ </page>
+</def>
+
+
+
+<def tag="approve-page" polymorphic/>
+<def tag="approve-page" for="AgendaItem">
+ <page title="#{ht 'agenda_items.approve.title', :default=>['Approve']}" merge>
+
+ <body: class="lifecycle-transition-page approve-page" param/>
+
+ <content:>
+ <header param="content-header">
+ <h2 param="heading">
+ <ht key="agenda_items.approve.heading">
+ Approve
+ </ht>
+ </h2>
+ </header>
+
+ <section param="content-body">
+ <approve-form param="form"/>
+ </section>
+ </content:>
+
+ </page>
+</def>
+
+<def tag="reject-page" polymorphic/>
+<def tag="reject-page" for="AgendaItem">
+ <page title="#{ht 'agenda_items.reject.title', :default=>['Reject']}" merge>
+
+ <body: class="lifecycle-transition-page reject-page" param/>
+
+ <content:>
+ <header param="content-header">
+ <h2 param="heading">
+ <ht key="agenda_items.reject.heading">
+ Reject
+ </ht>
+ </h2>
+ </header>
+
+ <section param="content-body">
+ <reject-form param="form"/>
+ </section>
+ </content:>
+
+ </page>
+</def>
+
+
+
<!-- ====== Agenda Pages ====== -->
<def tag="index-page" for="Agenda">
@@ -101,6 +272,15 @@
<section param="content-body">
<view:description param="description"/>
+ <section param="collection-section">
+ <h3 param="collection-heading">
+ <ht key="agendas.collection.heading.other" >
+ Agenda Items
+ </ht>
+ </h3>
+
+ <collection:agenda_items param/>
+ </section>
</section>
</content:>
diff --git a/db/migrate/20100606031333_create_agenda_items.rb b/db/migrate/20100606031333_create_agenda_items.rb
new file mode 100644
index 0000000..f40ff76
--- /dev/null
+++ b/db/migrate/20100606031333_create_agenda_items.rb
@@ -0,0 +1,23 @@
+class CreateAgendaItems < ActiveRecord::Migration
+ def self.up
+ create_table :agenda_items do |t|
+ t.string :name, :null => false
+ t.text :description, :null => false
+ t.text :reason_of_rejection
+ t.datetime :created_at
+ t.datetime :updated_at
+ t.string :state, :default => 'pending'
+ t.datetime :key_timestamp
+ t.integer :owner_id
+ t.integer :approved_by_id
+ t.integer :agenda_id
+ end
+ add_index :agenda_items, [:owner_id]
+ add_index :agenda_items, [:approved_by_id]
+ add_index :agenda_items, [:agenda_id]
+ end
+
+ def self.down
+ drop_table :agenda_items
+ end
+end
diff --git a/db/seeds.rb b/db/seeds.rb
index cd12661..0fff605 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -52,3 +52,14 @@ agendas = Agenda.create([
:end_at => 16.days.from_now,
:owner_id => users[1].id}
])
+
+puts 'adding agenda_items...'
+agenda_items = AgendaItem.create([
+ { :name => 'agenda_item_one',
+ :description => 'agenda item 1',
+ :owner_id => users[1].id},
+
+ { :name => 'agenda_item_two',
+ :description => 'agenda item 2',
+ :owner_id => users[1].id}
+])
diff --git a/features/agenda_items.feature b/features/agenda_items.feature
new file mode 100644
index 0000000..23976f0
--- /dev/null
+++ b/features/agenda_items.feature
@@ -0,0 +1,30 @@
+Feature: Propose Agenda Items
+ As a developer
+ I want to be able to propose agenda items
+ so that council makes decisions on them
+
+ Scenario: Propose Agenda items
+ Given I am logged in as a developer
+ When I go to the home page
+ And I follow "Agenda Items"
+ And I follow "New Agenda Item"
+ And I fill in "Name" with "agenda_item_test_name"
+ And I press "Create Agenda Item"
+ Then I should see "created successfully"
+
+ Scenario: Approve Agenda items
+ Given I am logged in as a council member
+ When I visit agenda item "agenda_item_one"
+ And I press "Approve"
+ Then I should see "approved"
+
+ Scenario: Reject Agenda item
+ Given I am logged in as a council member
+ When I visit agenda item "agenda_item_two"
+ And I press "Reject"
+ And I fill in "Reason of Rejection" with "not allowed"
+ And I press "Reject"
+ Then I should see "not allowed"
+
+ When I visit agenda item "agenda_item_two"
+ Then I should see "not allowed"
diff --git a/features/step_definitions/agenda_item_step.rb b/features/step_definitions/agenda_item_step.rb
new file mode 100644
index 0000000..104c3e4
--- /dev/null
+++ b/features/step_definitions/agenda_item_step.rb
@@ -0,0 +1,5 @@
+When /^I visit agenda item "([^\"]*)"$/ do |agenda_item|
+ Given 'I go to the home page'
+ And 'I follow "Agenda Items"'
+ And %Q{I follow "#{agenda_item}"}
+end
diff --git a/spec/fixtures/agenda_items.yml b/spec/fixtures/agenda_items.yml
new file mode 100644
index 0000000..dc0ee08
--- /dev/null
+++ b/spec/fixtures/agenda_items.yml
@@ -0,0 +1,11 @@
+agenda_item_one:
+ name: agenda_item_one
+ description: agenda item 1
+ agenda: agenda_one
+ owner: council_member
+
+agenda_item_two:
+ name: agenda_item_two
+ description: agenda item 2
+ agenda: agenda_one
+ owner: council_member
diff --git a/spec/models/agenda_item_spec.rb b/spec/models/agenda_item_spec.rb
new file mode 100644
index 0000000..9fa7af5
--- /dev/null
+++ b/spec/models/agenda_item_spec.rb
@@ -0,0 +1,32 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe AgendaItem do
+ before(:each) do
+ @agenda_item = agenda_items(:agenda_item_one)
+ end
+
+ it "should allow guest to view" do
+ @agenda_item.should be_viewable_by(users(:guest))
+ end
+
+ [:admin, :council_member, :developer].each do |role|
+ it "should allow user with role #{role} to create" do
+ agenda_item = AgendaItem.new(
+ :name => 'agenda_item1',
+ :description => 'agenda item 1'
+ )
+ agenda_item.should be_creatable_by(users(role))
+ end
+ end
+
+ [:developer, :guest].each do |role|
+ it "should not allow user with role #{role} to update" do
+ @agenda_item.should_not be_updatable_by(users(role))
+ end
+ end
+
+ it "should allow council member to update" do
+ @council_member = users(:council_member)
+ @agenda_item.should be_updatable_by(@council_member)
+ end
+end