[![Gem Version](https://badge.fury.io/rb/active_record_survey.svg)](http://badge.fury.io/rb/active_record_survey) [![Build Status](https://travis-ci.org/butchmarshall/active_record_survey.svg?branch=master)](https://travis-ci.org/butchmarshall/active_record_survey) # ActiveRecordSurvey An attempt at a more versatile data structure for making and taking surveys. This gem tries to be as unopinionated as possible on the peripheral details on how you implement a survey. The goal is to give a simple interface for creating surveys and validating the answers given to them. Release Notes ============ **0.1.31** - `ActiveRecordSurvey::Node::Answer#move_up` and `ActiveRecordSurvey::Node::Answer#move_down` implemented so you can change the position of answers relative to one another i both branching and chained types. I am not happy yet with this. [AwesomeNestedSet](https://github.com/collectiveidea/awesome_nested_set) seems to require nodes exist before moving them which is a limitation I'd like to not have. **0.1.30** - `ActiveRecordSurvey::Node::Question` now throws ArgumentError if answers of different types are added to it **0.1.29** - `ActiveRecordSurvey::Node::Answer` now cleans up associated node_maps on destruction - extending/including `ActiveRecordSurvey::Answer::Chained` now rebuilds broken links in the parent<->child node_maps if a middle node_map is destroyed **0.1.26** - Major refactor of answer#build_link and answer#remove_link - `ActiveRecordSurvey::Node` now has a direct reference to its survey. Don't forget to run the install task Update_0_1_26_ActiveRecordSurvey - survey#build_question removed, no longer needed, just use survey.questions.build **0.1.24** - Refactored class `ActiveRecordSurvey::Node::Answer::Chain` to module `ActiveRecordSurvey::Node::Answer::Chained` - this functionality makes way more sense implemented as a module. **0.1.23** - Added `ActiveRecordSurvey::Node::Answer::Chain` for common chainable interface for answers **0.1.22** - answer#remove_link cleaned up so it can be understood **0.1.21** - answer#build_link now detects and throws an error when a infinite loop is added **0.1.20** - answer#remove_link wasn't correct. Fixed and added unit tests **0.1.15** - Don't consider instance_nodes marked for destruction when validating **0.1.14** - build_question now only accepts a question - Exceptions added **0.1.13** - Added Question#build_answer and Answer#build_link to make survey creation possible without dealing with internal nodes ## Installation Add this line to your application's Gemfile: ```ruby gem 'active_record_survey' ``` And then execute: $ bundle Or install it yourself as: $ gem install active_record_survey ## Installation ```ruby rails generate active_record_survey:active_record ``` ## Usage See the spec file for more detailed usage. ***Please*** be aware that the default gem does not prescribe how your `ActiveRecordSurvey::Node` should store text - that's up for you to implement. See the spec for a sample implementation or [ActiveRecordSurveyApi](https://github.com/butchmarshall/active_record_survey_api) for a fully translatable implementation using the [Globalize](https://github.com/globalize/globalize) gem. The usage below with `:text => ""` will not actually work unless you implement `:text` ### Build a basic survey ```ruby # Building surveys @survey = ActiveRecordSurvey::Survey.new() @q1 = @survey.questions.build(:type => "ActiveRecordSurvey::Node::Question", :text => "Question #1", :survey => @survey) @q1_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #1") @q1_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #2") @q1_a3 = ActiveRecordSurvey::Node::Answer.new(:text => "Q1 Answer #3") @q1.build_answer(@q1_a1) @q1.build_answer(@q1_a2) @q1.build_answer(@q1_a3) @q2 = @survey.questions.build(:type => "ActiveRecordSurvey::Node::Question", :text => "Question #2", :survey => @survey) @q2_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q2 Answer #1") @q2_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q2 Answer #2") @q2.build_answer(@q2_a1) @q2.build_answer(@q2_a2) @q3 = @survey.questions.build(:type => "ActiveRecordSurvey::Node::Question", :text => "Question #3", :survey => @survey) @q3_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q3 Answer #1") @q3_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q3 Answer #2") @q3.build_answer(@q3_a1) @q3.build_answer(@q3_a2) @q4 = @survey.questions.build(:type => "ActiveRecordSurvey::Node::Question", :text => "Question #4", :survey => @survey) @q4_a1 = ActiveRecordSurvey::Node::Answer.new(:text => "Q4 Answer #1") @q4_a2 = ActiveRecordSurvey::Node::Answer.new(:text => "Q4 Answer #2") @q4.build_answer(@q4_a1) @q4.build_answer(@q4_a2) # Link up Q1 @q1_a1.build_link(@q2) @q1_a2.build_link(@q3) @q1_a3.build_link(@q4) # Link up Q2 @q2_a1.build_link(@q4) @q2_a2.build_link(@q3) # Link up Q3 @q3_a1.build_link(@q4) @q3_a2.build_link(@q4) # Commit everything to the database! @survey.save ``` The will build a survey with the following node structure. ![alt tag](https://raw.githubusercontent.com/butchmarshall/active_record_survey/master/bin/Example_1.png) ### Answer Types A number of different answer types are implemented by default. - [Default (a.k.a radio)](#answer_default) - [Boolean (a.k.a. checkbox)](#answer_checkbox) - [Rank](#answer_rank) - [Scale](#answer_scale) - [Text](#answer_text) #### Default ```ruby ActiveRecordSurvey::Node::Answer ``` The default answer type (think of it like a radio question) - this is currently the only answer type you can attach to a question so that depending on the answer given you can branch to a different question. #### Boolean ```ruby ActiveRecordSurvey::Node::Answer::Boolean ``` True/False (0/1 actually... think of it like a checkbox) answer types. #### Rank ```ruby ActiveRecordSurvey::Node::Answer::Rank ``` Rankable (answer value is 1..NUM_ANSWERS) to rank the answers in relation to one another. #### Scale ```ruby ActiveRecordSurvey::Node::Answer::Scale ``` Scale (answer value is a min/max on a scale of A->B) where you can specificy each answer on a scale #### Text ```ruby ActiveRecordSurvey::Node::Answer::Text ``` Textual answer to a question (e.g. please tell us what you thinik...) ### Answer Validation Enforcing answer criteria is accomplished by attaching validation nodes to `ActiveRecordSurvey::Node` records. When a survey is taken validations are automatically run and their criteria is enforced. A number of validations are already implemented by default. You can implement your own validation by extending the `ActiveRecordSurvey::ValidationNode` class. - [MaximumAnswer](#answer_maximum_answer) - [MinimumAnswer](#answer_minimum_answer) - [MaximumValue](#answer_maximum_value) - [MinimumValue](#answer_minimum_value) - [MaximumLength](#answer_maximum_length) - [MinimumLength](#answer_minimum_length) #### MaximumAnswer ```ruby ActiveRecordSurvey::ValidationNode::MaximumAnswer ``` These nodes should only be attached to nodes which extend `ActiveRecordSurvey::Node::Question` nodes. Ensures that a maximum numer of answers have been selected. For chained answer nodes such as `ActiveRecordSurvey::Node::Answer::Boolean`,`ActiveRecordSurvey::Node::Answer::Rank`, and `ActiveRecordSurvey::Node::Answer::Scale` it enforces a maximum amount that be answered. #### MinimumAnswer ```ruby ActiveRecordSurvey::ValidationNode::MinimumAnswer ``` These nodes should only be attached to nodes which extend `ActiveRecordSurvey::Node::Question` nodes. Ensures that a minimum numer of answers have been selected. For chained answer nodes such as `ActiveRecordSurvey::Node::Answer::Boolean`,`ActiveRecordSurvey::Node::Answer::Rank`, and `ActiveRecordSurvey::Node::Answer::Scale` it enforces a minimum amount that can be answered. #### MaximumValue ```ruby ActiveRecordSurvey::ValidationNode::MaximumValue ``` These nodes should only be attached to nodes which extend `ActiveRecordSurvey::Node::Answer` nodes which record an scalar answer value such as `ActiveRecordSurvey::Node::Answer::Scale`. Ensures that a minimum scalar value has been entered. #### MinimumValue ```ruby ActiveRecordSurvey::ValidationNode::MinimumValue ``` These nodes should only be attached to nodes which extend `ActiveRecordSurvey::Node::Answer` nodes which record an scalar answer value such as `ActiveRecordSurvey::Node::Answer::Scale`. Ensures that a maximum scalar value has been entered. #### MaximumLength ```ruby ActiveRecordSurvey::ValidationNode::MaximumLength ``` These nodes should only be attached to nodes which extend `ActiveRecordSurvey::Node::Answer` nodes nodes which record a text value such as `ActiveRecordSurvey::Node::Answer::Text`. Ensures that a maximum amount of text has been answered. #### MinimumLength ```ruby ActiveRecordSurvey::ValidationNode::MinimumLength ``` These nodes should only be attached to nodes which extend `ActiveRecordSurvey::Node::Answer` nodes nodes which record a text value such as `ActiveRecordSurvey::Node::Answer::Text`. Ensures that a minimum amount of text has been answered. ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/butchmarshall/active_record_survey. ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).