unique_rapidfire 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +199 -0
  3. data/Rakefile +36 -0
  4. data/app/assets/javascripts/rapidfire/application.js +12 -0
  5. data/app/assets/stylesheets/rapidfire/application.css +22 -0
  6. data/app/controllers/rapidfire/answer_groups_controller.rb +34 -0
  7. data/app/controllers/rapidfire/application_controller.rb +11 -0
  8. data/app/controllers/rapidfire/question_groups_controller.rb +48 -0
  9. data/app/controllers/rapidfire/questions_controller.rb +58 -0
  10. data/app/helpers/rapidfire/application_helper.rb +12 -0
  11. data/app/models/rapidfire/answer.rb +18 -0
  12. data/app/models/rapidfire/answer_group.rb +12 -0
  13. data/app/models/rapidfire/question.rb +44 -0
  14. data/app/models/rapidfire/question_group.rb +10 -0
  15. data/app/models/rapidfire/questions/checkbox.rb +21 -0
  16. data/app/models/rapidfire/questions/date.rb +16 -0
  17. data/app/models/rapidfire/questions/long.rb +6 -0
  18. data/app/models/rapidfire/questions/numeric.rb +21 -0
  19. data/app/models/rapidfire/questions/radio.rb +6 -0
  20. data/app/models/rapidfire/questions/select.rb +19 -0
  21. data/app/models/rapidfire/questions/short.rb +6 -0
  22. data/app/serializers/rapidfire/question_result_serializer.rb +15 -0
  23. data/app/services/rapidfire/answer_group_builder.rb +48 -0
  24. data/app/services/rapidfire/base_service.rb +21 -0
  25. data/app/services/rapidfire/question_form.rb +85 -0
  26. data/app/services/rapidfire/question_group_results.rb +29 -0
  27. data/app/services/rapidfire/question_result.rb +11 -0
  28. data/app/views/rapidfire/answer_groups/new.html.erb +11 -0
  29. data/app/views/rapidfire/answers/_checkbox.html.erb +11 -0
  30. data/app/views/rapidfire/answers/_date.html.erb +4 -0
  31. data/app/views/rapidfire/answers/_errors.html.erb +7 -0
  32. data/app/views/rapidfire/answers/_long.html.erb +4 -0
  33. data/app/views/rapidfire/answers/_numeric.html.erb +4 -0
  34. data/app/views/rapidfire/answers/_radio.html.erb +9 -0
  35. data/app/views/rapidfire/answers/_select.html.erb +4 -0
  36. data/app/views/rapidfire/answers/_short.html.erb +4 -0
  37. data/app/views/rapidfire/question_groups/_form.html.erb +15 -0
  38. data/app/views/rapidfire/question_groups/_question_group.html.erb +18 -0
  39. data/app/views/rapidfire/question_groups/index.html.erb +23 -0
  40. data/app/views/rapidfire/question_groups/new.html.erb +1 -0
  41. data/app/views/rapidfire/question_groups/results.html.erb +32 -0
  42. data/app/views/rapidfire/questions/_form.html.erb +40 -0
  43. data/app/views/rapidfire/questions/_question.html.erb +9 -0
  44. data/app/views/rapidfire/questions/edit.html.erb +1 -0
  45. data/app/views/rapidfire/questions/index.html.erb +21 -0
  46. data/app/views/rapidfire/questions/new.html.erb +1 -0
  47. data/config/database.yml +8 -0
  48. data/config/routes.rb +10 -0
  49. data/db/migrate/20130502170733_create_rapidfire_question_groups.rb +8 -0
  50. data/db/migrate/20130502195310_create_rapidfire_questions.rb +15 -0
  51. data/db/migrate/20130502195415_create_rapidfire_answer_groups.rb +12 -0
  52. data/db/migrate/20130502195504_create_rapidfire_answers.rb +13 -0
  53. data/lib/generators/rapidfire/views_generator.rb +23 -0
  54. data/lib/rapidfire.rb +6 -0
  55. data/lib/rapidfire/engine.rb +7 -0
  56. data/lib/rapidfire/version.rb +3 -0
  57. data/lib/tasks/rapidfire_tasks.rake +4 -0
  58. metadata +223 -0
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Yuva Kumar
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,199 @@
1
+ # Rapidfire
2
+ [![Code Climate](https://codeclimate.com/repos/51a70089f3ea000534070811/badges/aedc90c3b5481e7569bb/gpa.png)](https://codeclimate.com/repos/51a70089f3ea000534070811/feed)
3
+ [![Build Status](https://travis-ci.org/code-mancers/rapidfire.png?branch=master)](https://travis-ci.org/code-mancers/rapidfire)
4
+
5
+ One stop solution for all survey related requirements! Its tad easy!
6
+
7
+ This gem supports both **rails 3.2.13+** and **rails4** versions.
8
+
9
+ You can see a demo of this gem [here](https://rapidfire.herokuapp.com).
10
+ And the source code of demo [here](https://github.com/code-mancers/rapidfire-demo).
11
+
12
+ ## Installation
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'rapidfire'
16
+
17
+ And then execute:
18
+
19
+ $ bundle install
20
+ $ bundle exec rake rapidfire:install:migrations
21
+ $ bundle exec rake db:migrate
22
+
23
+ And if you want to customize rapidfire views, you can do
24
+
25
+ $ bundle exec rails generate rapidfire:views
26
+
27
+ ## Usage
28
+
29
+ Add this line to your routes will and you will be good to go!
30
+
31
+ mount Rapidfire::Engine => "/rapidfire"
32
+
33
+ And point your browser to [http://localhost:3000/rapidfire](http://localhost:3000/rapidfire)
34
+
35
+ All rapidfire controllers inherit from your `ApplicationController`. So define 2
36
+ methods `current_user` and `can_administer?` on your `ApplicationController`
37
+
38
+ 1. `current_user` : the user who is answering the survey. can be `nil`
39
+ 2. `can_administer?` : a method which determines whether current user can
40
+ create/update survey questions.
41
+
42
+ Typical implementation would be:
43
+
44
+ ```ruby
45
+ class ApplicationController < ActionController::Base
46
+ def current_user
47
+ @current_user ||= User.find(session[:user_id])
48
+ end
49
+
50
+ def can_administer?
51
+ current_user.try(:admin?)
52
+ end
53
+ end
54
+ ```
55
+
56
+ If you are using authentication gems like devise, you get `current_user` for free
57
+ and you don't have to define it.
58
+
59
+ ### Routes Information
60
+ Once this gem is mounted on, say at 'rapidfire', it generates several routes
61
+ You can see them by running `bundle exec rake routes`.
62
+
63
+ 1. The `root_path` i.e `localhost:3000/rapidfire` always points to list of
64
+ surveys {they are called question groups}. Admin can manage surveys, and
65
+ any user {who cannot administer} can see list of surveys.
66
+ 2. Optionally, each survey can by answered by visiting this path:
67
+
68
+ ```
69
+ localhost:3000/rapidfire/question_groups/<survey-id>/answer_groups/new
70
+ ```
71
+
72
+ You can distribute this url so that survey takers can answer a particular survey
73
+ of your interest.
74
+
75
+ ### Survey Results
76
+ A new api is released which helps in seeing results for each survey. The api is:
77
+
78
+ ```
79
+ GET /rapidfire/question_groups/<survey-id>/results
80
+ ```
81
+ This new api supports two formats: `html` and `json`. The `json` format is supported
82
+ so that end user can use any javascript based chart solutions and render results
83
+ in the format they pleased. An example can be seen [here](https://github.com/code-mancers/rapidfire-demo),
84
+ which uses chart.js to display results.
85
+
86
+ Diving into details of `json` format, all the questions can be categorized into
87
+ one of the two categories:
88
+ 1. **aggregatable**: questions like checkboxes, selects, radio buttons fall into
89
+ this category.
90
+ 2. **non-aggregatable**: questions like long answers, short answers, date, numeric
91
+ etc.
92
+
93
+ All the aggregatable answers will be returned in the form of hash, and the
94
+ non-aggregatable answers will be returned in the form of an array. A typical json
95
+ output will be like this:
96
+
97
+ ```json
98
+ [
99
+ {
100
+ "question_type": "Rapidfire::Questions::Radio",
101
+ "question_text": "Who is author of Waiting for godot?",
102
+ "results": {
103
+ "Sublime": 1,
104
+ "Emacs": 1,
105
+ "Vim": 1
106
+ }
107
+ },
108
+ {
109
+ "question_type": "Rapidfire::Questions::Checkbox",
110
+ "question_text": "Best rock band?",
111
+ "results": {
112
+ "Led Zeppelin": 2
113
+ }
114
+ },
115
+ {
116
+ "question_type": "Rapidfire::Questions::Date",
117
+ "question_text": "When is your birthday?",
118
+ "results": [
119
+ "04-02-1983",
120
+ "01/01/1970"
121
+ ]
122
+ },
123
+ {
124
+ "question_type": "Rapidfire::Questions::Long",
125
+ "question_text": "If Apple made a android phone what it will be called?",
126
+ "results": [
127
+ "Idude",
128
+ "apdroid"
129
+ ]
130
+ },
131
+ {
132
+ "question_type": "Rapidfire::Questions::Numeric",
133
+ "question_text": "Answer of life, universe and everything?",
134
+ "results": [
135
+ "42",
136
+ "0"
137
+ ]
138
+ },
139
+ {
140
+ "question_type": "Rapidfire::Questions::Select",
141
+ "question_text": "Places you want to visit after death",
142
+ "results": {
143
+ "Iran": 2
144
+ }
145
+ }
146
+ ]
147
+ ```
148
+
149
+ ## How it works
150
+ This gem gives you access to create questions in a groups, something similar to
151
+ survey. Once you have created a group and add questions to it, you can pass
152
+ around the form url where others can answer your questions.
153
+
154
+ The typical flow about how to use this gem is:
155
+
156
+ 1. Create a question group by giving it a name.
157
+ 2. Once group is created, you can click on the group which takes you to another
158
+ page where you can manage questions.
159
+ 3. Create a question by clicking on add new, and you will be provided by these
160
+ options: Each question will have a type
161
+ - **Checkbox** Create a question which contains multiple checkboxes with the
162
+ options that you provide in `answer options` field. Note that each option
163
+ should be on a separate line.
164
+ - **Date** It takes date as an answer
165
+ - **Long** It needs a description as answer. Renders a textarea.
166
+ - **Numeric** It takes a number as an answer
167
+ - **Radio** It renders set of radio buttons by taking answer options.
168
+ - **Select** It renders a dropdown by taking answer options.
169
+ - **Short** It takes a string as an answer. Short answer.
170
+
171
+ 4. Once the type is filled, you can optionally fill other details like
172
+ - **Question text** What is the question?
173
+ - **Answer options** Give options separated by newline for questions of type
174
+ checkbox, radio buttons or select.
175
+ - **Answer presence** Should you mandate answering this question?
176
+ - **min and max length** Checks whether answer if in between min and max length.
177
+ Ignores if blank.
178
+ - **greater than and less than** Applicable for numeric question where answer
179
+ is validated with these values.
180
+
181
+ 5. Once the questions are populated, you can return to root_path ie by clicking
182
+ `Question Groups` and share distribute answer url so that others can answer
183
+ the questions populated.
184
+ 6. Note that answers fail to persist of the criteria that you have provided while
185
+ creating questions fail.
186
+
187
+
188
+ ## TODO
189
+ 1. Add ability to sort questions, so that order is preserved.
190
+ 2. Add multi tenant support.
191
+ 3. Rename question-groups to surveys, and change routes accordingly.
192
+
193
+ ## Contributing
194
+
195
+ 1. Fork it
196
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
197
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
198
+ 4. Push to the branch (`git push origin my-new-feature`)
199
+ 5. Create new Pull Request
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Rapidfire'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ desc 'Default: run rspec tests.'
31
+ task :default => :rspec
32
+
33
+ desc 'Run rspec unit and integration tests'
34
+ task :rspec do |t|
35
+ exec('bundle exec rspec spec')
36
+ end
@@ -0,0 +1,12 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
@@ -0,0 +1,22 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ */
13
+
14
+ .horizontal-list {
15
+ list-style: none;
16
+ }
17
+
18
+ .horizontal-list > li {
19
+ float: left;
20
+ display: inline;
21
+ margin: 0px 5px;
22
+ }
@@ -0,0 +1,34 @@
1
+ module Rapidfire
2
+ class AnswerGroupsController < Rapidfire::ApplicationController
3
+ before_filter :find_question_group!
4
+
5
+ def new
6
+ @answer_group_builder = AnswerGroupBuilder.new(answer_group_params)
7
+ end
8
+
9
+ def create
10
+ @answer_group_builder = AnswerGroupBuilder.new(answer_group_params)
11
+
12
+ respond_to do |format|
13
+ if @answer_group_builder.save
14
+ format.html { redirect_to question_groups_path, notice: 'Survey was successfully created.' }
15
+ format.json { render json: @answer_group_builder, status: :created, location: @answer_group_builder }
16
+ else
17
+ @answer_group_builder.errors.add(:base, "already answered")
18
+ format.html { render "new" }
19
+ format.json { render json: @answer_group_builder.errors, status: :unprocessable_entity }
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+ def find_question_group!
26
+ @question_group = QuestionGroup.find(params[:question_group_id])
27
+ end
28
+
29
+ def answer_group_params
30
+ answer_params = { params: params[:answer_group] }
31
+ answer_params.merge(user: current_user, question_group: @question_group, unique_id: request.env["action_dispatch.secret_key_base"] )
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,11 @@
1
+ module Rapidfire
2
+ class ApplicationController < ::ApplicationController
3
+ helper_method :can_administer?
4
+
5
+ def authenticate_administrator!
6
+ unless can_administer?
7
+ raise Rapidfire::AccessDenied.new("cannot administer questions")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,48 @@
1
+ module Rapidfire
2
+ class QuestionGroupsController < Rapidfire::ApplicationController
3
+ before_filter :authenticate_administrator!, except: :index
4
+ respond_to :html, :js
5
+ respond_to :json, only: :results
6
+
7
+ def index
8
+ @question_groups = QuestionGroup.all
9
+ respond_with(@question_groups)
10
+ end
11
+
12
+ def new
13
+ @question_group = QuestionGroup.new
14
+ respond_with(@question_group)
15
+ end
16
+
17
+ def create
18
+ @question_group = QuestionGroup.new(question_group_params)
19
+ @question_group.save
20
+
21
+ respond_with(@question_group, location: rapidfire.question_groups_url)
22
+ end
23
+
24
+ def destroy
25
+ @question_group = QuestionGroup.find(params[:id])
26
+ @question_group.destroy
27
+
28
+ respond_with(@question_group)
29
+ end
30
+
31
+ def results
32
+ @question_group = QuestionGroup.find(params[:id])
33
+ @question_group_results =
34
+ QuestionGroupResults.new(question_group: @question_group).extract
35
+
36
+ respond_with(@question_group_results, root: false)
37
+ end
38
+
39
+ private
40
+ def question_group_params
41
+ if Rails::VERSION::MAJOR == 4
42
+ params.require(:question_group).permit(:name)
43
+ else
44
+ params[:question_group]
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ module Rapidfire
2
+ class QuestionsController < Rapidfire::ApplicationController
3
+ before_filter :authenticate_administrator!
4
+ respond_to :html, :js
5
+
6
+ before_filter :find_question_group!
7
+ before_filter :find_question!, :only => [:edit, :update, :destroy]
8
+
9
+ def index
10
+ @questions = @question_group.questions
11
+ respond_with(@questions)
12
+ end
13
+
14
+ def new
15
+ @question = QuestionForm.new(:question_group => @question_group)
16
+ respond_with(@question)
17
+ end
18
+
19
+ def create
20
+ form_params = params[:question].merge(:question_group => @question_group)
21
+ @question = QuestionForm.new(form_params)
22
+ @question.save
23
+
24
+ respond_with(@question, location: index_location)
25
+ end
26
+
27
+ def edit
28
+ @question = QuestionForm.new(:question => @question)
29
+ respond_with(@question)
30
+ end
31
+
32
+ def update
33
+ form_params = params[:question].merge(:question => @question)
34
+ @question = QuestionForm.new(form_params)
35
+ @question.save
36
+
37
+ respond_with(@question, location: index_location)
38
+ end
39
+
40
+ def destroy
41
+ @question.destroy
42
+ respond_with(@question, location: index_location)
43
+ end
44
+
45
+ private
46
+ def find_question_group!
47
+ @question_group = QuestionGroup.find(params[:question_group_id])
48
+ end
49
+
50
+ def find_question!
51
+ @question = @question_group.questions.find(params[:id])
52
+ end
53
+
54
+ def index_location
55
+ rapidfire.question_group_questions_url(@question_group)
56
+ end
57
+ end
58
+ end