effective_questions 0.0.2

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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +133 -0
  4. data/Rakefile +18 -0
  5. data/app/assets/config/effective_questions_manifest.js +1 -0
  6. data/app/assets/stylesheets/effective_questions.scss +0 -0
  7. data/app/controllers/admin/questions_controller.rb +13 -0
  8. data/app/datatables/admin/effective_questionable_results_datatable.rb +44 -0
  9. data/app/datatables/admin/effective_questions_datatable.rb +45 -0
  10. data/app/helpers/effective_questions_helper.rb +2 -0
  11. data/app/models/concerns/acts_as_questionable.rb +30 -0
  12. data/app/models/concerns/acts_as_responsable.rb +30 -0
  13. data/app/models/effective/question.rb +124 -0
  14. data/app/models/effective/question_option.rb +26 -0
  15. data/app/models/effective/response.rb +87 -0
  16. data/app/models/effective/response_option.rb +6 -0
  17. data/app/views/admin/questions/_form.html.haml +133 -0
  18. data/app/views/effective/questionable/_result.html.haml +2 -0
  19. data/app/views/effective/questionable/_results.html.haml +31 -0
  20. data/app/views/effective/questionable/results/_choose_one.html.haml +2 -0
  21. data/app/views/effective/questionable/results/_date.html.haml +14 -0
  22. data/app/views/effective/questionable/results/_email.html.haml +14 -0
  23. data/app/views/effective/questionable/results/_long_answer.html.haml +2 -0
  24. data/app/views/effective/questionable/results/_number.html.haml +14 -0
  25. data/app/views/effective/questionable/results/_question_option.html.haml +17 -0
  26. data/app/views/effective/questionable/results/_select_all_that_apply.html.haml +2 -0
  27. data/app/views/effective/questionable/results/_select_up_to_1.html.haml +2 -0
  28. data/app/views/effective/questionable/results/_select_up_to_2.html.haml +2 -0
  29. data/app/views/effective/questionable/results/_select_up_to_3.html.haml +2 -0
  30. data/app/views/effective/questionable/results/_select_up_to_4.html.haml +2 -0
  31. data/app/views/effective/questionable/results/_select_up_to_5.html.haml +2 -0
  32. data/app/views/effective/questionable/results/_short_answer.html.haml +2 -0
  33. data/app/views/effective/questionable/results/_upload_file.html.haml +3 -0
  34. data/app/views/effective/questions/_question.html.haml +6 -0
  35. data/app/views/effective/responsable/_fields.html.haml +19 -0
  36. data/app/views/effective/responsable/_results.html.haml +23 -0
  37. data/app/views/effective/responses/_fields.html.haml +15 -0
  38. data/app/views/effective/responses/_response.html.haml +2 -0
  39. data/app/views/effective/responses/fields/_choose_one.html.haml +1 -0
  40. data/app/views/effective/responses/fields/_date.html.haml +2 -0
  41. data/app/views/effective/responses/fields/_email.html.haml +2 -0
  42. data/app/views/effective/responses/fields/_long_answer.html.haml +1 -0
  43. data/app/views/effective/responses/fields/_number.html.haml +2 -0
  44. data/app/views/effective/responses/fields/_select_all_that_apply.html.haml +1 -0
  45. data/app/views/effective/responses/fields/_select_up_to_1.html.haml +1 -0
  46. data/app/views/effective/responses/fields/_select_up_to_2.html.haml +1 -0
  47. data/app/views/effective/responses/fields/_select_up_to_3.html.haml +1 -0
  48. data/app/views/effective/responses/fields/_select_up_to_4.html.haml +1 -0
  49. data/app/views/effective/responses/fields/_select_up_to_5.html.haml +1 -0
  50. data/app/views/effective/responses/fields/_short_answer.html.haml +1 -0
  51. data/app/views/effective/responses/fields/_upload_file.html.haml +2 -0
  52. data/app/views/effective/responses/responses/_choose_one.html.haml +1 -0
  53. data/app/views/effective/responses/responses/_date.html.haml +1 -0
  54. data/app/views/effective/responses/responses/_email.html.haml +1 -0
  55. data/app/views/effective/responses/responses/_long_answer.html.haml +1 -0
  56. data/app/views/effective/responses/responses/_number.html.haml +1 -0
  57. data/app/views/effective/responses/responses/_select_all_that_apply.html.haml +5 -0
  58. data/app/views/effective/responses/responses/_select_up_to_1.html.haml +1 -0
  59. data/app/views/effective/responses/responses/_select_up_to_2.html.haml +5 -0
  60. data/app/views/effective/responses/responses/_select_up_to_3.html.haml +5 -0
  61. data/app/views/effective/responses/responses/_select_up_to_4.html.haml +5 -0
  62. data/app/views/effective/responses/responses/_select_up_to_5.html.haml +5 -0
  63. data/app/views/effective/responses/responses/_short_answer.html.haml +1 -0
  64. data/app/views/effective/responses/responses/_upload_file.html.haml +4 -0
  65. data/config/effective_questions.rb +5 -0
  66. data/config/locales/effective_questions.yml +11 -0
  67. data/config/routes.rb +13 -0
  68. data/db/migrate/101_create_effective_questions.rb +60 -0
  69. data/db/seeds.rb +15 -0
  70. data/lib/effective_questions/engine.rb +19 -0
  71. data/lib/effective_questions/version.rb +3 -0
  72. data/lib/effective_questions.rb +13 -0
  73. data/lib/generators/effective_questions/install_generator.rb +28 -0
  74. data/lib/tasks/effective_questions_tasks.rake +8 -0
  75. metadata +270 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d948f3c70d98667858558ab90fc1da83bb053636c058c3684dad93f7ccdab507
