ask 0.4.1 → 0.5.0

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 (78) hide show
  1. data/README.markdown +14 -0
  2. data/Rakefile +1 -1
  3. data/app/assets/javascripts/ask.js.coffee +7 -3
  4. data/app/assets/stylesheets/ask.css.scss +1 -1
  5. data/app/models/answer.rb +20 -2
  6. data/app/models/question.rb +6 -1
  7. data/app/models/upload_question.rb +7 -0
  8. data/app/uploaders/uploaded_file_uploader.rb +55 -0
  9. data/app/views/answerer/_answers.html.erb +22 -0
  10. data/app/views/answerer/_form.html.erb +34 -0
  11. data/app/views/asker/_choice_fields.html.erb +7 -0
  12. data/app/views/asker/_choices.html.erb +27 -0
  13. data/app/views/asker/_form.html.erb +7 -0
  14. data/app/views/asker/_form_preview.html.erb +11 -0
  15. data/app/views/asker/_question_fields.html.erb +46 -0
  16. data/app/views/questions/_checklist_question.html.erb +5 -0
  17. data/app/views/questions/_choose_one_question.html.erb +10 -0
  18. data/app/views/questions/_essay_question.html.erb +1 -0
  19. data/app/views/questions/_form_section.erb +4 -0
  20. data/app/views/questions/_label.html.erb +4 -0
  21. data/app/views/questions/_text_question.html.erb +1 -0
  22. data/app/views/questions/_upload_question.html.erb +6 -0
  23. data/db/migrate/20130202010401_add_upload_field_to_answers.rb +5 -0
  24. data/lib/ask.rb +10 -0
  25. data/lib/ask/acts_as_answerer.rb +4 -3
  26. data/lib/ask/configuration.rb +9 -0
  27. data/lib/ask/version.rb +1 -1
  28. metadata +50 -109
  29. data/app/views/answerer/_answers.html.haml +0 -11
  30. data/app/views/answerer/_form.html.haml +0 -37
  31. data/app/views/asker/_choice_fields.html.haml +0 -6
  32. data/app/views/asker/_choices.html.haml +0 -17
  33. data/app/views/asker/_form.html.haml +0 -5
  34. data/app/views/asker/_form_preview.html.haml +0 -35
  35. data/app/views/asker/_question_fields.html.haml +0 -33
  36. data/test/ask_test.rb +0 -7
  37. data/test/dummy/Rakefile +0 -7
  38. data/test/dummy/app/assets/javascripts/application.js +0 -9
  39. data/test/dummy/app/assets/stylesheets/application.css +0 -7
  40. data/test/dummy/app/controllers/application_controller.rb +0 -3
  41. data/test/dummy/app/helpers/application_helper.rb +0 -2
  42. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  43. data/test/dummy/config.ru +0 -4
  44. data/test/dummy/config/application.rb +0 -45
  45. data/test/dummy/config/boot.rb +0 -10
  46. data/test/dummy/config/database.yml +0 -25
  47. data/test/dummy/config/environment.rb +0 -5
  48. data/test/dummy/config/environments/development.rb +0 -30
  49. data/test/dummy/config/environments/production.rb +0 -60
  50. data/test/dummy/config/environments/test.rb +0 -42
  51. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  52. data/test/dummy/config/initializers/inflections.rb +0 -10
  53. data/test/dummy/config/initializers/mime_types.rb +0 -5
  54. data/test/dummy/config/initializers/secret_token.rb +0 -7
  55. data/test/dummy/config/initializers/session_store.rb +0 -8
  56. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  57. data/test/dummy/config/locales/en.yml +0 -5
  58. data/test/dummy/config/routes.rb +0 -58
  59. data/test/dummy/public/404.html +0 -26
  60. data/test/dummy/public/422.html +0 -26
  61. data/test/dummy/public/500.html +0 -26
  62. data/test/dummy/public/favicon.ico +0 -0
  63. data/test/dummy/script/rails +0 -6
  64. data/test/fixtures/answers.yml +0 -13
  65. data/test/fixtures/choices.yml +0 -9
  66. data/test/fixtures/questions.yml +0 -17
  67. data/test/fixtures/submissions.yml +0 -9
  68. data/test/functional/answers_controller_test.rb +0 -49
  69. data/test/functional/choices_controller_test.rb +0 -49
  70. data/test/functional/questions_controller_test.rb +0 -49
  71. data/test/integration/navigation_test.rb +0 -10
  72. data/test/test_helper.rb +0 -10
  73. data/test/unit/answer_test.rb +0 -7
  74. data/test/unit/choice_test.rb +0 -7
  75. data/test/unit/helpers/answers_helper_test.rb +0 -4
  76. data/test/unit/helpers/choices_helper_test.rb +0 -4
  77. data/test/unit/helpers/questions_helper_test.rb +0 -4
  78. data/test/unit/question_test.rb +0 -7
