ask 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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