Coder Social home page Coder Social logo

nubic / surveyor Goto Github PK

View Code? Open in Web Editor NEW
750.0 35.0 275.0 6.6 MB

A Rails gem that lets you code surveys, questionnaires, quizzes, etc... and add them to your app.

Home Page: http://nubic.github.com/surveyor

License: MIT License

Ruby 85.37% JavaScript 5.21% CSS 9.08% Shell 0.34%

surveyor's Introduction

Status

Gem Version Build Status Code Climate

Why surveyor?

Surveyor is a developer tool to deliver surveys in Rails applications. Surveys are written in the surveyor DSL (Domain Specific Language). A DSL makes it significantly easier to import long surveys (one of the motivations for building surveyor was copy/paste fatigue). It enables non-programmers to write out, edit, and review surveys.

If your Rails app needs to asks users questions as part of a survey, quiz, or questionnaire then you should consider using surveyor. This gem was designed to deliver clinical research surveys to large populations, but it can be used for any type of survey.

Surveyor is a Rails engine distributed as a ruby gem, meaning it is straightforward to override or extend its behaviors in your Rails app without maintaining a fork.

Requirements

Surveyor works with:

  • Ruby 2.0.0 and 2.1.1
  • Rails 3.2 and 4.0

In keeping with the Rails team maintenance policy we no longer support Rails 3.1 (stick with v1.4.0 if you need Rails 3.1) or Ruby 1.9.3 (stick with v1.4.0 if you need Ruby 1.8.7 or 1.9.3).

Some key dependencies are:

  • HAML
  • Sass
  • Formtastic

A more exhaustive list can be found in the gemspec.

Install

Add surveyor to your Gemfile:

gem "surveyor"

Bundle, install, and migrate:

bundle install
script/rails generate surveyor:install
bundle exec rake db:migrate

Parse the "kitchen sink" survey (kitchen sink means almost everything)

bundle exec rake surveyor FILE=surveys/kitchen_sink_survey.rb

Start up your app, visit /surveys, compare what you see to kitchen_sink_survey.rb and try responding to the survey.

Customize surveyor

Surveyor's controller, helper, models, and views may be overridden by classes in your app folder. To generate a sample custom controller and layout run:

script/rails generate surveyor:custom

and read the instructions generated in surveys/EXTENDING_SURVEYOR.MD

Upgrade surveyor

To get the latest version of surveyor, bundle, install and migrate:

bundle update surveyor
script/rails generate surveyor:install
bundle exec rake db:migrate

and review the changelog for changes that may affect your customizations.

What surveyor does and doesn't do

Does do

  • use a DSL to parse large surveys without hours of copy/paste into a gui builder
  • support complex, rule-based dependencies (skip-logic)
  • JSON export of both surveys and response sets
  • allow customization of all models, views, and controller, as well as helpers and routes
  • follow semantic versioning
  • exclusive checkboxes - a checkbox that when checked, unchecks all the others

Doesn't do

  • Enforce mandatory questions... yet (although it does have some1 methods2 on ResponseSet to support that)
  • Dependencies within repeaters... yet #235
  • Validations within the UI... yet #34, although it does have model support and database representations
  • GUI creating, editing, deleting and administration of surveys... yet #414
  • Consistently support HTML tags in title, text, help_text attributes. We intend to move to markdown support #413 so that same survey definition can be used with nu_surveyor.

Users of spork

