effective_polls 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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