diff options
author | Joachim Filip Ignacy Bartosik <jbartosik@gmail.com> | 2011-05-18 17:19:38 +0200 |
---|---|---|
committer | Joachim Filip Ignacy Bartosik <jbartosik@gmail.com> | 2011-06-01 15:21:14 +0200 |
commit | 4b874a907e16dcfb61cc82a69f9c3891bff1bfa8 (patch) | |
tree | d364ca3c0983cfd383c550792df6529443ed8bbe | |
parent | MeetBot plugin from Debian (diff) | |
download | council-webapp-4b874a907e16dcfb61cc82a69f9c3891bff1bfa8.tar.gz council-webapp-4b874a907e16dcfb61cc82a69f9c3891bff1bfa8.tar.bz2 council-webapp-4b874a907e16dcfb61cc82a69f9c3891bff1bfa8.zip |
Application provides data for IRC bot
-rw-r--r-- | site/app/controllers/agendas_controller.rb | 4 | ||||
-rw-r--r-- | site/app/controllers/users_controller.rb | 3 | ||||
-rw-r--r-- | site/app/controllers/voting_options_controller.rb | 6 | ||||
-rw-r--r-- | site/app/models/agenda.rb | 6 | ||||
-rw-r--r-- | site/app/models/agenda_item.rb | 2 | ||||
-rw-r--r-- | site/app/models/user.rb | 1 | ||||
-rw-r--r-- | site/app/models/vote.rb | 54 | ||||
-rw-r--r-- | site/app/models/voting_option.rb | 41 | ||||
-rw-r--r-- | site/app/viewhints/agenda_item_hints.rb | 5 | ||||
-rw-r--r-- | site/app/views/agenda_items/show.dryml | 9 | ||||
-rw-r--r-- | site/config/hobo_routes.rb | 10 | ||||
-rw-r--r-- | site/config/routes.rb | 3 | ||||
-rw-r--r-- | site/db/schema.rb | 23 | ||||
-rw-r--r-- | site/features/step_definitions/voting_steps.rb | 22 | ||||
-rw-r--r-- | site/features/step_definitions/within_steps.rb | 4 | ||||
-rw-r--r-- | site/features/support/paths.rb | 7 | ||||
-rw-r--r-- | site/features/voting.feature | 23 | ||||
-rw-r--r-- | site/spec/factories.rb | 13 | ||||
-rw-r--r-- | site/spec/models/vote_spec.rb | 37 | ||||
-rw-r--r-- | site/spec/models/voting_option_spec.rb | 41 |
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 |