├── .gitignore
├── .rubocop.yml
├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app
└── models
│ └── survey
│ ├── answer.rb
│ ├── attempt.rb
│ ├── option.rb
│ ├── options_type.rb
│ ├── predefined_value.rb
│ ├── question.rb
│ ├── questions_type.rb
│ ├── section.rb
│ └── survey.rb
├── config
└── locales
│ ├── en.yml
│ ├── pt-PT.yml
│ └── pt.yml
├── lib
├── generators
│ ├── survey
│ │ ├── install_generator.rb
│ │ └── survey_generator.rb
│ └── templates
│ │ ├── active_admin.rb
│ │ ├── attempts_plain.rb
│ │ ├── attempts_views
│ │ ├── _form.html.erb
│ │ └── new.html.erb
│ │ ├── helper.rb
│ │ ├── migration.rb
│ │ ├── migration_add_head_number_to_options_table.rb
│ │ ├── migration_add_mandatory_to_questions_table.rb
│ │ ├── migration_add_types_to_questions_and_options.rb
│ │ ├── migration_create_predefined_values_table.rb
│ │ ├── migration_section.rb
│ │ ├── migration_update_survey_tables.rb
│ │ ├── rails_admin.rb
│ │ ├── survey_plain.rb
│ │ └── survey_views
│ │ ├── _form.html.erb
│ │ ├── _option_fields.html.erb
│ │ ├── _question_fields.html.erb
│ │ ├── _section_fields.html.erb
│ │ ├── edit.html.erb
│ │ ├── index.html.erb
│ │ └── new.html.erb
├── survey.rb
└── survey
│ ├── active_record.rb
│ ├── engine.rb
│ └── version.rb
├── survey.gemspec
└── test
├── dummy
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── app
│ ├── assets
│ │ ├── javascripts
│ │ │ ├── application.js
│ │ │ └── users.js
│ │ └── stylesheets
│ │ │ └── users.css
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── users_controller.rb
│ │ └── welcome_controller.rb
│ ├── helpers
│ │ ├── application_helper.rb
│ │ └── users_helper.rb
│ ├── models
│ │ ├── lesson.rb
│ │ ├── survey
│ │ │ └── belongs_to_lesson.rb
│ │ └── user.rb
│ └── views
│ │ └── layouts
│ │ └── application.html.erb
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── backtrace_silencers.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── secret_token.rb
│ │ ├── session_store.rb
│ │ └── survey.rb
│ ├── locales
│ │ ├── devise.en.yml
│ │ └── en.yml
│ └── routes.rb
├── db
│ ├── migrate
│ │ ├── 20130123110019_create_users.rb
│ │ ├── 20130201105206_create_survey.rb
│ │ ├── 20130904084520_create_sections.rb
│ │ ├── 20130904115621_update_survey_tables.rb
│ │ ├── 20130905093710_add_types_to_questions_and_options.rb
│ │ ├── 20130916081314_add_head_number_to_options_table.rb
│ │ ├── 20130916102353_create_predefined_values_table.rb
│ │ ├── 20130929102221_add_mandatory_to_questions_table.rb
│ │ ├── 20170619155054_create_lessons.rb
│ │ └── 20170619155608_add_lesson_id_to_survey_surveys.rb
│ └── schema.rb
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── favicon.ico
│ ├── javascripts
│ │ ├── application.js
│ │ ├── controls.js
│ │ ├── dragdrop.js
│ │ ├── effects.js
│ │ ├── prototype.js
│ │ └── rails.js
│ └── stylesheets
│ │ └── .gitkeep
└── script
│ └── rails
├── models
├── answer_test.rb
├── attempt_test.rb
├── option_test.rb
├── predefined_value_test.rb
├── question_test.rb
├── section_test.rb
└── survey_test.rb
├── support
├── assertions.rb
├── factories.rb
└── handlers.rb
├── survey_test.rb
└── test_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | .project
3 | log/*.log
4 | pkg/
5 | test/dummy/db/*.sqlite3
6 | test/dummy/log/*.log
7 | test/dummy/tmp/
8 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | TargetRubyVersion: 2.3
3 | TargetRailsVersion: 4.0
4 | Exclude:
5 | - 'tmp/**/*'
6 | - 'bin/**/*'
7 | - 'db/**/*'
8 | - 'test/fixtures/**/*'
9 | - 'Gemfile'
10 | - 'Rakefile'
11 | - 'node_modules/**/*'
12 | - 'Vagrantfile'
13 | Rails:
14 | Enabled: true
15 |
16 | Metrics/MethodLength:
17 | Max: 35
18 | Metrics/LineLength:
19 | Max: 120
20 | Metrics/AbcSize:
21 | # The ABC size is a calculated magnitude, so this number can be a Fixnum or
22 | # a Float.
23 | Max: 35
24 | Metrics/PerceivedComplexity:
25 | Max: 10
26 | Metrics/CyclomaticComplexity:
27 | Max: 10
28 | Metrics/ClassLength:
29 | CountComments: false # count full line comments?
30 | Max: 175
31 |
32 | Rails/HttpPositionalArguments:
33 | # see https://github.com/bbatsov/rubocop/issues/3629, this is for Rails 5 only
34 | Enabled: false
35 |
36 | Style/NumericLiterals:
37 | Enabled: false
38 | Style/Documentation:
39 | Description: 'Document classes and non-namespace modules.'
40 | Enabled: false
41 | Style/AsciiComments:
42 | Enabled: false
43 | Style/StructInheritance:
44 | Enabled: false
45 | Style/ClassAndModuleChildren:
46 | Enabled: false
47 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 1.9.3
4 | - 2.0.0
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | ruby '2.4.1'
3 |
4 | gemspec
5 |
6 | gem "rdoc"
7 |
8 | group :test do
9 | gem 'sqlite3'
10 | end
11 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | questionnaire_engine (0.1)
5 | rails (~> 5.1)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (5.2.8.1)
11 | actionpack (= 5.2.8.1)
12 | nio4r (~> 2.0)
13 | websocket-driver (>= 0.6.1)
14 | actionmailer (5.2.8.1)
15 | actionpack (= 5.2.8.1)
16 | actionview (= 5.2.8.1)
17 | activejob (= 5.2.8.1)
18 | mail (~> 2.5, >= 2.5.4)
19 | rails-dom-testing (~> 2.0)
20 | actionpack (5.2.8.1)
21 | actionview (= 5.2.8.1)
22 | activesupport (= 5.2.8.1)
23 | rack (~> 2.0, >= 2.0.8)
24 | rack-test (>= 0.6.3)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
27 | actionview (5.2.8.1)
28 | activesupport (= 5.2.8.1)
29 | builder (~> 3.1)
30 | erubi (~> 1.4)
31 | rails-dom-testing (~> 2.0)
32 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
33 | activejob (5.2.8.1)
34 | activesupport (= 5.2.8.1)
35 | globalid (>= 0.3.6)
36 | activemodel (5.2.8.1)
37 | activesupport (= 5.2.8.1)
38 | activerecord (5.2.8.1)
39 | activemodel (= 5.2.8.1)
40 | activesupport (= 5.2.8.1)
41 | arel (>= 9.0)
42 | activestorage (5.2.8.1)
43 | actionpack (= 5.2.8.1)
44 | activerecord (= 5.2.8.1)
45 | marcel (~> 1.0.0)
46 | activesupport (5.2.8.1)
47 | concurrent-ruby (~> 1.0, >= 1.0.2)
48 | i18n (>= 0.7, < 2)
49 | minitest (~> 5.1)
50 | tzinfo (~> 1.1)
51 | arel (9.0.0)
52 | ast (2.3.0)
53 | builder (3.2.4)
54 | byebug (9.0.6)
55 | coderay (1.1.1)
56 | concurrent-ruby (1.1.10)
57 | crass (1.0.6)
58 | erubi (1.10.0)
59 | faker (1.2.0)
60 | i18n (~> 0.5)
61 | globalid (0.4.2)
62 | activesupport (>= 4.2.0)
63 | i18n (0.9.5)
64 | concurrent-ruby (~> 1.0)
65 | loofah (2.19.1)
66 | crass (~> 1.0.2)
67 | nokogiri (>= 1.5.9)
68 | mail (2.7.1)
69 | mini_mime (>= 0.1.1)
70 | marcel (1.0.2)
71 | metaclass (0.0.1)
72 | method_source (0.8.2)
73 | mini_mime (1.1.2)
74 | mini_portile2 (2.4.0)
75 | minitest (5.15.0)
76 | mocha (0.14.0)
77 | metaclass (~> 0.0.1)
78 | nio4r (2.5.8)
79 | nokogiri (1.10.10)
80 | mini_portile2 (~> 2.4.0)
81 | parallel (1.11.2)
82 | parser (2.4.0.0)
83 | ast (~> 2.2)
84 | powerpack (0.1.1)
85 | pry (0.10.4)
86 | coderay (~> 1.1.0)
87 | method_source (~> 0.8.1)
88 | slop (~> 3.4)
89 | pry-byebug (3.4.2)
90 | byebug (~> 9.0)
91 | pry (~> 0.10)
92 | pry-rails (0.3.6)
93 | pry (>= 0.10.4)
94 | rack (2.2.8.1)
95 | rack-test (2.0.2)
96 | rack (>= 1.3)
97 | rails (5.2.8.1)
98 | actioncable (= 5.2.8.1)
99 | actionmailer (= 5.2.8.1)
100 | actionpack (= 5.2.8.1)
101 | actionview (= 5.2.8.1)
102 | activejob (= 5.2.8.1)
103 | activemodel (= 5.2.8.1)
104 | activerecord (= 5.2.8.1)
105 | activestorage (= 5.2.8.1)
106 | activesupport (= 5.2.8.1)
107 | bundler (>= 1.3.0)
108 | railties (= 5.2.8.1)
109 | sprockets-rails (>= 2.0.0)
110 | rails-dom-testing (2.0.3)
111 | activesupport (>= 4.2.0)
112 | nokogiri (>= 1.6)
113 | rails-html-sanitizer (1.4.4)
114 | loofah (~> 2.19, >= 2.19.1)
115 | railties (5.2.8.1)
116 | actionpack (= 5.2.8.1)
117 | activesupport (= 5.2.8.1)
118 | method_source
119 | rake (>= 0.8.7)
120 | thor (>= 0.19.0, < 2.0)
121 | rainbow (2.2.2)
122 | rake
123 | rake (13.0.1)
124 | rdoc (6.3.4.1)
125 | rubocop (0.49.1)
126 | parallel (~> 1.10)
127 | parser (>= 2.3.3.1, < 3.0)
128 | powerpack (~> 0.1)
129 | rainbow (>= 1.99.1, < 3.0)
130 | ruby-progressbar (~> 1.7)
131 | unicode-display_width (~> 1.0, >= 1.0.1)
132 | ruby-progressbar (1.8.1)
133 | slop (3.6.0)
134 | sprockets (3.7.2)
135 | concurrent-ruby (~> 1.0)
136 | rack (> 1, < 3)
137 | sprockets-rails (3.2.2)
138 | actionpack (>= 4.0)
139 | activesupport (>= 4.0)
140 | sprockets (>= 3.0.0)
141 | sqlite3 (1.3.13)
142 | thor (1.2.1)
143 | thread_safe (0.3.6)
144 | tzinfo (1.2.10)
145 | thread_safe (~> 0.1)
146 | unicode-display_width (1.3.0)
147 | websocket-driver (0.7.5)
148 | websocket-extensions (>= 0.1.0)
149 | websocket-extensions (0.1.5)
150 |
151 | PLATFORMS
152 | ruby
153 |
154 | DEPENDENCIES
155 | faker
156 | mocha
157 | pry-byebug
158 | pry-rails
159 | questionnaire_engine!
160 | rake
161 | rdoc
162 | rubocop
163 | sqlite3
164 |
165 | RUBY VERSION
166 | ruby 2.4.1p111
167 |
168 | BUNDLED WITH
169 | 1.16.2
170 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2013 Runtime Revolution
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.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Questionnaire
2 |
3 | [](https://codeclimate.com/github/dr-click/questionnaire)
4 | ### Questionnaire on Rails...
5 |
6 | Questionnaire is a Rails Engine that brings multi types of quizzes, surveys and contests into your Rails
7 | application. Questionnaire models were designed to be flexible enough in order to be extended and
8 | integrated with your own models. Questionnaire was initially extracted from a real application that handles contests and quizzes.
9 |
10 | ## Documentation
11 |
12 | You can view the Questionnaire documentation in RDoc format here:
13 |
14 | http://rubydoc.info/github/dr-click/questionnaire/master/frames
15 |
16 | ## Main Features:
17 | - Questionnaire can limit the number of attempts for each participant, can have multiple sections
18 | - Sections can have multiple questions
19 | - Questions can have multiple answers
20 | - Answers can have different weights and types (multi choices, single choice, number, text)
21 | - Can use 2 languages (Main language field, Localized field) for Surveys, Sections, Questions and Answers attributes
22 | - Base Scaffold Support for Active Admin, Rails Admin and default Rails Controllers
23 | - Base calculation for scores
24 | - Easy integration with your project
25 |
26 | ## Installation
27 |
28 | Add survey to your Gemfile:
29 | ```ruby
30 | gem 'questionnaire_engine', '0.1', :require=>"survey"
31 |
32 | ```
33 | or
34 | ```ruby
35 | gem 'questionnaire_engine', github: 'dr-click/questionnaire', branch: 'master', :require=>"survey"
36 |
37 | ```
38 | or use this for Rails 5
39 | ```ruby
40 | gem 'questionnaire_engine', github: 'clearfunction/questionnaire', branch: 'master', :require=>"survey"
41 |
42 | ```
43 | Then run bundle to install the Gem:
44 | ```sh
45 | bundle install
46 | ```
47 | Now generate and run migrations:
48 | ```sh
49 | rails generate survey:install
50 |
51 | bundle exec rake db:migrate
52 | ```
53 |
54 | ## Important notice for Rails 5.1
55 | Add Rails version to all generated migrations. Example
56 |
57 | ```ruby
58 | class CreateSurvey < ActiveRecord::Migration # change to: class CreateSurvey < ActiveRecord::Migration[5.1]
59 | ```
60 |
61 | ## Getting started with Survey
62 |
63 | ## Survey inside your models
64 | To make a model aware of you just need to add `has_surveys` on it:
65 | ```ruby
66 | class User < ActiveRecord::Base
67 | has_surveys
68 |
69 | #... (your code) ...
70 | end
71 | ```
72 | There is the concept of participant, in our example we choose the User Model.
73 | Every participant can respond to surveys and every response is registered as a attempt.
74 | By default, survey logic assumes an infinite number of attempts per participant
75 | but if your surveys need to have a maximum number of attempts
76 | you can pass the attribute `attempts_number` when creating them.
77 | ```ruby
78 | # Each Participant can respond 4 times this survey
79 | Survey::Survey.new(:name => "Star Wars Quiz", :attempts_number => 4)
80 | ```
81 | ## Questionnaire used in your controllers
82 | In this example we are using the current_user helper
83 | but you can do it in the way you want.
84 |
85 | ```ruby
86 | class ContestsController < ApplicationController
87 |
88 | helper_method :survey, :participant
89 |
90 | # create a new attempt to this survey
91 | def new
92 | @survey = Survey::Survey.active.last
93 | @attempt = @survey.attempts.new
94 | @attempt.answers.build
95 | @participant = current_user # you have to decide what to do here
96 | end
97 |
98 | # create a new attempt in this survey
99 | # an attempt needs to have a participant assigned
100 | def create
101 | @survey = Survey::Survey.active.last
102 | @attempt = @survey.attempts.new(attempt_params)
103 | @attempt.participant = current_user
104 | if @attempt.valid? and @attempt.save
105 | redirect_to view_context.new_attempt_path, alert: I18n.t("attempts_controller.#{action_name}")
106 | else
107 | flash.now[:error] = @attempt.errors.full_messages.join(', ')
108 | render :action => :new
109 | end
110 | end
111 |
112 | #######
113 | private
114 | #######
115 |
116 | # Rails 4 Strong Params
117 | def attempt_params
118 | if Rails::VERSION::MAJOR < 4
119 | params[:survey_attempt]
120 | else
121 | params.require(:survey_attempt).permit(answers_attributes: [:id, :question_id, :option_id, :option_text, :option_number, :predefined_value_id, :_destroy, :finished])
122 | end
123 | end
124 |
125 | end
126 | ```
127 |
128 | ## Survey Associations
129 | To add a survey to a particular model (model has_many :surveys), use the `has_many_surveys` helper
130 | ```ruby
131 | class Lesson < ActiveRecord::Base
132 | has_many_surveys
133 |
134 | #... (your code) ...
135 | end
136 | ```
137 | Then, create a module mixin that adds `belongs_to` to the survey model based on your class name
138 | ```ruby
139 | # app/models/survey/belongs_to_lesson.rb
140 | module Survey
141 | module BelongsToLesson
142 | extend ActiveSupport::Concern
143 | included do
144 | belongs_to :lesson
145 | end
146 | end
147 | end
148 | ```
149 | This will dynamically add the association to the survey model. However, you will need to generate a migration in order to add the foreign key to the `survey_surveys` table like so:
150 | ```ruby
151 | class AddLessonIdToSurveySurveys < ActiveRecord::Migration
152 | def change
153 | add_column :survey_surveys, :lesson_id, :integer
154 | end
155 | end
156 | ```
157 |
158 |
159 | ## Survey inside your Views
160 |
161 | ### Controlling Survey avaliability per participant
162 | To control which page participants see you can use method `avaliable_for_participant?`
163 | that checks if the participant already spent his attempts.
164 | ```erb
165 |
<%= flash[:alert]%>
166 | <%= flash[:error]%>
167 |
168 | <% if @survey.avaliable_for_participant?(@participant) %>
169 | <%= render 'form' %>
170 | <% else %>
171 |
172 | <%= @participant.name %> spent all the possible attempts to answer this Survey
173 |
174 | <% end -%>
175 |
176 | <% # in _form.html.erb %>
177 | <%= @survey.name %>
178 | <%= @survey.description %>
179 | <%= form_for(@attempt, :url => attempt_scope(@attempt)) do |f| %>
180 | <%= f.fields_for :answers do |builder| %>
181 |
182 | <% seq = 0 %>
183 | <% @survey.sections.each do |section| %>
184 | <%= "#{section.head_number} : " if section.head_number %><%= section.name%>
185 | <%= section.description if section.description %>
186 | <% section.questions.each do |question| %>
187 | <% seq += 1 %>
188 | -
189 |
<%= "#{question.head_number} : " if question.head_number %><%= question.text %>
190 | <%= question.description if question.description %>
191 | <% question.options.each do |option| %>
192 |
193 |
194 | <% if option.options_type_id == Survey::OptionsType.multi_choices %>
195 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
196 | <%= check_box_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
197 | <% seq += 1 %>
198 | <% elsif option.options_type_id == Survey::OptionsType.single_choice %>
199 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
200 | <%= radio_button_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
201 | <% elsif option.options_type_id == Survey::OptionsType.number %>
202 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
203 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
204 | <%= number_field_tag "survey_attempt[answers_attributes][#{seq}][option_number]", "", :style => "width: 40px;" %>
205 | <% seq += 1 %>
206 | <% elsif option.options_type_id == Survey::OptionsType.text %>
207 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
208 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
209 | <%= text_field_tag "survey_attempt[answers_attributes][#{seq}][option_text]", "" %>
210 | <% seq += 1 %>
211 | <% end %>
212 |
213 | <%= option.text %>
214 | <% end -%>
215 |
216 | <% end -%>
217 | <% end -%>
218 |
219 | <% end -%>
220 | <%= f.submit "Submit" %>
221 | <% end -%>
222 | ```
223 |
224 | ### Scaffolds and CRUD frameworks
225 | If you are using Rails Admin or Active Admin, you can generate base CRUD screens for Survey with:
226 | ```sh
227 | rails generate survey active_admin
228 |
229 | rails generate survey rails_admin
230 | ```
231 | If you want a simple way to get started you can use the `plain` option which is a simple Rails scaffold to generate the controller and views related with survey logic.
232 | By default when you type `rails g survey plain` it generates a controller in the `admin` namespace but you can choose your own namespace as well:
233 | ```sh
234 | rails generate survey plain namespace:contests
235 | ```
236 |
237 | By default when you generates your controllers using the `plain` command the task
238 | generates the associated routes as well.
239 | Afterwards if you want to generate more routes, you can using the command:
240 |
241 | ```sh
242 | rails generate survey routes namespace:admin
243 | ```
244 |
245 |
246 | ## How to use it
247 | Every user has a collection of attempts for each survey that he respond to. Is up to you to
248 | make averages and collect reports based on that information.
249 | What makes Survey useful is that all the logic behind surveys is now abstracted and well integrated,
250 | making your job easier.
251 |
252 | ## Hacking with Survey through your Models:
253 |
254 | ```ruby
255 | # select the first active Survey
256 | survey = Survey::Survey.active.first
257 |
258 | # select all the attempts from this survey
259 | survey_answers = survey.attempts
260 |
261 | # check the highest score for current user
262 | user_highest_score = survey_answers.for_participant(@user).high_score
263 |
264 | #check the highest score made for this survey
265 | global_highest_score = survey_answers.high_score
266 | ```
267 | # Compability
268 | ### Rails
269 | Survey supports Rails 3 and 4. For use in Rails 4 without using protected_attributes gem.
270 | Rails 4 support is recent, so some minor issues may still be present, please report them.
271 |
272 | ### Active Admin
273 | Only support versions of Active Admin higher than 0.3.1.
274 |
275 | # License
276 | - Modified by [Dr-Click](http://github.com/dr-click)
277 | - Copyright © 2013 [Runtime Revolution](http://www.runtime-revolution.com), released under the MIT license.
278 | - This repository was forked from the original one : https://github.com/runtimerevolution/survey
279 |
280 |
281 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | require 'rubygems'
3 | begin
4 | require 'bundler/setup'
5 | rescue LoadError
6 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7 | end
8 |
9 | require 'rake'
10 | require 'rdoc/task'
11 |
12 | require 'rake/testtask'
13 |
14 | Rake::TestTask.new(:test) do |t|
15 | t.libs << 'lib'
16 | t.libs << 'test'
17 | t.pattern = 'test/**/*_test.rb'
18 | t.verbose = true
19 | end
20 |
21 | task :default => :test
22 |
23 | Rake::RDocTask.new(:rdoc) do |rdoc|
24 | rdoc.rdoc_dir = 'rdoc'
25 | rdoc.title = 'Survey'
26 | rdoc.options << '--line-numbers' << '--inline-source'
27 | rdoc.rdoc_files.include('README.md')
28 | rdoc.rdoc_files.include('lib/**/*.rb')
29 | rdoc.rdoc_files.include('app/**/*.rb')
30 | end
--------------------------------------------------------------------------------
/app/models/survey/answer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::Answer < ActiveRecord::Base
4 | self.table_name = 'survey_answers'
5 | belongs_to :attempt
6 | belongs_to :option
7 | belongs_to :predefined_value
8 | belongs_to :question
9 |
10 | validates :option_id, :question_id, presence: true
11 | validates :predefined_value_id, presence: true, if: proc { |a| a.question && a.question.mandatory? && a.question.predefined_values.count > 0 && ![Survey::OptionsType.text, Survey::OptionsType.large_text].include?(a.option.options_type_id) }
12 | validates :option_text, presence: true, if: proc { |a| a.option && (a.question && a.question.mandatory? && a.question.predefined_values.count == 0 && [Survey::OptionsType.text, Survey::OptionsType.multi_choices_with_text, Survey::OptionsType.single_choice_with_text, Survey::OptionsType.large_text].include?(a.option.options_type_id)) }
13 | validates :option_number, presence: true, if: proc { |a| a.option && (a.question && a.question.mandatory? && [Survey::OptionsType.number, Survey::OptionsType.multi_choices_with_number, Survey::OptionsType.single_choice_with_number].include?(a.option.options_type_id)) }
14 |
15 | # rails 3 attr_accessible support
16 | if Rails::VERSION::MAJOR < 4
17 | attr_accessible :option, :attempt, :question, :question_id, :option_id, :predefined_value_id, :attempt_id, :option_text, :option_number
18 | end
19 |
20 | before_create :characterize_answer
21 | before_save :check_single_choice_with_field_case
22 |
23 | def value
24 | if option.nil?
25 | Survey::Option.find(option_id).weight
26 | else
27 | option.weight
28 | end
29 | end
30 |
31 | def correct?
32 | correct || option.correct?
33 | end
34 |
35 | #######
36 |
37 | private
38 |
39 | #######
40 |
41 | def characterize_answer
42 | self.correct = true if option.correct?
43 | end
44 |
45 | def check_single_choice_with_field_case
46 | if [Survey::OptionsType.multi_choices, Survey::OptionsType.single_choice].include?(option.options_type_id)
47 | self.option_text = nil
48 | self.option_number = nil
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/app/models/survey/attempt.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::Attempt < ActiveRecord::Base
4 | self.table_name = 'survey_attempts'
5 |
6 | # relations
7 |
8 | has_many :answers, dependent: :destroy
9 | belongs_to :survey
10 | belongs_to :participant, polymorphic: true
11 |
12 | # rails 3 attr_accessible support
13 | if Rails::VERSION::MAJOR < 4
14 | attr_accessible :participant_id, :survey_id, :answers_attributes, :survey, :winner, :participant
15 | end
16 |
17 | # validations
18 | validates :participant_id, :participant_type,
19 | presence: true
20 |
21 | accepts_nested_attributes_for :answers,
22 | reject_if: ->(q) { q[:question_id].blank? && q[:option_id].blank? },
23 | allow_destroy: true
24 |
25 | # scopes
26 |
27 | scope :for_survey, ->(survey) {
28 | where(survey_id: survey.try(:id))
29 | }
30 |
31 | scope :exclude_survey, ->(survey) {
32 | where("NOT survey_id = #{survey.try(:id)}")
33 | }
34 |
35 | scope :for_participant, ->(participant) {
36 | where(participant_id: participant.try(:id),
37 | participant_type: participant.class.to_s)
38 | }
39 |
40 | scope :wins, -> { where(winner: true) }
41 | scope :looses, -> { where(winner: false) }
42 | scope :scores, -> { order('score DESC') }
43 |
44 | # callbacks
45 |
46 | validate :check_number_of_attempts_by_survey, on: :create
47 | after_create :collect_scores
48 |
49 | def correct_answers
50 | answers.where(correct: true)
51 | end
52 |
53 | def incorrect_answers
54 | answers.where(correct: false)
55 | end
56 |
57 | def collect_scores!
58 | collect_scores
59 | save
60 | end
61 |
62 | def self.high_score
63 | scores.first.score
64 | end
65 |
66 | private
67 |
68 | def check_number_of_attempts_by_survey
69 | attempts = self.class.for_survey(survey).for_participant(participant)
70 | upper_bound = survey.attempts_number
71 | errors.add(:questionnaire_id, 'Number of attempts exceeded') if attempts.size >= upper_bound && upper_bound.nonzero?
72 | end
73 |
74 | def collect_scores
75 | multi_select_questions = Survey::Question.joins(:section)
76 | .where(survey_sections: { survey_id: survey.id },
77 | survey_questions: {
78 | questions_type_id: Survey::QuestionsType.multi_select
79 | })
80 | if multi_select_questions.empty? # No multi-select questions
81 | raw_score = answers.map(&:value).reduce(:+)
82 | self.score = raw_score
83 | else
84 | # Initial score without multi-select questions
85 | raw_score = answers.where.not(question_id: multi_select_questions.ids).map(&:value).reduce(:+) || 0
86 | multi_select_questions.each do |question|
87 | options = question.options
88 | correct_question_answers = answers.where(question_id: question.id, correct: true)
89 | break if correct_question_answers.empty? # If they didn't select any correct answers, then skip this step
90 | correct_options_sum = options.correct.map(&:weight).reduce(:+)
91 | correct_percentage = correct_question_answers.map(&:value).reduce(:+).fdiv(correct_options_sum)
92 | raw_score += correct_percentage
93 | if correct_percentage == 1
94 | option_value = 1 / options.count.to_f
95 | raw_score -= (option_value * answers.where(question_id: question.id, correct: false).count)
96 | end
97 | end
98 | self.score = raw_score || 0
99 | save
100 | end
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/app/models/survey/option.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::Option < ActiveRecord::Base
4 | self.table_name = 'survey_options'
5 | # relations
6 | belongs_to :question
7 | has_many :answers
8 |
9 | # rails 3 attr_accessible support
10 | if Rails::VERSION::MAJOR < 4
11 | attr_accessible :text, :correct, :weight, :question_id, :locale_text, :options_type_id, :head_number
12 | end
13 |
14 | # validations
15 | validates :text, presence: true, allow_blank: false, if: proc { |o| [Survey::OptionsType.multi_choices, Survey::OptionsType.single_choice, Survey::OptionsType.single_choice_with_text, Survey::OptionsType.single_choice_with_number, Survey::OptionsType.multi_choices_with_text, Survey::OptionsType.multi_choices_with_number, Survey::OptionsType.large_text].include?(o.options_type_id) }
16 | validates :options_type_id, presence: true
17 | validates :options_type_id, inclusion: { in: Survey::OptionsType.options_type_ids, unless: proc { |o| o.options_type_id.blank? } }
18 |
19 | scope :correct, -> { where(correct: true) }
20 | scope :incorrect, -> { where(correct: false) }
21 |
22 | before_create :default_option_weigth
23 |
24 | def to_s
25 | text
26 | end
27 |
28 | def correct?
29 | correct == true
30 | end
31 |
32 | def text
33 | I18n.locale == I18n.default_locale ? super : locale_text.blank? ? super : locale_text
34 | end
35 |
36 | #######
37 |
38 | private
39 |
40 | #######
41 |
42 | def default_option_weigth
43 | self.weight = 1 if correct && weight == 0
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/app/models/survey/options_type.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::OptionsType
4 | @@options_types = { multi_choices: 1,
5 | single_choice: 2,
6 | number: 3,
7 | text: 4,
8 | multi_choices_with_text: 5,
9 | single_choice_with_text: 6,
10 | multi_choices_with_number: 7,
11 | single_choice_with_number: 8,
12 | large_text: 9 }
13 |
14 | def self.options_types
15 | @@options_types
16 | end
17 |
18 | def self.options_types_title
19 | titled = {}
20 | Survey::OptionsType.options_types.each { |k, v| titled[k.to_s.titleize] = v }
21 | titled
22 | end
23 |
24 | def self.options_type_ids
25 | @@options_types.values
26 | end
27 |
28 | def self.options_type_keys
29 | @@options_types.keys
30 | end
31 |
32 | @@options_types.each do |key, val|
33 | define_singleton_method key.to_s do
34 | val
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/models/survey/predefined_value.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::PredefinedValue < ActiveRecord::Base
4 | self.table_name = 'survey_predefined_values'
5 |
6 | # relations
7 | belongs_to :question
8 |
9 | # rails 3 attr_accessible support
10 | if Rails::VERSION::MAJOR < 4
11 | attr_accessible :head_number, :name, :locale_name, :question_id
12 | end
13 |
14 | # validations
15 | validates :name, presence: true
16 |
17 | def to_s
18 | name
19 | end
20 |
21 | def name
22 | I18n.locale == I18n.default_locale ? super : locale_name || super
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/models/survey/question.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::Question < ActiveRecord::Base
4 | self.table_name = 'survey_questions'
5 | # relations
6 | has_many :options
7 | has_many :predefined_values
8 | has_many :answers
9 | belongs_to :section
10 |
11 | # rails 3 attr_accessible support
12 | if Rails::VERSION::MAJOR < 4
13 | attr_accessible :options_attributes, :predefined_values_attributes, :text, :section_id, :head_number, :description, :locale_text, :locale_head_number, :locale_description, :questions_type_id
14 | end
15 |
16 | accepts_nested_attributes_for :options,
17 | reject_if: ->(a) { a[:options_type_id].blank? },
18 | allow_destroy: true
19 |
20 | accepts_nested_attributes_for :predefined_values,
21 | reject_if: ->(a) { a[:name].blank? },
22 | allow_destroy: true
23 |
24 | # validations
25 | validates :text, presence: true, allow_blank: false
26 | validates :questions_type_id, presence: true
27 | validates :questions_type_id, inclusion: { in: Survey::QuestionsType.questions_type_ids, unless: proc { |q| q.questions_type_id.blank? } }
28 |
29 | scope :mandatory_only, -> { where(mandatory: true) }
30 |
31 | def correct_options
32 | options.correct
33 | end
34 |
35 | def incorrect_options
36 | options.incorrect
37 | end
38 |
39 | def text
40 | I18n.locale == I18n.default_locale ? super : locale_text.blank? ? super : locale_text
41 | end
42 |
43 | def description
44 | I18n.locale == I18n.default_locale ? super : locale_description.blank? ? super : locale_description
45 | end
46 |
47 | def head_number
48 | I18n.locale == I18n.default_locale ? super : locale_head_number.blank? ? super : locale_head_number
49 | end
50 |
51 | def mandatory?
52 | mandatory == true
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/app/models/survey/questions_type.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::QuestionsType
4 | @@questions_types = { multiple_choice: 2, free_response: 9, multi_select: 1 }
5 |
6 | def self.questions_types
7 | @@questions_types
8 | end
9 |
10 | def self.questions_types_title
11 | titled = {}
12 | Survey::QuestionsType.questions_types.each { |k, v| titled[k.to_s.titleize] = v }
13 | titled
14 | end
15 |
16 | def self.questions_type_ids
17 | @@questions_types.values
18 | end
19 |
20 | def self.questions_type_keys
21 | @@questions_types.keys
22 | end
23 |
24 | @@questions_types.each do |key, val|
25 | define_singleton_method key.to_s do
26 | val
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/app/models/survey/section.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::Section < ActiveRecord::Base
4 | self.table_name = 'survey_sections'
5 |
6 | # relations
7 | has_many :questions
8 | belongs_to :survey
9 |
10 | # rails 3 attr_accessible support
11 | if Rails::VERSION::MAJOR < 4
12 | attr_accessible :questions_attributes, :head_number, :name, :description, :survey_id, :locale_head_number, :locale_name, :locale_description
13 | end
14 |
15 | accepts_nested_attributes_for :questions,
16 | reject_if: ->(q) { q[:text].blank? }, allow_destroy: true
17 |
18 | # validations
19 | validates :name, presence: true, allow_blank: false
20 | validate :check_questions_requirements
21 |
22 | def name
23 | I18n.locale == I18n.default_locale ? super : locale_name.blank? ? super : locale_name
24 | end
25 |
26 | def description
27 | I18n.locale == I18n.default_locale ? super : locale_description.blank? ? super : locale_description
28 | end
29 |
30 | def head_number
31 | I18n.locale == I18n.default_locale ? super : locale_head_number.blank? ? super : locale_head_number
32 | end
33 |
34 | def full_name
35 | head_name = head_number.blank? ? '' : "#{head_number}: "
36 | "#{head_name}#{name}"
37 | end
38 |
39 | #######
40 |
41 | private
42 |
43 | #######
44 |
45 | # a section only can be saved if has one or more questions and options
46 | def check_questions_requirements
47 | if questions.empty? || questions.collect(&:options).empty?
48 | errors.add(:base, 'Section without questions or options cannot be saved')
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/app/models/survey/survey.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Survey::Survey < ActiveRecord::Base
4 | self.table_name = 'survey_surveys'
5 |
6 | # relations
7 | has_many :attempts
8 | has_many :sections
9 |
10 | # rails 3 attr_accessible support
11 | if Rails::VERSION::MAJOR < 4
12 | attr_accessible :name, :description, :finished, :active, :sections_attributes, :attempts_number, :locale_name, :locale_description
13 | end
14 |
15 | accepts_nested_attributes_for :sections,
16 | reject_if: ->(q) { q[:name].blank? }, allow_destroy: true
17 |
18 | scope :active, -> { where(active: true) }
19 | scope :inactive, -> { where(active: false) }
20 |
21 | validates :attempts_number,
22 | numericality: { only_integer: true, greater_than: -1 }
23 |
24 | # validations
25 | validates :description, :name, presence: true, allow_blank: false
26 | validate :check_active_requirements
27 |
28 | # returns all the correct options for current surveys
29 | def correct_options
30 | Survey::Question.where(section_id: section_ids).map(&:correct_options).flatten
31 | end
32 |
33 | # returns all the incorrect options for current surveys
34 | def incorrect_options
35 | Survey::Question.where(section_id: sections.collect(&:id)).map(&:incorrect_options).flatten
36 | end
37 |
38 | def avaliable_for_participant?(participant)
39 | current_number_of_attempts =
40 | attempts.for_participant(participant).size
41 | upper_bound = attempts_number
42 | !((current_number_of_attempts >= upper_bound && upper_bound != 0))
43 | end
44 |
45 | def name
46 | I18n.locale == I18n.default_locale ? super : locale_name.blank? ? super : locale_name
47 | end
48 |
49 | def description
50 | I18n.locale == I18n.default_locale ? super : locale_description.blank? ? super : locale_description
51 | end
52 |
53 | #######
54 |
55 | private
56 |
57 | #######
58 |
59 | # a surveys only can be activated if has one or more sections and questions
60 | def check_active_requirements
61 | if sections.empty? || sections.collect(&:questions).empty?
62 | errors.add(:base, 'Survey without sections or questions cannot be saved')
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | surveys_controller:
3 | create: Your Survey has been successfull created
4 | update: Your Survey has been successfull updated
5 | attempts_controller:
6 | create: "Congratulations, you have responded to Survey."
7 | surveys: Surveys
8 | survey_details: Survey Details
9 | questions: Questions
10 | sections: Sections
11 | predefined_values: Predefined Values
12 | activerecord:
13 | models:
14 | survey:
15 | survey: Survey
16 | other: Surveys
17 | attributes:
18 | survey:
19 | name: Name
20 | locale_name: Localized name
21 | finished: Finished
22 | description: Short description
23 | locale_description: Localized short description
24 | active: "Activated?"
25 | attempts_number: Number of Maximum Attempts
26 | sections_attributes: Sections
27 | section:
28 | head_number: Head Number
29 | name: Name
30 | description: Description
31 | locale_head_number: Localized head number
32 | locale_name: Localized name
33 | locale_description: Localized Description
34 | question:
35 | head_number: Head Number
36 | text: Question Text
37 | description: Description
38 | locale_head_number: Localized head number
39 | locale_text: Localized Question Text
40 | locale_description: Localized Description
41 | options_attributes: Options
42 | predefined_values_attributes: Predefined Values
43 | options:
44 | text: Option Text
45 | locale_text: Localized Option Text
46 | correct: Correct
47 | attempt:
48 | answers_attributes: Answers
49 | survey: Survey
50 | answer:
51 | attempt: Attempt
52 | question: Question
53 | option: Option
54 | correct: Answer Correct?
--------------------------------------------------------------------------------
/config/locales/pt-PT.yml:
--------------------------------------------------------------------------------
1 | pt-PT:
2 | questions: Perguntas
3 | survey_details: Detalhes do Questionário
4 | surveys_controller:
5 | create: O questionário foi criado com sucesso
6 | update: O questionário foi atualizado com sucesso
7 | attempts_controller:
8 | create: "A sua resposta foi efetuada com sucesso."
9 | surveys: Questionários
10 | survey_details: Detalhes do Questionário
11 | questions: Perguntas
12 | activerecord:
13 | models:
14 | survey:
15 | survey: Questionário
16 | other: Questionários
17 | question:
18 | question: Pergunta
19 | other: Perguntas
20 | option:
21 | option: Opção
22 | other: Opções
23 | attempt:
24 | option: Resposta
25 | other: Respostas
26 | attributes:
27 | survey:
28 | name: Nome
29 | finished: Acabado
30 | description: Pequena Descrição
31 | active: "Ativo?"
32 | attempts_number: Número máximo de tentativas
33 | questions_attributes: Perguntas
34 | question:
35 | text: Corpo da Pergunta
36 | options_attributes: Opções
37 | options:
38 | text: Corpo da Opção
39 | correct: Correta
40 | attempt:
41 | answers_attributes: Respostas
42 | survey: Questionário
43 | winner: Vencedor
44 | answer:
45 | attempt: Tentativa
46 | question: Pergunta
47 | option: Opção
48 | correct: Resposta Correta?
--------------------------------------------------------------------------------
/config/locales/pt.yml:
--------------------------------------------------------------------------------
1 | pt:
2 | questions: Perguntas
3 | survey_details: Detalhes do Questionário
4 | surveys_controller:
5 | create: O questionário foi criado com sucesso
6 | update: O questionário foi atualizado com sucesso
7 | attempts_controller:
8 | create: "A sua resposta foi efetuada com sucesso."
9 | surveys: Questionários
10 | survey_details: Detalhes do Questionário
11 | questions: Perguntas
12 | activerecord:
13 | models:
14 | survey:
15 | survey: Questionário
16 | other: Questionários
17 | question:
18 | question: Pergunta
19 | other: Perguntas
20 | option:
21 | option: Opção
22 | other: Opções
23 | attempt:
24 | option: Resposta
25 | other: Respostas
26 | attributes:
27 | survey:
28 | name: Nome
29 | finished: Acabado
30 | description: Pequena Descrição
31 | active: "Ativo?"
32 | attempts_number: Número máximo de tentativas
33 | questions_attributes: Perguntas
34 | question:
35 | text: Corpo da Pergunta
36 | options_attributes: Opções
37 | options:
38 | text: Corpo da Opção
39 | correct: Correta
40 | attempt:
41 | answers_attributes: Respostas
42 | survey: Questionário
43 | winner: Vencedor
44 | answer:
45 | attempt: Tentativa
46 | question: Pergunta
47 | option: Opção
48 | correct: Resposta Correta?
--------------------------------------------------------------------------------
/lib/generators/survey/install_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Survey
4 | module Generators
5 | class InstallGenerator < Rails::Generators::Base
6 | source_root File.expand_path('../../templates', __FILE__)
7 |
8 | def copy_migration
9 | timestamp_number = Time.now.utc.strftime('%Y%m%d%H%M%S').to_i
10 |
11 | migration_files = [{ new_file_name: 'create_survey', origin_file_name: 'migration' },
12 | { new_file_name: 'create_sections', origin_file_name: 'migration_section' },
13 | { new_file_name: 'update_survey_tables', origin_file_name: 'migration_update_survey_tables' },
14 | { new_file_name: 'add_types_to_questions_and_options', origin_file_name: 'migration_add_types_to_questions_and_options' },
15 | { new_file_name: 'add_head_number_to_options_table', origin_file_name: 'migration_add_head_number_to_options_table' },
16 | { new_file_name: 'create_predefined_values_table', origin_file_name: 'migration_create_predefined_values_table' },
17 | { new_file_name: 'add_mandatory_to_questions_table', origin_file_name: 'migration_add_mandatory_to_questions_table' }]
18 |
19 | migration_files.each do |migration_file|
20 | unless already_exists?(migration_file[:new_file_name])
21 | copy_file "#{migration_file[:origin_file_name]}.rb", "db/migrate/#{timestamp_number}_#{migration_file[:new_file_name]}.rb"
22 | timestamp_number += 1
23 | end
24 | end
25 | end
26 |
27 | #######
28 |
29 | private
30 |
31 | #######
32 |
33 | def already_exists?(file_name)
34 | Dir.glob("#{File.join(destination_root, File.join('db', 'migrate'))}/[0-9]*_*.rb").grep(Regexp.new('\d+_' + file_name + '.rb$')).first
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/generators/survey/survey_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Survey
4 | class SurveyGenerator < Rails::Generators::Base
5 | source_root File.expand_path('../../templates', __FILE__)
6 |
7 | TEMPLATES = %w[active_admin rails_admin plain routes].freeze
8 |
9 | argument :arguments,
10 | type: :array,
11 | default: [],
12 | banner: "< #{TEMPLATES.join('|')} > [options]"
13 |
14 | def create_resolution
15 | strategy = arguments.first
16 | if TEMPLATES.include? strategy
17 | send("generate_#{strategy}_resolution")
18 | success_message(strategy)
19 | else
20 | error_message(strategy)
21 | end
22 | end
23 |
24 | private
25 |
26 | def generate_active_admin_resolution
27 | copy_file 'active_admin.rb', 'app/admin/survey.rb'
28 | end
29 |
30 | def generate_rails_admin_resolution
31 | copy_file 'rails_admin.rb', 'config/initializers/survey_rails_admin.rb'
32 | end
33 |
34 | def generate_plain_resolution
35 | scope = get_scope
36 | template 'survey_plain.rb', "app/controllers/#{scope}/surveys_controller.rb"
37 | template 'attempts_plain.rb', "app/controllers/#{scope}/attempts_controller.rb"
38 | template 'helper.rb', "app/helpers/#{scope}/surveys_helper.rb"
39 | directory 'survey_views', "app/views/#{scope}/surveys", recursive: true
40 | directory 'attempts_views', "app/views/#{scope}/attempts", recursive: true
41 | generate_routes_for(scope)
42 | end
43 |
44 | def generate_routes_resolution
45 | generate_routes_for(get_scope)
46 | end
47 |
48 | # Error Handlers
49 | def error_message(argument)
50 | error_message = <<-CONTENT
51 | This Resolution: '#{argument}' is not supported by Survey:
52 | We only support Active Admin, Refinery and Active Scaffold
53 | CONTENT
54 | say error_message, :red
55 | end
56 |
57 | def success_message(argument)
58 | say "Generation of #{argument.capitalize} Template Complete :) enjoy Survey", :green
59 | end
60 |
61 | def generate_routes_for(namespace, _conditional = nil)
62 | content = <<-CONTENT
63 |
64 | namespace :#{namespace} do
65 | resources :surveys
66 | resources :attempts, :only => [:new, :create]
67 | end
68 | CONTENT
69 | inject_into_file 'config/routes.rb', "\n#{content}",
70 | after: "#{Rails.application.class}.routes.draw do"
71 | end
72 |
73 | def get_scope
74 | arguments.size == 1 ? 'admin' : arguments[1].split(':').last
75 | end
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/lib/generators/templates/active_admin.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ActiveAdmin.register Survey::Survey do
4 | menu label: I18n.t('surveys')
5 |
6 | filter :name,
7 | as: :select,
8 | collection: proc {
9 | Survey::Survey.select('distinct(name)').collect do |c|
10 | [c.name, c.name]
11 | end
12 | }
13 | filter :active,
14 | as: :select,
15 | collection: %w[true false]
16 |
17 | filter :created_at
18 |
19 | index do
20 | column :name
21 | column :description
22 | column :active
23 | column :attempts_number
24 | column :finished
25 | column :created_at
26 | default_actions
27 | end
28 |
29 | form do |f|
30 | f.inputs I18n.t('survey_details') do
31 | f.input :name
32 | f.input :locale_name
33 | f.input :description
34 | f.input :locale_description
35 | f.input :active, as: :select, collection: %w[true false]
36 | f.input :attempts_number
37 | end
38 |
39 | f.inputs I18n.t('sections') do
40 | f.has_many :sections do |s|
41 | s.input :head_number
42 | s.input :locale_head_number
43 | s.input :name
44 | s.input :locale_name
45 | s.input :description
46 | s.input :locale_description
47 |
48 | s.has_many :questions do |q|
49 | q.input :head_number
50 | q.input :locale_head_number
51 | q.input :text
52 | q.input :locale_text
53 | q.input :description
54 | q.input :locale_description
55 | q.input :questions_type_id, as: :select, collection: Survey::QuestionsType.questions_types_title
56 | q.input :mandatory
57 |
58 | q.inputs I18n.t('predefined_values') do
59 | q.has_many :predefined_values do |p|
60 | p.input :head_number
61 | p.input :name
62 | p.input :locale_name
63 | end
64 | end
65 |
66 | q.has_many :options do |a|
67 | a.input :head_number
68 | a.input :text
69 | a.input :locale_text
70 | a.input :options_type_id, as: :select, collection: Survey::OptionsType.options_types_title
71 | a.input :correct
72 | end
73 | end
74 | end
75 | end
76 |
77 | f.buttons
78 | end
79 |
80 | if Rails::VERSION::MAJOR >= 4
81 | controller do
82 | def permitted_params
83 | params.permit!
84 | end
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/lib/generators/templates/attempts_plain.rb:
--------------------------------------------------------------------------------
1 | class <%= get_scope.capitalize %>::AttemptsController < ApplicationController
2 |
3 | helper "<%= get_scope%>/surveys"
4 |
5 | def new
6 | @survey = Survey::Survey.active.last
7 | @attempt = @survey.attempts.new
8 | @attempt.answers.build
9 | @participant = current_user # you have to decide what to do here
10 | end
11 |
12 | def create
13 | @survey = Survey::Survey.active.last
14 | @attempt = @survey.attempts.new(attempt_params)
15 | @attempt.participant = current_user # you have to decide what to do here
16 | if @attempt.valid? and @attempt.save
17 | redirect_to view_context.new_attempt_path, alert: I18n.t("attempts_controller.#{action_name}")
18 | else
19 | flash.now[:error] = @attempt.errors.full_messages.join(', ')
20 | render :action => :new
21 | end
22 | end
23 |
24 | #######
25 | private
26 | #######
27 |
28 | # Rails 4 Strong Params
29 | def attempt_params
30 | if Rails::VERSION::MAJOR < 4
31 | params[:survey_attempt]
32 | else
33 | params.require(:survey_attempt).permit(answers_attributes: [:question_id, :option_id, :option_text, :option_number, :predefined_value_id])
34 | end
35 | end
36 | end
--------------------------------------------------------------------------------
/lib/generators/templates/attempts_views/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= @survey.name %>
2 | <%= @survey.description %>
3 | <%= form_for(@attempt, :url => attempt_scope(@attempt)) do |f| %>
4 | <%= f.fields_for :answers do |builder| %>
5 |
6 | <% seq = 0 %>
7 | <% @survey.sections.each do |section| %>
8 | <%= "#{section.head_number} : " if section.head_number %><%= section.name%>
9 | <%= section.description if section.description %>
10 | <% section.questions.each do |question| %>
11 | <% seq += 1 %>
12 | -
13 |
<%= "#{question.head_number} : " if question.head_number %><%= question.text %>
14 | <%= question.description if question.description %>
15 | <% question.options.each do |option| %>
16 |
17 | <% if [Survey::OptionsType.multi_choices, Survey::OptionsType.multi_choices_with_text, Survey::OptionsType.multi_choices_with_number].include? option.options_type_id %>
18 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
19 | <%= check_box_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
20 |
21 | <% if option.options_type_id == Survey::OptionsType.multi_choices_with_text %>
22 | <%= text_field_tag "survey_attempt[answers_attributes][#{seq}][option_text]", "" %>
23 | <% elsif option.options_type_id == Survey::OptionsType.multi_choices_with_number %>
24 | <%= number_field_tag "survey_attempt[answers_attributes][#{seq}][option_number]", "", class: "form-control" %>
25 | <% end %>
26 | <% seq += 1 %>
27 | <% elsif [Survey::OptionsType.single_choice, Survey::OptionsType.single_choice_with_text].include? option.options_type_id %>
28 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
29 | <%= radio_button_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
30 |
31 | <% if option.options_type_id == Survey::OptionsType.single_choice_with_text %>
32 | <%= text_field_tag "survey_attempt[answers_attributes][#{seq}][option_text]", "" %>
33 | <% elsif option.options_type_id == Survey::OptionsType.single_choice_with_number %>
34 | <%= number_field_tag "survey_attempt[answers_attributes][#{seq}][option_number]", "", class: "form-control" %>
35 | <% end %>
36 | <% elsif option.options_type_id == Survey::OptionsType.number %>
37 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
38 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
39 | <%= number_field_tag "survey_attempt[answers_attributes][#{seq}][option_number]", "", :style => "width: 40px;" %>
40 | <% seq += 1 %>
41 | <% elsif option.options_type_id == Survey::OptionsType.text %>
42 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
43 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
44 | <%= text_field_tag "survey_attempt[answers_attributes][#{seq}][option_text]", "" %>
45 | <% seq += 1 %>
46 | <% elsif option.options_type_id == Survey::OptionsType.large_text %>
47 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][question_id]", question.id %>
48 | <%= hidden_field_tag "survey_attempt[answers_attributes][#{seq}][option_id]", option.id %>
49 | <%= text_area_tag "survey_attempt[answers_attributes][#{seq}][option_text]", "" %>
50 | <% seq += 1 %>
51 | <% end %>
52 |
53 | <%= option.text %>
54 | <% end -%>
55 |
56 | <% end -%>
57 | <% end -%>
58 |
59 | <% end -%>
60 | <%= f.submit "Submit" %>
61 | <% end -%>
--------------------------------------------------------------------------------
/lib/generators/templates/attempts_views/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= flash[:alert]%>
2 | <%= flash[:error]%>
3 |
4 | <% if @survey.avaliable_for_participant?(@participant) %>
5 | <%= render 'form' %>
6 | <% else %>
7 |
8 | <%= @participant.name %> spent all the possible attempts to answer this Survey
9 |
10 | <% end -%>
--------------------------------------------------------------------------------
/lib/generators/templates/helper.rb:
--------------------------------------------------------------------------------
1 | module <%= get_scope.capitalize %>
2 | module SurveysHelper
3 | def link_to_remove_field(name, f)
4 | f.hidden_field(:_destroy) +
5 | link_to_function(raw(name), "removeField(this)", :id =>"remove-attach")
6 | end
7 |
8 | def new_attempt_path
9 | new_<%= get_scope %>_attempt_path
10 | end
11 |
12 | def new_survey_path
13 | new_<%= get_scope %>_survey_path
14 | end
15 |
16 | def edit_survey_path(resource)
17 | edit_<%= get_scope %>_survey_path(resource)
18 | end
19 |
20 | def attempt_scope(resource)
21 | if action_name =~ /new|create/
22 | <%= get_scope %>_attempts_path(resource)
23 | elsif action_name =~ /edit|update/
24 | <%= get_scope %>_attempt_path(resource)
25 | end
26 | end
27 |
28 | def survey_scope(resource)
29 | if action_name =~ /new|create/
30 | <%= get_scope %>_surveys_path(resource)
31 | elsif action_name =~ /edit|update/
32 | <%= get_scope %>_survey_path(resource)
33 | end
34 | end
35 |
36 | def link_to_add_field(name, f, association)
37 | new_object = f.object.class.reflect_on_association(association).klass.new
38 | fields = f.fields_for(association, new_object,:child_index => "new_#{association}") do |builder|
39 | render(association.to_s.singularize + "_fields", :f => builder)
40 | end
41 | link_to_function(name, "addField(this, \"#{association}\", \"#{escape_javascript(fields)}\")",
42 | :id=>"add-attach",
43 | :class=>"btn btn-small btn-info")
44 | end
45 |
46 | def link_to_function(name, *args, &block)
47 | html_options = args.extract_options!.symbolize_keys
48 |
49 | function = block_given? ? update_page(&block) : args[0] || ''
50 | onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
51 | href = html_options[:href] || '#'
52 |
53 | content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
54 | end
55 | end
56 | end
--------------------------------------------------------------------------------
/lib/generators/templates/migration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateSurvey < ActiveRecord::Migration
4 | def self.up
5 | # survey surveys logic
6 | create_table :survey_surveys do |t|
7 | t.string :name
8 | t.text :description
9 | t.integer :attempts_number, default: 0
10 | t.boolean :finished, default: false
11 | t.boolean :active, default: true
12 |
13 | t.timestamps
14 | end
15 |
16 | create_table :survey_questions do |t|
17 | t.integer :survey_id
18 | t.string :text
19 |
20 | t.timestamps
21 | end
22 |
23 | create_table :survey_options do |t|
24 | t.integer :question_id
25 | t.integer :weight, default: 0
26 | t.string :text
27 | t.boolean :correct
28 |
29 | t.timestamps
30 | end
31 |
32 | # survey answer logic
33 | create_table :survey_attempts do |t|
34 | t.belongs_to :participant, polymorphic: true
35 | t.integer :survey_id
36 | t.boolean :winner, null: false, default: false
37 | t.decimal :score
38 |
39 | t.timestamps
40 | end
41 |
42 | create_table :survey_answers do |t|
43 | t.integer :attempt_id
44 | t.integer :question_id
45 | t.integer :option_id
46 | t.boolean :correct, null: false, default: false
47 | t.timestamps
48 | end
49 | end
50 |
51 | def self.down
52 | drop_table :survey_surveys
53 | drop_table :survey_questions
54 | drop_table :survey_options
55 |
56 | drop_table :survey_attempts
57 | drop_table :survey_answers
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/generators/templates/migration_add_head_number_to_options_table.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddHeadNumberToOptionsTable < ActiveRecord::Migration
4 | def change
5 | # Survey Options table
6 | add_column :survey_options, :head_number, :string
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/generators/templates/migration_add_mandatory_to_questions_table.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddMandatoryToQuestionsTable < ActiveRecord::Migration
4 | def change
5 | # Survey Questions table
6 | add_column :survey_questions, :mandatory, :boolean, default: false
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/generators/templates/migration_add_types_to_questions_and_options.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddTypesToQuestionsAndOptions < ActiveRecord::Migration
4 | def change
5 | # Survey Questions table
6 | add_column :survey_questions, :questions_type_id, :integer
7 |
8 | # Survey Options table
9 | add_column :survey_options, :options_type_id, :integer
10 |
11 | # Survey Answers table
12 | add_column :survey_answers, :option_text, :text
13 | add_column :survey_answers, :option_number, :integer
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/generators/templates/migration_create_predefined_values_table.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreatePredefinedValuesTable < ActiveRecord::Migration
4 | def change
5 | create_table :survey_predefined_values do |t|
6 | t.string :head_number
7 | t.string :name
8 | t.string :locale_name
9 | t.integer :question_id
10 |
11 | t.timestamps
12 | end
13 |
14 | add_column :survey_answers, :predefined_value_id, :integer
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/generators/templates/migration_section.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateSections < ActiveRecord::Migration
4 | def self.up
5 | create_table :survey_sections do |t|
6 | t.string :head_number
7 | t.string :name
8 | t.text :description
9 | t.integer :survey_id
10 |
11 | t.timestamps
12 | end
13 |
14 | remove_column :survey_questions, :survey_id
15 | add_column :survey_questions, :section_id, :integer
16 | end
17 |
18 | def self.down
19 | drop_table :survey_sections
20 |
21 | remove_column :survey_questions, :section_id
22 | add_column :survey_questions, :survey_id, :integer
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/generators/templates/migration_update_survey_tables.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class UpdateSurveyTables < ActiveRecord::Migration
4 | def change
5 | # Survey Surveys table
6 | add_column :survey_surveys, :locale_name, :string
7 | add_column :survey_surveys, :locale_description, :text
8 |
9 | # Survey Sections table
10 | add_column :survey_sections, :locale_head_number, :string
11 | add_column :survey_sections, :locale_name, :string
12 | add_column :survey_sections, :locale_description, :text
13 |
14 | # Survey Questions table
15 | add_column :survey_questions, :head_number, :string
16 | add_column :survey_questions, :description, :text
17 | add_column :survey_questions, :locale_text, :string
18 | add_column :survey_questions, :locale_head_number, :string
19 | add_column :survey_questions, :locale_description, :text
20 |
21 | # Survey Options table
22 | add_column :survey_options, :locale_text, :string
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/generators/templates/rails_admin.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RailsAdmin.config do |c|
4 | c.excluded_models = [
5 | Survey::Answer,
6 | Survey::Option,
7 | Survey::Attempt,
8 | Survey::Question
9 | ]
10 | end
11 |
--------------------------------------------------------------------------------
/lib/generators/templates/survey_plain.rb:
--------------------------------------------------------------------------------
1 | class <%= get_scope.capitalize %>::SurveysController < ApplicationController
2 | before_filter :load_survey, :only => [:show, :edit, :update]
3 |
4 | def index
5 | @surveys = Survey::Survey.all
6 | end
7 |
8 | def new
9 | @survey = Survey::Survey.new
10 | end
11 |
12 | def create
13 | @survey = Survey::Survey.new(survey_params)
14 | if @survey.valid? and @survey.save
15 | default_redirect
16 | else
17 | render :action => :new
18 | end
19 | end
20 |
21 | def edit
22 | end
23 |
24 | def show
25 | end
26 |
27 | def update
28 | if @survey.update_attributes(survey_params)
29 | default_redirect
30 | else
31 | render :action => :edit
32 | end
33 | end
34 |
35 | private
36 |
37 | def default_redirect
38 | redirect_to <%= get_scope %>_surveys_path, alert: I18n.t("surveys_controller.#{action_name}")
39 | end
40 |
41 | def load_survey
42 | @survey = Survey::Survey.find(params[:id])
43 | end
44 |
45 | #######
46 | private
47 | #######
48 |
49 | # Rails 4 Strong Params
50 | def survey_params
51 | if Rails::VERSION::MAJOR < 4
52 | params[:survey_survey]
53 | else
54 | protected_attrs = ["created_at", "updated_at"]
55 | params.require(:survey_survey).permit(Survey::Survey.new.attributes.keys - protected_attrs, :sections_attributes => [Survey::Section.new.attributes.keys - protected_attrs, :questions_attributes => [Survey::Question.new.attributes.keys - protected_attrs, :options_attributes => [Survey::Option.new.attributes.keys - protected_attrs]]])
56 | end
57 | end
58 | end
--------------------------------------------------------------------------------
/lib/generators/templates/survey_views/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_for(@survey, :url => survey_scope(@survey)) do |f| %>
2 |
3 | <% if f.object.errors.messages.any? %>
4 |
5 | <% f.object.errors.messages.each_pair do |field, msg| %>
6 | -
7 | <%= "#{field} - #{msg}" %>
8 |
9 | <% end -%>
10 |
11 | <% end -%>
12 |
13 |
14 | <%= f.label :name %>
15 | <%= f.text_field :name %>
16 |
17 |
18 | <%= f.label :locale_name %>
19 | <%= f.text_field :locale_name %>
20 |
21 |
22 | <%= f.label :description %>
23 | <%= f.text_area :description, :size => "100x5" %>
24 |
25 |
26 | <%= f.label :locale_description %>
27 | <%= f.text_area :locale_description %>
28 |
29 |
30 | <%= f.label :attempts_number %>
31 | <%= f.text_field :attempts_number %>
32 |
33 |
34 | <%= f.label :active %>
35 | <%= f.select :active, ["true", "false"] %>
36 |
37 |
38 |
39 |
40 |
41 |
42 | <%= f.fields_for :sections do |builder| %>
43 | <%= render "section_fields", :f => builder %>
44 | <% end %>
45 |
46 |
47 | <%= link_to_add_field "Add a new Section", f, :sections %>
48 |
49 |
50 |
51 |
52 |
53 | <%= f.submit %>
54 |
55 | <% end -%>
56 |
57 |
--------------------------------------------------------------------------------
/lib/generators/templates/survey_views/_option_fields.html.erb:
--------------------------------------------------------------------------------
1 |
2 | Option
3 |
4 | <%= f.label :head_number %>
5 | <%= f.text_field :head_number %>
6 |
7 |
8 | <%= f.label :text %>
9 | <%= f.text_field :text %>
10 |
11 |
12 | <%= f.label :locale_text %>
13 | <%= f.text_field :locale_text %>
14 |
15 |
16 | <%= f.label :options_type_id %>
17 | <%= f.select :options_type_id, Survey::OptionsType.options_types_title %>
18 |
19 |
20 | <%= f.check_box :correct %>
21 | <%= f.label :correct %>
22 |
23 |
24 | <%= link_to_remove_field "Remove", f %>
25 |
26 |
--------------------------------------------------------------------------------
/lib/generators/templates/survey_views/_question_fields.html.erb:
--------------------------------------------------------------------------------
1 |
2 | Question
3 |
4 | <%= f.label :head_number %>
5 | <%= f.text_field :head_number %>
6 |
7 |
8 | <%= f.label :locale_head_number %>
9 | <%= f.text_field :locale_head_number %>
10 |
11 |
12 | <%= f.label :text %>
13 | <%= f.text_field :text %>
14 |
15 |
16 | <%= f.label :locale_text %>
17 | <%= f.text_field :locale_text %>
18 |
19 |
20 | <%= f.label :description %>
21 | <%= f.text_area :description %>
22 |
23 |
24 | <%= f.label :locale_description %>
25 | <%= f.text_area :locale_description %>
26 |
27 |
28 | <%= f.label :questions_type_id %>
29 | <%= f.select :questions_type_id, Survey::QuestionsType.questions_types_title %>
30 |
31 |
32 |
33 |
34 | <%= f.fields_for :options do |builder| %>
35 | <%= render "option_fields", :f => builder %>
36 | <% end -%>
37 |
38 | <%= link_to_add_field "Add a new Option", f, :options %>
39 |
40 | <%= link_to_remove_field "Remove Question", f %>
41 |
42 |
43 |
--------------------------------------------------------------------------------
/lib/generators/templates/survey_views/_section_fields.html.erb:
--------------------------------------------------------------------------------
1 |
2 | Section
3 | <%= f.hidden_field :id %>
4 |
5 | <%= f.label :head_number %>
6 | <%= f.text_field :locale_head_number %>
7 |
8 |
9 | <%= f.label :name %>
10 | <%= f.text_field :name %>
11 |
12 |
13 | <%= f.label :locale_name %>
14 | <%= f.text_field :locale_name %>
15 |
16 |
17 | <%= f.label :description %>
18 | <%= f.text_field :locale_description %>
19 |
20 |
21 |
22 | <%= f.fields_for :questions do |builder| %>
23 | <%= render "question_fields", :f => builder %>
24 | <% end %>
25 |
26 |
27 | <%= link_to_add_field "Add a new Question", f, :questions %>
28 |
29 |
--------------------------------------------------------------------------------
/lib/generators/templates/survey_views/edit.html.erb:
--------------------------------------------------------------------------------
1 | Edit Survey
2 | <%= render 'form' %>
--------------------------------------------------------------------------------
/lib/generators/templates/survey_views/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | Surveys Index
4 |
5 |
6 | <%= link_to "New Survey", new_survey_path %>
7 |
8 | <% @surveys.each do |survey| %>
9 | -
10 | <%= link_to survey.name, edit_survey_path(survey) %>
11 |
12 | <% end -%>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lib/generators/templates/survey_views/new.html.erb:
--------------------------------------------------------------------------------
1 | New Survey
2 | <%= render 'form' %>
--------------------------------------------------------------------------------
/lib/survey.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'survey/engine'
4 | require 'survey/version'
5 | require 'survey/active_record'
6 |
7 | ActiveRecord::Base.send(:include, Survey::ActiveRecord)
8 |
--------------------------------------------------------------------------------
/lib/survey/active_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Survey
4 | module ActiveRecord
5 | extend ActiveSupport::Concern
6 |
7 | module ClassMethods
8 | def has_surveys
9 | has_many :survey_attempts, as: :participant, class_name: 'Survey::Attempt'
10 |
11 | define_method('for_survey') do |survey|
12 | survey_attempts.where(survey_id: survey.id)
13 | end
14 | end
15 |
16 | def has_many_surveys
17 | has_many :surveys, class_name: 'Survey::Survey'
18 | end
19 |
20 | def has_one_survey
21 | has_one :survey, class_name: 'Survey::Survey'
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/survey/engine.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails'
4 |
5 | module Survey
6 | class Engine < Rails::Engine
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/survey/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Survey
4 | VERSION = '0.1'
5 | end
6 |
--------------------------------------------------------------------------------
/survey.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | # frozen_string_literal: true
3 |
4 | $LOAD_PATH.push File.expand_path('../lib', __FILE__)
5 | require 'survey/version'
6 |
7 | Gem::Specification.new do |s|
8 | s.name = 'questionnaire_engine'
9 | s.version = Survey::VERSION
10 | s.summary = %(Questionnaire is a user oriented tool that brings surveys into Rails applications.)
11 | s.description = %(A rails gem to enable surveys in your application as easy as possible)
12 | s.files = Dir['{app,lib,config}/**/*'] + ['MIT-LICENSE', 'Rakefile', 'Gemfile', 'README.md']
13 | s.authors = 'Clearfunction'
14 | s.email = 'keith@clearfunction.com'
15 | s.homepage = 'https://github.com/clearfunction/questionnaire'
16 | s.licenses = 'MIT'
17 | s.require_paths = %w[lib]
18 |
19 | s.add_dependency('rails', '~> 5.1')
20 | s.add_development_dependency('mocha')
21 | s.add_development_dependency('faker')
22 | s.add_development_dependency('rake')
23 | s.add_development_dependency('rubocop')
24 | s.add_development_dependency('pry-rails')
25 | s.add_development_dependency('pry-byebug')
26 | end
27 |
--------------------------------------------------------------------------------
/test/dummy/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 | ruby '2.1.5'
5 |
6 | gem 'execjs'
7 | gem 'rails', '3.1'
8 | gem 'therubyracer'
9 |
10 | group :development, :test do
11 | gem 'faker'
12 | gem 'pry-rails'
13 | end
14 |
15 | gem 'sqlite3'
16 |
17 | # Gems used only for assets and not required
18 | # in production environments by default.
19 | group :assets do
20 | gem 'coffee-rails'
21 | gem 'meta_search'
22 | gem 'sass-rails'
23 | gem 'uglifier'
24 | end
25 |
26 | gem 'jquery-rails'
27 |
--------------------------------------------------------------------------------
/test/dummy/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actionmailer (3.1.0)
5 | actionpack (= 3.1.0)
6 | mail (~> 2.3.0)
7 | actionpack (3.1.0)
8 | activemodel (= 3.1.0)
9 | activesupport (= 3.1.0)
10 | builder (~> 3.0.0)
11 | erubis (~> 2.7.0)
12 | i18n (~> 0.6)
13 | rack (~> 1.3.2)
14 | rack-cache (~> 1.0.3)
15 | rack-mount (~> 0.8.2)
16 | rack-test (~> 0.6.1)
17 | sprockets (~> 2.0.0)
18 | activemodel (3.1.0)
19 | activesupport (= 3.1.0)
20 | bcrypt-ruby (~> 3.0.0)
21 | builder (~> 3.0.0)
22 | i18n (~> 0.6)
23 | activerecord (3.1.0)
24 | activemodel (= 3.1.0)
25 | activesupport (= 3.1.0)
26 | arel (~> 2.2.1)
27 | tzinfo (~> 0.3.29)
28 | activeresource (3.1.0)
29 | activemodel (= 3.1.0)
30 | activesupport (= 3.1.0)
31 | activesupport (3.1.0)
32 | multi_json (~> 1.0)
33 | arel (2.2.3)
34 | bcrypt-ruby (3.0.1)
35 | builder (3.0.4)
36 | coderay (1.1.1)
37 | coffee-rails (3.1.1)
38 | coffee-script (>= 2.2.0)
39 | railties (~> 3.1.0)
40 | coffee-script (2.4.1)
41 | coffee-script-source
42 | execjs
43 | coffee-script-source (1.12.2)
44 | erubis (2.7.0)
45 | execjs (2.7.0)
46 | faker (1.7.3)
47 | i18n (~> 0.5)
48 | hike (1.2.3)
49 | i18n (0.8.4)
50 | jquery-rails (3.1.4)
51 | railties (>= 3.0, < 5.0)
52 | thor (>= 0.14, < 2.0)
53 | json (1.8.6)
54 | libv8 (3.16.14.19)
55 | mail (2.3.3)
56 | i18n (>= 0.4.0)
57 | mime-types (~> 1.16)
58 | treetop (~> 1.4.8)
59 | meta_search (1.1.3)
60 | actionpack (~> 3.1)
61 | activerecord (~> 3.1)
62 | activesupport (~> 3.1)
63 | polyamorous (~> 0.5.0)
64 | method_source (0.8.2)
65 | mime-types (1.25.1)
66 | multi_json (1.12.1)
67 | polyamorous (0.5.0)
68 | activerecord (~> 3.0)
69 | polyglot (0.3.5)
70 | pry (0.10.4)
71 | coderay (~> 1.1.0)
72 | method_source (~> 0.8.1)
73 | slop (~> 3.4)
74 | pry-rails (0.3.6)
75 | pry (>= 0.10.4)
76 | rack (1.3.10)
77 | rack-cache (1.0.3)
78 | rack (>= 0.4)
79 | rack-mount (0.8.3)
80 | rack (>= 1.0.0)
81 | rack-ssl (1.3.4)
82 | rack
83 | rack-test (0.6.3)
84 | rack (>= 1.0)
85 | rails (3.1.0)
86 | actionmailer (= 3.1.0)
87 | actionpack (= 3.1.0)
88 | activerecord (= 3.1.0)
89 | activeresource (= 3.1.0)
90 | activesupport (= 3.1.0)
91 | bundler (~> 1.0)
92 | railties (= 3.1.0)
93 | railties (3.1.0)
94 | actionpack (= 3.1.0)
95 | activesupport (= 3.1.0)
96 | rack-ssl (~> 1.3.2)
97 | rake (>= 0.8.7)
98 | rdoc (~> 3.4)
99 | thor (~> 0.14.6)
100 | rake (12.3.3)
101 | rdoc (3.12.2)
102 | json (~> 1.4)
103 | ref (2.0.0)
104 | sass (3.4.24)
105 | sass-rails (3.1.7)
106 | actionpack (~> 3.1.0)
107 | railties (~> 3.1.0)
108 | sass (>= 3.1.10)
109 | tilt (~> 1.3.2)
110 | slop (3.6.0)
111 | sprockets (2.0.5)
112 | hike (~> 1.2)
113 | rack (~> 1.0)
114 | tilt (~> 1.1, != 1.3.0)
115 | sqlite3 (1.3.13)
116 | therubyracer (0.12.3)
117 | libv8 (~> 3.16.14.15)
118 | ref
119 | thor (0.14.6)
120 | tilt (1.3.7)
121 | treetop (1.4.15)
122 | polyglot
123 | polyglot (>= 0.3.1)
124 | tzinfo (0.3.61)
125 | uglifier (3.2.0)
126 | execjs (>= 0.3.0, < 3)
127 |
128 | PLATFORMS
129 | ruby
130 |
131 | DEPENDENCIES
132 | coffee-rails
133 | execjs
134 | faker
135 | jquery-rails
136 | meta_search
137 | pry-rails
138 | rails (= 3.1)
139 | sass-rails
140 | sqlite3
141 | therubyracer
142 | uglifier
143 |
--------------------------------------------------------------------------------
/test/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Add your own tasks in files placed in lib/tasks ending in .rake,
4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5 |
6 | require File.expand_path('../config/application', __FILE__)
7 | require 'rake'
8 |
9 | Dummy::Application.load_tasks
10 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // Place all the behaviors and hooks related to the matching controller here.
2 | // All this logic will automatically be available in application.js.
3 |
4 | //= require jquery
5 | //= require jquery_ujs
6 | //= require_tree .
7 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/javascripts/users.js:
--------------------------------------------------------------------------------
1 | // Place all the behaviors and hooks related to the matching controller here.
2 | // All this logic will automatically be available in application.js.
3 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/users.css:
--------------------------------------------------------------------------------
1 | /*
2 | Place all the styles related to the matching controller here.
3 | They will automatically be included in application.css.
4 | */
5 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | protect_from_forgery
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class UsersController < ApplicationController
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/welcome_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class WelcomeController < ApplicationController
4 | def index
5 | render text: 'HELLO WORLD'
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationHelper
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/users_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module UsersHelper
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/models/lesson.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class Lesson < ActiveRecord::Base
4 | has_many_surveys
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy/app/models/survey/belongs_to_lesson.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Survey
4 | module BelongsToLesson
5 | extend ActiveSupport::Concern
6 | included do
7 | belongs_to :lesson
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/test/dummy/app/models/user.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class User < ActiveRecord::Base
4 | has_surveys
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%#= stylesheet_link_tag :all %>
6 | <%= javascript_include_tag "application" %>
7 | <%= csrf_meta_tag %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is used by Rack-based servers to start the application.
4 |
5 | require ::File.expand_path('../config/environment', __FILE__)
6 | run Dummy::Application
7 |
--------------------------------------------------------------------------------
/test/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require File.expand_path('../boot', __FILE__)
4 |
5 | require 'active_model/railtie'
6 | require 'active_record/railtie'
7 | require 'action_controller/railtie'
8 | require 'action_view/railtie'
9 | require 'action_mailer/railtie'
10 | require 'sprockets/railtie'
11 |
12 | Bundler.require
13 | require 'survey'
14 | module Dummy
15 | class Application < Rails::Application
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration should go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded.
19 |
20 | # Custom directories with classes and modules you want to be autoloadable.
21 | # config.autoload_paths += %W(#{config.root}/extras)
22 |
23 | # Only load the plugins named here, in the order given (default is alphabetical).
24 | # :all can be used as a placeholder for all plugins not explicitly named.
25 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
26 |
27 | # Activate observers that should always be running.
28 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
29 |
30 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
31 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
32 | # config.time_zone = 'Central Time (US & Canada)'
33 |
34 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
35 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
36 | # config.i18n.default_locale = :de
37 |
38 | # JavaScript files you want as :defaults (application.js is always included).
39 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
40 |
41 | # Enable the asset pipeline
42 | config.assets.enabled = true
43 | # Version of your assets, change this if you want to expire all your assets
44 | config.assets.version = '1.0'
45 | config.assets.initialize_on_precompile = true
46 |
47 | # Configure the default encoding used in templates for Ruby 1.9.
48 | config.encoding = 'utf-8'
49 |
50 | # Configure sensitive parameters which will be filtered from the log file.
51 | config.filter_parameters += [:password]
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/test/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rubygems'
4 | gemfile = File.expand_path('../../../../Gemfile', __FILE__)
5 |
6 | if File.exist?(gemfile)
7 | ENV['BUNDLE_GEMFILE'] = gemfile
8 | require 'bundler'
9 | Bundler.setup
10 | end
11 |
12 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
13 |
--------------------------------------------------------------------------------
/test/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | development:
4 | adapter: sqlite3
5 | database: db/development.sqlite3
6 | pool: 5
7 | timeout: 5000
8 |
9 | # Warning: The database defined as "test" will be erased and
10 | # re-generated from your development database when you run "rake".
11 | # Do not set this db to the same as development or production.
12 | test:
13 | adapter: sqlite3
14 | database: db/test.sqlite3
15 | pool: 5
16 | timeout: 5000
17 |
18 | production:
19 | adapter: sqlite3
20 | database: db/production.sqlite3
21 | pool: 5
22 | timeout: 5000
23 |
--------------------------------------------------------------------------------
/test/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load the rails application
4 | require File.expand_path('../application', __FILE__)
5 |
6 | # Initialize the rails application
7 | Dummy::Application.initialize!
8 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Dummy::Application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb
5 |
6 | # In the development environment your application's code is reloaded on
7 | # every request. This slows down response time but is perfect for development
8 | # since you don't have to restart the webserver when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Log error messages when you accidentally call methods on nil.
12 | config.whiny_nils = true
13 |
14 | # Show full error reports and disable caching
15 | config.consider_all_requests_local = true
16 | config.action_controller.perform_caching = false
17 |
18 | # Don't care if the mailer can't send
19 | config.action_mailer.raise_delivery_errors = false
20 |
21 | # Print deprecation notices to the Rails logger
22 | config.active_support.deprecation = :log
23 |
24 | # Only use best-standards-support built into browsers
25 | config.action_dispatch.best_standards_support = :builtin
26 | end
27 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Dummy::Application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb
5 |
6 | # The production environment is meant for finished, "live" apps.
7 | # Code is not reloaded between requests
8 | config.cache_classes = true
9 |
10 | # Full error reports are disabled and caching is turned on
11 | config.consider_all_requests_local = false
12 | config.action_controller.perform_caching = true
13 |
14 | # Specifies the header that your server uses for sending files
15 | config.action_dispatch.x_sendfile_header = 'X-Sendfile'
16 |
17 | # For nginx:
18 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
19 |
20 | # If you have no front-end server that supports something like X-Sendfile,
21 | # just comment this out and Rails will serve the files
22 |
23 | # See everything in the log (default is :info)
24 | # config.log_level = :debug
25 |
26 | # Use a different logger for distributed setups
27 | # config.logger = SyslogLogger.new
28 |
29 | # Use a different cache store in production
30 | # config.cache_store = :mem_cache_store
31 |
32 | # Disable Rails's static asset server
33 | # In production, Apache or nginx will already do this
34 | config.serve_static_assets = false
35 |
36 | # Enable serving of images, stylesheets, and javascripts from an asset server
37 | # config.action_controller.asset_host = "http://assets.example.com"
38 |
39 | # Disable delivery errors, bad email addresses will be ignored
40 | # config.action_mailer.raise_delivery_errors = false
41 |
42 | # Enable threaded mode
43 | # config.threadsafe!
44 |
45 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
46 | # the I18n.default_locale when a translation can not be found)
47 | config.i18n.fallbacks = true
48 |
49 | # Send deprecation notices to registered listeners
50 | config.active_support.deprecation = :notify
51 | end
52 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Dummy::Application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb
5 |
6 | # The test environment is used exclusively to run your application's
7 | # test suite. You never need to work with it otherwise. Remember that
8 | # your test database is "scratch space" for the test suite and is wiped
9 | # and recreated between test runs. Don't rely on the data there!
10 | config.cache_classes = true
11 |
12 | # Log error messages when you accidentally call methods on nil.
13 | config.whiny_nils = true
14 |
15 | # Show full error reports and disable caching
16 | config.consider_all_requests_local = true
17 | config.action_controller.perform_caching = false
18 |
19 | # Raise exceptions instead of rendering exception templates
20 | config.action_dispatch.show_exceptions = false
21 |
22 | # Disable request forgery protection in test environment
23 | config.action_controller.allow_forgery_protection = false
24 |
25 | # Tell Action Mailer not to deliver emails to the real world.
26 | # The :test delivery method accumulates sent emails in the
27 | # ActionMailer::Base.deliveries array.
28 | config.action_mailer.delivery_method = :test
29 |
30 | # Use SQL instead of Active Record's schema dumper when creating the test database.
31 | # This is necessary if your schema can't be completely dumped by the schema dumper,
32 | # like if you have constraints or database-specific column types
33 | # config.active_record.schema_format = :sql
34 |
35 | # Print deprecation notices to the stderr
36 | config.active_support.deprecation = :stderr
37 | end
38 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
6 |
7 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
8 | # Rails.backtrace_cleaner.remove_silencers!
9 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new inflection rules using the following format
5 | # (all these examples are active by default):
6 | # ActiveSupport::Inflector.inflections do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new mime types for use in respond_to blocks:
5 | # Mime::Type.register "text/richtext", :rtf
6 | # Mime::Type.register_alias "text/html", :iphone
7 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Your secret key for verifying the integrity of signed cookies.
6 | # If you change this key, all old signed cookies will become invalid!
7 | # Make sure the secret is at least 30 characters and all random,
8 | # no regular words or you'll be exposed to dictionary attacks.
9 | Dummy::Application.config.secret_token = 'a96634b5f2750043d337dc7cf99f46dae98fe918cadb32e93b85312f247149b633b5bb3499da3826ee6b67588e64c57f0c86feaca2dc0a51db143a72da530c65'
10 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
6 |
7 | # Use the database for sessions instead of the cookie-based default,
8 | # which shouldn't be used to store highly confidential information
9 | # (create the session table with "rails generate session_migration")
10 | # Dummy::Application.config.session_store :active_record_store
11 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/survey.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Survey::Survey.include Survey::BelongsToLesson
4 |
--------------------------------------------------------------------------------
/test/dummy/config/locales/devise.en.yml:
--------------------------------------------------------------------------------
1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2 |
3 | en:
4 | errors:
5 | messages:
6 | expired: "has expired, please request a new one"
7 | not_found: "not found"
8 | already_confirmed: "was already confirmed, please try signing in"
9 | not_locked: "was not locked"
10 | not_saved:
11 | one: "1 error prohibited this %{resource} from being saved:"
12 | other: "%{count} errors prohibited this %{resource} from being saved:"
13 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
14 |
15 | devise:
16 | failure:
17 | already_authenticated: 'You are already signed in.'
18 | unauthenticated: 'You need to sign in or sign up before continuing.'
19 | unconfirmed: 'You have to confirm your account before continuing.'
20 | locked: 'Your account is locked.'
21 | not_found_in_database: 'Invalid email or password.'
22 | invalid: 'Invalid email or password.'
23 | invalid_token: 'Invalid authentication token.'
24 | timeout: 'Your session expired, please sign in again to continue.'
25 | inactive: 'Your account was not activated yet.'
26 | sessions:
27 | signed_in: 'Signed in successfully.'
28 | signed_out: 'Signed out successfully.'
29 | passwords:
30 | send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
31 | updated: 'Your password was changed successfully. You are now signed in.'
32 | updated_not_active: 'Your password was changed successfully.'
33 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
35 | confirmations:
36 | send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
37 | send_paranoid_instructions: 'If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes.'
38 | confirmed: 'Your account was successfully confirmed. You are now signed in.'
39 | registrations:
40 | signed_up: 'Welcome! You have signed up successfully.'
41 | signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
42 | signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.'
43 | signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'
44 | updated: 'You updated your account successfully.'
45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address."
46 | destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
47 | unlocks:
48 | send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
49 | unlocked: 'Your account has been unlocked successfully. Please sign in to continue.'
50 | send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.'
51 | omniauth_callbacks:
52 | success: 'Successfully authenticated from %{kind} account.'
53 | failure: 'Could not authenticate you from %{kind} because "%{reason}".'
54 | mailer:
55 | confirmation_instructions:
56 | subject: 'Confirmation instructions'
57 | reset_password_instructions:
58 | subject: 'Reset password instructions'
59 | unlock_instructions:
60 | subject: 'Unlock Instructions'
61 |
--------------------------------------------------------------------------------
/test/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | en:
5 | hello: "Hello world"
6 |
--------------------------------------------------------------------------------
/test/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Dummy::Application.routes.draw do
4 | root to: 'welcome#index'
5 | resources :users
6 | end
7 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20130123110019_create_users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateUsers < ActiveRecord::Migration
4 | def change
5 | create_table :users do |t|
6 | t.string :name
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20130201105206_create_survey.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateSurvey < ActiveRecord::Migration
4 | def self.up
5 | # survey surveys logic
6 | create_table :survey_surveys do |t|
7 | t.string :name
8 | t.text :description
9 | t.integer :attempts_number, default: 0
10 | t.boolean :finished, default: false
11 | t.boolean :active, default: true
12 |
13 | t.timestamps
14 | end
15 |
16 | create_table :survey_questions do |t|
17 | t.integer :survey_id
18 | t.string :text
19 |
20 | t.timestamps
21 | end
22 |
23 | create_table :survey_options do |t|
24 | t.integer :question_id
25 | t.integer :weight, default: 0
26 | t.string :text
27 | t.boolean :correct
28 |
29 | t.timestamps
30 | end
31 |
32 | # survey answer logic
33 | create_table :survey_attempts do |t|
34 | t.belongs_to :participant, polymorphic: true
35 | t.integer :survey_id
36 | t.integer :score
37 | end
38 |
39 | create_table :survey_answers do |t|
40 | t.integer :attempt_id
41 | t.integer :question_id
42 | t.integer :option_id
43 | t.boolean :correct
44 | t.timestamps
45 | end
46 | end
47 |
48 | def self.down
49 | drop_table :survey_surveys
50 | drop_table :survey_questions
51 | drop_table :survey_options
52 |
53 | drop_table :survey_attempts
54 | drop_table :survey_answers
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20130904084520_create_sections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateSections < ActiveRecord::Migration
4 | def self.up
5 | create_table :survey_sections do |t|
6 | t.string :head_number
7 | t.string :name
8 | t.text :description
9 | t.integer :survey_id
10 |
11 | t.timestamps
12 | end
13 |
14 | remove_column :survey_questions, :survey_id
15 | add_column :survey_questions, :section_id, :integer
16 | end
17 |
18 | def self.down
19 | drop_table :survey_sections
20 |
21 | remove_column :survey_questions, :section_id
22 | add_column :survey_questions, :survey_id, :integer
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20130904115621_update_survey_tables.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class UpdateSurveyTables < ActiveRecord::Migration
4 | def change
5 | # Survey Surveys table
6 | add_column :survey_surveys, :locale_name, :string
7 | add_column :survey_surveys, :locale_description, :text
8 |
9 | # Survey Sections table
10 | add_column :survey_sections, :locale_head_number, :string
11 | add_column :survey_sections, :locale_name, :string
12 | add_column :survey_sections, :locale_description, :text
13 |
14 | # Survey Questions table
15 | add_column :survey_questions, :head_number, :string
16 | add_column :survey_questions, :description, :text
17 | add_column :survey_questions, :locale_text, :string
18 | add_column :survey_questions, :locale_head_number, :string
19 | add_column :survey_questions, :locale_description, :text
20 |
21 | # Survey Options table
22 | add_column :survey_options, :locale_text, :string
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20130905093710_add_types_to_questions_and_options.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddTypesToQuestionsAndOptions < ActiveRecord::Migration
4 | def change
5 | # Survey Questions table
6 | add_column :survey_questions, :questions_type_id, :integer
7 |
8 | # Survey Options table
9 | add_column :survey_options, :options_type_id, :integer
10 |
11 | # Survey Answers table
12 | add_column :survey_answers, :option_text, :text
13 | add_column :survey_answers, :option_number, :integer
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20130916081314_add_head_number_to_options_table.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddHeadNumberToOptionsTable < ActiveRecord::Migration
4 | def change
5 | # Survey Options table
6 | add_column :survey_options, :head_number, :string
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20130916102353_create_predefined_values_table.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreatePredefinedValuesTable < ActiveRecord::Migration
4 | def change
5 | create_table :survey_predefined_values do |t|
6 | t.string :head_number
7 | t.string :name
8 | t.string :locale_name
9 | t.integer :question_id
10 |
11 | t.timestamps
12 | end
13 |
14 | add_column :survey_answers, :predefined_value_id, :integer
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20130929102221_add_mandatory_to_questions_table.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddMandatoryToQuestionsTable < ActiveRecord::Migration
4 | def change
5 | # Survey Questions table
6 | add_column :survey_questions, :mandatory, :boolean, default: false
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20170619155054_create_lessons.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateLessons < ActiveRecord::Migration
4 | def change
5 | create_table :lessons do |t|
6 | t.string :name
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/dummy/db/migrate/20170619155608_add_lesson_id_to_survey_surveys.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddLessonIdToSurveySurveys < ActiveRecord::Migration
4 | def change
5 | add_column :survey_surveys, :lesson_id, :integer
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # frozen_string_literal: true
3 |
4 | # This file is auto-generated from the current state of the database. Instead
5 | # of editing this file, please use the migrations feature of Active Record to
6 | # incrementally modify your database, and then regenerate this schema definition.
7 | #
8 | # Note that this schema.rb definition is the authoritative source for your
9 | # database schema. If you need to create the application database on another
10 | # system, you should be using db:schema:load, not running all the migrations
11 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
12 | # you'll amass, the slower it'll run and the greater likelihood for issues).
13 | #
14 | # It's strongly recommended to check this file into your version control system.
15 |
16 | ActiveRecord::Schema.define(version: 20170619155608) do
17 | create_table 'lessons', force: true do |t|
18 | t.string 'name'
19 | t.datetime 'created_at'
20 | t.datetime 'updated_at'
21 | end
22 |
23 | create_table 'survey_answers', force: true do |t|
24 | t.integer 'attempt_id'
25 | t.integer 'question_id'
26 | t.integer 'option_id'
27 | t.boolean 'correct'
28 | t.datetime 'created_at'
29 | t.datetime 'updated_at'
30 | t.text 'option_text'
31 | t.integer 'option_number'
32 | t.integer 'predefined_value_id'
33 | end
34 |
35 | create_table 'survey_attempts', force: true do |t|
36 | t.integer 'participant_id'
37 | t.string 'participant_type'
38 | t.integer 'survey_id'
39 | t.integer 'score'
40 | end
41 |
42 | create_table 'survey_options', force: true do |t|
43 | t.integer 'question_id'
44 | t.integer 'weight', default: 0
45 | t.string 'text'
46 | t.boolean 'correct'
47 | t.datetime 'created_at'
48 | t.datetime 'updated_at'
49 | t.string 'locale_text'
50 | t.integer 'options_type_id'
51 | t.string 'head_number'
52 | end
53 |
54 | create_table 'survey_predefined_values', force: true do |t|
55 | t.string 'head_number'
56 | t.string 'name'
57 | t.string 'locale_name'
58 | t.integer 'question_id'
59 | t.datetime 'created_at'
60 | t.datetime 'updated_at'
61 | end
62 |
63 | create_table 'survey_questions', force: true do |t|
64 | t.string 'text'
65 | t.datetime 'created_at'
66 | t.datetime 'updated_at'
67 | t.integer 'section_id'
68 | t.string 'head_number'
69 | t.text 'description'
70 | t.string 'locale_text'
71 | t.string 'locale_head_number'
72 | t.text 'locale_description'
73 | t.integer 'questions_type_id'
74 | t.boolean 'mandatory', default: false
75 | end
76 |
77 | create_table 'survey_sections', force: true do |t|
78 | t.string 'head_number'
79 | t.string 'name'
80 | t.text 'description'
81 | t.integer 'survey_id'
82 | t.datetime 'created_at'
83 | t.datetime 'updated_at'
84 | t.string 'locale_head_number'
85 | t.string 'locale_name'
86 | t.text 'locale_description'
87 | end
88 |
89 | create_table 'survey_surveys', force: true do |t|
90 | t.string 'name'
91 | t.text 'description'
92 | t.integer 'attempts_number', default: 0
93 | t.boolean 'finished', default: false
94 | t.boolean 'active', default: true
95 | t.datetime 'created_at'
96 | t.datetime 'updated_at'
97 | t.string 'locale_name'
98 | t.text 'locale_description'
99 | t.integer 'lesson_id'
100 | end
101 |
102 | create_table 'users', force: true do |t|
103 | t.string 'name'
104 | t.datetime 'created_at'
105 | t.datetime 'updated_at'
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/test/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
We've been notified about this issue and we'll take a look at it shortly.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clearfunction/questionnaire/61be93261334b89ea812ad38bd75d1c361295e88/test/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/test/dummy/public/javascripts/controls.js:
--------------------------------------------------------------------------------
1 | // script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
2 |
3 | // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4 | // (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
5 | // (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
6 | // Contributors:
7 | // Richard Livsey
8 | // Rahul Bhargava
9 | // Rob Wills
10 | //
11 | // script.aculo.us is freely distributable under the terms of an MIT-style license.
12 | // For details, see the script.aculo.us web site: http://script.aculo.us/
13 |
14 | // Autocompleter.Base handles all the autocompletion functionality
15 | // that's independent of the data source for autocompletion. This
16 | // includes drawing the autocompletion menu, observing keyboard
17 | // and mouse events, and similar.
18 | //
19 | // Specific autocompleters need to provide, at the very least,
20 | // a getUpdatedChoices function that will be invoked every time
21 | // the text inside the monitored textbox changes. This method
22 | // should get the text for which to provide autocompletion by
23 | // invoking this.getToken(), NOT by directly accessing
24 | // this.element.value. This is to allow incremental tokenized
25 | // autocompletion. Specific auto-completion logic (AJAX, etc)
26 | // belongs in getUpdatedChoices.
27 | //
28 | // Tokenized incremental autocompletion is enabled automatically
29 | // when an autocompleter is instantiated with the 'tokens' option
30 | // in the options parameter, e.g.:
31 | // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
32 | // will incrementally autocomplete with a comma as the token.
33 | // Additionally, ',' in the above example can be replaced with
34 | // a token array, e.g. { tokens: [',', '\n'] } which
35 | // enables autocompletion on multiple tokens. This is most
36 | // useful when one of the tokens is \n (a newline), as it
37 | // allows smart autocompletion after linebreaks.
38 |
39 | if(typeof Effect == 'undefined')
40 | throw("controls.js requires including script.aculo.us' effects.js library");
41 |
42 | var Autocompleter = { };
43 | Autocompleter.Base = Class.create({
44 | baseInitialize: function(element, update, options) {
45 | element = $(element);
46 | this.element = element;
47 | this.update = $(update);
48 | this.hasFocus = false;
49 | this.changed = false;
50 | this.active = false;
51 | this.index = 0;
52 | this.entryCount = 0;
53 | this.oldElementValue = this.element.value;
54 |
55 | if(this.setOptions)
56 | this.setOptions(options);
57 | else
58 | this.options = options || { };
59 |
60 | this.options.paramName = this.options.paramName || this.element.name;
61 | this.options.tokens = this.options.tokens || [];
62 | this.options.frequency = this.options.frequency || 0.4;
63 | this.options.minChars = this.options.minChars || 1;
64 | this.options.onShow = this.options.onShow ||
65 | function(element, update){
66 | if(!update.style.position || update.style.position=='absolute') {
67 | update.style.position = 'absolute';
68 | Position.clone(element, update, {
69 | setHeight: false,
70 | offsetTop: element.offsetHeight
71 | });
72 | }
73 | Effect.Appear(update,{duration:0.15});
74 | };
75 | this.options.onHide = this.options.onHide ||
76 | function(element, update){ new Effect.Fade(update,{duration:0.15}) };
77 |
78 | if(typeof(this.options.tokens) == 'string')
79 | this.options.tokens = new Array(this.options.tokens);
80 | // Force carriage returns as token delimiters anyway
81 | if (!this.options.tokens.include('\n'))
82 | this.options.tokens.push('\n');
83 |
84 | this.observer = null;
85 |
86 | this.element.setAttribute('autocomplete','off');
87 |
88 | Element.hide(this.update);
89 |
90 | Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
91 | Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
92 | },
93 |
94 | show: function() {
95 | if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
96 | if(!this.iefix &&
97 | (Prototype.Browser.IE) &&
98 | (Element.getStyle(this.update, 'position')=='absolute')) {
99 | new Insertion.After(this.update,
100 | '');
103 | this.iefix = $(this.update.id+'_iefix');
104 | }
105 | if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
106 | },
107 |
108 | fixIEOverlapping: function() {
109 | Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
110 | this.iefix.style.zIndex = 1;
111 | this.update.style.zIndex = 2;
112 | Element.show(this.iefix);
113 | },
114 |
115 | hide: function() {
116 | this.stopIndicator();
117 | if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
118 | if(this.iefix) Element.hide(this.iefix);
119 | },
120 |
121 | startIndicator: function() {
122 | if(this.options.indicator) Element.show(this.options.indicator);
123 | },
124 |
125 | stopIndicator: function() {
126 | if(this.options.indicator) Element.hide(this.options.indicator);
127 | },
128 |
129 | onKeyPress: function(event) {
130 | if(this.active)
131 | switch(event.keyCode) {
132 | case Event.KEY_TAB:
133 | case Event.KEY_RETURN:
134 | this.selectEntry();
135 | Event.stop(event);
136 | case Event.KEY_ESC:
137 | this.hide();
138 | this.active = false;
139 | Event.stop(event);
140 | return;
141 | case Event.KEY_LEFT:
142 | case Event.KEY_RIGHT:
143 | return;
144 | case Event.KEY_UP:
145 | this.markPrevious();
146 | this.render();
147 | Event.stop(event);
148 | return;
149 | case Event.KEY_DOWN:
150 | this.markNext();
151 | this.render();
152 | Event.stop(event);
153 | return;
154 | }
155 | else
156 | if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
157 | (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
158 |
159 | this.changed = true;
160 | this.hasFocus = true;
161 |
162 | if(this.observer) clearTimeout(this.observer);
163 | this.observer =
164 | setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
165 | },
166 |
167 | activate: function() {
168 | this.changed = false;
169 | this.hasFocus = true;
170 | this.getUpdatedChoices();
171 | },
172 |
173 | onHover: function(event) {
174 | var element = Event.findElement(event, 'LI');
175 | if(this.index != element.autocompleteIndex)
176 | {
177 | this.index = element.autocompleteIndex;
178 | this.render();
179 | }
180 | Event.stop(event);
181 | },
182 |
183 | onClick: function(event) {
184 | var element = Event.findElement(event, 'LI');
185 | this.index = element.autocompleteIndex;
186 | this.selectEntry();
187 | this.hide();
188 | },
189 |
190 | onBlur: function(event) {
191 | // needed to make click events working
192 | setTimeout(this.hide.bind(this), 250);
193 | this.hasFocus = false;
194 | this.active = false;
195 | },
196 |
197 | render: function() {
198 | if(this.entryCount > 0) {
199 | for (var i = 0; i < this.entryCount; i++)
200 | this.index==i ?
201 | Element.addClassName(this.getEntry(i),"selected") :
202 | Element.removeClassName(this.getEntry(i),"selected");
203 | if(this.hasFocus) {
204 | this.show();
205 | this.active = true;
206 | }
207 | } else {
208 | this.active = false;
209 | this.hide();
210 | }
211 | },
212 |
213 | markPrevious: function() {
214 | if(this.index > 0) this.index--;
215 | else this.index = this.entryCount-1;
216 | this.getEntry(this.index).scrollIntoView(true);
217 | },
218 |
219 | markNext: function() {
220 | if(this.index < this.entryCount-1) this.index++;
221 | else this.index = 0;
222 | this.getEntry(this.index).scrollIntoView(false);
223 | },
224 |
225 | getEntry: function(index) {
226 | return this.update.firstChild.childNodes[index];
227 | },
228 |
229 | getCurrentEntry: function() {
230 | return this.getEntry(this.index);
231 | },
232 |
233 | selectEntry: function() {
234 | this.active = false;
235 | this.updateElement(this.getCurrentEntry());
236 | },
237 |
238 | updateElement: function(selectedElement) {
239 | if (this.options.updateElement) {
240 | this.options.updateElement(selectedElement);
241 | return;
242 | }
243 | var value = '';
244 | if (this.options.select) {
245 | var nodes = $(selectedElement).select('.' + this.options.select) || [];
246 | if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
247 | } else
248 | value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
249 |
250 | var bounds = this.getTokenBounds();
251 | if (bounds[0] != -1) {
252 | var newValue = this.element.value.substr(0, bounds[0]);
253 | var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
254 | if (whitespace)
255 | newValue += whitespace[0];
256 | this.element.value = newValue + value + this.element.value.substr(bounds[1]);
257 | } else {
258 | this.element.value = value;
259 | }
260 | this.oldElementValue = this.element.value;
261 | this.element.focus();
262 |
263 | if (this.options.afterUpdateElement)
264 | this.options.afterUpdateElement(this.element, selectedElement);
265 | },
266 |
267 | updateChoices: function(choices) {
268 | if(!this.changed && this.hasFocus) {
269 | this.update.innerHTML = choices;
270 | Element.cleanWhitespace(this.update);
271 | Element.cleanWhitespace(this.update.down());
272 |
273 | if(this.update.firstChild && this.update.down().childNodes) {
274 | this.entryCount =
275 | this.update.down().childNodes.length;
276 | for (var i = 0; i < this.entryCount; i++) {
277 | var entry = this.getEntry(i);
278 | entry.autocompleteIndex = i;
279 | this.addObservers(entry);
280 | }
281 | } else {
282 | this.entryCount = 0;
283 | }
284 |
285 | this.stopIndicator();
286 | this.index = 0;
287 |
288 | if(this.entryCount==1 && this.options.autoSelect) {
289 | this.selectEntry();
290 | this.hide();
291 | } else {
292 | this.render();
293 | }
294 | }
295 | },
296 |
297 | addObservers: function(element) {
298 | Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
299 | Event.observe(element, "click", this.onClick.bindAsEventListener(this));
300 | },
301 |
302 | onObserverEvent: function() {
303 | this.changed = false;
304 | this.tokenBounds = null;
305 | if(this.getToken().length>=this.options.minChars) {
306 | this.getUpdatedChoices();
307 | } else {
308 | this.active = false;
309 | this.hide();
310 | }
311 | this.oldElementValue = this.element.value;
312 | },
313 |
314 | getToken: function() {
315 | var bounds = this.getTokenBounds();
316 | return this.element.value.substring(bounds[0], bounds[1]).strip();
317 | },
318 |
319 | getTokenBounds: function() {
320 | if (null != this.tokenBounds) return this.tokenBounds;
321 | var value = this.element.value;
322 | if (value.strip().empty()) return [-1, 0];
323 | var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
324 | var offset = (diff == this.oldElementValue.length ? 1 : 0);
325 | var prevTokenPos = -1, nextTokenPos = value.length;
326 | var tp;
327 | for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
328 | tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
329 | if (tp > prevTokenPos) prevTokenPos = tp;
330 | tp = value.indexOf(this.options.tokens[index], diff + offset);
331 | if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
332 | }
333 | return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
334 | }
335 | });
336 |
337 | Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
338 | var boundary = Math.min(newS.length, oldS.length);
339 | for (var index = 0; index < boundary; ++index)
340 | if (newS[index] != oldS[index])
341 | return index;
342 | return boundary;
343 | };
344 |
345 | Ajax.Autocompleter = Class.create(Autocompleter.Base, {
346 | initialize: function(element, update, url, options) {
347 | this.baseInitialize(element, update, options);
348 | this.options.asynchronous = true;
349 | this.options.onComplete = this.onComplete.bind(this);
350 | this.options.defaultParams = this.options.parameters || null;
351 | this.url = url;
352 | },
353 |
354 | getUpdatedChoices: function() {
355 | this.startIndicator();
356 |
357 | var entry = encodeURIComponent(this.options.paramName) + '=' +
358 | encodeURIComponent(this.getToken());
359 |
360 | this.options.parameters = this.options.callback ?
361 | this.options.callback(this.element, entry) : entry;
362 |
363 | if(this.options.defaultParams)
364 | this.options.parameters += '&' + this.options.defaultParams;
365 |
366 | new Ajax.Request(this.url, this.options);
367 | },
368 |
369 | onComplete: function(request) {
370 | this.updateChoices(request.responseText);
371 | }
372 | });
373 |
374 | // The local array autocompleter. Used when you'd prefer to
375 | // inject an array of autocompletion options into the page, rather
376 | // than sending out Ajax queries, which can be quite slow sometimes.
377 | //
378 | // The constructor takes four parameters. The first two are, as usual,
379 | // the id of the monitored textbox, and id of the autocompletion menu.
380 | // The third is the array you want to autocomplete from, and the fourth
381 | // is the options block.
382 | //
383 | // Extra local autocompletion options:
384 | // - choices - How many autocompletion choices to offer
385 | //
386 | // - partialSearch - If false, the autocompleter will match entered
387 | // text only at the beginning of strings in the
388 | // autocomplete array. Defaults to true, which will
389 | // match text at the beginning of any *word* in the
390 | // strings in the autocomplete array. If you want to
391 | // search anywhere in the string, additionally set
392 | // the option fullSearch to true (default: off).
393 | //
394 | // - fullSsearch - Search anywhere in autocomplete array strings.
395 | //
396 | // - partialChars - How many characters to enter before triggering
397 | // a partial match (unlike minChars, which defines
398 | // how many characters are required to do any match
399 | // at all). Defaults to 2.
400 | //
401 | // - ignoreCase - Whether to ignore case when autocompleting.
402 | // Defaults to true.
403 | //
404 | // It's possible to pass in a custom function as the 'selector'
405 | // option, if you prefer to write your own autocompletion logic.
406 | // In that case, the other options above will not apply unless
407 | // you support them.
408 |
409 | Autocompleter.Local = Class.create(Autocompleter.Base, {
410 | initialize: function(element, update, array, options) {
411 | this.baseInitialize(element, update, options);
412 | this.options.array = array;
413 | },
414 |
415 | getUpdatedChoices: function() {
416 | this.updateChoices(this.options.selector(this));
417 | },
418 |
419 | setOptions: function(options) {
420 | this.options = Object.extend({
421 | choices: 10,
422 | partialSearch: true,
423 | partialChars: 2,
424 | ignoreCase: true,
425 | fullSearch: false,
426 | selector: function(instance) {
427 | var ret = []; // Beginning matches
428 | var partial = []; // Inside matches
429 | var entry = instance.getToken();
430 | var count = 0;
431 |
432 | for (var i = 0; i < instance.options.array.length &&
433 | ret.length < instance.options.choices ; i++) {
434 |
435 | var elem = instance.options.array[i];
436 | var foundPos = instance.options.ignoreCase ?
437 | elem.toLowerCase().indexOf(entry.toLowerCase()) :
438 | elem.indexOf(entry);
439 |
440 | while (foundPos != -1) {
441 | if (foundPos == 0 && elem.length != entry.length) {
442 | ret.push("" + elem.substr(0, entry.length) + "" +
443 | elem.substr(entry.length) + "");
444 | break;
445 | } else if (entry.length >= instance.options.partialChars &&
446 | instance.options.partialSearch && foundPos != -1) {
447 | if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
448 | partial.push("" + elem.substr(0, foundPos) + "" +
449 | elem.substr(foundPos, entry.length) + "" + elem.substr(
450 | foundPos + entry.length) + "");
451 | break;
452 | }
453 | }
454 |
455 | foundPos = instance.options.ignoreCase ?
456 | elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
457 | elem.indexOf(entry, foundPos + 1);
458 |
459 | }
460 | }
461 | if (partial.length)
462 | ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
463 | return "";
464 | }
465 | }, options || { });
466 | }
467 | });
468 |
469 | // AJAX in-place editor and collection editor
470 | // Full rewrite by Christophe Porteneuve (April 2007).
471 |
472 | // Use this if you notice weird scrolling problems on some browsers,
473 | // the DOM might be a bit confused when this gets called so do this
474 | // waits 1 ms (with setTimeout) until it does the activation
475 | Field.scrollFreeActivate = function(field) {
476 | setTimeout(function() {
477 | Field.activate(field);
478 | }, 1);
479 | };
480 |
481 | Ajax.InPlaceEditor = Class.create({
482 | initialize: function(element, url, options) {
483 | this.url = url;
484 | this.element = element = $(element);
485 | this.prepareOptions();
486 | this._controls = { };
487 | arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
488 | Object.extend(this.options, options || { });
489 | if (!this.options.formId && this.element.id) {
490 | this.options.formId = this.element.id + '-inplaceeditor';
491 | if ($(this.options.formId))
492 | this.options.formId = '';
493 | }
494 | if (this.options.externalControl)
495 | this.options.externalControl = $(this.options.externalControl);
496 | if (!this.options.externalControl)
497 | this.options.externalControlOnly = false;
498 | this._originalBackground = this.element.getStyle('background-color') || 'transparent';
499 | this.element.title = this.options.clickToEditText;
500 | this._boundCancelHandler = this.handleFormCancellation.bind(this);
501 | this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
502 | this._boundFailureHandler = this.handleAJAXFailure.bind(this);
503 | this._boundSubmitHandler = this.handleFormSubmission.bind(this);
504 | this._boundWrapperHandler = this.wrapUp.bind(this);
505 | this.registerListeners();
506 | },
507 | checkForEscapeOrReturn: function(e) {
508 | if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
509 | if (Event.KEY_ESC == e.keyCode)
510 | this.handleFormCancellation(e);
511 | else if (Event.KEY_RETURN == e.keyCode)
512 | this.handleFormSubmission(e);
513 | },
514 | createControl: function(mode, handler, extraClasses) {
515 | var control = this.options[mode + 'Control'];
516 | var text = this.options[mode + 'Text'];
517 | if ('button' == control) {
518 | var btn = document.createElement('input');
519 | btn.type = 'submit';
520 | btn.value = text;
521 | btn.className = 'editor_' + mode + '_button';
522 | if ('cancel' == mode)
523 | btn.onclick = this._boundCancelHandler;
524 | this._form.appendChild(btn);
525 | this._controls[mode] = btn;
526 | } else if ('link' == control) {
527 | var link = document.createElement('a');
528 | link.href = '#';
529 | link.appendChild(document.createTextNode(text));
530 | link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
531 | link.className = 'editor_' + mode + '_link';
532 | if (extraClasses)
533 | link.className += ' ' + extraClasses;
534 | this._form.appendChild(link);
535 | this._controls[mode] = link;
536 | }
537 | },
538 | createEditField: function() {
539 | var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
540 | var fld;
541 | if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
542 | fld = document.createElement('input');
543 | fld.type = 'text';
544 | var size = this.options.size || this.options.cols || 0;
545 | if (0 < size) fld.size = size;
546 | } else {
547 | fld = document.createElement('textarea');
548 | fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
549 | fld.cols = this.options.cols || 40;
550 | }
551 | fld.name = this.options.paramName;
552 | fld.value = text; // No HTML breaks conversion anymore
553 | fld.className = 'editor_field';
554 | if (this.options.submitOnBlur)
555 | fld.onblur = this._boundSubmitHandler;
556 | this._controls.editor = fld;
557 | if (this.options.loadTextURL)
558 | this.loadExternalText();
559 | this._form.appendChild(this._controls.editor);
560 | },
561 | createForm: function() {
562 | var ipe = this;
563 | function addText(mode, condition) {
564 | var text = ipe.options['text' + mode + 'Controls'];
565 | if (!text || condition === false) return;
566 | ipe._form.appendChild(document.createTextNode(text));
567 | };
568 | this._form = $(document.createElement('form'));
569 | this._form.id = this.options.formId;
570 | this._form.addClassName(this.options.formClassName);
571 | this._form.onsubmit = this._boundSubmitHandler;
572 | this.createEditField();
573 | if ('textarea' == this._controls.editor.tagName.toLowerCase())
574 | this._form.appendChild(document.createElement('br'));
575 | if (this.options.onFormCustomization)
576 | this.options.onFormCustomization(this, this._form);
577 | addText('Before', this.options.okControl || this.options.cancelControl);
578 | this.createControl('ok', this._boundSubmitHandler);
579 | addText('Between', this.options.okControl && this.options.cancelControl);
580 | this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
581 | addText('After', this.options.okControl || this.options.cancelControl);
582 | },
583 | destroy: function() {
584 | if (this._oldInnerHTML)
585 | this.element.innerHTML = this._oldInnerHTML;
586 | this.leaveEditMode();
587 | this.unregisterListeners();
588 | },
589 | enterEditMode: function(e) {
590 | if (this._saving || this._editing) return;
591 | this._editing = true;
592 | this.triggerCallback('onEnterEditMode');
593 | if (this.options.externalControl)
594 | this.options.externalControl.hide();
595 | this.element.hide();
596 | this.createForm();
597 | this.element.parentNode.insertBefore(this._form, this.element);
598 | if (!this.options.loadTextURL)
599 | this.postProcessEditField();
600 | if (e) Event.stop(e);
601 | },
602 | enterHover: function(e) {
603 | if (this.options.hoverClassName)
604 | this.element.addClassName(this.options.hoverClassName);
605 | if (this._saving) return;
606 | this.triggerCallback('onEnterHover');
607 | },
608 | getText: function() {
609 | return this.element.innerHTML.unescapeHTML();
610 | },
611 | handleAJAXFailure: function(transport) {
612 | this.triggerCallback('onFailure', transport);
613 | if (this._oldInnerHTML) {
614 | this.element.innerHTML = this._oldInnerHTML;
615 | this._oldInnerHTML = null;
616 | }
617 | },
618 | handleFormCancellation: function(e) {
619 | this.wrapUp();
620 | if (e) Event.stop(e);
621 | },
622 | handleFormSubmission: function(e) {
623 | var form = this._form;
624 | var value = $F(this._controls.editor);
625 | this.prepareSubmission();
626 | var params = this.options.callback(form, value) || '';
627 | if (Object.isString(params))
628 | params = params.toQueryParams();
629 | params.editorId = this.element.id;
630 | if (this.options.htmlResponse) {
631 | var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
632 | Object.extend(options, {
633 | parameters: params,
634 | onComplete: this._boundWrapperHandler,
635 | onFailure: this._boundFailureHandler
636 | });
637 | new Ajax.Updater({ success: this.element }, this.url, options);
638 | } else {
639 | var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
640 | Object.extend(options, {
641 | parameters: params,
642 | onComplete: this._boundWrapperHandler,
643 | onFailure: this._boundFailureHandler
644 | });
645 | new Ajax.Request(this.url, options);
646 | }
647 | if (e) Event.stop(e);
648 | },
649 | leaveEditMode: function() {
650 | this.element.removeClassName(this.options.savingClassName);
651 | this.removeForm();
652 | this.leaveHover();
653 | this.element.style.backgroundColor = this._originalBackground;
654 | this.element.show();
655 | if (this.options.externalControl)
656 | this.options.externalControl.show();
657 | this._saving = false;
658 | this._editing = false;
659 | this._oldInnerHTML = null;
660 | this.triggerCallback('onLeaveEditMode');
661 | },
662 | leaveHover: function(e) {
663 | if (this.options.hoverClassName)
664 | this.element.removeClassName(this.options.hoverClassName);
665 | if (this._saving) return;
666 | this.triggerCallback('onLeaveHover');
667 | },
668 | loadExternalText: function() {
669 | this._form.addClassName(this.options.loadingClassName);
670 | this._controls.editor.disabled = true;
671 | var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
672 | Object.extend(options, {
673 | parameters: 'editorId=' + encodeURIComponent(this.element.id),
674 | onComplete: Prototype.emptyFunction,
675 | onSuccess: function(transport) {
676 | this._form.removeClassName(this.options.loadingClassName);
677 | var text = transport.responseText;
678 | if (this.options.stripLoadedTextTags)
679 | text = text.stripTags();
680 | this._controls.editor.value = text;
681 | this._controls.editor.disabled = false;
682 | this.postProcessEditField();
683 | }.bind(this),
684 | onFailure: this._boundFailureHandler
685 | });
686 | new Ajax.Request(this.options.loadTextURL, options);
687 | },
688 | postProcessEditField: function() {
689 | var fpc = this.options.fieldPostCreation;
690 | if (fpc)
691 | $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
692 | },
693 | prepareOptions: function() {
694 | this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
695 | Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
696 | [this._extraDefaultOptions].flatten().compact().each(function(defs) {
697 | Object.extend(this.options, defs);
698 | }.bind(this));
699 | },
700 | prepareSubmission: function() {
701 | this._saving = true;
702 | this.removeForm();
703 | this.leaveHover();
704 | this.showSaving();
705 | },
706 | registerListeners: function() {
707 | this._listeners = { };
708 | var listener;
709 | $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
710 | listener = this[pair.value].bind(this);
711 | this._listeners[pair.key] = listener;
712 | if (!this.options.externalControlOnly)
713 | this.element.observe(pair.key, listener);
714 | if (this.options.externalControl)
715 | this.options.externalControl.observe(pair.key, listener);
716 | }.bind(this));
717 | },
718 | removeForm: function() {
719 | if (!this._form) return;
720 | this._form.remove();
721 | this._form = null;
722 | this._controls = { };
723 | },
724 | showSaving: function() {
725 | this._oldInnerHTML = this.element.innerHTML;
726 | this.element.innerHTML = this.options.savingText;
727 | this.element.addClassName(this.options.savingClassName);
728 | this.element.style.backgroundColor = this._originalBackground;
729 | this.element.show();
730 | },
731 | triggerCallback: function(cbName, arg) {
732 | if ('function' == typeof this.options[cbName]) {
733 | this.options[cbName](this, arg);
734 | }
735 | },
736 | unregisterListeners: function() {
737 | $H(this._listeners).each(function(pair) {
738 | if (!this.options.externalControlOnly)
739 | this.element.stopObserving(pair.key, pair.value);
740 | if (this.options.externalControl)
741 | this.options.externalControl.stopObserving(pair.key, pair.value);
742 | }.bind(this));
743 | },
744 | wrapUp: function(transport) {
745 | this.leaveEditMode();
746 | // Can't use triggerCallback due to backward compatibility: requires
747 | // binding + direct element
748 | this._boundComplete(transport, this.element);
749 | }
750 | });
751 |
752 | Object.extend(Ajax.InPlaceEditor.prototype, {
753 | dispose: Ajax.InPlaceEditor.prototype.destroy
754 | });
755 |
756 | Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
757 | initialize: function($super, element, url, options) {
758 | this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
759 | $super(element, url, options);
760 | },
761 |
762 | createEditField: function() {
763 | var list = document.createElement('select');
764 | list.name = this.options.paramName;
765 | list.size = 1;
766 | this._controls.editor = list;
767 | this._collection = this.options.collection || [];
768 | if (this.options.loadCollectionURL)
769 | this.loadCollection();
770 | else
771 | this.checkForExternalText();
772 | this._form.appendChild(this._controls.editor);
773 | },
774 |
775 | loadCollection: function() {
776 | this._form.addClassName(this.options.loadingClassName);
777 | this.showLoadingText(this.options.loadingCollectionText);
778 | var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
779 | Object.extend(options, {
780 | parameters: 'editorId=' + encodeURIComponent(this.element.id),
781 | onComplete: Prototype.emptyFunction,
782 | onSuccess: function(transport) {
783 | var js = transport.responseText.strip();
784 | if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
785 | throw('Server returned an invalid collection representation.');
786 | this._collection = eval(js);
787 | this.checkForExternalText();
788 | }.bind(this),
789 | onFailure: this.onFailure
790 | });
791 | new Ajax.Request(this.options.loadCollectionURL, options);
792 | },
793 |
794 | showLoadingText: function(text) {
795 | this._controls.editor.disabled = true;
796 | var tempOption = this._controls.editor.firstChild;
797 | if (!tempOption) {
798 | tempOption = document.createElement('option');
799 | tempOption.value = '';
800 | this._controls.editor.appendChild(tempOption);
801 | tempOption.selected = true;
802 | }
803 | tempOption.update((text || '').stripScripts().stripTags());
804 | },
805 |
806 | checkForExternalText: function() {
807 | this._text = this.getText();
808 | if (this.options.loadTextURL)
809 | this.loadExternalText();
810 | else
811 | this.buildOptionList();
812 | },
813 |
814 | loadExternalText: function() {
815 | this.showLoadingText(this.options.loadingText);
816 | var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
817 | Object.extend(options, {
818 | parameters: 'editorId=' + encodeURIComponent(this.element.id),
819 | onComplete: Prototype.emptyFunction,
820 | onSuccess: function(transport) {
821 | this._text = transport.responseText.strip();
822 | this.buildOptionList();
823 | }.bind(this),
824 | onFailure: this.onFailure
825 | });
826 | new Ajax.Request(this.options.loadTextURL, options);
827 | },
828 |
829 | buildOptionList: function() {
830 | this._form.removeClassName(this.options.loadingClassName);
831 | this._collection = this._collection.map(function(entry) {
832 | return 2 === entry.length ? entry : [entry, entry].flatten();
833 | });
834 | var marker = ('value' in this.options) ? this.options.value : this._text;
835 | var textFound = this._collection.any(function(entry) {
836 | return entry[0] == marker;
837 | }.bind(this));
838 | this._controls.editor.update('');
839 | var option;
840 | this._collection.each(function(entry, index) {
841 | option = document.createElement('option');
842 | option.value = entry[0];
843 | option.selected = textFound ? entry[0] == marker : 0 == index;
844 | option.appendChild(document.createTextNode(entry[1]));
845 | this._controls.editor.appendChild(option);
846 | }.bind(this));
847 | this._controls.editor.disabled = false;
848 | Field.scrollFreeActivate(this._controls.editor);
849 | }
850 | });
851 |
852 | //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
853 | //**** This only exists for a while, in order to let ****
854 | //**** users adapt to the new API. Read up on the new ****
855 | //**** API and convert your code to it ASAP! ****
856 |
857 | Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
858 | if (!options) return;
859 | function fallback(name, expr) {
860 | if (name in options || expr === undefined) return;
861 | options[name] = expr;
862 | };
863 | fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
864 | options.cancelLink == options.cancelButton == false ? false : undefined)));
865 | fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
866 | options.okLink == options.okButton == false ? false : undefined)));
867 | fallback('highlightColor', options.highlightcolor);
868 | fallback('highlightEndColor', options.highlightendcolor);
869 | };
870 |
871 | Object.extend(Ajax.InPlaceEditor, {
872 | DefaultOptions: {
873 | ajaxOptions: { },
874 | autoRows: 3, // Use when multi-line w/ rows == 1
875 | cancelControl: 'link', // 'link'|'button'|false
876 | cancelText: 'cancel',
877 | clickToEditText: 'Click to edit',
878 | externalControl: null, // id|elt
879 | externalControlOnly: false,
880 | fieldPostCreation: 'activate', // 'activate'|'focus'|false
881 | formClassName: 'inplaceeditor-form',
882 | formId: null, // id|elt
883 | highlightColor: '#ffff99',
884 | highlightEndColor: '#ffffff',
885 | hoverClassName: '',
886 | htmlResponse: true,
887 | loadingClassName: 'inplaceeditor-loading',
888 | loadingText: 'Loading...',
889 | okControl: 'button', // 'link'|'button'|false
890 | okText: 'ok',
891 | paramName: 'value',
892 | rows: 1, // If 1 and multi-line, uses autoRows
893 | savingClassName: 'inplaceeditor-saving',
894 | savingText: 'Saving...',
895 | size: 0,
896 | stripLoadedTextTags: false,
897 | submitOnBlur: false,
898 | textAfterControls: '',
899 | textBeforeControls: '',
900 | textBetweenControls: ''
901 | },
902 | DefaultCallbacks: {
903 | callback: function(form) {
904 | return Form.serialize(form);
905 | },
906 | onComplete: function(transport, element) {
907 | // For backward compatibility, this one is bound to the IPE, and passes
908 | // the element directly. It was too often customized, so we don't break it.
909 | new Effect.Highlight(element, {
910 | startcolor: this.options.highlightColor, keepBackgroundImage: true });
911 | },
912 | onEnterEditMode: null,
913 | onEnterHover: function(ipe) {
914 | ipe.element.style.backgroundColor = ipe.options.highlightColor;
915 | if (ipe._effect)
916 | ipe._effect.cancel();
917 | },
918 | onFailure: function(transport, ipe) {
919 | alert('Error communication with the server: ' + transport.responseText.stripTags());
920 | },
921 | onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
922 | onLeaveEditMode: null,
923 | onLeaveHover: function(ipe) {
924 | ipe._effect = new Effect.Highlight(ipe.element, {
925 | startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
926 | restorecolor: ipe._originalBackground, keepBackgroundImage: true
927 | });
928 | }
929 | },
930 | Listeners: {
931 | click: 'enterEditMode',
932 | keydown: 'checkForEscapeOrReturn',
933 | mouseover: 'enterHover',
934 | mouseout: 'leaveHover'
935 | }
936 | });
937 |
938 | Ajax.InPlaceCollectionEditor.DefaultOptions = {
939 | loadingCollectionText: 'Loading options...'
940 | };
941 |
942 | // Delayed observer, like Form.Element.Observer,
943 | // but waits for delay after last key input
944 | // Ideal for live-search fields
945 |
946 | Form.Element.DelayedObserver = Class.create({
947 | initialize: function(element, delay, callback) {
948 | this.delay = delay || 0.5;
949 | this.element = $(element);
950 | this.callback = callback;
951 | this.timer = null;
952 | this.lastValue = $F(this.element);
953 | Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
954 | },
955 | delayedListener: function(event) {
956 | if(this.lastValue == $F(this.element)) return;
957 | if(this.timer) clearTimeout(this.timer);
958 | this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
959 | this.lastValue = $F(this.element);
960 | },
961 | onTimerEvent: function() {
962 | this.timer = null;
963 | this.callback(this.element, $F(this.element));
964 | }
965 | });
--------------------------------------------------------------------------------
/test/dummy/public/javascripts/dragdrop.js:
--------------------------------------------------------------------------------
1 | // script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
2 |
3 | // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4 | //
5 | // script.aculo.us is freely distributable under the terms of an MIT-style license.
6 | // For details, see the script.aculo.us web site: http://script.aculo.us/
7 |
8 | if(Object.isUndefined(Effect))
9 | throw("dragdrop.js requires including script.aculo.us' effects.js library");
10 |
11 | var Droppables = {
12 | drops: [],
13 |
14 | remove: function(element) {
15 | this.drops = this.drops.reject(function(d) { return d.element==$(element) });
16 | },
17 |
18 | add: function(element) {
19 | element = $(element);
20 | var options = Object.extend({
21 | greedy: true,
22 | hoverclass: null,
23 | tree: false
24 | }, arguments[1] || { });
25 |
26 | // cache containers
27 | if(options.containment) {
28 | options._containers = [];
29 | var containment = options.containment;
30 | if(Object.isArray(containment)) {
31 | containment.each( function(c) { options._containers.push($(c)) });
32 | } else {
33 | options._containers.push($(containment));
34 | }
35 | }
36 |
37 | if(options.accept) options.accept = [options.accept].flatten();
38 |
39 | Element.makePositioned(element); // fix IE
40 | options.element = element;
41 |
42 | this.drops.push(options);
43 | },
44 |
45 | findDeepestChild: function(drops) {
46 | deepest = drops[0];
47 |
48 | for (i = 1; i < drops.length; ++i)
49 | if (Element.isParent(drops[i].element, deepest.element))
50 | deepest = drops[i];
51 |
52 | return deepest;
53 | },
54 |
55 | isContained: function(element, drop) {
56 | var containmentNode;
57 | if(drop.tree) {
58 | containmentNode = element.treeNode;
59 | } else {
60 | containmentNode = element.parentNode;
61 | }
62 | return drop._containers.detect(function(c) { return containmentNode == c });
63 | },
64 |
65 | isAffected: function(point, element, drop) {
66 | return (
67 | (drop.element!=element) &&
68 | ((!drop._containers) ||
69 | this.isContained(element, drop)) &&
70 | ((!drop.accept) ||
71 | (Element.classNames(element).detect(
72 | function(v) { return drop.accept.include(v) } ) )) &&
73 | Position.within(drop.element, point[0], point[1]) );
74 | },
75 |
76 | deactivate: function(drop) {
77 | if(drop.hoverclass)
78 | Element.removeClassName(drop.element, drop.hoverclass);
79 | this.last_active = null;
80 | },
81 |
82 | activate: function(drop) {
83 | if(drop.hoverclass)
84 | Element.addClassName(drop.element, drop.hoverclass);
85 | this.last_active = drop;
86 | },
87 |
88 | show: function(point, element) {
89 | if(!this.drops.length) return;
90 | var drop, affected = [];
91 |
92 | this.drops.each( function(drop) {
93 | if(Droppables.isAffected(point, element, drop))
94 | affected.push(drop);
95 | });
96 |
97 | if(affected.length>0)
98 | drop = Droppables.findDeepestChild(affected);
99 |
100 | if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
101 | if (drop) {
102 | Position.within(drop.element, point[0], point[1]);
103 | if(drop.onHover)
104 | drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
105 |
106 | if (drop != this.last_active) Droppables.activate(drop);
107 | }
108 | },
109 |
110 | fire: function(event, element) {
111 | if(!this.last_active) return;
112 | Position.prepare();
113 |
114 | if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
115 | if (this.last_active.onDrop) {
116 | this.last_active.onDrop(element, this.last_active.element, event);
117 | return true;
118 | }
119 | },
120 |
121 | reset: function() {
122 | if(this.last_active)
123 | this.deactivate(this.last_active);
124 | }
125 | };
126 |
127 | var Draggables = {
128 | drags: [],
129 | observers: [],
130 |
131 | register: function(draggable) {
132 | if(this.drags.length == 0) {
133 | this.eventMouseUp = this.endDrag.bindAsEventListener(this);
134 | this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
135 | this.eventKeypress = this.keyPress.bindAsEventListener(this);
136 |
137 | Event.observe(document, "mouseup", this.eventMouseUp);
138 | Event.observe(document, "mousemove", this.eventMouseMove);
139 | Event.observe(document, "keypress", this.eventKeypress);
140 | }
141 | this.drags.push(draggable);
142 | },
143 |
144 | unregister: function(draggable) {
145 | this.drags = this.drags.reject(function(d) { return d==draggable });
146 | if(this.drags.length == 0) {
147 | Event.stopObserving(document, "mouseup", this.eventMouseUp);
148 | Event.stopObserving(document, "mousemove", this.eventMouseMove);
149 | Event.stopObserving(document, "keypress", this.eventKeypress);
150 | }
151 | },
152 |
153 | activate: function(draggable) {
154 | if(draggable.options.delay) {
155 | this._timeout = setTimeout(function() {
156 | Draggables._timeout = null;
157 | window.focus();
158 | Draggables.activeDraggable = draggable;
159 | }.bind(this), draggable.options.delay);
160 | } else {
161 | window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
162 | this.activeDraggable = draggable;
163 | }
164 | },
165 |
166 | deactivate: function() {
167 | this.activeDraggable = null;
168 | },
169 |
170 | updateDrag: function(event) {
171 | if(!this.activeDraggable) return;
172 | var pointer = [Event.pointerX(event), Event.pointerY(event)];
173 | // Mozilla-based browsers fire successive mousemove events with
174 | // the same coordinates, prevent needless redrawing (moz bug?)
175 | if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
176 | this._lastPointer = pointer;
177 |
178 | this.activeDraggable.updateDrag(event, pointer);
179 | },
180 |
181 | endDrag: function(event) {
182 | if(this._timeout) {
183 | clearTimeout(this._timeout);
184 | this._timeout = null;
185 | }
186 | if(!this.activeDraggable) return;
187 | this._lastPointer = null;
188 | this.activeDraggable.endDrag(event);
189 | this.activeDraggable = null;
190 | },
191 |
192 | keyPress: function(event) {
193 | if(this.activeDraggable)
194 | this.activeDraggable.keyPress(event);
195 | },
196 |
197 | addObserver: function(observer) {
198 | this.observers.push(observer);
199 | this._cacheObserverCallbacks();
200 | },
201 |
202 | removeObserver: function(element) { // element instead of observer fixes mem leaks
203 | this.observers = this.observers.reject( function(o) { return o.element==element });
204 | this._cacheObserverCallbacks();
205 | },
206 |
207 | notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
208 | if(this[eventName+'Count'] > 0)
209 | this.observers.each( function(o) {
210 | if(o[eventName]) o[eventName](eventName, draggable, event);
211 | });
212 | if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
213 | },
214 |
215 | _cacheObserverCallbacks: function() {
216 | ['onStart','onEnd','onDrag'].each( function(eventName) {
217 | Draggables[eventName+'Count'] = Draggables.observers.select(
218 | function(o) { return o[eventName]; }
219 | ).length;
220 | });
221 | }
222 | };
223 |
224 | /*--------------------------------------------------------------------------*/
225 |
226 | var Draggable = Class.create({
227 | initialize: function(element) {
228 | var defaults = {
229 | handle: false,
230 | reverteffect: function(element, top_offset, left_offset) {
231 | var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
232 | new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
233 | queue: {scope:'_draggable', position:'end'}
234 | });
235 | },
236 | endeffect: function(element) {
237 | var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
238 | new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
239 | queue: {scope:'_draggable', position:'end'},
240 | afterFinish: function(){
241 | Draggable._dragging[element] = false
242 | }
243 | });
244 | },
245 | zindex: 1000,
246 | revert: false,
247 | quiet: false,
248 | scroll: false,
249 | scrollSensitivity: 20,
250 | scrollSpeed: 15,
251 | snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
252 | delay: 0
253 | };
254 |
255 | if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
256 | Object.extend(defaults, {
257 | starteffect: function(element) {
258 | element._opacity = Element.getOpacity(element);
259 | Draggable._dragging[element] = true;
260 | new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
261 | }
262 | });
263 |
264 | var options = Object.extend(defaults, arguments[1] || { });
265 |
266 | this.element = $(element);
267 |
268 | if(options.handle && Object.isString(options.handle))
269 | this.handle = this.element.down('.'+options.handle, 0);
270 |
271 | if(!this.handle) this.handle = $(options.handle);
272 | if(!this.handle) this.handle = this.element;
273 |
274 | if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
275 | options.scroll = $(options.scroll);
276 | this._isScrollChild = Element.childOf(this.element, options.scroll);
277 | }
278 |
279 | Element.makePositioned(this.element); // fix IE
280 |
281 | this.options = options;
282 | this.dragging = false;
283 |
284 | this.eventMouseDown = this.initDrag.bindAsEventListener(this);
285 | Event.observe(this.handle, "mousedown", this.eventMouseDown);
286 |
287 | Draggables.register(this);
288 | },
289 |
290 | destroy: function() {
291 | Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
292 | Draggables.unregister(this);
293 | },
294 |
295 | currentDelta: function() {
296 | return([
297 | parseInt(Element.getStyle(this.element,'left') || '0'),
298 | parseInt(Element.getStyle(this.element,'top') || '0')]);
299 | },
300 |
301 | initDrag: function(event) {
302 | if(!Object.isUndefined(Draggable._dragging[this.element]) &&
303 | Draggable._dragging[this.element]) return;
304 | if(Event.isLeftClick(event)) {
305 | // abort on form elements, fixes a Firefox issue
306 | var src = Event.element(event);
307 | if((tag_name = src.tagName.toUpperCase()) && (
308 | tag_name=='INPUT' ||
309 | tag_name=='SELECT' ||
310 | tag_name=='OPTION' ||
311 | tag_name=='BUTTON' ||
312 | tag_name=='TEXTAREA')) return;
313 |
314 | var pointer = [Event.pointerX(event), Event.pointerY(event)];
315 | var pos = this.element.cumulativeOffset();
316 | this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
317 |
318 | Draggables.activate(this);
319 | Event.stop(event);
320 | }
321 | },
322 |
323 | startDrag: function(event) {
324 | this.dragging = true;
325 | if(!this.delta)
326 | this.delta = this.currentDelta();
327 |
328 | if(this.options.zindex) {
329 | this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
330 | this.element.style.zIndex = this.options.zindex;
331 | }
332 |
333 | if(this.options.ghosting) {
334 | this._clone = this.element.cloneNode(true);
335 | this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
336 | if (!this._originallyAbsolute)
337 | Position.absolutize(this.element);
338 | this.element.parentNode.insertBefore(this._clone, this.element);
339 | }
340 |
341 | if(this.options.scroll) {
342 | if (this.options.scroll == window) {
343 | var where = this._getWindowScroll(this.options.scroll);
344 | this.originalScrollLeft = where.left;
345 | this.originalScrollTop = where.top;
346 | } else {
347 | this.originalScrollLeft = this.options.scroll.scrollLeft;
348 | this.originalScrollTop = this.options.scroll.scrollTop;
349 | }
350 | }
351 |
352 | Draggables.notify('onStart', this, event);
353 |
354 | if(this.options.starteffect) this.options.starteffect(this.element);
355 | },
356 |
357 | updateDrag: function(event, pointer) {
358 | if(!this.dragging) this.startDrag(event);
359 |
360 | if(!this.options.quiet){
361 | Position.prepare();
362 | Droppables.show(pointer, this.element);
363 | }
364 |
365 | Draggables.notify('onDrag', this, event);
366 |
367 | this.draw(pointer);
368 | if(this.options.change) this.options.change(this);
369 |
370 | if(this.options.scroll) {
371 | this.stopScrolling();
372 |
373 | var p;
374 | if (this.options.scroll == window) {
375 | with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
376 | } else {
377 | p = Position.page(this.options.scroll);
378 | p[0] += this.options.scroll.scrollLeft + Position.deltaX;
379 | p[1] += this.options.scroll.scrollTop + Position.deltaY;
380 | p.push(p[0]+this.options.scroll.offsetWidth);
381 | p.push(p[1]+this.options.scroll.offsetHeight);
382 | }
383 | var speed = [0,0];
384 | if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
385 | if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
386 | if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
387 | if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
388 | this.startScrolling(speed);
389 | }
390 |
391 | // fix AppleWebKit rendering
392 | if(Prototype.Browser.WebKit) window.scrollBy(0,0);
393 |
394 | Event.stop(event);
395 | },
396 |
397 | finishDrag: function(event, success) {
398 | this.dragging = false;
399 |
400 | if(this.options.quiet){
401 | Position.prepare();
402 | var pointer = [Event.pointerX(event), Event.pointerY(event)];
403 | Droppables.show(pointer, this.element);
404 | }
405 |
406 | if(this.options.ghosting) {
407 | if (!this._originallyAbsolute)
408 | Position.relativize(this.element);
409 | delete this._originallyAbsolute;
410 | Element.remove(this._clone);
411 | this._clone = null;
412 | }
413 |
414 | var dropped = false;
415 | if(success) {
416 | dropped = Droppables.fire(event, this.element);
417 | if (!dropped) dropped = false;
418 | }
419 | if(dropped && this.options.onDropped) this.options.onDropped(this.element);
420 | Draggables.notify('onEnd', this, event);
421 |
422 | var revert = this.options.revert;
423 | if(revert && Object.isFunction(revert)) revert = revert(this.element);
424 |
425 | var d = this.currentDelta();
426 | if(revert && this.options.reverteffect) {
427 | if (dropped == 0 || revert != 'failure')
428 | this.options.reverteffect(this.element,
429 | d[1]-this.delta[1], d[0]-this.delta[0]);
430 | } else {
431 | this.delta = d;
432 | }
433 |
434 | if(this.options.zindex)
435 | this.element.style.zIndex = this.originalZ;
436 |
437 | if(this.options.endeffect)
438 | this.options.endeffect(this.element);
439 |
440 | Draggables.deactivate(this);
441 | Droppables.reset();
442 | },
443 |
444 | keyPress: function(event) {
445 | if(event.keyCode!=Event.KEY_ESC) return;
446 | this.finishDrag(event, false);
447 | Event.stop(event);
448 | },
449 |
450 | endDrag: function(event) {
451 | if(!this.dragging) return;
452 | this.stopScrolling();
453 | this.finishDrag(event, true);
454 | Event.stop(event);
455 | },
456 |
457 | draw: function(point) {
458 | var pos = this.element.cumulativeOffset();
459 | if(this.options.ghosting) {
460 | var r = Position.realOffset(this.element);
461 | pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
462 | }
463 |
464 | var d = this.currentDelta();
465 | pos[0] -= d[0]; pos[1] -= d[1];
466 |
467 | if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
468 | pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
469 | pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
470 | }
471 |
472 | var p = [0,1].map(function(i){
473 | return (point[i]-pos[i]-this.offset[i])
474 | }.bind(this));
475 |
476 | if(this.options.snap) {
477 | if(Object.isFunction(this.options.snap)) {
478 | p = this.options.snap(p[0],p[1],this);
479 | } else {
480 | if(Object.isArray(this.options.snap)) {
481 | p = p.map( function(v, i) {
482 | return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
483 | } else {
484 | p = p.map( function(v) {
485 | return (v/this.options.snap).round()*this.options.snap }.bind(this));
486 | }
487 | }}
488 |
489 | var style = this.element.style;
490 | if((!this.options.constraint) || (this.options.constraint=='horizontal'))
491 | style.left = p[0] + "px";
492 | if((!this.options.constraint) || (this.options.constraint=='vertical'))
493 | style.top = p[1] + "px";
494 |
495 | if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
496 | },
497 |
498 | stopScrolling: function() {
499 | if(this.scrollInterval) {
500 | clearInterval(this.scrollInterval);
501 | this.scrollInterval = null;
502 | Draggables._lastScrollPointer = null;
503 | }
504 | },
505 |
506 | startScrolling: function(speed) {
507 | if(!(speed[0] || speed[1])) return;
508 | this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
509 | this.lastScrolled = new Date();
510 | this.scrollInterval = setInterval(this.scroll.bind(this), 10);
511 | },
512 |
513 | scroll: function() {
514 | var current = new Date();
515 | var delta = current - this.lastScrolled;
516 | this.lastScrolled = current;
517 | if(this.options.scroll == window) {
518 | with (this._getWindowScroll(this.options.scroll)) {
519 | if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
520 | var d = delta / 1000;
521 | this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
522 | }
523 | }
524 | } else {
525 | this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
526 | this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
527 | }
528 |
529 | Position.prepare();
530 | Droppables.show(Draggables._lastPointer, this.element);
531 | Draggables.notify('onDrag', this);
532 | if (this._isScrollChild) {
533 | Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
534 | Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
535 | Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
536 | if (Draggables._lastScrollPointer[0] < 0)
537 | Draggables._lastScrollPointer[0] = 0;
538 | if (Draggables._lastScrollPointer[1] < 0)
539 | Draggables._lastScrollPointer[1] = 0;
540 | this.draw(Draggables._lastScrollPointer);
541 | }
542 |
543 | if(this.options.change) this.options.change(this);
544 | },
545 |
546 | _getWindowScroll: function(w) {
547 | var T, L, W, H;
548 | with (w.document) {
549 | if (w.document.documentElement && documentElement.scrollTop) {
550 | T = documentElement.scrollTop;
551 | L = documentElement.scrollLeft;
552 | } else if (w.document.body) {
553 | T = body.scrollTop;
554 | L = body.scrollLeft;
555 | }
556 | if (w.innerWidth) {
557 | W = w.innerWidth;
558 | H = w.innerHeight;
559 | } else if (w.document.documentElement && documentElement.clientWidth) {
560 | W = documentElement.clientWidth;
561 | H = documentElement.clientHeight;
562 | } else {
563 | W = body.offsetWidth;
564 | H = body.offsetHeight;
565 | }
566 | }
567 | return { top: T, left: L, width: W, height: H };
568 | }
569 | });
570 |
571 | Draggable._dragging = { };
572 |
573 | /*--------------------------------------------------------------------------*/
574 |
575 | var SortableObserver = Class.create({
576 | initialize: function(element, observer) {
577 | this.element = $(element);
578 | this.observer = observer;
579 | this.lastValue = Sortable.serialize(this.element);
580 | },
581 |
582 | onStart: function() {
583 | this.lastValue = Sortable.serialize(this.element);
584 | },
585 |
586 | onEnd: function() {
587 | Sortable.unmark();
588 | if(this.lastValue != Sortable.serialize(this.element))
589 | this.observer(this.element)
590 | }
591 | });
592 |
593 | var Sortable = {
594 | SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
595 |
596 | sortables: { },
597 |
598 | _findRootElement: function(element) {
599 | while (element.tagName.toUpperCase() != "BODY") {
600 | if(element.id && Sortable.sortables[element.id]) return element;
601 | element = element.parentNode;
602 | }
603 | },
604 |
605 | options: function(element) {
606 | element = Sortable._findRootElement($(element));
607 | if(!element) return;
608 | return Sortable.sortables[element.id];
609 | },
610 |
611 | destroy: function(element){
612 | element = $(element);
613 | var s = Sortable.sortables[element.id];
614 |
615 | if(s) {
616 | Draggables.removeObserver(s.element);
617 | s.droppables.each(function(d){ Droppables.remove(d) });
618 | s.draggables.invoke('destroy');
619 |
620 | delete Sortable.sortables[s.element.id];
621 | }
622 | },
623 |
624 | create: function(element) {
625 | element = $(element);
626 | var options = Object.extend({
627 | element: element,
628 | tag: 'li', // assumes li children, override with tag: 'tagname'
629 | dropOnEmpty: false,
630 | tree: false,
631 | treeTag: 'ul',
632 | overlap: 'vertical', // one of 'vertical', 'horizontal'
633 | constraint: 'vertical', // one of 'vertical', 'horizontal', false
634 | containment: element, // also takes array of elements (or id's); or false
635 | handle: false, // or a CSS class
636 | only: false,
637 | delay: 0,
638 | hoverclass: null,
639 | ghosting: false,
640 | quiet: false,
641 | scroll: false,
642 | scrollSensitivity: 20,
643 | scrollSpeed: 15,
644 | format: this.SERIALIZE_RULE,
645 |
646 | // these take arrays of elements or ids and can be
647 | // used for better initialization performance
648 | elements: false,
649 | handles: false,
650 |
651 | onChange: Prototype.emptyFunction,
652 | onUpdate: Prototype.emptyFunction
653 | }, arguments[1] || { });
654 |
655 | // clear any old sortable with same element
656 | this.destroy(element);
657 |
658 | // build options for the draggables
659 | var options_for_draggable = {
660 | revert: true,
661 | quiet: options.quiet,
662 | scroll: options.scroll,
663 | scrollSpeed: options.scrollSpeed,
664 | scrollSensitivity: options.scrollSensitivity,
665 | delay: options.delay,
666 | ghosting: options.ghosting,
667 | constraint: options.constraint,
668 | handle: options.handle };
669 |
670 | if(options.starteffect)
671 | options_for_draggable.starteffect = options.starteffect;
672 |
673 | if(options.reverteffect)
674 | options_for_draggable.reverteffect = options.reverteffect;
675 | else
676 | if(options.ghosting) options_for_draggable.reverteffect = function(element) {
677 | element.style.top = 0;
678 | element.style.left = 0;
679 | };
680 |
681 | if(options.endeffect)
682 | options_for_draggable.endeffect = options.endeffect;
683 |
684 | if(options.zindex)
685 | options_for_draggable.zindex = options.zindex;
686 |
687 | // build options for the droppables
688 | var options_for_droppable = {
689 | overlap: options.overlap,
690 | containment: options.containment,
691 | tree: options.tree,
692 | hoverclass: options.hoverclass,
693 | onHover: Sortable.onHover
694 | };
695 |
696 | var options_for_tree = {
697 | onHover: Sortable.onEmptyHover,
698 | overlap: options.overlap,
699 | containment: options.containment,
700 | hoverclass: options.hoverclass
701 | };
702 |
703 | // fix for gecko engine
704 | Element.cleanWhitespace(element);
705 |
706 | options.draggables = [];
707 | options.droppables = [];
708 |
709 | // drop on empty handling
710 | if(options.dropOnEmpty || options.tree) {
711 | Droppables.add(element, options_for_tree);
712 | options.droppables.push(element);
713 | }
714 |
715 | (options.elements || this.findElements(element, options) || []).each( function(e,i) {
716 | var handle = options.handles ? $(options.handles[i]) :
717 | (options.handle ? $(e).select('.' + options.handle)[0] : e);
718 | options.draggables.push(
719 | new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
720 | Droppables.add(e, options_for_droppable);
721 | if(options.tree) e.treeNode = element;
722 | options.droppables.push(e);
723 | });
724 |
725 | if(options.tree) {
726 | (Sortable.findTreeElements(element, options) || []).each( function(e) {
727 | Droppables.add(e, options_for_tree);
728 | e.treeNode = element;
729 | options.droppables.push(e);
730 | });
731 | }
732 |
733 | // keep reference
734 | this.sortables[element.identify()] = options;
735 |
736 | // for onupdate
737 | Draggables.addObserver(new SortableObserver(element, options.onUpdate));
738 |
739 | },
740 |
741 | // return all suitable-for-sortable elements in a guaranteed order
742 | findElements: function(element, options) {
743 | return Element.findChildren(
744 | element, options.only, options.tree ? true : false, options.tag);
745 | },
746 |
747 | findTreeElements: function(element, options) {
748 | return Element.findChildren(
749 | element, options.only, options.tree ? true : false, options.treeTag);
750 | },
751 |
752 | onHover: function(element, dropon, overlap) {
753 | if(Element.isParent(dropon, element)) return;
754 |
755 | if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
756 | return;
757 | } else if(overlap>0.5) {
758 | Sortable.mark(dropon, 'before');
759 | if(dropon.previousSibling != element) {
760 | var oldParentNode = element.parentNode;
761 | element.style.visibility = "hidden"; // fix gecko rendering
762 | dropon.parentNode.insertBefore(element, dropon);
763 | if(dropon.parentNode!=oldParentNode)
764 | Sortable.options(oldParentNode).onChange(element);
765 | Sortable.options(dropon.parentNode).onChange(element);
766 | }
767 | } else {
768 | Sortable.mark(dropon, 'after');
769 | var nextElement = dropon.nextSibling || null;
770 | if(nextElement != element) {
771 | var oldParentNode = element.parentNode;
772 | element.style.visibility = "hidden"; // fix gecko rendering
773 | dropon.parentNode.insertBefore(element, nextElement);
774 | if(dropon.parentNode!=oldParentNode)
775 | Sortable.options(oldParentNode).onChange(element);
776 | Sortable.options(dropon.parentNode).onChange(element);
777 | }
778 | }
779 | },
780 |
781 | onEmptyHover: function(element, dropon, overlap) {
782 | var oldParentNode = element.parentNode;
783 | var droponOptions = Sortable.options(dropon);
784 |
785 | if(!Element.isParent(dropon, element)) {
786 | var index;
787 |
788 | var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
789 | var child = null;
790 |
791 | if(children) {
792 | var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
793 |
794 | for (index = 0; index < children.length; index += 1) {
795 | if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
796 | offset -= Element.offsetSize (children[index], droponOptions.overlap);
797 | } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
798 | child = index + 1 < children.length ? children[index + 1] : null;
799 | break;
800 | } else {
801 | child = children[index];
802 | break;
803 | }
804 | }
805 | }
806 |
807 | dropon.insertBefore(element, child);
808 |
809 | Sortable.options(oldParentNode).onChange(element);
810 | droponOptions.onChange(element);
811 | }
812 | },
813 |
814 | unmark: function() {
815 | if(Sortable._marker) Sortable._marker.hide();
816 | },
817 |
818 | mark: function(dropon, position) {
819 | // mark on ghosting only
820 | var sortable = Sortable.options(dropon.parentNode);
821 | if(sortable && !sortable.ghosting) return;
822 |
823 | if(!Sortable._marker) {
824 | Sortable._marker =
825 | ($('dropmarker') || Element.extend(document.createElement('DIV'))).
826 | hide().addClassName('dropmarker').setStyle({position:'absolute'});
827 | document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
828 | }
829 | var offsets = dropon.cumulativeOffset();
830 | Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
831 |
832 | if(position=='after')
833 | if(sortable.overlap == 'horizontal')
834 | Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
835 | else
836 | Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
837 |
838 | Sortable._marker.show();
839 | },
840 |
841 | _tree: function(element, options, parent) {
842 | var children = Sortable.findElements(element, options) || [];
843 |
844 | for (var i = 0; i < children.length; ++i) {
845 | var match = children[i].id.match(options.format);
846 |
847 | if (!match) continue;
848 |
849 | var child = {
850 | id: encodeURIComponent(match ? match[1] : null),
851 | element: element,
852 | parent: parent,
853 | children: [],
854 | position: parent.children.length,
855 | container: $(children[i]).down(options.treeTag)
856 | };
857 |
858 | /* Get the element containing the children and recurse over it */
859 | if (child.container)
860 | this._tree(child.container, options, child);
861 |
862 | parent.children.push (child);
863 | }
864 |
865 | return parent;
866 | },
867 |
868 | tree: function(element) {
869 | element = $(element);
870 | var sortableOptions = this.options(element);
871 | var options = Object.extend({
872 | tag: sortableOptions.tag,
873 | treeTag: sortableOptions.treeTag,
874 | only: sortableOptions.only,
875 | name: element.id,
876 | format: sortableOptions.format
877 | }, arguments[1] || { });
878 |
879 | var root = {
880 | id: null,
881 | parent: null,
882 | children: [],
883 | container: element,
884 | position: 0
885 | };
886 |
887 | return Sortable._tree(element, options, root);
888 | },
889 |
890 | /* Construct a [i] index for a particular node */
891 | _constructIndex: function(node) {
892 | var index = '';
893 | do {
894 | if (node.id) index = '[' + node.position + ']' + index;
895 | } while ((node = node.parent) != null);
896 | return index;
897 | },
898 |
899 | sequence: function(element) {
900 | element = $(element);
901 | var options = Object.extend(this.options(element), arguments[1] || { });
902 |
903 | return $(this.findElements(element, options) || []).map( function(item) {
904 | return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
905 | });
906 | },
907 |
908 | setSequence: function(element, new_sequence) {
909 | element = $(element);
910 | var options = Object.extend(this.options(element), arguments[2] || { });
911 |
912 | var nodeMap = { };
913 | this.findElements(element, options).each( function(n) {
914 | if (n.id.match(options.format))
915 | nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
916 | n.parentNode.removeChild(n);
917 | });
918 |
919 | new_sequence.each(function(ident) {
920 | var n = nodeMap[ident];
921 | if (n) {
922 | n[1].appendChild(n[0]);
923 | delete nodeMap[ident];
924 | }
925 | });
926 | },
927 |
928 | serialize: function(element) {
929 | element = $(element);
930 | var options = Object.extend(Sortable.options(element), arguments[1] || { });
931 | var name = encodeURIComponent(
932 | (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
933 |
934 | if (options.tree) {
935 | return Sortable.tree(element, arguments[1]).children.map( function (item) {
936 | return [name + Sortable._constructIndex(item) + "[id]=" +
937 | encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
938 | }).flatten().join('&');
939 | } else {
940 | return Sortable.sequence(element, arguments[1]).map( function(item) {
941 | return name + "[]=" + encodeURIComponent(item);
942 | }).join('&');
943 | }
944 | }
945 | };
946 |
947 | // Returns true if child is contained within element
948 | Element.isParent = function(child, element) {
949 | if (!child.parentNode || child == element) return false;
950 | if (child.parentNode == element) return true;
951 | return Element.isParent(child.parentNode, element);
952 | };
953 |
954 | Element.findChildren = function(element, only, recursive, tagName) {
955 | if(!element.hasChildNodes()) return null;
956 | tagName = tagName.toUpperCase();
957 | if(only) only = [only].flatten();
958 | var elements = [];
959 | $A(element.childNodes).each( function(e) {
960 | if(e.tagName && e.tagName.toUpperCase()==tagName &&
961 | (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
962 | elements.push(e);
963 | if(recursive) {
964 | var grandchildren = Element.findChildren(e, only, recursive, tagName);
965 | if(grandchildren) elements.push(grandchildren);
966 | }
967 | });
968 |
969 | return (elements.length>0 ? elements.flatten() : []);
970 | };
971 |
972 | Element.offsetSize = function (element, type) {
973 | return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
974 | };
--------------------------------------------------------------------------------
/test/dummy/public/javascripts/rails.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | Ajax.Responders.register({
3 | onCreate: function(request) {
4 | var token = $$('meta[name=csrf-token]')[0];
5 | if (token) {
6 | if (!request.options.requestHeaders) request.options.requestHeaders = {};
7 | request.options.requestHeaders['X-CSRF-Token'] = token.readAttribute('content');
8 | }
9 | }
10 | });
11 |
12 | // Technique from Juriy Zaytsev
13 | // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
14 | function isEventSupported(eventName) {
15 | var el = document.createElement('div');
16 | eventName = 'on' + eventName;
17 | var isSupported = (eventName in el);
18 | if (!isSupported) {
19 | el.setAttribute(eventName, 'return;');
20 | isSupported = typeof el[eventName] == 'function';
21 | }
22 | el = null;
23 | return isSupported;
24 | }
25 |
26 | function isForm(element) {
27 | return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM';
28 | }
29 |
30 | function isInput(element) {
31 | if (Object.isElement(element)) {
32 | var name = element.nodeName.toUpperCase();
33 | return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA';
34 | }
35 | else return false;
36 | }
37 |
38 | var submitBubbles = isEventSupported('submit'),
39 | changeBubbles = isEventSupported('change');
40 |
41 | if (!submitBubbles || !changeBubbles) {
42 | // augment the Event.Handler class to observe custom events when needed
43 | Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
44 | function(init, element, eventName, selector, callback) {
45 | init(element, eventName, selector, callback);
46 | // is the handler being attached to an element that doesn't support this event?
47 | if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
48 | (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
49 | // "submit" => "emulated:submit"
50 | this.eventName = 'emulated:' + this.eventName;
51 | }
52 | }
53 | );
54 | }
55 |
56 | if (!submitBubbles) {
57 | // discover forms on the page by observing focus events which always bubble
58 | document.on('focusin', 'form', function(focusEvent, form) {
59 | // special handler for the real "submit" event (one-time operation)
60 | if (!form.retrieve('emulated:submit')) {
61 | form.on('submit', function(submitEvent) {
62 | var emulated = form.fire('emulated:submit', submitEvent, true);
63 | // if custom event received preventDefault, cancel the real one too
64 | if (emulated.returnValue === false) submitEvent.preventDefault();
65 | });
66 | form.store('emulated:submit', true);
67 | }
68 | });
69 | }
70 |
71 | if (!changeBubbles) {
72 | // discover form inputs on the page
73 | document.on('focusin', 'input, select, textarea', function(focusEvent, input) {
74 | // special handler for real "change" events
75 | if (!input.retrieve('emulated:change')) {
76 | input.on('change', function(changeEvent) {
77 | input.fire('emulated:change', changeEvent, true);
78 | });
79 | input.store('emulated:change', true);
80 | }
81 | });
82 | }
83 |
84 | function handleRemote(element) {
85 | var method, url, params;
86 |
87 | var event = element.fire("ajax:before");
88 | if (event.stopped) return false;
89 |
90 | if (element.tagName.toLowerCase() === 'form') {
91 | method = element.readAttribute('method') || 'post';
92 | url = element.readAttribute('action');
93 | // serialize the form with respect to the submit button that was pressed
94 | params = element.serialize({ submit: element.retrieve('rails:submit-button') });
95 | // clear the pressed submit button information
96 | element.store('rails:submit-button', null);
97 | } else {
98 | method = element.readAttribute('data-method') || 'get';
99 | url = element.readAttribute('href');
100 | params = {};
101 | }
102 |
103 | new Ajax.Request(url, {
104 | method: method,
105 | parameters: params,
106 | evalScripts: true,
107 |
108 | onCreate: function(response) { element.fire("ajax:create", response); },
109 | onComplete: function(response) { element.fire("ajax:complete", response); },
110 | onSuccess: function(response) { element.fire("ajax:success", response); },
111 | onFailure: function(response) { element.fire("ajax:failure", response); }
112 | });
113 |
114 | element.fire("ajax:after");
115 | }
116 |
117 | function insertHiddenField(form, name, value) {
118 | form.insert(new Element('input', { type: 'hidden', name: name, value: value }));
119 | }
120 |
121 | function handleMethod(element) {
122 | var method = element.readAttribute('data-method'),
123 | url = element.readAttribute('href'),
124 | csrf_param = $$('meta[name=csrf-param]')[0],
125 | csrf_token = $$('meta[name=csrf-token]')[0];
126 |
127 | var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
128 | $(element.parentNode).insert(form);
129 |
130 | if (method !== 'post') {
131 | insertHiddenField(form, '_method', method);
132 | }
133 |
134 | if (csrf_param) {
135 | insertHiddenField(form, csrf_param.readAttribute('content'), csrf_token.readAttribute('content'));
136 | }
137 |
138 | form.submit();
139 | }
140 |
141 | function disableFormElements(form) {
142 | form.select('input[type=submit][data-disable-with]').each(function(input) {
143 | input.store('rails:original-value', input.getValue());
144 | input.setValue(input.readAttribute('data-disable-with')).disable();
145 | });
146 | }
147 |
148 | function enableFormElements(form) {
149 | form.select('input[type=submit][data-disable-with]').each(function(input) {
150 | input.setValue(input.retrieve('rails:original-value')).enable();
151 | });
152 | }
153 |
154 | function allowAction(element) {
155 | var message = element.readAttribute('data-confirm');
156 | return !message || confirm(message);
157 | }
158 |
159 | document.on('click', 'a[data-confirm], a[data-remote], a[data-method]', function(event, link) {
160 | if (!allowAction(link)) {
161 | event.stop();
162 | return false;
163 | }
164 |
165 | if (link.readAttribute('data-remote')) {
166 | handleRemote(link);
167 | event.stop();
168 | } else if (link.readAttribute('data-method')) {
169 | handleMethod(link);
170 | event.stop();
171 | }
172 | });
173 |
174 | document.on("click", "form input[type=submit], form button[type=submit], form button:not([type])", function(event, button) {
175 | // register the pressed submit button
176 | event.findElement('form').store('rails:submit-button', button.name || false);
177 | });
178 |
179 | document.on("submit", function(event) {
180 | var form = event.findElement();
181 |
182 | if (!allowAction(form)) {
183 | event.stop();
184 | return false;
185 | }
186 |
187 | if (form.readAttribute('data-remote')) {
188 | handleRemote(form);
189 | event.stop();
190 | } else {
191 | disableFormElements(form);
192 | }
193 | });
194 |
195 | document.on('ajax:create', 'form', function(event, form) {
196 | if (form == event.findElement()) disableFormElements(form);
197 | });
198 |
199 | document.on('ajax:complete', 'form', function(event, form) {
200 | if (form == event.findElement()) enableFormElements(form);
201 | });
202 | })();
203 |
--------------------------------------------------------------------------------
/test/dummy/public/stylesheets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clearfunction/questionnaire/61be93261334b89ea812ad38bd75d1c361295e88/test/dummy/public/stylesheets/.gitkeep
--------------------------------------------------------------------------------
/test/dummy/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
5 |
6 | APP_PATH = File.expand_path('../../config/application', __FILE__)
7 | require File.expand_path('../../config/boot', __FILE__)
8 | require 'rails/commands'
9 |
--------------------------------------------------------------------------------
/test/models/answer_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class AnswerTest < ActiveSupport::TestCase
6 | test 'should create a valid answer' do
7 | answer = create_answer
8 | should_be_persisted answer
9 | end
10 |
11 | test 'should not create an answer with a nil option' do
12 | answer = create_answer(option: nil)
13 | should_not_be_persisted answer
14 | end
15 |
16 | test 'should not create an answer with a nil question' do
17 | answer = create_answer(question: nil)
18 | should_not_be_persisted answer
19 | end
20 |
21 | test 'should create an answer with a option_number field for options with number type' do
22 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.number)
23 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_number: 12)
24 |
25 | should_be_persisted survey
26 | should_be_persisted answer_try_1
27 | end
28 |
29 | test 'should not create an answer with a nil option_number field for options with number type' do
30 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.number, true)
31 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_number: nil)
32 |
33 | should_be_persisted survey
34 | should_not_be_persisted answer_try_1
35 | end
36 |
37 | test 'should create an answer with a option_text field for options with text type' do
38 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.text, true)
39 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: Faker::Name.name)
40 |
41 | should_be_persisted survey
42 | should_be_persisted answer_try_1
43 | end
44 |
45 | test 'should not create an answer with a nil option_text field for options with text type' do
46 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.text, true)
47 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: nil)
48 |
49 | should_be_persisted survey
50 | should_not_be_persisted answer_try_1
51 | end
52 |
53 | test 'should create an answer with a option_text field for options with large_text type' do
54 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.large_text, true)
55 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: Faker::Name.name)
56 |
57 | should_be_persisted survey
58 | should_be_persisted answer_try_1
59 | end
60 |
61 | test 'should not create an answer with a nil option_text field for options with large_text type' do
62 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.large_text, true)
63 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: nil)
64 |
65 | should_be_persisted survey
66 | should_not_be_persisted answer_try_1
67 | end
68 |
69 | test 'should create an answer with a option_text field for options with multi_choices_with_text type' do
70 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.multi_choices_with_text, true)
71 | faker_name = Faker::Name.name
72 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: faker_name)
73 |
74 | should_be_persisted survey
75 | should_be_persisted answer_try_1
76 | assert_equal answer_try_1.option_text, faker_name
77 | end
78 |
79 | test 'should not create an answer with empty option_text field for options with multi_choices_with_text type' do
80 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.multi_choices_with_text, true)
81 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: nil)
82 |
83 | should_be_persisted survey
84 | should_not_be_persisted answer_try_1
85 | end
86 |
87 | test 'should not create an answer with empty option_text field for options with single_choice_with_text type' do
88 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.single_choice_with_text, true)
89 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: nil)
90 |
91 | should_be_persisted survey
92 | should_not_be_persisted answer_try_1
93 | end
94 |
95 | test 'should not create an answer with empty option_number field for options with multi_choices_with_number type' do
96 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.multi_choices_with_number, true)
97 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_number: nil)
98 |
99 | should_be_persisted survey
100 | should_not_be_persisted answer_try_1
101 | end
102 |
103 | test 'should not create an answer with empty option_number field for options with single_choice_with_number type' do
104 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.single_choice_with_number, true)
105 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_number: nil)
106 |
107 | should_be_persisted survey
108 | should_not_be_persisted answer_try_1
109 | end
110 |
111 | test 'should create an answer with options with multi_choices type, and text field should be empty' do
112 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.multi_choices, true)
113 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: Faker::Name.name)
114 |
115 | should_be_persisted survey
116 | should_be_persisted answer_try_1
117 | assert_equal answer_try_1.option_text, nil
118 | end
119 |
120 | test 'should create an answer with options with single_choice type, and text field should be empty' do
121 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.single_choice, true)
122 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, option_text: Faker::Name.name)
123 |
124 | should_be_persisted survey
125 | should_be_persisted answer_try_1
126 | assert_equal answer_try_1.option_text, nil
127 | end
128 |
129 | test 'should create an answer with a predefined_value_id field for single_choice type' do
130 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.single_choice, true)
131 | predefined_value = create_predefined_value
132 | question.predefined_values << predefined_value
133 | question.save
134 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, predefined_value_id: predefined_value.id)
135 |
136 | should_be_persisted survey
137 | should_be_persisted question
138 | should_be_persisted answer_try_1
139 | assert_equal answer_try_1.predefined_value_id, predefined_value.id
140 | end
141 |
142 | test 'should not create an answer with an empty predefined_value_id field for single_choice type' do
143 | survey, option, attempt, question = create_answer_with_option_type(Survey::OptionsType.single_choice, true)
144 | question.predefined_values << create_predefined_value
145 | question.save
146 | answer_try_1 = create_answer(option: option, attempt: attempt, question: question, predefined_value_id: nil)
147 |
148 | should_be_persisted survey
149 | should_not_be_persisted answer_try_1
150 | end
151 |
152 | test 'can create an answer already made to the same attempt' do
153 | answer_try_1 = create_answer
154 | attempt = answer_try_1.attempt
155 | question = answer_try_1.question
156 | option = (question.options - [answer_try_1.option]).first
157 | answer_try_2 = create_answer(attempt: attempt, question: question, option: option)
158 |
159 | should_be_persisted answer_try_1
160 | should_be_persisted answer_try_2
161 | end
162 | end
163 |
--------------------------------------------------------------------------------
/test/models/attempt_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class AttemptTest < ActiveSupport::TestCase
6 | NUMBER_OF_ATTEMPTS = 5
7 |
8 | test "should pass if the user has #{NUMBER_OF_ATTEMPTS} attempts completed" do
9 | user = create_user
10 | survey = create_survey(attempts_number: NUMBER_OF_ATTEMPTS)
11 | NUMBER_OF_ATTEMPTS.times { create_attempt_for(user, survey) }
12 | assert_equal NUMBER_OF_ATTEMPTS, number_of_current_attempts(user, survey)
13 | end
14 |
15 | test 'should raise error when the User tries to respond more times than acceptable' do
16 | user = create_user
17 | survey = create_survey(attempts_number: NUMBER_OF_ATTEMPTS)
18 | (NUMBER_OF_ATTEMPTS + 1).times { create_attempt_for(user, survey) }
19 | assert_not_equal (NUMBER_OF_ATTEMPTS + 1), number_of_current_attempts(user, survey)
20 | assert_equal NUMBER_OF_ATTEMPTS, number_of_current_attempts(user, survey)
21 | end
22 |
23 | test 'should compute the score correctly for a perfect attempt' do
24 | user = create_user
25 | survey = create_survey_with_sections(4) # These are multi-choice questions and there are 5 options per question
26 | attempt = create_attempt_for(user, survey, all: :right)
27 | assert_equal 20, attempt.score
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/test/models/option_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class OptionTest < ActiveSupport::TestCase
6 | test 'should create a valid option' do
7 | option = create_option
8 | should_be_persisted option
9 | end
10 |
11 | test 'should create a valid option with multi choices type' do
12 | option = create_option(options_type_id: Survey::OptionsType.multi_choices)
13 |
14 | should_be_persisted option
15 | assert_equal option.options_type_id, Survey::OptionsType.multi_choices
16 | end
17 |
18 | test 'should create a valid option with single choice type' do
19 | option = create_option(options_type_id: Survey::OptionsType.single_choice)
20 |
21 | should_be_persisted option
22 | assert_equal option.options_type_id, Survey::OptionsType.single_choice
23 | end
24 |
25 | test 'should create a valid option with number type' do
26 | option = create_option(options_type_id: Survey::OptionsType.number)
27 |
28 | should_be_persisted option
29 | assert_equal option.options_type_id, Survey::OptionsType.number
30 | end
31 |
32 | test 'should create a valid option with text type' do
33 | option = create_option(options_type_id: Survey::OptionsType.text)
34 |
35 | should_be_persisted option
36 | assert_equal option.options_type_id, Survey::OptionsType.text
37 | end
38 |
39 | test 'should create a valid option with large_text type' do
40 | option = create_option(options_type_id: Survey::OptionsType.large_text)
41 |
42 | should_be_persisted option
43 | assert_equal option.options_type_id, Survey::OptionsType.large_text
44 | end
45 |
46 | test 'should create a valid option with accepted type' do
47 | option = create_option(options_type_id: 99)
48 |
49 | should_not_be_persisted option
50 | end
51 |
52 | test 'should not create an option with a empty or nil options_type_id field' do
53 | option = create_option(options_type_id: nil)
54 |
55 | should_not_be_persisted option
56 | end
57 |
58 | test 'should create a option with empty or nil text fields for text or number types' do
59 | optionA = create_option(text: '', options_type_id: Survey::OptionsType.text)
60 | optionB = create_option(text: nil, options_type_id: Survey::OptionsType.text)
61 |
62 | optionC = create_option(text: '', options_type_id: Survey::OptionsType.number)
63 | optionD = create_option(text: nil, options_type_id: Survey::OptionsType.number)
64 |
65 | should_be_persisted optionA
66 | should_be_persisted optionB
67 |
68 | should_be_persisted optionC
69 | should_be_persisted optionD
70 | end
71 |
72 | test 'should not create a option with empty or nil text fields for multi_choices or single_choice types' do
73 | optionA = create_option(text: '', options_type_id: Survey::OptionsType.multi_choices)
74 | optionB = create_option(text: nil, options_type_id: Survey::OptionsType.multi_choices)
75 |
76 | optionC = create_option(text: '', options_type_id: Survey::OptionsType.single_choice)
77 | optionD = create_option(text: nil, options_type_id: Survey::OptionsType.single_choice)
78 |
79 | optionE = create_option(text: '', options_type_id: Survey::OptionsType.multi_choices_with_text)
80 | optionF = create_option(text: nil, options_type_id: Survey::OptionsType.multi_choices_with_text)
81 |
82 | optionG = create_option(text: '', options_type_id: Survey::OptionsType.single_choice_with_text)
83 | optionH = create_option(text: nil, options_type_id: Survey::OptionsType.single_choice_with_text)
84 |
85 | optionI = create_option(text: '', options_type_id: Survey::OptionsType.multi_choices_with_number)
86 | optionJ = create_option(text: nil, options_type_id: Survey::OptionsType.multi_choices_with_number)
87 |
88 | optionK = create_option(text: '', options_type_id: Survey::OptionsType.single_choice_with_number)
89 | optionL = create_option(text: nil, options_type_id: Survey::OptionsType.single_choice_with_number)
90 |
91 | should_not_be_persisted optionA
92 | should_not_be_persisted optionB
93 |
94 | should_not_be_persisted optionC
95 | should_not_be_persisted optionD
96 |
97 | should_not_be_persisted optionE
98 | should_not_be_persisted optionF
99 |
100 | should_not_be_persisted optionG
101 | should_not_be_persisted optionH
102 |
103 | should_not_be_persisted optionI
104 | should_not_be_persisted optionJ
105 |
106 | should_not_be_persisted optionK
107 | should_not_be_persisted optionL
108 | end
109 |
110 | test 'should be true if option A is correct and option B incorrect' do
111 | optionA = create_option(correct: false)
112 | optionB = create_option(correct: true)
113 |
114 | should_be_false optionA.correct?
115 | should_be_true optionB.correct?
116 | end
117 |
118 | # correct => default weight is 1
119 | # incorrect => default weight is 0
120 | test 'should be true weights are synchronized with the correct flag' do
121 | optionA = create_option(correct: false)
122 | optionB = create_option(correct: true)
123 | optionC = create_option(correct: true, weight: 5)
124 |
125 | should_be_true (optionA.weight == 0)
126 | should_be_true (optionB.weight == 1)
127 | should_be_true (optionC.weight == 5)
128 | end
129 | end
130 |
--------------------------------------------------------------------------------
/test/models/predefined_value_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class PredefinedValueTest < ActiveSupport::TestCase
6 | test 'should create a valid predefined_value' do
7 | predefined_value = create_predefined_value
8 | should_be_persisted predefined_value
9 | end
10 |
11 | test 'should not create a predefined_value with a empty or nil name field' do
12 | predefined_value_a = create_predefined_value(name: nil)
13 | predefined_value_b = create_predefined_value(name: '')
14 |
15 | should_not_be_persisted predefined_value_a
16 | should_not_be_persisted predefined_value_b
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/question_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class QuestionTest < ActiveSupport::TestCase
6 | test 'should create a valid question' do
7 | question = create_question
8 | should_be_persisted question
9 | end
10 |
11 | test 'should create a valid question with multi choices type' do
12 | question = create_question(questions_type_id: Survey::QuestionsType.multiple_choice)
13 | should_be_persisted question
14 | assert_equal question.questions_type_id, Survey::QuestionsType.multiple_choice
15 | end
16 |
17 | test 'should create a valid question with accepted type' do
18 | question = create_question(questions_type_id: 99)
19 |
20 | should_not_be_persisted question
21 | end
22 |
23 | test 'should create a valid question with predefined_values' do
24 | question = create_question(predefined_values: [create_predefined_value])
25 |
26 | should_be_persisted question
27 | assert_equal question.predefined_values.count, 1
28 | end
29 |
30 | test 'should not create a question with a empty or nil questions_type_id field' do
31 | question = create_question(questions_type_id: nil)
32 |
33 | should_not_be_persisted question
34 | end
35 |
36 | test 'should not create a question with a empty or nil text fields' do
37 | question1 = create_question(text: nil)
38 | question2 = create_question(text: '')
39 |
40 | should_not_be_persisted question1
41 | should_not_be_persisted question2
42 | end
43 |
44 | test 'should return true when passed a correct answer to the question object' do
45 | question = create_question
46 | question.options.create(correct_option_attributes)
47 | 6.times { question.options.create(option_attributes) }
48 |
49 | correct_option = question.options.first
50 | should_be_true question.correct_options.include?(correct_option)
51 |
52 | # by default when we create a new question it creates a correct answer directly
53 | # when we create the second question with the correct flag equal a true
54 | # we have to start the iteration in position number two
55 | question.options[2..question.options.size - 2].each do |option|
56 | should_be_false question.correct_options.include?(option)
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/test/models/section_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SectionTest < ActiveSupport::TestCase
4 | test 'should not create a valid section without questions' do
5 | section = create_section
6 | should_not_be_persisted section
7 | end
8 |
9 | test 'should create a section with 3 questions' do
10 | num_questions = 3
11 | survey = create_survey_with_sections(num_questions, 1)
12 | should_be_persisted survey
13 | assert_equal survey.sections.first.questions.size, num_questions
14 | end
15 |
16 | test 'should not save section without all the needed fields' do
17 | section_without_name = create_section(name: nil)
18 | %w[name].each do |suffix|
19 | should_not_be_persisted eval("section_without_#{suffix}")
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/models/survey_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SurveyTest < ActiveSupport::TestCase
4 | test 'should not create a valid survey without sections' do
5 | survey = create_survey
6 | should_not_be_persisted survey
7 | end
8 |
9 | test 'should not create a survey with active flag true and empty questions collection' do
10 | surveyA = create_survey(active: true)
11 | surveyB = create_survey_with_sections(2)
12 | surveyB.active = true
13 | surveyB.save
14 |
15 | should_not_be_persisted surveyA
16 | should_be_persisted surveyB
17 | should_be_true surveyB.valid?
18 | end
19 |
20 | test 'should create a survey with 3 sections' do
21 | num_questions = 3
22 | survey = create_survey_with_sections(num_questions, num_questions)
23 | should_be_persisted survey
24 | assert_equal survey.sections.size, num_questions
25 | end
26 |
27 | test 'should create a survey with 2 questions' do
28 | num_questions = 2
29 | survey = create_survey_with_sections(num_questions, 1)
30 | should_be_persisted survey
31 | assert_equal survey.sections.first.questions.size, num_questions
32 | end
33 |
34 | test 'should not create a survey with attempts_number lower than 0' do
35 | survey = create_survey(attempts_number: -1)
36 | should_not_be_persisted survey
37 | end
38 |
39 | test 'should not save survey without all the needed fields' do
40 | survey_without_name = create_survey(name: nil)
41 | survey_without_description = create_survey(description: nil)
42 | %w[name description].each do |suffix|
43 | should_not_be_persisted eval("survey_without_#{suffix}")
44 | end
45 | end
46 |
47 | test 'should have the correct associations via "has_many_surveys"' do
48 | lesson = create_lesson
49 | survey = create_survey_with_sections(2, 1)
50 | survey.lesson_id = lesson.id
51 | survey.save
52 |
53 | assert_equal survey, lesson.surveys.first
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/test/support/assertions.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'active_support/test_case'
4 |
5 | class ActiveSupport::TestCase
6 | def should_be_true(assertion)
7 | assert assertion
8 | end
9 |
10 | def should_be_false(assertion)
11 | assert !assertion
12 | end
13 |
14 | def assert_not_with_message(assertion, message)
15 | assert !assertion, message
16 | end
17 |
18 | def assert_blank(assertion)
19 | should_be_true assertion.blank?
20 | end
21 |
22 | def assert_not_blank(assertion)
23 | assert assertion.present?
24 | end
25 |
26 | def assert_not_nil(assertion)
27 | should_be_true !assertion.nil?
28 | end
29 |
30 | def should_be_persisted(assertion)
31 | should_be_true assertion.valid?
32 | should_be_false assertion.new_record?
33 | end
34 |
35 | def should_not_be_persisted(assertion)
36 | should_be_true assertion.new_record?
37 | should_be_false assertion.valid?
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/test/support/factories.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Factories
4 |
5 | # Create a Survey::Survey
6 | def create_survey(opts = {})
7 | Survey::Survey.create({
8 | name: ::Faker::Name.name,
9 | attempts_number: 3,
10 | description: ::Faker::Lorem.paragraph(1)
11 | }.merge(opts))
12 | end
13 |
14 | # Create a Survey::Section
15 | def create_section(opts = {})
16 | Survey::Section.create({
17 | head_number: ::Faker::Name.name,
18 | name: ::Faker::Name.name,
19 | description: ::Faker::Lorem.paragraph(1)
20 | }.merge(opts))
21 | end
22 |
23 | # Create a Survey::Question
24 | def create_question(opts = {})
25 | Survey::Question.create({
26 | text: ::Faker::Lorem.paragraph(1),
27 | options_attributes: { option: correct_option_attributes },
28 | questions_type_id: Survey::QuestionsType.multiple_choice,
29 | mandatory: false
30 | }.merge(opts))
31 | end
32 |
33 | # Create a Survey::PredefinedValue
34 | def create_predefined_value(opts = {})
35 | Survey::PredefinedValue.create({
36 | name: ::Faker::Name.name
37 | }.merge(opts))
38 | end
39 |
40 | # Create a Survey::option but not saved
41 | def new_option(opts = {})
42 | Survey::Option.new(option_attributes.merge(opts))
43 | end
44 |
45 | # Create a Survey::Option
46 | def create_option(opts = {})
47 | Survey::Option.create(option_attributes.merge(opts))
48 | end
49 |
50 | def option_attributes
51 | { text: ::Faker::Lorem.paragraph(1),
52 | options_type_id: Survey::OptionsType.multi_choices }
53 | end
54 |
55 | def correct_option_attributes
56 | option_attributes.merge(correct: true)
57 | end
58 |
59 | def create_attempt(opts = {})
60 | attempt = Survey::Attempt.create do |t|
61 | t.survey = opts.fetch(:survey, nil)
62 | t.participant = opts.fetch(:user, nil)
63 | opts.fetch(:options, []).each do |option|
64 | t.answers.new(option: option, question: option.question, attempt: t)
65 | end
66 | end
67 | end
68 |
69 | def create_survey_with_sections(num, sections_num = 1)
70 | survey = create_survey
71 | sections_num.times do
72 | section = create_section
73 | num.times do
74 | question = create_question
75 | num.times do
76 | question.options << create_option(correct_option_attributes)
77 | end
78 | section.questions << question
79 | end
80 | survey.sections << section
81 | end
82 | survey.save
83 | survey
84 | end
85 |
86 | def create_attempt_for(user, survey, opts = {})
87 | if opts.fetch(:all, :wrong) == :right
88 | correct_survey = survey.correct_options
89 | create_attempt(options: correct_survey,
90 | user: user,
91 | survey: survey)
92 | else
93 | incorrect_survey = survey.correct_options
94 | incorrect_survey.shift
95 | create_attempt(options: incorrect_survey,
96 | user: user,
97 | survey: survey)
98 | end
99 | end
100 |
101 | def create_answer(opts = {})
102 | survey = create_survey_with_sections(1)
103 | section = survey.sections.first
104 | question = section.questions.first
105 | option = section.questions.first.options.first
106 | attempt = create_attempt(user: create_user, survey: survey)
107 | Survey::Answer.create({ option: option, attempt: attempt, question: question }.merge(opts))
108 | end
109 |
110 | def create_answer_with_option_type(options_type, mandatory = false)
111 | option = create_option(options_type_id: options_type)
112 | question = create_question(questions_type_id: Survey::QuestionsType.multiple_choice, mandatory: mandatory)
113 | section = create_section
114 | survey = create_survey
115 |
116 | question.options << option
117 | section.questions << question
118 | survey.sections << section
119 | survey.save
120 |
121 | attempt = create_attempt(user: create_user, survey: survey)
122 |
123 | [survey, option, attempt, question]
124 | end
125 |
126 | # Dummy Model from Dummy Application
127 | def create_user
128 | User.create(name: Faker::Name.name)
129 | end
130 |
131 | def create_lesson
132 | Lesson.create(name: Faker::Company.catch_phrase)
133 | end
134 |
--------------------------------------------------------------------------------
/test/support/handlers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | def number_of_current_attempts(participant, survey)
4 | participant.for_survey(survey).size if participant.respond_to?(:for_survey)
5 | end
6 |
7 | def participant_score(user, survey)
8 | survey.attempts.for_participant(user).high_score
9 | end
10 |
11 | def participant_with_more_right_answers(survey)
12 | survey.attempts.scores.first.participant
13 | end
14 |
15 | def participant_with_more_wrong_answers(survey)
16 | survey.attempts.scores.last.participant
17 | end
18 |
--------------------------------------------------------------------------------
/test/survey_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class SurveyTest < ActiveSupport::TestCase
6 | test 'should pass if all the users has the same score' do
7 | user_a = create_user
8 | user_b = create_user
9 | survey = create_survey_with_sections(2)
10 |
11 | create_attempt_for(user_a, survey, all: :right)
12 | create_attempt_for(user_b, survey, all: :right)
13 |
14 | assert_equal participant_score(user_a, survey),
15 | participant_score(user_b, survey)
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Configure Rails Envinronment
4 | ENV['RAILS_ENV'] = 'test'
5 |
6 | require File.expand_path('../dummy/config/environment.rb', __FILE__)
7 | require 'rails/test_help'
8 | require 'mocha/setup'
9 | require 'faker'
10 | require 'pry'
11 |
12 | Rails.backtrace_cleaner.remove_silencers!
13 |
14 | # Run any available migration
15 | ActiveRecord::MigrationContext.new(File.expand_path('../dummy/db/migrate/', __FILE__))
16 |
17 | # Load support files
18 | # Add support to load paths so we can overwrite broken webrat setup
19 | $LOAD_PATH.unshift File.expand_path('../support', __FILE__)
20 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
21 |
--------------------------------------------------------------------------------