There is an issue with spork and custom inputs in formatstic (#851). A workaround (thanks rmm5t!):

Spork.prefork do
  # ...
  surveyor_path = Gem.loaded_specs['surveyor'].full_gem_path
  Dir["#{surveyor_path}/app/inputs/*_input.rb"].each { |f| require File.basename(f) }
  # ...
end

Follow master

If you are following pre-release versions of surveyor using a :git source in your Gemfile, be particularly careful about reviewing migrations after updating surveyor and re-running the generator. We will never change a migration between two released versions of surveyor. However, we may on rare occasions change a migration which has been merged into master. When this happens, you'll need to assess the differences and decide on an appropriate course of action for your app. If you aren't sure what this means, we do not recommend that you deploy an app that's locked to surveyor master into production.

Support

For general discussion (e.g., "how do I do this?"), please send a message to the surveyor-dev group. This group is moderated to keep out spam; don't be surprised if your message isn't posted immediately.

For reproducible bugs, please file an issue on the GitHub issue tracker. Please include a minimal test case (a detailed description of how to trigger the bug in a clean rails application). If you aren't sure how to isolate the bug, send a message to surveyor-dev with what you know and we'll try to help.

For build status see our continuous integration page.

Take a look at our screencast (a bit dated now).

Contribute, test

To work on the code, fork this github project. Install bundler if you don't have it, then bundle, generate the app in testbed, and run the specs and features

$ bundle update
$ bundle exec rake testbed
$ bundle exec rake spec

Copyright (c) 2008-2013 Brian Chamberlain and Mark Yoon, released under the MIT license

surveyor's People

Contributors

alanjcfs avatar ariel-perez-birchbox avatar bigtunacan avatar bnadav avatar brian-lc avatar denniscastro avatar egspoony avatar ferrisoxide avatar flockom avatar gaurish avatar hannahwhy avatar ignu avatar jadetr avatar jdzak avatar jefflunt avatar jlcapps avatar jtoy avatar lancejpollard avatar majofi avatar markedmondson avatar mgurley avatar nataliya avatar pfriedman avatar pragtob avatar rsutphin avatar sweded avatar ybushmanova avatar yoon avatar yorzi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

surveyor's Issues

Survey access_code not guaranteed to be unique

As there is no model validation or unique index in the database, there is no guarantee that your survey access_code will be unique. It is just a normalized version of your survey title.

Running ...

 rake surveyor FILE=surveys/kitchen_sink_survey.rb APPEND=true
 rake surveyor FILE=surveys/kitchen_sink_survey.rb APPEND=true
 script/console
 Survey.all.collect(&:access_code)

should show this to be true.

This will show both surveys as able to be taken, but will only allow the first one when the attempt is made as the survey is found by

@survey = Survey.find_by_access_code(params[:survey_code])

In addition, adding a unique index to the db and model will make a mess when running

rake surveyor FILE=surveys/kitchen_sink_survey.rb APPEND=true
rake surveyor FILE=surveys/kitchen_sink_survey.rb APPEND=true

as the access_code is not unique and the survey fixtures are loaded last, leaving the associations with an invalid survey_id. To avoid this, the survey fixture should probably be attempted first.

Update...

Adding something like ...

def access_code=(value)
  counter = 2
  original_value = value
  while( ( survey = Survey.find_by_access_code(value) ) && 
    ( self.id != survey.id ) )
    value = [original_value,"_",counter].join
    counter += 1
  end
  super
end

... to Survey seems to provide a "fix" for this "problem", although I don't know how kosher it is.

It works in my test environment, but does not actually work in the "rake surveyor" task as the survey is built from a different Survey model completely and then loaded into the database and not created though the application.

My final "fix" was to add

require 'lib/surveyor/survey_extensions'

to my Rakefile and create the file

# > cat lib/surveyor/survey_extensions.rb
if surveyor_gem = Gem.searcher.find('surveyor')
  require surveyor_gem.full_gem_path + '/script/surveyor/parser'
  require surveyor_gem.full_gem_path + '/script/surveyor/survey'
end

module SurveyParser
module SurveyExtensions
  def self.included(base)
    base.class_eval do
      def initialize_with_unique_access_code(obj, args, opts)
        initialize_without_unique_access_code(obj, args, opts)
        counter = 2
        ac = self.access_code
        original_ac = self.access_code
        while( survey = ::Survey.find_by_access_code(ac) ) 
          ac = [original_ac,"_",counter].join
          counter += 1
        end
        self.access_code = ac
      end
      alias_method_chain :initialize, :unique_access_code
    end
  end
end
end
SurveyParser::Survey.send(:include, SurveyParser::SurveyExtensions)

It is not as clean as I would've liked, but works.

Of course, now you have to different surveys that probably have the same name. But at least you can take them both!

Distinguish deletions when updating responses in the controller

In response_set.rb: lines 57 and 109, in the "response_attributes=" and "response_group_attributes=" functions, updating the responses requires calling delete on all of the Response objects in a particular section, but it would be good to delete only responses that have been cleared by the user of the survey instead of deleting everything - this probably would require the program to know the response values beforehand.

Duplicate responses

Surveyor sporadically saves duplicate responses. Currently, the gem binds an ajaxSubmit to the change event of the #survey_form. If you have a text box as the last question in a section and enter a response and then click the 'Previous Section', 'Next Section' or 'Finish' buttons, two put requests are submitted to the SurveyorController's update action. One ajax request and one standard HTTP request. These submissions are right next to each other.

Currently the update action deletes and inserts the responses every time.

      @response_set.clear_responses
      saved = @response_set.update_attributes(:response_attributes => (params[:responses] || {}).dup , :response_group_attributes => (params[:response_groups] || {}).dup) #copy (dup) to preserve params because we manipulate params in the response_set methods

But if the the first request has not been committed to the database by the time the second request is processed, then duplicate response occur because the first response set is not deleted.

Forcing a reload in the clear_responses method did not solve the problem:

  def clear_responses
    question_ids = Question.find_all_by_survey_section_id(current_section_id).map(&:id)
    responses(true).select{|r| question_ids.include? r.question_id}.map(&:destroy)
    responses.reload
  end

The workaround that I came up with was to delay the submission of the request submitted by a button with some JavaScript.

  var pause = true;

  $('.next_section input[type=submit], .previous_section input[type=submit]').click(function () {
    var button = $(this);
    if ( pause ) {
      $(this).delay(1000, function () {
        pause = false;
        button.click();
      });
      return false;
    }
    else {
      return true;
    }
  });

Simply changing the implementation to "update if exists" will probably not solve the problem because you would still need to check if the response exists. Most likely, some kind of client-side coordination between the submission of ajax and non-ajax requests will be necessary. Nevertheless, updating instead of deleting and inserting would be much better for auditing purposes.

ArgumentError in SurveyorController#create

Hey. I'm getting this issue using surveyor in development mode with phusion. Can't say I properly understand what's going on but see these two:
http://dev.rubyonrails.org/ticket/6001
http://strd6.com/?p=250

I don't properly understand what the right fix is, but a simple one is to add 'unloadable' to the definition of SurveyorController. Apparently (according to the comments of the second link) that is deprecated, but I can't find any Rails documentation about this.

PostgreSql 8.3 Compatibility

In order to get the Surveyor plugin/gem and PostgreSql to work with my application I had to modify the following files:
app/views/question_display_types/_repeater_dropdown.html.haml
app/views/question_display_types/_repeater_default.html.haml

The call in both of these files to ResponseSet#response_for model needs to convert the argument for the response_group to a string like so:

response_set.response_for(question.id, a.id, response_group.to_s)

Having multiple surveys generates invalid html (duplicate id='current_user_id')

  • Home Exposure survey ย 
  •   <li>
        <form action="/surveys/home_exposure_survey" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="0z2orvAjNy5gXt3DzDHQcbwwhiVBO958pfpTcdmkL5o=" /></div>
          <input id="current_user_id" name="current_user_id" type="hidden" value="1" />
          Home Exposure survey
          &nbsp;
          <input name="commit" type="submit" value="Take it" />
        </form>
      </li>
    </ul>
    

The id isn't needed so should probably be set to 'nil' in app/views/surveyor/new.html.haml to avoid this.

= hidden_field_tag("current_user_id", @current_user.nil? ? nil : @current_user.id, :id => nil)

Method of extending the Survey model does not work in development

The method of extending a model suggested by running

script/generate extend_surveyor

does not work within a Rails development environment because

config.cache_classes = false

Consequently, the Survey model gets reloaded each time you reference it but the initializer code does not. So methods start disappearing.

Maybe a different way would be to use inheritance instead of method injection. For example:

class ExtendedSurvey < Survey
end

Loaded foxy fixture ids not guaranteed to be unique

This is more of an FYI for those who may be interested and don't already know. When using foxy fixtures, the id is generated based on the fixture label. As the id is a finite, albeit large, number, it is not guaranteed to be unique. A collision, intended in this case, can be demonstrated by ...

rake surveyor FILE=surveys/kitchen_sink_survey.rb APPEND=true
rake surveyor:load_fixtures APPEND=true

and should show something like

Loading ............../surveys/fixtures/answers.yml...
Appending, skipping delete...
rake aborted!
Mysql::Error: Duplicate entry '943057946' for key 'PRIMARY': INSERT INTO `answers` (`question_id`, `is_exclusive`, `created_at`, `updated_at`, `hide_label`, `text`, `response_class`, `id`, `short_text`, `display_order`, `reference_identifier`, `data_export_identifier`) VALUES (304607657, 0, '2010-03-24 23:21:39', '2010-03-24 23:21:39', 0, '2', 'answer', 943057946, '2', 5, NULL, '2')

In the random chance that this collision occurs on 2 labels that are not identical in the middle of a fixture load, you will be left with a mess. The only foreseeable solution would be to regenerate the fixtures and try again.

support validating responses in the UI

Surveyor's Validation model now determines whether or not an answer is_valid? per the criteria set out by the author of the survey. This functionality is not yet hooked into the views, and needs to be implemented in a style similar to Question and QuestionGroup Dependencies.

ResponseSet access_code not guaranteed to be unique

As there is no model validation or unique index in the database, there is no guarantee that your response_set access_code will be unique. It is just the output from Surveyor.make_tiny_code which is just 10 randomly selected characters. The character set is upper and lower case letters plus digits, giving a set size of 62. I concede that 62 ^ 10 is a HUGE number, but each time a survey is taken, the possibility of those 10 randomly selected characters already existing goes up.

In the ridiculously unlikely chance that a duplicate access_code is created, when the create action redirects to the edit action, the response set will be the first one and not the one just created.

@response_set = ResponseSet.find_by_access_code(params[:response_set_code])

Update....

Adding ...

def access_code=(value)
  while ResponseSet.find_by_access_code(value)
    value = Surveyor.make_tiny_code
  end
  super
end

... to ResponseSet seems to "fix" this "problem", although I don't know how kosher it is.

mandatory_questions_answered

the method "mandatory_questions_answered" returns false if a label is included e.g

survey "portal 4 screen" do
section "screen" do
label "Before you take this survey we would like to know some things about you"
question_1 "Are you Ugly?",:pick=>:one
answer "no"
answer "yes"
end
end

jQuery form events in IE

Checkbox and radio button events do not fire properly in IE. This causes a problem with dependency triggering.

Having a counter_cache column defined in the extension causes double incrementations

I attached my ResponseSets to a Subject and added
belongs_to :subject, :counter_cache => true
to the base.class_eval section of the ResponseSetExtensions. Now when I add a new response set, the counter cache column in the subject is double incremented. I doubt that this is a Surveyor issue, but I fixed it by wrapping the association in a condition like

unless base.instance_methods.include?('subject')
   belongs_to :subject, :counter_cache => true
end

This sounds more like a rails problem, but it shows its head here. Has anyone else seen this?

[Syntax error] undefined method `transation' for ActiveRecord::Base:Class

Hello,

When I try the kitchen sink example, then click "Next section" (bottom of page), I get this:

NoMethodError (undefined method `transation' for ActiveRecord::Base:Class):
  vendor/plugins/surveyor/app/controllers/surveyor_controller.rb:64:in `update'
  haml (2.2.20) lib/sass/plugin/rails.rb:20:in `process'
  /usr/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'
  /usr/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'
  /usr/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'
  /usr/lib/ruby/1.8/webrick/server.rb:162:in `start'
  /usr/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'
  /usr/lib/ruby/1.8/webrick/server.rb:95:in `start'
  /usr/lib/ruby/1.8/webrick/server.rb:92:in `each'
  /usr/lib/ruby/1.8/webrick/server.rb:92:in `start'
  /usr/lib/ruby/1.8/webrick/server.rb:23:in `start'
  /usr/lib/ruby/1.8/webrick/server.rb:82:in `start'

Fixing the typo "transation" to "transaction" on line 64 of app/controllers/surveyor_controller.rb seems to get it working again. Thought you may want to include this fix.

Thanks for the great gem.

Dependency Logic in Controller should not check if a dependent question has been answered

The dependents method of app/controllers/surveying_controller.rb checks to see if a question has met its dependency condition and been answered. This check for the second condition causes a dependent question to disappear after you answer it:

if dep.met?(response_set) and response_set.has_not_answered_question?(dep.question)

I removed the check for the second condition in my application.

Loading Surveys

Currently all previous surveys are deleted when you load new ones.....Ideally, it should just load new surveys

Bug in surveyor_controller.rb

Line #29 says
if (@Survey = Survey.find_by_access_code(params[:survey_code])) && (@response_set = ResponseSet.create(:survey => @Survey, :user_id => @current_user))

Shouldn't is say:
if (@Survey = Survey.find_by_access_code(params[:survey_code])) && (@response_set = ResponseSet.create(:survey => @Survey, :user => @current_user))

(the results it was creating didn't properly have users attached).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.