effective_polls 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +132 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/effective_polls_manifest.js +1 -0
  6. data/app/assets/stylesheets/effective_polls.scss +0 -0
  7. data/app/controllers/admin/poll_notifications_controller.rb +15 -0
  8. data/app/controllers/admin/poll_questions_controller.rb +15 -0
  9. data/app/controllers/admin/polls_controller.rb +23 -0
  10. data/app/controllers/effective/ballots_controller.rb +70 -0
  11. data/app/controllers/effective/polls_controller.rb +19 -0
  12. data/app/datatables/admin/effective_poll_notifications_datatable.rb +43 -0
  13. data/app/datatables/admin/effective_poll_questions_datatable.rb +28 -0
  14. data/app/datatables/admin/effective_poll_results_datatable.rb +50 -0
  15. data/app/datatables/admin/effective_polls_datatable.rb +29 -0
  16. data/app/datatables/effective_polls_datatable.rb +30 -0
  17. data/app/helpers/effective_polls_helper.rb +23 -0
  18. data/app/mailers/effective/polls_mailer.rb +23 -0
  19. data/app/models/effective/access_denied.rb +17 -0
  20. data/app/models/effective/ballot.rb +73 -0
  21. data/app/models/effective/ballot_response.rb +92 -0
  22. data/app/models/effective/ballot_response_option.rb +6 -0
  23. data/app/models/effective/poll.rb +149 -0
  24. data/app/models/effective/poll_notification.rb +144 -0
  25. data/app/models/effective/poll_question.rb +76 -0
  26. data/app/models/effective/poll_question_option.rb +26 -0
  27. data/app/views/admin/poll_notifications/_form.html.haml +55 -0
  28. data/app/views/admin/poll_questions/_form.html.haml +100 -0
  29. data/app/views/admin/polls/_form.html.haml +20 -0
  30. data/app/views/admin/polls/_form_content.html.haml +27 -0
  31. data/app/views/admin/polls/_form_poll.html.haml +32 -0
  32. data/app/views/admin/polls/_poll.html.haml +2 -0
  33. data/app/views/admin/polls/results.html.haml +6 -0
  34. data/app/views/effective/ballot_responses/_ballot_response.html.haml +2 -0
  35. data/app/views/effective/ballot_responses/_fields.html.haml +14 -0
  36. data/app/views/effective/ballot_responses/fields/_choose_one.html.haml +1 -0
  37. data/app/views/effective/ballot_responses/fields/_date.html.haml +1 -0
  38. data/app/views/effective/ballot_responses/fields/_email.html.haml +1 -0
  39. data/app/views/effective/ballot_responses/fields/_long_answer.html.haml +1 -0
  40. data/app/views/effective/ballot_responses/fields/_number.html.haml +1 -0
  41. data/app/views/effective/ballot_responses/fields/_select_all_that_apply.html.haml +1 -0
  42. data/app/views/effective/ballot_responses/fields/_select_upto_1.html.haml +1 -0
  43. data/app/views/effective/ballot_responses/fields/_select_upto_2.html.haml +1 -0
  44. data/app/views/effective/ballot_responses/fields/_select_upto_3.html.haml +1 -0
  45. data/app/views/effective/ballot_responses/fields/_select_upto_4.html.haml +1 -0
  46. data/app/views/effective/ballot_responses/fields/_select_upto_5.html.haml +1 -0
  47. data/app/views/effective/ballot_responses/fields/_short_answer.html.haml +1 -0
  48. data/app/views/effective/ballot_responses/fields/_upload_file.html.haml +1 -0
  49. data/app/views/effective/ballot_responses/responses/_choose_one.html.haml +1 -0
  50. data/app/views/effective/ballot_responses/responses/_date.html.haml +1 -0
  51. data/app/views/effective/ballot_responses/responses/_email.html.haml +1 -0
  52. data/app/views/effective/ballot_responses/responses/_long_answer.html.haml +1 -0
  53. data/app/views/effective/ballot_responses/responses/_number.html.haml +1 -0
  54. data/app/views/effective/ballot_responses/responses/_select_all_that_apply.html.haml +5 -0
  55. data/app/views/effective/ballot_responses/responses/_select_upto_1.html.haml +1 -0
  56. data/app/views/effective/ballot_responses/responses/_select_upto_2.html.haml +5 -0
  57. data/app/views/effective/ballot_responses/responses/_select_upto_3.html.haml +5 -0
  58. data/app/views/effective/ballot_responses/responses/_select_upto_4.html.haml +5 -0
  59. data/app/views/effective/ballot_responses/responses/_select_upto_5.html.haml +5 -0
  60. data/app/views/effective/ballot_responses/responses/_short_answer.html.haml +1 -0
  61. data/app/views/effective/ballot_responses/responses/_upload_file.html.haml +4 -0
  62. data/app/views/effective/ballots/_ballot.html.haml +14 -0
  63. data/app/views/effective/ballots/complete.html.haml +11 -0
  64. data/app/views/effective/ballots/start.html.haml +17 -0
  65. data/app/views/effective/ballots/submit.html.haml +17 -0
  66. data/app/views/effective/ballots/vote.html.haml +19 -0
  67. data/app/views/effective/poll_results/_poll_result.html.haml +2 -0
  68. data/app/views/effective/poll_results/_results.html.haml +38 -0
  69. data/app/views/effective/poll_results/results/_choose_one.html.haml +2 -0
  70. data/app/views/effective/poll_results/results/_date.html.haml +14 -0
  71. data/app/views/effective/poll_results/results/_email.html.haml +14 -0
  72. data/app/views/effective/poll_results/results/_long_answer.html.haml +2 -0
  73. data/app/views/effective/poll_results/results/_number.html.haml +14 -0
  74. data/app/views/effective/poll_results/results/_poll_question_option.html.haml +17 -0
  75. data/app/views/effective/poll_results/results/_select_all_that_apply.html.haml +2 -0
  76. data/app/views/effective/poll_results/results/_select_upto_1.html.haml +2 -0
  77. data/app/views/effective/poll_results/results/_select_upto_2.html.haml +2 -0
  78. data/app/views/effective/poll_results/results/_select_upto_3.html.haml +2 -0
  79. data/app/views/effective/poll_results/results/_select_upto_4.html.haml +2 -0
  80. data/app/views/effective/poll_results/results/_select_upto_5.html.haml +2 -0
  81. data/app/views/effective/poll_results/results/_short_answer.html.haml +2 -0
  82. data/app/views/effective/poll_results/results/_upload_file.html.haml +3 -0
  83. data/app/views/effective/polls/_form.html.haml +10 -0
  84. data/app/views/effective/polls/_poll.html.haml +2 -0
  85. data/app/views/layouts/effective_polls_mailer_layout.html.haml +7 -0
  86. data/config/effective_polls.rb +59 -0
  87. data/config/routes.rb +23 -0
  88. data/db/migrate/01_create_effective_polls.rb.erb +92 -0
  89. data/lib/effective_polls.rb +50 -0
  90. data/lib/effective_polls/engine.rb +11 -0
  91. data/lib/effective_polls/version.rb +3 -0
  92. data/lib/generators/effective_polls/install_generator.rb +46 -0
  93. data/lib/generators/templates/effective_polls_mailer_preview.rb +35 -0
  94. data/lib/tasks/effective_polls_tasks.rake +24 -0
  95. metadata +276 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b49f5df5989ddd470b3090d2ac28ab5974e902bd33f8a57e05487025074cf0a8