4
+ data.tar.gz: 479a4faf525f5d1f31a5fcc4d2039b0d23783eb736d1cc0338a711316e7cf3bb
5
+ SHA512:
6
+ metadata.gz: af99111296b23031caa4325bc8fe7a85460d54defc6344efb7627b65d5f9d55e4f60909d9d10cdece24b041c1aad36155e03759f2f77bc6949c5a8624eb6936d
7
+ data.tar.gz: 65db1d73024c9a9457da21febc75789578e7ca28a4efd607aabd443692a017536e512e7455fc7ffabf46221d1bff6671c9a76712df78a97800d8ceb97810919f
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2026 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.
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # Effective Questions
2
+
3
+ Underlying questions and responses module for use with polls, cpd audits and more.
4
+
5
+ An admin creates an acts_as_questionable resource with one or more questions.
6
+
7
+ The responses are collected by an acts_as_responsable resource with one or more responses
8
+
9
+ Works with action_text for content bodies, and active_storage for file uploads.
10
+
11
+ ## Getting Started
12
+
13
+ This requires Rails 6 and Twitter Bootstrap 4 and just works with Devise.
14
+
15
+ Please first install the [effective_datatables](https://github.com/code-and-effect/effective_datatables) gem.
16
+
17
+ Please download and install the [Twitter Bootstrap4](http://getbootstrap.com)
18
+
19
+ Add to your Gemfile:
20
+
21
+ ```ruby
22
+ gem 'haml'
23
+ gem 'effective_questions'
24
+ ```
25
+
26
+ Run the bundle command to install it:
27
+
28
+ ```console
29
+ bundle install
30
+ ```
31
+
32
+ Then run the generator:
33
+
34
+ ```ruby
35
+ rails generate effective_questions:install
36
+ ```
37
+
38
+ The generator will install an initializer which describes all configuration options and creates a database migration.
39
+
40
+ If you want to tweak the table names, manually adjust both the configuration file and the migration now.
41
+
42
+ Then migrate the database:
43
+
44
+ ```ruby
45
+ rake db:migrate
46
+ ```
47
+
48
+ Set up your permissions:
49
+
50
+ ```ruby
51
+ # Regular signed up user. Guest users not supported.
52
+ if user.persisted?
53
+ end
54
+
55
+ if user.admin?
56
+ end
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ You can render the results with ``.
62
+
63
+ ## Authorization
64
+
65
+ All authorization checks are handled via the config.authorization_method found in the `app/config/initializers/effective_questions.rb` file.
66
+
67
+ It is intended for flow through to CanCan or Pundit, but neither of those gems are required.
68
+
69
+ This method is called by all controller actions with the appropriate action and resource
70
+
71
+ Action will be one of [:index, :show, :new, :create, :edit, :update, :destroy]
72
+
73
+ Resource will the appropriate object or class
74
+
75
+ The authorization method is defined in the initializer file:
76
+
77
+ ```ruby
78
+ # As a Proc (with CanCan)
79
+ config.authorization_method = Proc.new { |controller, action, resource| authorize!(action, resource) }
80
+ ```
81
+
82
+ ```ruby
83
+ # As a Custom Method
84
+ config.authorization_method = :my_authorization_method
85
+ ```
86
+
87
+ and then in your application_controller.rb:
88
+
89
+ ```ruby
90
+ def my_authorization_method(action, resource)
91
+ current_user.is?(:admin) || EffectivePunditPolicy.new(current_user, resource).send('#{action}?')
92
+ end
93
+ ```
94
+
95
+ or disabled entirely:
96
+
97
+ ```ruby
98
+ config.authorization_method = false
99
+ ```
100
+
101
+ If the method or proc returns false (user is not authorized) an Effective::AccessDenied exception will be raised
102
+
103
+ You can rescue from this exception by adding the following to your application_controller.rb:
104
+
105
+ ```ruby
106
+ rescue_from Effective::AccessDenied do |exception|
107
+ respond_to do |format|
108
+ format.html { render 'static_pages/access_denied', status: 403 }
109
+ format.any { render text: 'Access Denied', status: 403 }
110
+ end
111
+ end
112
+ ```
113
+
114
+ ## License
115
+
116
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
117
+
118
+ ## Testing
119
+
120
+ Run tests by:
121
+
122
+ ```ruby
123
+ rails test
124
+ ```
125
+
126
+ ## Contributing
127
+
128
+ 1. Fork it
129
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
130
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
131
+ 4. Push to the branch (`git push origin my-new-feature`)
132
+ 5. Bonus points for test coverage
133
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
9
+
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets .css
File without changes
@@ -0,0 +1,13 @@
1
+ module Admin
2
+ class QuestionsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_questions) }
5
+
6
+ include Effective::CrudController
7
+
8
+ def permitted_params
9
+ params.require(:effective_question).permit!
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ class Admin::EffectiveQuestionableResultsDatatable < Effective::Datatable
2
+ datatable do
3
+ col :responsable
4
+
5
+ col :position, visible: false
6
+ col :category, search: Effective::Question::CATEGORIES, visible: false
7
+
8
+ col :question, search: questionable.questions.pluck(:title).sort
9
+ col :responses
10
+ end
11
+
12
+ collection do
13
+ questionable.completed_responses.flat_map do |response|
14
+ rows = if response.question.question_option?
15
+ response.question_options.map do |question_option|
16
+ [
17
+ response.responsable.try(:token) || response.responsable_id,
18
+ response.question.position,
19
+ response.question.category,
20
+ response.question.to_s,
21
+ question_option.to_s
22
+ ]
23
+ end
24
+ elsif response.response.present?
25
+ [
26
+ [
27
+ response.responsable.try(:token) || response.responsable_id,
28
+ response.question.position,
29
+ response.question.category,
30
+ response.question.to_s,
31
+ response.response.to_s
32
+ ]
33
+ ]
34
+ else
35
+ []
36
+ end
37
+ end
38
+ end
39
+
40
+ def questionable
41
+ @questionable ||= (attributes[:questionable_type].constantize).where(id: attributes[:questionable_id]).first!
42
+ end
43
+
44
+ end
@@ -0,0 +1,45 @@
1
+ class Admin::EffectiveQuestionsDatatable < 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
+ if attributes[:follow_up]
10
+ col :show_if_value_to_s, label: 'When answered with'
11
+ else
12
+ col :questionable
13
+ end
14
+
15
+ col :position, visible: false do |question|
16
+ question.position.to_i + 1
17
+ end
18
+
19
+ col :title
20
+ col :body, as: :text, visible: !attributes[:follow_up]
21
+ col :required
22
+
23
+ col :category, label: 'Type'
24
+ col :question_options, label: 'Options'
25
+
26
+ unless attributes[:follow_up]
27
+ col :follow_up_questions, action: false, label: 'Follow up questions'
28
+ end
29
+
30
+ actions_col
31
+ end
32
+
33
+ collection do
34
+ scope = Effective::Question.all.deep
35
+
36
+ if attributes[:follow_up]
37
+ scope = scope.where(follow_up: true, question_id: attributes[:question_id])
38
+ else
39
+ scope = scope.where(question_id: nil)
40
+ end
41
+
42
+ scope
43
+ end
44
+
45
+ end
@@ -0,0 +1,2 @@
1
+ module EffectiveQuestionsHelper
2
+ end
@@ -0,0 +1,30 @@
1
+ #
2
+ # ActsAsQuestionable
3
+ #
4
+ module ActsAsQuestionable
5
+ extend ActiveSupport::Concern
6
+
7
+ module Base
8
+ def acts_as_questionable(options = nil)
9
+ include ::ActsAsQuestionable
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def acts_as_questionable?; true; end
15
+ end
16
+
17
+ included do
18
+ has_many :questions, -> { order(:position) }, class_name: 'Effective::Question', as: :questionable, dependent: :destroy
19
+ accepts_nested_attributes_for :questions, allow_destroy: true
20
+
21
+ has_many :responses, -> { order(:id) }, class_name: 'Effective::Response', as: :questionable, dependent: :destroy
22
+ accepts_nested_attributes_for :responses, allow_destroy: true
23
+ end
24
+
25
+ def completed_responses(question: nil)
26
+ return responses.select(&:completed?) if question.nil?
27
+ responses.select { |response| response.completed? && response.question_id == question.id }
28
+ end
29
+
30
+ end
@@ -0,0 +1,30 @@
1
+ #
2
+ # ActsAsResponsable
3
+ #
4
+ module ActsAsResponsable
5
+ extend ActiveSupport::Concern
6
+
7
+ module Base
8
+ def acts_as_responsable(options = nil)
9
+ include ::ActsAsResponsable
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def acts_as_responsable?; true; end
15
+ end
16
+
17
+ included do
18
+ has_many :responses, class_name: 'Effective::Response', as: :responsable, dependent: :destroy
19
+ accepts_nested_attributes_for :responses, allow_destroy: true
20
+
21
+ validates :responses, associated: true
22
+ end
23
+
24
+ # Find or build
25
+ def response(question:)
26
+ response = responses.find { |r| r.question_id == question.id }
27
+ response ||= responses.build(questionable: question.questionable, question: question)
28
+ end
29
+
30
+ end
@@ -0,0 +1,124 @@
1
+ module Effective
2
+ class Question < ActiveRecord::Base
3
+ belongs_to :questionable, polymorphic: true
4
+
5
+ belongs_to :question, optional: true # Present when I'm a follow up question
6
+ belongs_to :question_option, optional: true # Might be present when I'm a follow up question
7
+
8
+ has_many :question_options, -> { order(:position) }, inverse_of: :question, dependent: :delete_all
9
+ accepts_nested_attributes_for :question_options, reject_if: :all_blank, allow_destroy: true
10
+
11
+ has_many :follow_up_questions, -> { order(:position) }, class_name: 'Effective::Question', foreign_key: :question_id, dependent: :destroy
12
+
13
+ has_rich_text :body
14
+ log_changes(to: :questionable) if respond_to?(:log_changes)
15
+
16
+ has_many :responses
17
+
18
+ CATEGORIES = [
19
+ 'Choose one', # Radios
20
+ 'Select all that apply', # Checks
21
+ 'Select up to 1',
22
+ 'Select up to 2',
23
+ 'Select up to 3',
24
+ 'Select up to 4',
25
+ 'Select up to 5',
26
+ 'Date', # Date Field
27
+ 'Email', # Email Field
28
+ 'Number', # Numeric Field
29
+ 'Long Answer', # Text Area
30
+ 'Short Answer', # Text Field
31
+ 'Upload File' # File field
32
+ ]
33
+
34
+ WITH_OPTIONS_CATEGORIES = [
35
+ 'Choose one', # Radios
36
+ 'Select all that apply', # Checks
37
+ 'Select up to 1',
38
+ 'Select up to 2',
39
+ 'Select up to 3',
40
+ 'Select up to 4',
41
+ 'Select up to 5'
42
+ ]
43
+
44
+ UNSUPPORTED_FOLLOW_UP_QUESTION_CATEGORIES = ['Upload File']
45
+
46
+ effective_resource do
47
+ title :string
48
+ category :string
49
+ required :boolean
50
+
51
+ position :integer
52
+
53
+ follow_up :boolean
54
+ follow_up_value :string
55
+
56
+ timestamps
57
+ end
58
+
59
+ before_validation(if: -> { question.present? }) do
60
+ assign_attributes(questionable: question.questionable)
61
+ end
62
+
63
+ # Set position
64
+ before_validation do
65
+ source = question_option.try(:follow_up_questions) || questionable.try(:questions) || []
66
+ self.position ||= (source.map { |obj| obj.position }.compact.max || -1) + 1
67
+ end
68
+
69
+ scope :deep, -> { with_rich_text_body_and_embeds.includes(:question_options) }
70
+ scope :sorted, -> { order(:position) }
71
+
72
+ scope :top_level, -> { where(follow_up: false) }
73
+ scope :follow_up, -> { where(follow_up: true) }
74
+
75
+ validates :title, presence: true
76
+ validates :category, presence: true, inclusion: { in: CATEGORIES }
77
+ validates :position, presence: true
78
+ validates :question_options, presence: true, if: -> { question_option? }
79
+
80
+ validates :question, presence: true, if: -> { follow_up? }
81
+ validates :question_option, presence: true, if: -> { follow_up? && question.try(:question_option?) }
82
+ validates :follow_up_value, presence: true, if: -> { follow_up? && !question.try(:question_option?) }
83
+
84
+ # Create choose_one? and select_all_that_apply? methods for each category
85
+ CATEGORIES.each do |category|
86
+ define_method(category.parameterize.underscore + '?') { self.category == category }
87
+ end
88
+
89
+ def to_s
90
+ title.presence || model_name.human
91
+ end
92
+
93
+ def show_if_attribute
94
+ return :question_option_ids if question_option?
95
+
96
+ case category
97
+ when 'Date' then :date
98
+ when 'Email' then :email
99
+ when 'Number' then :number
100
+ when 'Long Answer' then :long_answer
101
+ when 'Short Answer' then :short_answer
102
+ when 'Upload File' then :upload_file
103
+ else :unknown
104
+ end
105
+ end
106
+
107
+ def show_if_value
108
+ question.try(:question_option?) ? question_option_id : follow_up_value
109
+ end
110
+
111
+ def show_if_value_to_s
112
+ (question.try(:question_option?) ? question_option : follow_up_value).to_s
113
+ end
114
+
115
+ def question_option?
116
+ WITH_OPTIONS_CATEGORIES.include?(category)
117
+ end
118
+
119
+ def category_partial
120
+ category.to_s.parameterize.underscore
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,26 @@
1
+ module Effective
2
+ class QuestionOption < ActiveRecord::Base
3
+ belongs_to :question
4
+
5
+ effective_resource do
6
+ title :text
7
+ position :integer
8
+
9
+ timestamps
10
+ end
11
+
12
+ before_validation(if: -> { question.present? }) do
13
+ self.position ||= (question.question_options.map { |obj| obj.position }.compact.max || -1) + 1
14
+ end
15
+
16
+ scope :sorted, -> { order(:position) }
17
+
18
+ validates :title, presence: true
19
+ validates :position, presence: true
20
+
21
+ def to_s
22
+ title.presence || model_name.human
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,87 @@
1
+ module Effective
2
+ class Response < ActiveRecord::Base
3
+ belongs_to :responsable, polymorphic: true
4
+ belongs_to :questionable, polymorphic: true
5
+ belongs_to :question
6
+
7
+ has_many :response_options, dependent: :delete_all
8
+ has_many :question_options, through: :response_options
9
+
10
+ has_one_attached :upload_file
11
+
12
+ effective_resource do
13
+ # The response
14
+ date :date
15
+ email :string
16
+ number :integer
17
+ long_answer :text
18
+ short_answer :text
19
+
20
+ timestamps
21
+ end
22
+
23
+ scope :deep, -> { includes(:question, :question_options) }
24
+
25
+ validate(if: -> { questionable.present? && question.present? }) do
26
+ errors.add(:question, 'must match questionable') unless question.questionable == questionable
27
+ end
28
+
29
+ validates :date, presence: true, if: -> { question&.required? && question.date? }
30
+ validates :email, presence: true, email: true, if: -> { question&.required? && question.email? }
31
+ validates :number, presence: true, if: -> { question&.required? && question.number? }
32
+ validates :long_answer, presence: true, if: -> { question&.required? && question.long_answer? }
33
+ validates :short_answer, presence: true, if: -> { question&.required? && question.short_answer? }
34
+ validates :upload_file, presence: true, if: -> { question&.required? && question.upload_file? }
35
+ validates :question_option_ids, presence: true, if: -> { question&.required? && question.question_option? }
36
+
37
+ validates :question_option_ids, if: -> { question&.choose_one? },
38
+ length: { maximum: 1, message: 'please choose 1 option only' }
39
+
40
+ validates :question_option_ids, if: -> { question&.select_up_to_1? },
41
+ length: { maximum: 1, message: 'please select 1 option or fewer' }
42
+
43
+ validates :question_option_ids, if: -> { question&.select_up_to_2? },
44
+ length: { maximum: 2, message: 'please select 2 options or fewer' }
45
+
46
+ validates :question_option_ids, if: -> { question&.select_up_to_3? },
47
+ length: { maximum: 3, message: 'please select 3 options or fewer' }
48
+
49
+ validates :question_option_ids, if: -> { question&.select_up_to_4? },
50
+ length: { maximum: 4, message: 'please select 4 options or fewer' }
51
+
52
+ validates :question_option_ids, if: -> { question&.select_up_to_5? },
53
+ length: { maximum: 5, message: 'please select 5 options or fewer' }
54
+
55
+ def to_s
56
+ #model_name.human
57
+ response.to_s
58
+ end
59
+
60
+ def response
61
+ return nil unless question.present?
62
+
63
+ return date if question.date?
64
+ return email if question.email?
65
+ return number if question.number?
66
+ return long_answer if question.long_answer?
67
+ return short_answer if question.short_answer?
68
+ return upload_file if question.upload_file?
69
+
70
+ return question_options.first if question.choose_one?
71
+ return question_options.first if question.select_up_to_1?
72
+ return question_options if question.question_option?
73
+
74
+ raise('unknown response for unexpected question category')
75
+ end
76
+
77
+ def category_partial
78
+ question&.category_partial
79
+ end
80
+
81
+ def completed?
82
+ return false if responsable.blank?
83
+ responsable.completed?
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,6 @@
1
+ module Effective
2
+ class ResponseOption < ActiveRecord::Base
3
+ belongs_to :response
4
+ belongs_to :question_option
5
+ end
6
+ end