effective_polls 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +132 -0
- data/Rakefile +32 -0
- data/app/assets/config/effective_polls_manifest.js +1 -0
- data/app/assets/stylesheets/effective_polls.scss +0 -0
- data/app/controllers/admin/poll_notifications_controller.rb +15 -0
- data/app/controllers/admin/poll_questions_controller.rb +15 -0
- data/app/controllers/admin/polls_controller.rb +23 -0
- data/app/controllers/effective/ballots_controller.rb +70 -0
- data/app/controllers/effective/polls_controller.rb +19 -0
- data/app/datatables/admin/effective_poll_notifications_datatable.rb +43 -0
- data/app/datatables/admin/effective_poll_questions_datatable.rb +28 -0
- data/app/datatables/admin/effective_poll_results_datatable.rb +50 -0
- data/app/datatables/admin/effective_polls_datatable.rb +29 -0
- data/app/datatables/effective_polls_datatable.rb +30 -0
- data/app/helpers/effective_polls_helper.rb +23 -0
- data/app/mailers/effective/polls_mailer.rb +23 -0
- data/app/models/effective/access_denied.rb +17 -0
- data/app/models/effective/ballot.rb +73 -0
- data/app/models/effective/ballot_response.rb +92 -0
- data/app/models/effective/ballot_response_option.rb +6 -0
- data/app/models/effective/poll.rb +149 -0
- data/app/models/effective/poll_notification.rb +144 -0
- data/app/models/effective/poll_question.rb +76 -0
- data/app/models/effective/poll_question_option.rb +26 -0
- data/app/views/admin/poll_notifications/_form.html.haml +55 -0
- data/app/views/admin/poll_questions/_form.html.haml +100 -0
- data/app/views/admin/polls/_form.html.haml +20 -0
- data/app/views/admin/polls/_form_content.html.haml +27 -0
- data/app/views/admin/polls/_form_poll.html.haml +32 -0
- data/app/views/admin/polls/_poll.html.haml +2 -0
- data/app/views/admin/polls/results.html.haml +6 -0
- data/app/views/effective/ballot_responses/_ballot_response.html.haml +2 -0
- data/app/views/effective/ballot_responses/_fields.html.haml +14 -0
- data/app/views/effective/ballot_responses/fields/_choose_one.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_date.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_email.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_long_answer.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_number.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_select_all_that_apply.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_select_upto_1.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_select_upto_2.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_select_upto_3.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_select_upto_4.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_select_upto_5.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_short_answer.html.haml +1 -0
- data/app/views/effective/ballot_responses/fields/_upload_file.html.haml +1 -0
- data/app/views/effective/ballot_responses/responses/_choose_one.html.haml +1 -0
- data/app/views/effective/ballot_responses/responses/_date.html.haml +1 -0
- data/app/views/effective/ballot_responses/responses/_email.html.haml +1 -0
- data/app/views/effective/ballot_responses/responses/_long_answer.html.haml +1 -0
- data/app/views/effective/ballot_responses/responses/_number.html.haml +1 -0
- data/app/views/effective/ballot_responses/responses/_select_all_that_apply.html.haml +5 -0
- data/app/views/effective/ballot_responses/responses/_select_upto_1.html.haml +1 -0
- data/app/views/effective/ballot_responses/responses/_select_upto_2.html.haml +5 -0
- data/app/views/effective/ballot_responses/responses/_select_upto_3.html.haml +5 -0
- data/app/views/effective/ballot_responses/responses/_select_upto_4.html.haml +5 -0
- data/app/views/effective/ballot_responses/responses/_select_upto_5.html.haml +5 -0
- data/app/views/effective/ballot_responses/responses/_short_answer.html.haml +1 -0
- data/app/views/effective/ballot_responses/responses/_upload_file.html.haml +4 -0
- data/app/views/effective/ballots/_ballot.html.haml +14 -0
- data/app/views/effective/ballots/complete.html.haml +11 -0
- data/app/views/effective/ballots/start.html.haml +17 -0
- data/app/views/effective/ballots/submit.html.haml +17 -0
- data/app/views/effective/ballots/vote.html.haml +19 -0
- data/app/views/effective/poll_results/_poll_result.html.haml +2 -0
- data/app/views/effective/poll_results/_results.html.haml +38 -0
- data/app/views/effective/poll_results/results/_choose_one.html.haml +2 -0
- data/app/views/effective/poll_results/results/_date.html.haml +14 -0
- data/app/views/effective/poll_results/results/_email.html.haml +14 -0
- data/app/views/effective/poll_results/results/_long_answer.html.haml +2 -0
- data/app/views/effective/poll_results/results/_number.html.haml +14 -0
- data/app/views/effective/poll_results/results/_poll_question_option.html.haml +17 -0
- data/app/views/effective/poll_results/results/_select_all_that_apply.html.haml +2 -0
- data/app/views/effective/poll_results/results/_select_upto_1.html.haml +2 -0
- data/app/views/effective/poll_results/results/_select_upto_2.html.haml +2 -0
- data/app/views/effective/poll_results/results/_select_upto_3.html.haml +2 -0
- data/app/views/effective/poll_results/results/_select_upto_4.html.haml +2 -0
- data/app/views/effective/poll_results/results/_select_upto_5.html.haml +2 -0
- data/app/views/effective/poll_results/results/_short_answer.html.haml +2 -0
- data/app/views/effective/poll_results/results/_upload_file.html.haml +3 -0
- data/app/views/effective/polls/_form.html.haml +10 -0
- data/app/views/effective/polls/_poll.html.haml +2 -0
- data/app/views/layouts/effective_polls_mailer_layout.html.haml +7 -0
- data/config/effective_polls.rb +59 -0
- data/config/routes.rb +23 -0
- data/db/migrate/01_create_effective_polls.rb.erb +92 -0
- data/lib/effective_polls.rb +50 -0
- data/lib/effective_polls/engine.rb +11 -0
- data/lib/effective_polls/version.rb +3 -0
- data/lib/generators/effective_polls/install_generator.rb +46 -0
- data/lib/generators/templates/effective_polls_mailer_preview.rb +35 -0
- data/lib/tasks/effective_polls_tasks.rake +24 -0
- metadata +276 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
class Admin::EffectivePollsDatatable < Effective::Datatable
|
2
|
+
filters do
|
3
|
+
scope :upcoming
|
4
|
+
scope :available
|
5
|
+
scope :completed
|
6
|
+
scope :all
|
7
|
+
end
|
8
|
+
|
9
|
+
datatable do
|
10
|
+
order :start_at, :desc
|
11
|
+
|
12
|
+
col :id, visible: false
|
13
|
+
col :created_at, visible: false
|
14
|
+
col :updated_at, visible: false
|
15
|
+
|
16
|
+
col :title
|
17
|
+
col :start_at
|
18
|
+
col :end_at
|
19
|
+
col :audience
|
20
|
+
|
21
|
+
col :poll_questions, visible: false
|
22
|
+
|
23
|
+
actions_col
|
24
|
+
end
|
25
|
+
|
26
|
+
collection do
|
27
|
+
Effective::Poll.all.deep
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Displays available polls that the current_user may complete
|
2
|
+
|
3
|
+
class EffectivePollsDatatable < Effective::Datatable
|
4
|
+
datatable do
|
5
|
+
order :start_at
|
6
|
+
|
7
|
+
col :start_at, visible: false
|
8
|
+
|
9
|
+
col :title
|
10
|
+
col :available_date, label: 'Date'
|
11
|
+
|
12
|
+
actions_col(actions: []) do |poll|
|
13
|
+
ballot = poll.ballots.where(user: current_user).first
|
14
|
+
|
15
|
+
if ballot.blank?
|
16
|
+
dropdown_link_to('Start', effective_polls.poll_ballot_build_path(poll, :new, :start))
|
17
|
+
elsif ballot.completed?
|
18
|
+
'Complete'
|
19
|
+
else
|
20
|
+
dropdown_link_to('Continue', effective_polls.poll_ballot_build_path(poll, ballot, ballot.next_step))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
collection do
|
26
|
+
raise('expected a current_user') unless current_user.present?
|
27
|
+
Effective::Poll.available.select { |poll| poll.available_for?(current_user) }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EffectivePollsHelper
|
2
|
+
|
3
|
+
# Used by admin/polls form
|
4
|
+
def effective_polls_audience_scope_collection
|
5
|
+
# Normalize the collection into [[label, value], [label, value]]
|
6
|
+
scopes = Array(EffectivePolls.audience_user_scopes).map do |key, value|
|
7
|
+
(key.present? && value.present?) ? [key, value] : [key.to_s.titleize, key]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Makes sure the User model responds to all values
|
11
|
+
scopes.each do |_, scope|
|
12
|
+
unless defined?(User) && User.respond_to?(scope)
|
13
|
+
raise("invalid effective_polls config.audience_user_scopes value. The user model must respond to the scope User.#{scope}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Append the number of users in this scope
|
18
|
+
scopes.map! do |label, scope|
|
19
|
+
["#{label} (#{pluralize(User.send(scope).count, 'user')})", scope]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Effective
|
2
|
+
class PollsMailer < ActionMailer::Base
|
3
|
+
default from: EffectivePolls.mailer[:default_from]
|
4
|
+
layout EffectivePolls.mailer[:layout].presence || 'effective_polls_mailer_layout'
|
5
|
+
|
6
|
+
def poll_upcoming_reminder(poll_notification, bcc)
|
7
|
+
mail(bcc: bcc, body: poll_notification.body, subject: poll_notification.subject)
|
8
|
+
end
|
9
|
+
|
10
|
+
def poll_start(poll_notification, bcc)
|
11
|
+
mail(bcc: bcc, body: poll_notification.body, subject: poll_notification.subject)
|
12
|
+
end
|
13
|
+
|
14
|
+
def poll_reminder(poll_notification, bcc)
|
15
|
+
mail(bcc: bcc, body: poll_notification.body, subject: poll_notification.subject)
|
16
|
+
end
|
17
|
+
|
18
|
+
def poll_end(poll_notification, bcc)
|
19
|
+
mail(bcc: bcc, body: poll_notification.body, subject: poll_notification.subject)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
unless defined?(Effective::AccessDenied)
|
2
|
+
module Effective
|
3
|
+
class AccessDenied < StandardError
|
4
|
+
attr_reader :action, :subject
|
5
|
+
|
6
|
+
def initialize(message = nil, action = nil, subject = nil)
|
7
|
+
@message = message
|
8
|
+
@action = action
|
9
|
+
@subject = subject
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
@message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Effective
|
2
|
+
class Ballot < ActiveRecord::Base
|
3
|
+
attr_accessor :current_user
|
4
|
+
attr_accessor :current_step
|
5
|
+
|
6
|
+
belongs_to :poll
|
7
|
+
belongs_to :user
|
8
|
+
|
9
|
+
has_many :ballot_responses, dependent: :destroy
|
10
|
+
accepts_nested_attributes_for :ballot_responses
|
11
|
+
|
12
|
+
acts_as_tokened
|
13
|
+
|
14
|
+
acts_as_wizard(
|
15
|
+
start: 'Start',
|
16
|
+
vote: 'Ballot',
|
17
|
+
submit: 'Review', # They submit on this step
|
18
|
+
complete: 'Complete'
|
19
|
+
)
|
20
|
+
|
21
|
+
effective_resource do
|
22
|
+
# Acts as tokened
|
23
|
+
token :string, permitted: false
|
24
|
+
|
25
|
+
# Acts as Wizard
|
26
|
+
wizard_steps :text, permitted: false
|
27
|
+
|
28
|
+
# More fields
|
29
|
+
completed_at :datetime, permitted: false
|
30
|
+
|
31
|
+
current_step permitted: true
|
32
|
+
timestamps
|
33
|
+
end
|
34
|
+
|
35
|
+
scope :deep, -> { includes(:poll, :user, ballot_responses: [:poll, :poll_question, :poll_question_options]) }
|
36
|
+
scope :sorted, -> { order(:id) }
|
37
|
+
scope :completed, -> { where.not(completed_at: nil) }
|
38
|
+
|
39
|
+
before_validation(if: -> { new_record? }) do
|
40
|
+
self.user ||= current_user
|
41
|
+
end
|
42
|
+
|
43
|
+
validates :user_id, uniqueness: {
|
44
|
+
scope: :poll_id, allow_blank: true, message: 'ballot already exists for this poll'
|
45
|
+
}
|
46
|
+
|
47
|
+
# I seem to need this even tho I accept_nested_attributes
|
48
|
+
validates :ballot_responses, associated: true
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
'ballot'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Find or build
|
55
|
+
def ballot_response(poll_question)
|
56
|
+
ballot_response = ballot_responses.find { |br| br.poll_question_id == poll_question.id }
|
57
|
+
ballot_response ||= ballot_responses.build(poll: poll_question.poll, poll_question: poll_question)
|
58
|
+
end
|
59
|
+
|
60
|
+
# This is the review step where they click Submit Ballot
|
61
|
+
def submit!
|
62
|
+
wizard_steps[:complete] ||= Time.zone.now
|
63
|
+
self.completed_at ||= Time.zone.now
|
64
|
+
|
65
|
+
save!
|
66
|
+
end
|
67
|
+
|
68
|
+
def completed?
|
69
|
+
completed_at.present?
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Effective
|
2
|
+
class BallotResponse < ActiveRecord::Base
|
3
|
+
belongs_to :ballot
|
4
|
+
|
5
|
+
belongs_to :poll
|
6
|
+
belongs_to :poll_question
|
7
|
+
|
8
|
+
has_many :ballot_response_options, dependent: :delete_all
|
9
|
+
has_many :poll_question_options, through: :ballot_response_options
|
10
|
+
|
11
|
+
has_one_attached :upload_file
|
12
|
+
|
13
|
+
effective_resource do
|
14
|
+
# The response
|
15
|
+
date :date
|
16
|
+
email :string
|
17
|
+
number :integer
|
18
|
+
long_answer :text
|
19
|
+
short_answer :text
|
20
|
+
|
21
|
+
timestamps
|
22
|
+
end
|
23
|
+
|
24
|
+
scope :completed, -> { where(ballot: Effective::Ballot.completed) }
|
25
|
+
scope :deep, -> { includes(:ballot, :poll, :poll_question, :poll_question_options) }
|
26
|
+
|
27
|
+
validates :poll, presence: true
|
28
|
+
validates :ballot, presence: true
|
29
|
+
validates :poll_question, presence: true
|
30
|
+
|
31
|
+
# Sanity check. These shouldn't happen.
|
32
|
+
validate(if: -> { poll.present? && ballot.present? }) do
|
33
|
+
self.errors.add(:ballot, 'must match poll') unless ballot.poll_id == poll.id
|
34
|
+
end
|
35
|
+
|
36
|
+
validate(if: -> { poll.present? && poll_question.present? }) do
|
37
|
+
self.errors.add(:poll_question, 'must match poll') unless poll_question.poll_id == poll.id
|
38
|
+
end
|
39
|
+
|
40
|
+
validates :date, presence: true, if: -> { poll_question&.required? && poll_question.date? }
|
41
|
+
validates :email, presence: true, email: true, if: -> { poll_question&.required? && poll_question.email? }
|
42
|
+
validates :number, presence: true, if: -> { poll_question&.required? && poll_question.number? }
|
43
|
+
validates :long_answer, presence: true, if: -> { poll_question&.required? && poll_question.long_answer? }
|
44
|
+
validates :short_answer, presence: true, if: -> { poll_question&.required? && poll_question.short_answer? }
|
45
|
+
validates :upload_file, presence: true, if: -> { poll_question&.required? && poll_question.upload_file? }
|
46
|
+
validates :poll_question_option_ids, presence: true, if: -> { poll_question&.required? && poll_question.poll_question_option? }
|
47
|
+
|
48
|
+
validates :poll_question_option_ids, if: -> { poll_question&.choose_one? },
|
49
|
+
length: { maximum: 1, message: 'please choose 1 option only' }
|
50
|
+
|
51
|
+
validates :poll_question_option_ids, if: -> { poll_question&.select_upto_1? },
|
52
|
+
length: { maximum: 1, message: 'please select 1 option or fewer' }
|
53
|
+
|
54
|
+
validates :poll_question_option_ids, if: -> { poll_question&.select_upto_2? },
|
55
|
+
length: { maximum: 2, message: 'please select 2 options or fewer' }
|
56
|
+
|
57
|
+
validates :poll_question_option_ids, if: -> { poll_question&.select_upto_3? },
|
58
|
+
length: { maximum: 3, message: 'please select 3 options or fewer' }
|
59
|
+
|
60
|
+
validates :poll_question_option_ids, if: -> { poll_question&.select_upto_4? },
|
61
|
+
length: { maximum: 4, message: 'please select 4 options or fewer' }
|
62
|
+
|
63
|
+
validates :poll_question_option_ids, if: -> { poll_question&.select_upto_5? },
|
64
|
+
length: { maximum: 5, message: 'please select 5 options or fewer' }
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
'ballot reponse'
|
68
|
+
end
|
69
|
+
|
70
|
+
def response
|
71
|
+
return nil unless poll_question.present?
|
72
|
+
|
73
|
+
return date if poll_question.date?
|
74
|
+
return email if poll_question.email?
|
75
|
+
return number if poll_question.number?
|
76
|
+
return long_answer if poll_question.long_answer?
|
77
|
+
return short_answer if poll_question.short_answer?
|
78
|
+
return upload_file if poll_question.upload_file?
|
79
|
+
|
80
|
+
return poll_question_options.first if poll_question.choose_one?
|
81
|
+
return poll_question_options.first if poll_question.select_upto_1?
|
82
|
+
return poll_question_options if poll_question.poll_question_option?
|
83
|
+
|
84
|
+
raise('unknown response for unexpected poll question category')
|
85
|
+
end
|
86
|
+
|
87
|
+
def category_partial
|
88
|
+
poll_question&.category_partial
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Effective
|
2
|
+
class Poll < ActiveRecord::Base
|
3
|
+
attr_accessor :skip_started_validation
|
4
|
+
|
5
|
+
has_rich_text :all_steps_content
|
6
|
+
has_rich_text :start_content
|
7
|
+
has_rich_text :vote_content
|
8
|
+
has_rich_text :submit_content
|
9
|
+
has_rich_text :complete_content
|
10
|
+
|
11
|
+
has_many :poll_notifications, -> { order(:id) }, inverse_of: :poll, dependent: :destroy
|
12
|
+
accepts_nested_attributes_for :poll_notifications, allow_destroy: true
|
13
|
+
|
14
|
+
has_many :poll_questions, -> { order(:position) }, inverse_of: :poll, dependent: :destroy
|
15
|
+
accepts_nested_attributes_for :poll_questions, allow_destroy: true
|
16
|
+
|
17
|
+
has_many :ballots
|
18
|
+
has_many :ballot_responses
|
19
|
+
|
20
|
+
# For the poll_results screens
|
21
|
+
has_many :completed_ballots, -> { Effective::Ballot.completed }, class_name: 'Effective::Ballot'
|
22
|
+
has_many :completed_ballot_responses, -> { where(ballot: Effective::Ballot.completed) }, class_name: 'Effective::BallotResponse'
|
23
|
+
|
24
|
+
acts_as_tokened
|
25
|
+
|
26
|
+
AUDIENCES = ['All Users', 'Individual Users', 'Selected Users']
|
27
|
+
|
28
|
+
effective_resource do
|
29
|
+
# Acts as tokened
|
30
|
+
token :string, permitted: false
|
31
|
+
|
32
|
+
title :string
|
33
|
+
|
34
|
+
start_at :datetime
|
35
|
+
end_at :datetime
|
36
|
+
|
37
|
+
audience :string
|
38
|
+
audience_scope :text # An Array of user_ids or named scopes on the User model
|
39
|
+
|
40
|
+
timestamps
|
41
|
+
end
|
42
|
+
|
43
|
+
serialize :audience_scope, Array
|
44
|
+
|
45
|
+
scope :deep, -> {
|
46
|
+
includes(poll_questions: :poll_question_options)
|
47
|
+
.with_rich_text_all_steps_content
|
48
|
+
.with_rich_text_start_content
|
49
|
+
.with_rich_text_vote_content
|
50
|
+
.with_rich_text_submit_content
|
51
|
+
.with_rich_text_complete_content
|
52
|
+
}
|
53
|
+
|
54
|
+
scope :deep_results, -> {
|
55
|
+
includes(poll_questions: :poll_question_options)
|
56
|
+
.includes(ballots: [ballot_responses: [:poll, :poll_question, :poll_question_options]])
|
57
|
+
}
|
58
|
+
|
59
|
+
scope :sorted, -> { order(:start_at) }
|
60
|
+
scope :editable, -> { upcoming }
|
61
|
+
|
62
|
+
scope :upcoming, -> { where('start_at > ?', Time.zone.now) }
|
63
|
+
scope :available, -> { where('start_at <= ? AND (end_at > ? OR end_at IS NULL)', Time.zone.now, Time.zone.now) }
|
64
|
+
scope :completed, -> { where('end_at < ?', Time.zone.now) }
|
65
|
+
|
66
|
+
validates :title, presence: true
|
67
|
+
validates :start_at, presence: true
|
68
|
+
|
69
|
+
validates :audience, inclusion: { in: AUDIENCES }
|
70
|
+
validates :audience_scope, presence: true, unless: -> { audience == 'All Users' }
|
71
|
+
|
72
|
+
validate(if: -> { started? }, unless: -> { skip_started_validation }) do
|
73
|
+
self.errors.add(:base, 'has already started. a poll cannot be changed after it has started.')
|
74
|
+
end
|
75
|
+
|
76
|
+
validate(if: -> { start_at.present? && !started? }, unless: -> { skip_started_validation }) do
|
77
|
+
self.errors.add(:start_at, 'must be a future date') if start_at < Time.zone.now
|
78
|
+
end
|
79
|
+
|
80
|
+
validate(if: -> { start_at.present? && end_at.present? }) do
|
81
|
+
self.errors.add(:end_at, 'must be after the start date') unless end_at > start_at
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
title.presence || 'New Poll'
|
86
|
+
end
|
87
|
+
|
88
|
+
def available_for?(user)
|
89
|
+
raise('expected a user') unless user.kind_of?(User)
|
90
|
+
available? && users.include?(user)
|
91
|
+
end
|
92
|
+
|
93
|
+
def users
|
94
|
+
case audience
|
95
|
+
when 'All Users'
|
96
|
+
User.all
|
97
|
+
when 'Individual Users'
|
98
|
+
User.where(id: audience_scope)
|
99
|
+
when 'Selected Users'
|
100
|
+
collection = User.none
|
101
|
+
audience_scope.each { |scope| collection = collection.or(User.send(scope)) }
|
102
|
+
collection
|
103
|
+
else
|
104
|
+
raise('unexpected audience')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def emails(exclude_completed: false)
|
109
|
+
if exclude_completed
|
110
|
+
users.where.not(id: completed_ballots.select('user_id as id')).pluck(:email)
|
111
|
+
else
|
112
|
+
users.pluck(:email)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def available?
|
117
|
+
started? && !ended?
|
118
|
+
end
|
119
|
+
|
120
|
+
def started?
|
121
|
+
start_at_was.present? && Time.zone.now >= start_at_was
|
122
|
+
end
|
123
|
+
|
124
|
+
def ended?
|
125
|
+
end_at.present? && end_at < Time.zone.now
|
126
|
+
end
|
127
|
+
|
128
|
+
def available_date
|
129
|
+
if start_at && end_at && start_at.to_date == end_at.to_date
|
130
|
+
"#{start_at.strftime('%F at %H:%M')} to #{end_at.strftime('%H:%M')}"
|
131
|
+
elsif start_at && end_at
|
132
|
+
"#{start_at.strftime('%F at %H:%M')} to #{end_at.strftime('%F %H:%M')}"
|
133
|
+
elsif start_at
|
134
|
+
"#{start_at.strftime('%F at %H:%M')}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def audience_scope
|
139
|
+
Array(super) - [nil, '']
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns all completed_ballot_responses
|
143
|
+
def poll_results(poll_question: nil)
|
144
|
+
return completed_ballot_responses if poll_question.nil?
|
145
|
+
completed_ballot_responses.select { |br| br.poll_question_id == poll_question.id }
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|