4
+ data.tar.gz: f3465afeb7bee7279348f24f7e85128d136cac9d47add5f3c67f8a1ee740062f
5
+ SHA512:
6
+ metadata.gz: 5ea3cf981517d7d8081feae614b054114f156826b5a694410d48f3418b7cf9df2db606c290d3f89a9ca1018ff4a8c64550528b9071436d7c7c02c863217467d1
7
+ data.tar.gz: 76898e90f2e456e3ccbc4b24e022ce785b1375ff0afbc91cab27b31319c4bf40dab731900d77bced3788d9acb1cdceaec4b9d6483c984c0e8e0d4a20549f598c
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,132 @@
1
+ # Effective Polls
2
+
3
+ Online polls and user voting.
4
+
5
+ An admin creates polls with one or more poll_questions. The poll can be assigned to users through a poll_ballot.
6
+
7
+ The user can complete the poll_ballot and anonymously vote for each option.
8
+
9
+ Some reports to display results.
10
+
11
+ ## Getting Started
12
+
13
+ Please first install the [effective_datatables](https://github.com/code-and-effect/effective_datatables) gem.
14
+
15
+ Please download and install [Twitter Bootstrap4](http://getbootstrap.com)
16
+
17
+ Add to your Gemfile:
18
+
19
+ ```ruby
20
+ gem 'effective_polls'
21
+ ```
22
+
23
+ Run the bundle command to install it:
24
+
25
+ ```console
26
+ bundle install
27
+ ```
28
+
29
+ Then run the generator:
30
+
31
+ ```ruby
32
+ rails generate effective_polls:install
33
+ ```
34
+
35
+ The generator will install an initializer which describes all configuration options and creates a database migration.
36
+
37
+ If you want to tweak the table name (to use something other than the default 'polls'), manually adjust both the configuration file and the migration now.
38
+
39
+ Then migrate the database:
40
+
41
+ ```ruby
42
+ rake db:migrate
43
+ ```
44
+
45
+ ## Polls
46
+
47
+ TODO
48
+
49
+ ## Authorization
50
+
51
+ All authorization checks are handled via the config.authorization_method found in the `app/config/initializers/effective_polls.rb` file.
52
+
53
+ It is intended for flow through to CanCan or Pundit, but neither of those gems are required.
54
+
55
+ This method is called by all controller actions with the appropriate action and resource
56
+
57
+ Action will be one of [:index, :show, :new, :create, :edit, :update, :destroy]
58
+
59
+ Resource will the appropriate Effective::Poll object or class
60
+
61
+ The authorization method is defined in the initializer file:
62
+
63
+ ```ruby
64
+ # As a Proc (with CanCan)
65
+ config.authorization_method = Proc.new { |controller, action, resource| authorize!(action, resource) }
66
+ ```
67
+
68
+ ```ruby
69
+ # As a Custom Method
70
+ config.authorization_method = :my_authorization_method
71
+ ```
72
+
73
+ and then in your application_controller.rb:
74
+
75
+ ```ruby
76
+ def my_authorization_method(action, resource)
77
+ current_user.is?(:admin) || EffectivePunditPolicy.new(current_user, resource).send('#{action}?')
78
+ end
79
+ ```
80
+
81
+ or disabled entirely:
82
+
83
+ ```ruby
84
+ config.authorization_method = false
85
+ ```
86
+
87
+ If the method or proc returns false (user is not authorized) an Effective::AccessDenied exception will be raised
88
+
89
+ You can rescue from this exception by adding the following to your application_controller.rb:
90
+
91
+ ```ruby
92
+ rescue_from Effective::AccessDenied do |exception|
93
+ respond_to do |format|
94
+ format.html { render 'static_pages/access_denied', status: 403 }
95
+ format.any { render text: 'Access Denied', status: 403 }
96
+ end
97
+ end
98
+ ```
99
+
100
+ ### Permissions
101
+
102
+ The permissions you actually want to define are as follows (using CanCan):
103
+
104
+ ```ruby
105
+ can [:index, :show], Effective::Poll
106
+
107
+ if user.admin?
108
+ can :manage, Effective::Poll
109
+ can :admin, :effective_polls
110
+ end
111
+ ```
112
+
113
+ ## License
114
+
115
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
116
+
117
+ ## Testing
118
+
119
+ Run tests by:
120
+
121
+ ```ruby
122
+ rails test
123
+ ```
124
+
125
+ ## Contributing
126
+
127
+ 1. Fork it
128
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
129
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
130
+ 4. Push to the branch (`git push origin my-new-feature`)
131
+ 5. Bonus points for test coverage
132
+ 6. Create new Pull Request
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'EffectivePolls'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets .css
@@ -0,0 +1,15 @@
1
+ module Admin
2
+ class PollNotificationsController < ApplicationController
3
+ layout (EffectivePolls.layout.kind_of?(Hash) ? EffectivePolls.layout[:admin] : EffectivePolls.layout)
4
+
5
+ before_action(:authenticate_user!) if defined?(Devise)
6
+ before_action { EffectivePolls.authorize!(self, :admin, :effective_polls) }
7
+
8
+ include Effective::CrudController
9
+
10
+ def permitted_params
11
+ params.require(:effective_poll_notification).permit!
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Admin
2
+ class PollQuestionsController < ApplicationController
3
+ layout (EffectivePolls.layout.kind_of?(Hash) ? EffectivePolls.layout[:admin] : EffectivePolls.layout)
4
+
5
+ before_action(:authenticate_user!) if defined?(Devise)
6
+ before_action { EffectivePolls.authorize!(self, :admin, :effective_polls) }
7
+
8
+ include Effective::CrudController
9
+
10
+ def permitted_params
11
+ params.require(:effective_poll_question).permit!
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module Admin
2
+ class PollsController < ApplicationController
3
+ layout (EffectivePolls.layout.kind_of?(Hash) ? EffectivePolls.layout[:admin] : EffectivePolls.layout)
4
+
5
+ before_action(:authenticate_user!) if defined?(Devise)
6
+ before_action { EffectivePolls.authorize!(self, :admin, :effective_polls) }
7
+
8
+ include Effective::CrudController
9
+
10
+ def results
11
+ @poll = Effective::Poll.find(params[:id])
12
+ EffectivePolls.authorize!(self, :results, @poll)
13
+
14
+ @datatable = Admin::EffectivePollResultsDatatable.new(poll_token: @poll.token)
15
+ @page_title = "#{@poll} Results"
16
+ end
17
+
18
+ def permitted_params
19
+ params.require(:effective_poll).permit!
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,70 @@
1
+ module Effective
2
+ class BallotsController < ApplicationController
3
+ layout (EffectivePolls.layout.kind_of?(Hash) ? EffectivePolls.layout[:polls] : EffectivePolls.layout)
4
+
5
+ before_action(:authenticate_user!) if defined?(Devise)
6
+ include Effective::WizardController
7
+
8
+ resource_scope do
9
+ poll = Effective::Poll.find(params[:poll_id])
10
+ Effective::Ballot.deep.where(poll: poll, user: current_user)
11
+ end
12
+
13
+ # Enforce one ballot per user. Redirect them to an existing ballot for this poll.
14
+ before_action(only: [:new, :show]) do
15
+ poll = Effective::Poll.find(params[:poll_id])
16
+ existing = Effective::Ballot.where(poll: poll, user: current_user).where.not(id: resource).first
17
+
18
+ if existing&.completed?
19
+ flash[:danger] = 'You have already completed a ballot for this poll.'
20
+ redirect_to(root_path)
21
+ elsif existing.present?
22
+ flash[:success] = "You have been redirected to the #{resource_wizard_step_title(existing.next_step)} step."
23
+ redirect_to effective_polls.poll_ballot_build_path(existing.poll, existing, existing.next_step)
24
+ end
25
+ end
26
+
27
+ # Enforce poll availability
28
+ before_action(only: [:show, :update]) do
29
+ poll = resource.poll
30
+
31
+ unless poll.available_for?(current_user)
32
+ flash[:danger] = begin
33
+ if poll.ended?
34
+ 'This poll has ended'
35
+ elsif !poll.started?
36
+ 'This poll has not yet started'
37
+ elsif !poll.users.include?(current_user)
38
+ 'This poll is not available to you'
39
+ else
40
+ 'This poll is unavailable'
41
+ end
42
+ end
43
+
44
+ redirect_to(root_path)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def permitted_params
51
+ case step
52
+ when :start
53
+ params.require(:effective_ballot).permit(:current_step)
54
+ when :vote
55
+ params.require(:effective_ballot).permit(:current_step, ballot_responses_attributes: [
56
+ :id, :poll_id, :poll_question_id,
57
+ :date, :email, :number, :long_answer, :short_answer, :upload_file,
58
+ :poll_question_option_ids, poll_question_option_ids: []
59
+ ])
60
+ when :submit
61
+ params.require(:effective_ballot).permit(:current_step)
62
+ when :complete
63
+ raise('unexpected post to complete')
64
+ else
65
+ raise('unexpected step')
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,19 @@
1
+ module Effective
2
+ class PollsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+
5
+ def show
6
+ poll = Effective::Poll.find(params[:id])
7
+ EffectivePolls.authorize!(self, :show, poll)
8
+
9
+ ballot = Effective::Ballot.where(poll: poll, user: current_user).first
10
+
11
+ if ballot.present?
12
+ redirect_to effective_polls.poll_ballot_build_path(poll, ballot, ballot.next_step)
13
+ else
14
+ redirect_to effective_polls.poll_ballot_build_path(poll, :new, :start)
15
+ end
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,43 @@
1
+ class Admin::EffectivePollNotificationsDatatable < Effective::Datatable
2
+ filters do
3
+ scope :all
4
+ scope :started
5
+ scope :completed
6
+ end
7
+
8
+ datatable do
9
+ col :updated_at, visible: false
10
+ col :created_at, visible: false
11
+ col :id, visible: false
12
+
13
+ col :poll
14
+ col :category
15
+
16
+ col :reminder do |poll_notification|
17
+ case poll_notification.category
18
+ when 'When poll starts'
19
+ poll_notification.poll.start_at&.strftime('%F %H:%M')
20
+ when 'When poll ends'
21
+ poll_notification.poll.end_at&.strftime('%F %H:%M')
22
+ when 'Upcoming reminder'
23
+ Effective::PollNotification::UPCOMING_REMINDERS.invert[poll_notification.reminder]
24
+ when 'Reminder'
25
+ Effective::PollNotification::REMINDERS.invert[poll_notification.reminder]
26
+ else
27
+ raise('unexpected category')
28
+ end
29
+ end
30
+
31
+ col :subject
32
+ col :body
33
+
34
+ col :started_at, visible: false
35
+ col :completed_at
36
+
37
+ actions_col
38
+ end
39
+
40
+ collection do
41
+ Effective::PollNotification.all.deep
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ class Admin::EffectivePollQuestionsDatatable < Effective::Datatable
2
+ datatable do
3
+ reorder :position
4
+
5
+ col :updated_at, visible: false
6
+ col :created_at, visible: false
7
+ col :id, visible: false
8
+
9
+ col :poll
10
+
11
+ col :position do |poll_question|
12
+ poll_question.position.to_i + 1
13
+ end
14
+
15
+ col :title
16
+ col :body
17
+ col :required
18
+
19
+ col :category, label: 'Type'
20
+ col :poll_question_options, label: 'Options'
21
+
22
+ actions_col
23
+ end
24
+
25
+ collection do
26
+ Effective::PollQuestion.all.deep
27
+ end
28
+ end
@@ -0,0 +1,50 @@
1
+ class Admin::EffectivePollResultsDatatable < Effective::Datatable
2
+ datatable do
3
+ col :ballot, search: poll.completed_ballots.order(:token).pluck(:token)
4
+
5
+ col :position, visible: false
6
+ col :category, search: Effective::PollQuestion::CATEGORIES, visible: false
7
+
8
+ col :question, search: poll.poll_questions.pluck(:title)
9
+ col :responses
10
+ end
11
+
12
+ collection do
13
+ ballot_responses = Effective::BallotResponse.completed.deep.where(poll: poll)
14
+
15
+ ballot_responses.flat_map do |br|
16
+ rows = if br.poll_question.poll_question_option?
17
+ br.poll_question_options.map do |response|
18
+ [
19
+ br.ballot.token,
20
+ br.poll_question.position,
21
+ br.poll_question.category,
22
+ br.poll_question.to_s,
23
+ response.to_s
24
+ ]
25
+ end
26
+ elsif br.response.present?
27
+ [
28
+ [
29
+ br.ballot.token,
30
+ br.poll_question.position,
31
+ br.poll_question.category,
32
+ br.poll_question.to_s,
33
+ br.response.to_s
34
+ ]
35
+ ]
36
+ else
37
+ []
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ def poll
44
+ @poll ||= begin
45
+ raise('expected datatable poll_token attribute') unless attributes[:poll_token]
46
+ Effective::Poll.deep_results.find(attributes[:poll_token])
47
+ end
48
+ end
49
+
50
+ end