diff options
author | Wei Xie <xieconnect@gmail.com> | 2010-06-06 11:02:38 +0800 |
---|---|---|
committer | Hans de Graaff <hans@degraaff.org> | 2010-07-28 20:01:54 +0200 |
commit | 2898476c00c98c7c5f8a0243778c29f4e1268490 (patch) | |
tree | cb843b70373abf14b7b589ce352a227655b180ea | |
parent | Rename User Role guest to user (diff) | |
download | council-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.rb | 6 | ||||
-rw-r--r-- | app/models/agenda.rb | 1 | ||||
-rw-r--r-- | app/models/agenda_item.rb | 54 | ||||
-rw-r--r-- | app/viewhints/agenda_hints.rb | 3 | ||||
-rw-r--r-- | app/views/agenda_items/show.dryml | 5 | ||||
-rw-r--r-- | app/views/taglibs/application.dryml | 31 | ||||
-rw-r--r-- | app/views/taglibs/auto/rapid/cards.dryml | 14 | ||||
-rw-r--r-- | app/views/taglibs/auto/rapid/forms.dryml | 34 | ||||
-rw-r--r-- | app/views/taglibs/auto/rapid/pages.dryml | 180 | ||||
-rw-r--r-- | db/migrate/20100606031333_create_agenda_items.rb | 23 | ||||
-rw-r--r-- | db/seeds.rb | 11 | ||||
-rw-r--r-- | features/agenda_items.feature | 30 | ||||
-rw-r--r-- | features/step_definitions/agenda_item_step.rb | 5 | ||||
-rw-r--r-- | spec/fixtures/agenda_items.yml | 11 | ||||
-rw-r--r-- | spec/models/agenda_item_spec.rb | 32 |
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">« <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 |