aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--site/app/controllers/agendas_controller.rb4
-rw-r--r--site/app/controllers/users_controller.rb3
-rw-r--r--site/app/controllers/voting_options_controller.rb6
-rw-r--r--site/app/models/agenda.rb6
-rw-r--r--site/app/models/agenda_item.rb2
-rw-r--r--site/app/models/user.rb1
-rw-r--r--site/app/models/vote.rb54
-rw-r--r--site/app/models/voting_option.rb41
-rw-r--r--site/app/viewhints/agenda_item_hints.rb5
-rw-r--r--site/app/views/agenda_items/show.dryml9
-rw-r--r--site/config/hobo_routes.rb10
-rw-r--r--site/config/routes.rb3
-rw-r--r--site/db/schema.rb23
-rw-r--r--site/features/step_definitions/voting_steps.rb22
-rw-r--r--site/features/step_definitions/within_steps.rb4
-rw-r--r--site/features/support/paths.rb7
-rw-r--r--site/features/voting.feature23
-rw-r--r--site/spec/factories.rb13
-rw-r--r--site/spec/models/vote_spec.rb37
-rw-r--r--site/spec/models/voting_option_spec.rb41
20 files changed, 309 insertions, 5 deletions
diff --git a/site/app/controllers/agendas_controller.rb b/site/app/controllers/agendas_controller.rb
index bfc32a4..0ee7ae0 100644
--- a/site/app/controllers/agendas_controller.rb
+++ b/site/app/controllers/agendas_controller.rb
@@ -7,4 +7,8 @@ class AgendasController < ApplicationController
def index
hobo_index Agenda.state_is(:old)
end
+
+ def current_items
+ render :json => Agenda.current.voting_array
+ end
end
diff --git a/site/app/controllers/users_controller.rb b/site/app/controllers/users_controller.rb
index 651de7d..eccc510 100644
--- a/site/app/controllers/users_controller.rb
+++ b/site/app/controllers/users_controller.rb
@@ -14,4 +14,7 @@ class UsersController < ApplicationController
end
end
+ def voters
+ render :json => ::User.council_member_is(true).*.irc_nick
+ end
end
diff --git a/site/app/controllers/voting_options_controller.rb b/site/app/controllers/voting_options_controller.rb
new file mode 100644
index 0000000..951ddbb
--- /dev/null
+++ b/site/app/controllers/voting_options_controller.rb
@@ -0,0 +1,6 @@
+class VotingOptionsController < ApplicationController
+
+ hobo_model_controller
+
+ auto_actions :all
+end
diff --git a/site/app/models/agenda.rb b/site/app/models/agenda.rb
index f8f90df..ed8e385 100644
--- a/site/app/models/agenda.rb
+++ b/site/app/models/agenda.rb
@@ -81,6 +81,12 @@ class Agenda < ActiveRecord::Base
['open', 'submissions_closed'].include?(state.to_s)
end
+ def voting_array
+ agenda_items.collect do |item|
+ [item.title, item.voting_options.*.description]
+ end
+ end
+
protected
def there_is_only_one_non_archival_agenda
return if(state.to_s == 'old')
diff --git a/site/app/models/agenda_item.rb b/site/app/models/agenda_item.rb
index f5108bd..f590bb1 100644
--- a/site/app/models/agenda_item.rb
+++ b/site/app/models/agenda_item.rb
@@ -12,6 +12,7 @@ class AgendaItem < ActiveRecord::Base
belongs_to :user, :creator => true
belongs_to :agenda
+ has_many :voting_options
# --- Permissions --- #
def create_permitted?
@@ -49,5 +50,4 @@ class AgendaItem < ActiveRecord::Base
return false unless agenda.nil?
return acting_user == user if [nil, :title, :discussion, :body].include?(field)
end
-
end
diff --git a/site/app/models/user.rb b/site/app/models/user.rb
index e49fa77..738165e 100644
--- a/site/app/models/user.rb
+++ b/site/app/models/user.rb
@@ -11,6 +11,7 @@ class User < ActiveRecord::Base
timestamps
end
+ has_many :votes
# --- Signup lifecycle --- #
lifecycle do
diff --git a/site/app/models/vote.rb b/site/app/models/vote.rb
new file mode 100644
index 0000000..00c64a7
--- /dev/null
+++ b/site/app/models/vote.rb
@@ -0,0 +1,54 @@
+class Vote < ActiveRecord::Base
+
+ hobo_model # Don't put anything above this
+
+ fields do
+ timestamps
+ end
+
+ belongs_to :voting_option, :null => false
+ belongs_to :user, :null => false
+
+ index [:voting_option_id, :user_id], :unique => true
+
+ validates_presence_of :voting_option
+ validates_presence_of :user
+ validates_uniqueness_of :voting_option_id, :scope => :user_id
+ validate :user_voted_only_once
+ validate :user_is_council_member
+ # --- Permissions --- #
+
+ def create_permitted?
+ false
+ end
+
+ def update_permitted?
+ false
+ end
+
+ def destroy_permitted?
+ false
+ end
+
+ def view_permitted?(field)
+ true
+ end
+
+ protected
+ def user_voted_only_once
+ return if user.nil?
+ return if voting_option.nil?
+ return if voting_option.agenda_item.nil?
+ other_votes = Vote.joins(:voting_option).where(['voting_options.agenda_item_id = ? AND votes.user_id = ?',
+ voting_option.agenda_item_id, user_id])
+ other_votes = other_votes.id_is_not(id) unless new_record?
+ if other_votes.count > 0
+ errors.add(:user, 'User can vote only once per agenda item.')
+ end
+ end
+
+ def user_is_council_member
+ return if user.nil?
+ errors.add(:user, 'Only council members can vote.') unless user.council_member?
+ end
+end
diff --git a/site/app/models/voting_option.rb b/site/app/models/voting_option.rb
new file mode 100644
index 0000000..b9c2226
--- /dev/null
+++ b/site/app/models/voting_option.rb
@@ -0,0 +1,41 @@
+class VotingOption < ActiveRecord::Base
+
+ hobo_model # Don't put anything above this
+
+ fields do
+ description :string
+ timestamps
+ end
+
+ belongs_to :agenda_item, :null => false
+ has_many :votes
+
+ validates_presence_of :agenda_item
+ validates_uniqueness_of :description, :scope => :agenda_item_id
+
+ def name
+ description
+ end
+
+ # --- Permissions --- #
+
+ def create_permitted?
+ acting_user.council_member?
+ end
+
+ def update_permitted?
+ return false unless acting_user.council_member?
+ return true if agenda_item.nil?
+ return true if agenda_item.agenda.nil?
+ return true if agenda_item.agenda.state == 'open'
+ false
+ end
+
+ def destroy_permitted?
+ updatable_by?(acting_user)
+ end
+
+ def view_permitted?(field)
+ true
+ end
+end
diff --git a/site/app/viewhints/agenda_item_hints.rb b/site/app/viewhints/agenda_item_hints.rb
new file mode 100644
index 0000000..3fe2071
--- /dev/null
+++ b/site/app/viewhints/agenda_item_hints.rb
@@ -0,0 +1,5 @@
+class AgendaItemHints < Hobo::ViewHints
+
+ children :voting_options
+
+end
diff --git a/site/app/views/agenda_items/show.dryml b/site/app/views/agenda_items/show.dryml
index 626298d..ba27673 100644
--- a/site/app/views/agenda_items/show.dryml
+++ b/site/app/views/agenda_items/show.dryml
@@ -11,4 +11,13 @@
<submit label="Un-reject" if="&this.rejected"/>
</form>
</append-content:>
+
+ <after-collection:>
+ <form if="&VotingOption.new.creatable_by?(current_user)" action="&create_voting_option_path">
+ <input id="voting_option_description" name="voting_option[description]" type="text"/>
+ <input id="voting_option_agenda_item_id" name="voting_option[agenda_item_id]" type="hidden" value="&this.id"/>
+ <after-submit stay-here/>
+ <submit label="Add a voting option"/>
+ </form>
+ </after-collection:>
</show-page>
diff --git a/site/config/hobo_routes.rb b/site/config/hobo_routes.rb
index 11fb9b0..ad63a15 100644
--- a/site/config/hobo_routes.rb
+++ b/site/config/hobo_routes.rb
@@ -53,4 +53,14 @@ Council::Application.routes.draw do
put 'agenda_items/:id(.:format)' => 'agenda_items#update', :as => 'update_agenda_item', :constraints => { :id => %r([^/.?]+) }
delete 'agenda_items/:id(.:format)' => 'agenda_items#destroy', :as => 'destroy_agenda_item', :constraints => { :id => %r([^/.?]+) }
+
+ # Resource routes for controller "voting_options"
+ get 'voting_options(.:format)' => 'voting_options#index', :as => 'voting_options'
+ get 'voting_options/new(.:format)', :as => 'new_voting_option'
+ get 'voting_options/:id/edit(.:format)' => 'voting_options#edit', :as => 'edit_voting_option'
+ get 'voting_options/:id(.:format)' => 'voting_options#show', :as => 'voting_option', :constraints => { :id => %r([^/.?]+) }
+ post 'voting_options(.:format)' => 'voting_options#create', :as => 'create_voting_option'
+ put 'voting_options/:id(.:format)' => 'voting_options#update', :as => 'update_voting_option', :constraints => { :id => %r([^/.?]+) }
+ delete 'voting_options/:id(.:format)' => 'voting_options#destroy', :as => 'destroy_voting_option', :constraints => { :id => %r([^/.?]+) }
+
end
diff --git a/site/config/routes.rb b/site/config/routes.rb
index e36d680..e3337bb 100644
--- a/site/config/routes.rb
+++ b/site/config/routes.rb
@@ -3,6 +3,9 @@ Council::Application.routes.draw do
match 'search' => 'front#search', :as => 'site_search'
+ match 'users/voters' => 'users#voters', :as => 'voters'
+ match 'agendas/current_items' => 'agendas#current_items', :as => 'current_items'
+
# The priority is based upon order of creation:
# first created -> highest priority.
diff --git a/site/db/schema.rb b/site/db/schema.rb
index cf1a791..ea99919 100644
--- a/site/db/schema.rb
+++ b/site/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20110523180453) do
+ActiveRecord::Schema.define(:version => 20110601094807) do
create_table "agenda_items", :force => true do |t|
t.string "title"
@@ -37,7 +37,6 @@ ActiveRecord::Schema.define(:version => 20110523180453) do
add_index "agendas", ["state"], :name => "index_agendas_on_state"
create_table "participations", :force => true do |t|
- t.string "name"
t.string "irc_nick"
t.datetime "created_at"
t.datetime "updated_at"
@@ -66,4 +65,24 @@ ActiveRecord::Schema.define(:version => 20110523180453) do
add_index "users", ["state"], :name => "index_users_on_state"
+ create_table "votes", :force => true do |t|
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "voting_option_id", :null => false
+ t.integer "user_id", :null => false
+ end
+
+ add_index "votes", ["user_id"], :name => "index_votes_on_user_id"
+ add_index "votes", ["voting_option_id", "user_id"], :name => "index_votes_on_voting_option_id_and_user_id", :unique => true
+ add_index "votes", ["voting_option_id"], :name => "index_votes_on_voting_option_id"
+
+ create_table "voting_options", :force => true do |t|
+ t.string "description"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "agenda_item_id", :null => false
+ end
+
+ add_index "voting_options", ["agenda_item_id"], :name => "index_voting_options_on_agenda_item_id"
+
end
diff --git a/site/features/step_definitions/voting_steps.rb b/site/features/step_definitions/voting_steps.rb
new file mode 100644
index 0000000..0519397
--- /dev/null
+++ b/site/features/step_definitions/voting_steps.rb
@@ -0,0 +1,22 @@
+When /^I follow first agenda item link$/ do
+ When "I follow \"#{AgendaItem.first.title}\""
+end
+
+When /^I add example voting option$/ do
+ When 'I fill in "voting_option_description" with "example"'
+ When 'I press "Add a voting option"'
+end
+
+Given /^example voting option$/ do
+ Given 'example agenda item'
+ VotingOption.new(:description => 'example', :agenda_item => AgendaItem.last).save!
+end
+
+When /^I go from the homepage to edit last voting option page$/ do
+ When 'I am on the homepage'
+ When 'I follow "Agendas"'
+ When 'I follow link to current agenda'
+ When "I follow \"#{AgendaItem.last.title}\""
+ When "I follow \"#{VotingOption.last.description}\""
+ When 'I follow "Edit Voting option"'
+end
diff --git a/site/features/step_definitions/within_steps.rb b/site/features/step_definitions/within_steps.rb
index 7d56fc4..57bf030 100644
--- a/site/features/step_definitions/within_steps.rb
+++ b/site/features/step_definitions/within_steps.rb
@@ -9,7 +9,9 @@
'in the agendas collection' => '.collection.agendas',
'as empty collection message' => '.empty-collection-message',
'as meeting time' => '.meeting-time-view',
- 'as the user nick' => '.user-irc-nick'
+ 'as the user nick' => '.user-irc-nick',
+ 'as voting option' => '.collection.voting-options',
+ 'as voting option description' => '.voting-option-description'
}.
each do |within, selector|
Then /^I should( not)? see "([^"]*)" #{within}$/ do |negation, text|
diff --git a/site/features/support/paths.rb b/site/features/support/paths.rb
index ab24b9e..6f8dad8 100644
--- a/site/features/support/paths.rb
+++ b/site/features/support/paths.rb
@@ -23,6 +23,13 @@ module NavigationHelpers
when /the first suggested agenda page/
agenda_item_path(AgendaItem.first(:conditions => {:agenda_id => nil}))
+ when /the voters page/
+ voters_path
+
+ when /the current items page/
+ current_items_path
+
+ # Add more mappings here.
# Add more mappings here.
# Here is an example that pulls values out of the Regexp:
#
diff --git a/site/features/voting.feature b/site/features/voting.feature
new file mode 100644
index 0000000..2e25318
--- /dev/null
+++ b/site/features/voting.feature
@@ -0,0 +1,23 @@
+Feature: Application side of voting
+ In order to handle voting with IRC bot
+ I want application to help with that
+
+ Scenario: Add voting option
+ Given example agenda item
+ And an agenda
+ And some council members
+ And I am logged in as council member
+ When I am on the current agenda page
+ And I follow first agenda item link
+ And I add example voting option
+ Then I should see "example" as voting option
+
+ Scenario: Edit voting option
+ Given example voting option
+ Given an agenda
+ And some council members
+ And I am logged in as council member
+ When I go from the homepage to edit last voting option page
+ And I fill in "voting_option_description" with "some description"
+ And I press "Save Voting option"
+ Then I should see "some description" as voting option description
diff --git a/site/spec/factories.rb b/site/spec/factories.rb
index 7405ee9..bc6fe8e 100644
--- a/site/spec/factories.rb
+++ b/site/spec/factories.rb
@@ -10,6 +10,17 @@ end
Factory.define :agenda do |a|; end
-Factory.define :agenda_item do |a|; end
+Factory.define :agenda_item do |a|
+end
Factory.define :participation do |p|; end
+
+Factory.define :vote do |v|;
+ v.association :voting_option
+ v.user { users_factory(:council) }
+end
+
+Factory.define :voting_option do |v|;
+ v.agenda_item { AgendaItem.create! }
+ v.description { "example" }
+end
diff --git a/site/spec/models/vote_spec.rb b/site/spec/models/vote_spec.rb
new file mode 100644
index 0000000..9936829
--- /dev/null
+++ b/site/spec/models/vote_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Vote do
+ it 'should not allow anyone to create update or destroy to anyone' do
+ vote = Factory(:vote)
+ for u in users_factory(AllRoles) do
+ vote.should_not be_creatable_by(u)
+ vote.should_not be_updatable_by(u)
+ vote.should_not be_destroyable_by(u)
+ end
+ end
+
+ it 'should anyone to view' do
+ vote = Factory(:vote)
+ for u in users_factory(AllRoles) do
+ vote.should be_viewable_by(u)
+ end
+ end
+
+ it 'should allow council members to vote' do
+ for u in users_factory(:council, :council_admin) do
+ Vote.new(:user => u, :voting_option => Factory(:voting_option)).should be_valid
+ end
+ end
+
+ it 'should prevent non-council members from voting' do
+ for u in users_factory(:user, :admin) do
+ Vote.new(:user => u, :voting_option => Factory(:voting_option)).should_not be_valid
+ end
+ end
+
+ it 'should prevent users from voting multiple times' do
+ v = Factory(:vote)
+ o = Factory(:voting_option, :agenda_item => v.voting_option.agenda_item, :description => 'other option')
+ Vote.new(:user => v.user, :voting_option => o).should_not be_valid
+ end
+end
diff --git a/site/spec/models/voting_option_spec.rb b/site/spec/models/voting_option_spec.rb
new file mode 100644
index 0000000..4a41067
--- /dev/null
+++ b/site/spec/models/voting_option_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe VotingOption do
+ it 'should allow only council members to create' do
+ v = Factory(:voting_option)
+ for u in users_factory(:guest, :user, :admin)
+ v.should_not be_creatable_by(u)
+ end
+
+ for u in users_factory(:council, :council_admin)
+ v.should be_creatable_by(u)
+ end
+ end
+
+ it 'should allow only council members to update and destroy if it belongs to open agenda' do
+ v = Factory(:voting_option)
+ for u in users_factory(:guest, :user, :admin)
+ v.should_not be_updatable_by(u)
+ v.should_not be_destroyable_by(u)
+ end
+ for u in users_factory(:council, :council_admin)
+ v.should be_updatable_by(u)
+ v.should be_destroyable_by(u)
+ end
+ end
+
+ it 'should allow no one to update and destroy if it belongs to closed or archived agenda' do
+ a1 = Factory(:agenda, :state => 'closed')
+ i1 = Factory(:agenda_item, :agenda => a1)
+ v1 = Factory(:voting_option, :agenda_item => i1)
+ a2 = Factory(:agenda, :state => 'old')
+ i2 = Factory(:agenda_item, :agenda => a2)
+ v2 = Factory(:voting_option, :agenda_item => i2)
+ for u in users_factory(AllRoles)
+ v1.should_not be_updatable_by(u)
+ v1.should_not be_destroyable_by(u)
+ v2.should_not be_updatable_by(u)
+ v2.should_not be_destroyable_by(u)
+ end
+ end
+end