@@ -7,6 +7,10 @@ Requirements
7
7
  - Rails 3.1 or higher
8
8
  - jQuery
9
9
  - jQuery UI
10
+ - Uses Carrierwave for uploads
11
+
12
+ More information will be on the [wiki](https://github.com/biola/ask/wiki).
13
+
10
14
 
11
15
  Installation
12
16
  ------------
@@ -76,6 +80,16 @@ Finally you'll need to define an `asker` method on your answerer model, like so:
76
80
  end
77
81
  end
78
82
 
83
+
84
+ Configuration
85
+ -------------
86
+ If you want to changed the default whitelisted file types for upload questions, just drop this into an initializer file.
87
+
88
+ Ask.configure do |config|
89
+ @upload_whitelist = %w(jpg jpeg gif png doc docx txt pdf xls xlsx zip)
90
+ end
91
+
92
+
79
93
  License
80
94
  -------
81
95
  MIT License
data/Rakefile CHANGED
@@ -20,7 +20,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
20
20
  rdoc.rdoc_files.include('lib/**/*.rb')
21
21
  end
22
22
 
23
- APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
24
  load 'rails/tasks/engine.rake'
25
25
 
26
26
 
@@ -34,9 +34,10 @@ jQuery ->
34
34
  question.find('.required-option').hide()
35
35
 
36
36
  # Update preview type whenever the user changes the type of this question
37
+ $('form').on "change", ".select-question-type select", ->
38
+ updateChoiceVisibility $(this)
39
+
37
40
  bindTypeChange = (select) ->
38
- select.live "change", ->
39
- updateChoiceVisibility $(this)
40
41
 
41
42
  # Update once when the DOM first loads
42
43
  selects = $(".select-question-type select")
@@ -82,6 +83,9 @@ jQuery ->
82
83
  when "EssayQuestion"
83
84
  preview_section.append(q_label).append("<textarea></textarea>")
84
85
  preview_section.append(q_help_text) if q_help_text
86
+ when "UploadQuestion"
87
+ preview_section.append(q_label).append("<input type='file'>")
88
+ preview_section.append(q_help_text) if q_help_text
85
89
  when "ChooseOneQuestion"
86
90
  preview_section.append(q_label)
87
91
  preview_section.append(q_help_text) if q_help_text
@@ -122,7 +126,7 @@ jQuery ->
122
126
  $('fieldset.question').each ->
123
127
  createQuestionPreview $(this)
124
128
 
125
- $('a.edit-question-link').live "click", (e) ->
129
+ $('form').on "click", "a.edit-question-link", (e) ->
126
130
  toggleEditMode($(this).closest('fieldset.question'))
127
131
  e.preventDefault()
128
132
 
@@ -17,7 +17,7 @@
17
17
  &.preview { // hide edit fields when in preview mode
18
18
  .edit-question-fields { display: none; }
19
19
  input[type="checkbox"], input[type="radio"] { margin: 0 8px 4px; }
20
- label.preview-question-name { font-weight: bold; }
20
+ label.preview-question-name { font-weight: bold; display: block; }
21
21
  h3 { margin-bottom: 0; }
22
22
  hr { margin-bottom: 0; border-top: 1px solid #EEE; border-bottom: none; }
23
23
  .help-block { color: #595959; }
@@ -1,6 +1,6 @@
1
1
  class Answer < ActiveRecord::Base
2
2
 
3
- attr_accessible :question_id, :answer, :choice_id
3
+ attr_accessible :question_id, :answer, :choice_id, :uploaded_file, :uploaded_file_cache
4
4
 
5
5
  belongs_to :answerer, :polymorphic => true
6
6
  belongs_to :question
@@ -8,12 +8,30 @@ class Answer < ActiveRecord::Base
8
8
 
9
9
  validates_presence_of :question_id
10
10
 
11
+ mount_uploader :uploaded_file, UploadedFileUploader
12
+
11
13
  default_scope joins(:question).order('questions.position')
12
14
  scope :for_answerer, lambda{|answerer| where(:answerer_type => answerer.class.to_s, :answerer_id => answerer.id)}
13
15
  scope :for_question, lambda{|question| where(:question_id => question.id)}
14
16
 
15
17
  def to_s
16
- self.answer.to_s
18
+ if question.supports_uploads?
19
+ self.uploaded_file.url
20
+ else
21
+ self.answer.to_s
22
+ end
23
+ end
24
+
25
+ # This method is used to see if the answer fails to meat "required" status if the question is required.
26
+ # should return true on fail and nil/false on success.
27
+ def fails_required?
28
+ if question.required
29
+ if question.supports_uploads?
30
+ uploaded_file.blank?
31
+ else
32
+ answer.blank?
33
+ end
34
+ end
17
35
  end
18
36
 
19
37
  end
@@ -2,7 +2,7 @@ class Question < ActiveRecord::Base
2
2
 
3
3
  require 'acts_as_list'
4
4
 
5
- TYPES = ['TextQuestion', 'EssayQuestion', 'ChooseOneQuestion', 'ChecklistQuestion', 'FormSection']
5
+ TYPES = ['TextQuestion', 'EssayQuestion', 'ChooseOneQuestion', 'ChecklistQuestion', 'UploadQuestion', 'FormSection']
6
6
 
7
7
  belongs_to :asker, :polymorphic => true
8
8
  has_many :choices, :dependent => :destroy
@@ -25,6 +25,7 @@ class Question < ActiveRecord::Base
25
25
  default
26
26
  end
27
27
 
28
+ # This just means it doesn't expect an answer, such as a form_section question
28
29
  def rhetorical?
29
30
  false
30
31
  end
@@ -42,4 +43,8 @@ class Question < ActiveRecord::Base
42
43
  false
43
44
  end
44
45
 
46
+ def supports_uploads?
47
+ false
48
+ end
49
+
45
50
  end
@@ -0,0 +1,7 @@
1
+ class UploadQuestion < Question
2
+
3
+ def supports_uploads?
4
+ true
5
+ end
6
+
7
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ class UploadedFileUploader < CarrierWave::Uploader::Base
4
+
5
+ # Include RMagick or MiniMagick support:
6
+ # include CarrierWave::RMagick
7
+ # include CarrierWave::MiniMagick
8
+
9
+ # Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
10
+ # include Sprockets::Helpers::RailsHelper
11
+ # include Sprockets::Helpers::IsolatedHelper
12
+
13
+ # Choose what kind of storage to use for this uploader:
14
+ storage :file
15
+ # storage :fog
16
+
17
+ # Override the directory where uploaded files will be stored.
18
+ # This is a sensible default for uploaders that are meant to be mounted:
19
+ def store_dir
20
+ "file_uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
21
+ end
22
+
23
+ # Provide a default URL as a default if there hasn't been a file uploaded:
24
+ # def default_url
25
+ # # For Rails 3.1+ asset pipeline compatibility:
26
+ # # asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
27
+ #
28
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
29
+ # end
30
+
31
+ # Process files as they are uploaded:
32
+ # process :scale => [200, 300]
33
+ #
34
+ # def scale(width, height)
35
+ # # do something
36
+ # end
37
+
38
+ # Create different versions of your uploaded files:
39
+ # version :thumb do
40
+ # process :scale => [50, 50]
41
+ # end
42
+
43
+ # Add a white list of extensions which are allowed to be uploaded.
44
+ # For images you might use something like this:
45
+ def extension_white_list
46
+ Ask.config.upload_whitelist
47
+ end
48
+
49
+ # Override the filename of the uploaded files:
50
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
51
+ # def filename
52
+ # "something.jpg" if original_filename
53
+ # end
54
+
55
+ end
@@ -0,0 +1,22 @@
1
+ <% raise 'You must pass in an "answerer" to use this partial' unless defined?(answerer) %>
2
+
3
+ <% answerer.questions_by_section.each do |section, questions| %>
4
+ <% unless section.nil? %>
5
+ <h3><%= section.name %></h3>
6
+ <p><%= section.instructions %></p>
7
+ <% end %>
8
+ <dl>
9
+ <% questions.each do |question| %>
10
+ <dt><%= question.name %></dt>
11
+ <% answerer.answers_to(question).each do |answer| %>
12
+ <dd>
13
+ <% if answer.is_a?(Answer) && answer.question.supports_uploads? && !answer.uploaded_file.blank? %>
14
+ <%= link_to answer.to_s, answer.uploaded_file.url, target: "_blank" %>
15
+ <% else %>
16
+ <%= simple_format(answer.to_s) %>
17
+ <% end %>
18
+ </dd>
19
+ <% end %>
20
+ <% end %>
21
+ </dl>
22
+ <% end %>
@@ -0,0 +1,34 @@
1
+ <% @prev_question_id == nil %>
2
+ <%= f.fields_for(:answers) do |answer| %>
3
+
4
+ <% question = answer.object.question %>
5
+ <%= answer.hidden_field(:question_id, value: question.id) %>
6
+
7
+ <% if question.rhetorical? # aka, it does not need to be answered %>
8
+ <% case(question.type.to_sym) %>
9
+ <% when :FormSection %>
10
+ <%= render 'questions/form_section', answer: answer, question: question %>
11
+ <% end %>
12
+
13
+ <% else %>
14
+ <% if question.supports_multiple_answers? && (@prev_question_id == question.id) %>
15
+ <% # Don't redisplay the label again for each checklist choice, just display it the first time %>
16
+ <% else %>
17
+ <%= render 'questions/label', answer: answer, question: question %>
18
+ <% @prev_question_id = question.id %>
19
+ <% end %>
20
+
21
+ <% case(question.type.to_sym) %>
22
+ <% when :TextQuestion %>
23
+ <%= render 'questions/text_question', answer: answer, question: question %>
24
+ <% when :EssayQuestion %>
25
+ <%= render 'questions/essay_question', answer: answer, question: question %>
26
+ <% when :UploadQuestion %>
27
+ <%= render 'questions/upload_question', answer: answer, question: question %>
28
+ <% when :ChooseOneQuestion %>
29
+ <%= render 'questions/choose_one_question', answer: answer, question: question %>
30
+ <% when :ChecklistQuestion %>
31
+ <%= render 'questions/checklist_question', answer: answer, question: question %>
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <fieldset class = "choice">
2
+ <input type = "radio" disabled = "disabled" class = "radio-preview"></input>
3
+ <input type = "checkbox" disabled = "disabled" class = "checkbox-preview"></input>
4
+ <%= f.text_field :name, :placeholder=>'Choice' %>
5
+ <%= f.hidden_field :_destroy %>
6
+ <%= link_to "remove", '#', class: "remove_fields" %>
7
+ </fieldset>
@@ -0,0 +1,27 @@
1
+ <div class = "question-type TextQuestion">
2
+ <input type = "text" disabled = "disabled" name = "undefined" value = "Their answer" class = "askwidget-disabledpreview"></input>
3
+ </div>
4
+
5
+ <div class = "question-type EssayQuestion start-hidden">
6
+ <textarea disabled = "disabled" name = "undefined" type = "text" value = "Their answer" class = "askwidget-disabledpreview"></textarea>
7
+ </div>
8
+
9
+ <div class = "question-type UploadQuestion">
10
+ <input type = "file" disabled = "disabled" name = "undefined" value = "Their answer" class = "askwidget-disabledpreview"></input>
11
+ </div>
12
+
13
+ <div class = "question-type ChooseOneQuestion ChecklistQuestion start-hidden">
14
+ <%= f.fields_for(:choices) do |choice| %>
15
+ <%= render 'asker/choice_fields', f: choice %>
16
+ <% end %>
17
+
18
+ <%= link_to_add_fields "Add Choice", f, :choices %>
19
+ </div>
20
+
21
+ <div class = "question-type FormSection start-hidden">
22
+ <h4> Name of section </h4>
23
+ <p>
24
+ This will provide a divider between form sections
25
+ </p>
26
+ </div>
27
+
@@ -0,0 +1,7 @@
1
+ <div class = "ask questions">
2
+ <%= f.fields_for(:questions) do |f| %>
3
+ <%= render 'asker/question_fields', f: f %>
4
+ <% end %>
5
+
6
+ <%= link_to_add_fields "Add Question", f, :questions %>
7
+ </div>
@@ -0,0 +1,11 @@
1
+ <% raise 'You must pass in a "answerer" to use this partial' unless defined?(answerer) %>
2
+
3
+ <%= form_for answerer, url: "#", html: {class: "form-horizontal form-preview"} do |f| %>
4
+ <fieldset>
5
+ <% # This partial is loaded from the ask gem %>
6
+ <%= render(partial: 'answerer/form', locals: { f: f }) %>
7
+ </fieldset>
8
+ <div class="form-actions">
9
+ <%= f.submit 'Submit Form', class: "btn btn-primary disabled", disabled: :disabled %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,46 @@
1
+ <fieldset class = "question preview">
2
+ <div class = "question-action-links">
3
+ <%= link_to 'Edit', '#', class: 'edit-question-link' %>
4
+ |
5
+ <%= f.hidden_field :_destroy %>
6
+ <%= link_to "Remove", '#', class: "remove_fields" %>
7
+ </div>
8
+ <div class = "hidden position-wrapper">
9
+ <%= f.hidden_field(:position) %>
10
+ </div>
11
+ <div class = "edit-question-fields">
12
+ <table class = "question-details">
13
+ <tbody>
14
+ <tr>
15
+ <th>
16
+ <span class = "askwidget-fieldlabel"><%= f.label(:name) %></span>
17
+ </th>
18
+ <td><%= f.text_field :name, class: 'askwidget-inputfield' %></td>
19
+ </tr>
20
+ <tr>
21
+ <th>
22
+ <span class = "askwidget-fieldlabel"><%= f.label(:instructions, 'Help Text') %></span>
23
+ </th>
24
+ <td><%= f.text_field :instructions, class: 'askwidget-inputfield' %></td>
25
+ </tr>
26
+ <tr class = "select-question-type">
27
+ <th>
28
+ <span class = "askwidget-fieldlabel"><%= f.label(:type) %></span>
29
+ </th>
30
+ <td><%= f.collection_select :type, Question::TYPES, :to_s, :titleize, class: 'askwidget-inputfield' %></td>
31
+ </table>
32
+
33
+
34
+ <div class = "edit-question-choices">
35
+ <%= render 'asker/choices', f: f %>
36
+ </div>
37
+
38
+ <div class = "askwidget-buttons">
39
+ <%= link_to "Done", "#", class: 'edit-question-link btn' %>
40
+ <div class = "required-option">
41
+ <%= f.check_box :required, class: 'askwidget-inputfield' %>
42
+ <span class = "askwidget-fieldlabel"><%= f.label(:required, "Make this a required question") %></span>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </fieldset>
@@ -0,0 +1,5 @@
1
+ <dd>
2
+ <%= answer.hidden_field(:choice_id, :value=>answer.object.choice_id) %>
3
+ <%= answer.check_box(:answer, {}, answer.object.choice.try(:name), '') %>
4
+ <%= answer.label(:answer, answer.object.choice.try(:name)) %>
5
+ </dd>
@@ -0,0 +1,10 @@
1
+ <% if question.choices.count > 5 %>
2
+ <dd><%= answer.collection_select(:answer, question.choice_names, :to_s, :to_s, :include_blank=>!question.required)%></dd>
3
+ <% else %>
4
+ <% question.choices.each do |choice| %>
5
+ <dd>
6
+ <%=answer.radio_button(:answer, choice.name) %>
7
+ <%=answer.label(:answer, choice.name, :value=>choice.name) %>
8
+ </dd>
9
+ <% end %>
10
+ <% end %>
@@ -0,0 +1 @@
1
+ <dd><%= answer.text_area(:answer) %></dd>
@@ -0,0 +1,4 @@
1
+ <h3 class="section"><%= question.name %></h3>
2
+ <% unless question.instructions.blank? %>
3
+ <p class="section instructions"><%= question.instructions %></p>
4
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <dt class="<%= question.required?? 'required' : nil %>">
2
+ <%= question_label(question, answer) %>
3
+ <%= question_instructions(question) %>
4
+ </dt>
@@ -0,0 +1 @@
1
+ <dd><%= answer.text_field(:answer) %></dd>
@@ -0,0 +1,6 @@
1
+ <dd>
2
+ <%= answer.file_field(:uploaded_file) %>
3
+ <%= answer.hidden_field :uploaded_file_cache %>
4
+ <br>
5
+ <%= answer.object.uploaded_file.to_s unless answer.object.uploaded_file.nil? %>
6
+ </dd>
@@ -0,0 +1,5 @@
1
+ class AddUploadFieldToAnswers < ActiveRecord::Migration
2
+ def change
3
+ add_column :answers, :uploaded_file, :string
4
+ end
5
+ end
data/lib/ask.rb CHANGED
@@ -1,6 +1,16 @@
1
1
  require "ask/engine"
2
2
  require "ask/acts_as_asker"
3
3
  require "ask/acts_as_answerer"
4
+ require "carrierwave"
4
5
 
5
6
  module Ask
7
+ require 'ask/configuration'
8
+
9
+ def self.configure
10
+ yield config
11
+ end
12
+
13
+ def self.config
14
+ @config ||= Configuration.new
15
+ end
6
16
  end
@@ -76,11 +76,11 @@ module Ask
76
76
 
77
77
  # Set the correct order and make sure we have all the questions
78
78
  all_questions.each do |q|
79
- qa[q.name] = []
79
+ qa[q.id] = []
80
80
  end
81
81
 
82
82
  answers.each do |a|
83
- qa[a.question.name] << a.answer.to_s.strip unless a.answer.blank?
83
+ qa[a.question.id] << a.answer.to_s.strip unless a.answer.blank?
84
84
  end
85
85
 
86
86
  qa
@@ -89,8 +89,9 @@ module Ask
89
89
  private
90
90
 
91
91
  def validate_required_questions
92
+ return if asker.blank?
92
93
  asker.questions.required.each do |question|
93
- if answers.select{|a| a.question_id == question.id}.all?{|a| a.answer.blank?}
94
+ if answers.select{|a| a.question_id == question.id}.all?{|a| a.fails_required?}
94
95
  errors[:base] << "\"#{question.name}\" is required"
95
96
  end
96
97
  end