fe 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +20 -0
  4. data/Rakefile +48 -0
  5. data/app/assets/images/fe/ajax-loader.gif +0 -0
  6. data/app/assets/images/fe/butgrad.png +0 -0
  7. data/app/assets/images/fe/icons/add.png +0 -0
  8. data/app/assets/images/fe/icons/application_form.png +0 -0
  9. data/app/assets/images/fe/icons/application_form_add.png +0 -0
  10. data/app/assets/images/fe/icons/arrow-move.png +0 -0
  11. data/app/assets/images/fe/icons/arrow_out.png +0 -0
  12. data/app/assets/images/fe/icons/asterisk_yellow.png +0 -0
  13. data/app/assets/images/fe/icons/book-open-list.png +0 -0
  14. data/app/assets/images/fe/icons/book_open.png +0 -0
  15. data/app/assets/images/fe/icons/briefcase.png +0 -0
  16. data/app/assets/images/fe/icons/calendar_view_day.png +0 -0
  17. data/app/assets/images/fe/icons/checkbox.png +0 -0
  18. data/app/assets/images/fe/icons/copy_paste.gif +0 -0
  19. data/app/assets/images/fe/icons/disk.png +0 -0
  20. data/app/assets/images/fe/icons/document--pencil.png +0 -0
  21. data/app/assets/images/fe/icons/document-copy.png +0 -0
  22. data/app/assets/images/fe/icons/map_magnify.png +0 -0
  23. data/app/assets/images/fe/icons/minus-button.png +0 -0
  24. data/app/assets/images/fe/icons/move.png +0 -0
  25. data/app/assets/images/fe/icons/page_error.png +0 -0
  26. data/app/assets/images/fe/icons/page_valid.png +0 -0
  27. data/app/assets/images/fe/icons/page_white.png +0 -0
  28. data/app/assets/images/fe/icons/page_white_add.png +0 -0
  29. data/app/assets/images/fe/icons/page_white_error.png +0 -0
  30. data/app/assets/images/fe/icons/paper-clip.png +0 -0
  31. data/app/assets/images/fe/icons/pencil-small.png +0 -0
  32. data/app/assets/images/fe/icons/pencil.png +0 -0
  33. data/app/assets/images/fe/icons/question-balloon.png +0 -0
  34. data/app/assets/images/fe/icons/radio.png +0 -0
  35. data/app/assets/images/fe/icons/star-small.png +0 -0
  36. data/app/assets/images/fe/icons/table.png +0 -0
  37. data/app/assets/images/fe/icons/text_align_left.png +0 -0
  38. data/app/assets/images/fe/icons/text_horizontalrule.png +0 -0
  39. data/app/assets/images/fe/icons/textfield.png +0 -0
  40. data/app/assets/images/fe/indent.png +0 -0
  41. data/app/assets/images/fe/overlay.png +0 -0
  42. data/app/assets/images/fe/selected_bg.jpg +0 -0
  43. data/app/assets/images/fe/status.gif +0 -0
  44. data/app/assets/images/fe/tooltip/black.png +0 -0
  45. data/app/assets/images/fe/tooltip/black_arrow.png +0 -0
  46. data/app/assets/images/fe/tooltip/black_arrow_big.png +0 -0
  47. data/app/assets/images/fe/tooltip/black_big.png +0 -0
  48. data/app/assets/images/fe/tooltip/white.png +0 -0
  49. data/app/assets/images/fe/tooltip/white_arrow.png +0 -0
  50. data/app/assets/images/fe/tooltip/white_arrow_big.png +0 -0
  51. data/app/assets/images/fe/tooltip/white_big.png +0 -0
  52. data/app/assets/images/fe/uploadify/cancel.png +0 -0
  53. data/app/assets/images/fe/uploadify/uploadify.swf +0 -0
  54. data/app/assets/javascripts/fe/admin.js +7 -0
  55. data/app/assets/javascripts/fe/jquery.metadata.js +122 -0
  56. data/app/assets/javascripts/fe/jquery.scrollTo-min.js +7 -0
  57. data/app/assets/javascripts/fe/jquery.uploadify.v2.1.0.js +258 -0
  58. data/app/assets/javascripts/fe/jquery.validate.pack.js +15 -0
  59. data/app/assets/javascripts/fe/qe.admin.js +115 -0
  60. data/app/assets/javascripts/fe/qe.common.js +57 -0
  61. data/app/assets/javascripts/fe/qe.public.js +331 -0
  62. data/app/assets/javascripts/fe/swfobject.js +4 -0
  63. data/app/assets/stylesheets/fe/qe.screen.css.scss.erb +552 -0
  64. data/app/assets/stylesheets/fe/reset.css +55 -0
  65. data/app/assets/stylesheets/fe/validation.css +91 -0
  66. data/app/controllers/fe/admin/elements_controller.rb +185 -0
  67. data/app/controllers/fe/admin/email_templates_controller.rb +61 -0
  68. data/app/controllers/fe/admin/question_pages_controller.rb +113 -0
  69. data/app/controllers/fe/admin/question_sheets_controller.rb +106 -0
  70. data/app/controllers/fe/answer_pages_controller.rb +3 -0
  71. data/app/controllers/fe/answer_sheets_controller.rb +3 -0
  72. data/app/controllers/fe/concerns/answer_pages_controller_concern.rb +80 -0
  73. data/app/controllers/fe/concerns/answer_sheets_controller_concern.rb +84 -0
  74. data/app/controllers/fe/reference_sheets_controller.rb +29 -0
  75. data/app/helpers/fe/answer_pages_helper.rb +19 -0
  76. data/app/helpers/fe/application_helper.rb +49 -0
  77. data/app/mailers/fe/notifier.rb +22 -0
  78. data/app/models/fe/answer.rb +13 -0
  79. data/app/models/fe/answer_pages_presenter.rb +13 -0
  80. data/app/models/fe/answer_sheet.rb +10 -0
  81. data/app/models/fe/answer_sheet_question_sheet.rb +7 -0
  82. data/app/models/fe/attachment_field.rb +7 -0
  83. data/app/models/fe/choice_field.rb +7 -0
  84. data/app/models/fe/concerns/answer_concern.rb +36 -0
  85. data/app/models/fe/concerns/answer_pages_presenter_concern.rb +99 -0
  86. data/app/models/fe/concerns/answer_sheet_concern.rb +61 -0
  87. data/app/models/fe/concerns/choice_field_concern.rb +131 -0
  88. data/app/models/fe/condition.rb +53 -0
  89. data/app/models/fe/date_field.rb +52 -0
  90. data/app/models/fe/element.rb +164 -0
  91. data/app/models/fe/email_template.rb +7 -0
  92. data/app/models/fe/option.rb +10 -0
  93. data/app/models/fe/option_group.rb +10 -0
  94. data/app/models/fe/page.rb +101 -0
  95. data/app/models/fe/page_element.rb +9 -0
  96. data/app/models/fe/page_link.rb +15 -0
  97. data/app/models/fe/paragraph.rb +10 -0
  98. data/app/models/fe/payment.rb +52 -0
  99. data/app/models/fe/payment_question.rb +22 -0
  100. data/app/models/fe/presenter.rb +23 -0
  101. data/app/models/fe/question.rb +256 -0
  102. data/app/models/fe/question_grid.rb +26 -0
  103. data/app/models/fe/question_grid_with_total.rb +11 -0
  104. data/app/models/fe/question_set.rb +82 -0
  105. data/app/models/fe/question_sheet.rb +73 -0
  106. data/app/models/fe/reference_question.rb +42 -0
  107. data/app/models/fe/reference_sheet.rb +149 -0
  108. data/app/models/fe/section.rb +12 -0
  109. data/app/models/fe/state_chooser.rb +10 -0
  110. data/app/models/fe/text_field.rb +29 -0
  111. data/app/views/fe/admin/elements/_errors.html.erb +1 -0
  112. data/app/views/fe/admin/elements/create.js.erb +11 -0
  113. data/app/views/fe/admin/elements/destroy.js.erb +4 -0
  114. data/app/views/fe/admin/elements/drop.js.erb +2 -0
  115. data/app/views/fe/admin/elements/duplicate.js.erb +2 -0
  116. data/app/views/fe/admin/elements/edit.js.erb +4 -0
  117. data/app/views/fe/admin/elements/error.js.erb +4 -0
  118. data/app/views/fe/admin/elements/new.js.erb +10 -0
  119. data/app/views/fe/admin/elements/reorder.js.erb +0 -0
  120. data/app/views/fe/admin/elements/update.js.erb +9 -0
  121. data/app/views/fe/admin/email_templates/_form.html.erb +8 -0
  122. data/app/views/fe/admin/email_templates/edit.html.erb +13 -0
  123. data/app/views/fe/admin/email_templates/index.html.erb +20 -0
  124. data/app/views/fe/admin/email_templates/new.html.erb +11 -0
  125. data/app/views/fe/admin/panels/_advanced_options.html.erb +16 -0
  126. data/app/views/fe/admin/panels/_common_fields.html.erb +24 -0
  127. data/app/views/fe/admin/panels/_condition.html.erb +6 -0
  128. data/app/views/fe/admin/panels/_insert.html.erb +39 -0
  129. data/app/views/fe/admin/panels/_nav_controls.html.erb +6 -0
  130. data/app/views/fe/admin/panels/_page.html.erb +3 -0
  131. data/app/views/fe/admin/panels/_pages_list.html.erb +16 -0
  132. data/app/views/fe/admin/panels/_prop_attachment_field.html.erb +2 -0
  133. data/app/views/fe/admin/panels/_prop_choice_field.html.erb +46 -0
  134. data/app/views/fe/admin/panels/_prop_date_field.html.erb +7 -0
  135. data/app/views/fe/admin/panels/_prop_element.html.erb +30 -0
  136. data/app/views/fe/admin/panels/_prop_page.html.erb +21 -0
  137. data/app/views/fe/admin/panels/_prop_paragraph.html.erb +32 -0
  138. data/app/views/fe/admin/panels/_prop_question_grid.html.erb +23 -0
  139. data/app/views/fe/admin/panels/_prop_question_grid_with_total.html.erb +14 -0
  140. data/app/views/fe/admin/panels/_prop_reference_question.html.erb +12 -0
  141. data/app/views/fe/admin/panels/_prop_section.html.erb +2 -0
  142. data/app/views/fe/admin/panels/_prop_sheet.html.erb +17 -0
  143. data/app/views/fe/admin/panels/_prop_text_field.html.erb +20 -0
  144. data/app/views/fe/admin/question_pages/_element.html.erb +18 -0
  145. data/app/views/fe/admin/question_pages/_element_show.html.erb +10 -0
  146. data/app/views/fe/admin/question_pages/_errors.html.erb +1 -0
  147. data/app/views/fe/admin/question_pages/_question_page.html.erb +13 -0
  148. data/app/views/fe/admin/question_pages/create.js.erb +11 -0
  149. data/app/views/fe/admin/question_pages/destroy.js.erb +5 -0
  150. data/app/views/fe/admin/question_pages/edit.js.erb +3 -0
  151. data/app/views/fe/admin/question_pages/error.js.erb +4 -0
  152. data/app/views/fe/admin/question_pages/show.js.erb +8 -0
  153. data/app/views/fe/admin/question_pages/show_panel.js.erb +3 -0
  154. data/app/views/fe/admin/question_pages/update.js.erb +2 -0
  155. data/app/views/fe/admin/question_sheets/_errors.html.erb +1 -0
  156. data/app/views/fe/admin/question_sheets/edit.js.erb +3 -0
  157. data/app/views/fe/admin/question_sheets/error.js.erb +5 -0
  158. data/app/views/fe/admin/question_sheets/index.html.erb +41 -0
  159. data/app/views/fe/admin/question_sheets/new.html.erb +15 -0
  160. data/app/views/fe/admin/question_sheets/show.html.erb +27 -0
  161. data/app/views/fe/admin/question_sheets/update.js.erb +2 -0
  162. data/app/views/fe/answer_pages/_answer_page.html.erb +56 -0
  163. data/app/views/fe/answer_pages/_element.html.erb +9 -0
  164. data/app/views/fe/answer_pages/update.js.erb +5 -0
  165. data/app/views/fe/answer_sheets/_answer_sheet.html.erb +24 -0
  166. data/app/views/fe/answer_sheets/_element.html.erb +54 -0
  167. data/app/views/fe/answer_sheets/_incomplete.html.erb +10 -0
  168. data/app/views/fe/answer_sheets/_page_link.html.erb +7 -0
  169. data/app/views/fe/answer_sheets/_pages_list.html.erb +11 -0
  170. data/app/views/fe/answer_sheets/_title.html.erb +1 -0
  171. data/app/views/fe/answer_sheets/edit.html.erb +60 -0
  172. data/app/views/fe/answer_sheets/incomplete.js.erb +5 -0
  173. data/app/views/fe/answer_sheets/index.html.erb +18 -0
  174. data/app/views/fe/answer_sheets/send_reference_invite.js.erb +8 -0
  175. data/app/views/fe/answer_sheets/show.html.erb +13 -0
  176. data/app/views/fe/help/builder.html +33 -0
  177. data/app/views/fe/help/question_grid.html +18 -0
  178. data/app/views/fe/questions/_acceptance.html.erb +13 -0
  179. data/app/views/fe/questions/_attachment_field.html.erb +42 -0
  180. data/app/views/fe/questions/_checkbox_field.html.erb +49 -0
  181. data/app/views/fe/questions/_country.html.erb +7 -0
  182. data/app/views/fe/questions/_date_field.html.erb +7 -0
  183. data/app/views/fe/questions/_date_field_mmyy.html.erb +9 -0
  184. data/app/views/fe/questions/_drop_down_field.html.erb +7 -0
  185. data/app/views/fe/questions/_paragraph.html.erb +1 -0
  186. data/app/views/fe/questions/_question_grid.html.erb +65 -0
  187. data/app/views/fe/questions/_question_grid_with_total.html.erb +57 -0
  188. data/app/views/fe/questions/_questions.html.erb +9 -0
  189. data/app/views/fe/questions/_radio_button_field.html.erb +41 -0
  190. data/app/views/fe/questions/_rating.html.erb +25 -0
  191. data/app/views/fe/questions/_reference_discipler.html.erb +1 -0
  192. data/app/views/fe/questions/_reference_friend.html.erb +1 -0
  193. data/app/views/fe/questions/_reference_parent.html.erb +1 -0
  194. data/app/views/fe/questions/_reference_peer.html.erb +1 -0
  195. data/app/views/fe/questions/_reference_question.html.erb +38 -0
  196. data/app/views/fe/questions/_reference_roommate.html.erb +1 -0
  197. data/app/views/fe/questions/_reference_spiritual.html.erb +1 -0
  198. data/app/views/fe/questions/_reference_staff.html.erb +1 -0
  199. data/app/views/fe/questions/_section.html.erb +1 -0
  200. data/app/views/fe/questions/_state_chooser.html.erb +7 -0
  201. data/app/views/fe/questions/_text_area_field.html.erb +6 -0
  202. data/app/views/fe/questions/_text_field.html.erb +8 -0
  203. data/app/views/fe/questions/_yes_no.html.erb +17 -0
  204. data/app/views/fe/questions/_yes_no_field.erb +17 -0
  205. data/app/views/fe/reference_sheets/not_found.html.erb +5 -0
  206. data/app/views/layouts/fe/application.html.erb +31 -0
  207. data/app/views/layouts/fe/qe.admin.html.erb +30 -0
  208. data/config/routes.rb +49 -0
  209. data/db/migrate/20131003041856_core.rb +91 -0
  210. data/db/migrate/20131003044250_create_reference_sheets.rb +23 -0
  211. data/db/migrate/20131003044436_add_element_and_answer_fields.rb +10 -0
  212. data/db/migrate/20131003044518_create_email_templates.rb +17 -0
  213. data/db/migrate/20131003044621_add_max_lengths.rb +8 -0
  214. data/db/migrate/20131003044714_create_join_table.rb +11 -0
  215. data/db/migrate/20131016162128_remove_question_id_from_element.rb +9 -0
  216. data/lib/fe.rb +22 -0
  217. data/lib/fe/engine.rb +19 -0
  218. data/lib/fe/version.rb +3 -0
  219. data/lib/generators/fe/install_generator.rb +132 -0
  220. data/lib/tasks/qe_tasks.rake +4 -0
  221. data/spec/controllers/fe/admin/elements_controller_spec.rb +4 -0
  222. data/spec/controllers/fe/admin/email_templates_controller_spec.rb +4 -0
  223. data/spec/controllers/fe/admin/question_pages_controller_spec.rb +4 -0
  224. data/spec/controllers/fe/admin/question_sheets_controller_spec.rb +4 -0
  225. data/spec/controllers/fe/answer_pages_controller_spec.rb +4 -0
  226. data/spec/controllers/fe/answer_sheets_controller_spec.rb +4 -0
  227. data/spec/controllers/fe/reference_sheets_controller_spec.rb +4 -0
  228. data/spec/factories/answer_sheets.rb +5 -0
  229. data/spec/factories/answers.rb +4 -0
  230. data/spec/factories/paragraphs.rb +4 -0
  231. data/spec/factories/question_sheet.rb +5 -0
  232. data/spec/factories/reference_questions.rb +4 -0
  233. data/spec/models/fe/answer_sheet_question_sheet_spec.rb +6 -0
  234. data/spec/models/fe/answer_sheet_spec.rb +8 -0
  235. data/spec/models/fe/answer_spec.rb +13 -0
  236. data/spec/models/fe/attachment_field_spec.rb +4 -0
  237. data/spec/models/fe/choice_field_spec.rb +5 -0
  238. data/spec/models/fe/condition_spec.rb +8 -0
  239. data/spec/models/fe/date_field_spec.rb +49 -0
  240. data/spec/models/fe/element_spec.rb +12 -0
  241. data/spec/models/fe/email_template_spec.rb +5 -0
  242. data/spec/models/fe/option_group_spec.rb +4 -0
  243. data/spec/models/fe/option_spec.rb +4 -0
  244. data/spec/models/fe/page_element_spec.rb +6 -0
  245. data/spec/models/fe/page_link_spec.rb +4 -0
  246. data/spec/models/fe/page_spec.rb +14 -0
  247. data/spec/models/fe/paragraph_spec.rb +17 -0
  248. data/spec/models/fe/question_grid_spec.rb +4 -0
  249. data/spec/models/fe/question_grid_with_total_spec.rb +4 -0
  250. data/spec/models/fe/question_set_spec.rb +4 -0
  251. data/spec/models/fe/question_sheet_spec.rb +8 -0
  252. data/spec/models/fe/question_spec.rb +20 -0
  253. data/spec/models/fe/reference_question_spec.rb +17 -0
  254. data/spec/models/fe/reference_sheet_spec.rb +11 -0
  255. data/spec/models/fe/section_spec.rb +4 -0
  256. data/spec/models/fe/state_chooser_spec.rb +4 -0
  257. data/spec/models/fe/text_field_spec.rb +17 -0
  258. data/spec/spec_helper.rb +66 -0
  259. data/spec/support/database.txt +28 -0
  260. metadata +508 -0
@@ -0,0 +1,164 @@
1
+ # Element represents a section, question or content element on the question sheet
2
+ module Fe
3
+ class Element < ActiveRecord::Base
4
+ self.table_name = self.table_name.sub('fe_', Fe.table_name_prefix)
5
+
6
+ belongs_to :question_grid,
7
+ :class_name => "Fe::QuestionGrid",
8
+ :foreign_key => "question_grid_id"
9
+
10
+ belongs_to :choice_field,
11
+ :class_name => "Fe::ChoiceField",
12
+ :foreign_key => "conditional_id"
13
+
14
+ self.inheritance_column = :kind
15
+
16
+ has_many :page_elements, :dependent => :destroy
17
+ has_many :pages, :through => :page_elements
18
+
19
+ scope :active, -> { select("distinct(#{Fe::Element.table_name}.id), #{Fe::Element.table_name}.*").where(Fe::QuestionSheet.table_name + '.archived' => false).joins({:pages => :question_sheet}) }
20
+
21
+ validates_presence_of :kind
22
+ validates_presence_of :style
23
+ # validates_presence_of :label, :style, :on => :update
24
+
25
+ validates_length_of :kind, :maximum => 40, :allow_nil => true
26
+ validates_length_of :style, :maximum => 40, :allow_nil => true
27
+ # validates_length_of :label, :maximum => 255, :allow_nil => true
28
+
29
+ before_validation :set_defaults, :on => :create
30
+
31
+ # HUMANIZED_ATTRIBUTES = {
32
+ # :slug => "Variable"
33
+ # }changed.include?('address1')
34
+ #
35
+ # def self.human_attrib_name(attr)
36
+ # HUMANIZED_ATTRIBUTES[attr.to_sym] || super
37
+ # end
38
+
39
+ def has_response?(answer_sheet = nil)
40
+ false
41
+ end
42
+
43
+ def limit(answer_sheet = nil)
44
+ if answer_sheet && object_name.present? && attribute_name.present?
45
+ begin
46
+ unless eval("answer_sheet." + self.object_name + ".nil?")
47
+ klass = eval("answer_sheet." + self.object_name + ".class")
48
+ column = klass.columns_hash[self.attribute_name]
49
+ column.limit
50
+ end
51
+ rescue
52
+ nil
53
+ end
54
+ end
55
+ end
56
+
57
+ def required?(answer_sheet = nil)
58
+ required == true
59
+ end
60
+
61
+ def position(page = nil)
62
+ if page
63
+ page_elements.where(:page_id => page.id).first.try(:position)
64
+ else
65
+ self[:position]
66
+ end
67
+ end
68
+
69
+ def set_position(position, page = nil)
70
+ if page
71
+ pe = page_elements.where(:page_id => page.id).first
72
+ pe.update_attribute(:position, position) if pe
73
+ else
74
+ self[:position] = position
75
+ end
76
+ position
77
+ end
78
+
79
+ def page_id(page = nil)
80
+ if page
81
+ page.id
82
+ else
83
+ pages.first.id
84
+ end
85
+ end
86
+
87
+ def question?
88
+ self.kind_of?(Question)
89
+ end
90
+
91
+
92
+ # by default the partial for an element matches the class name (override as necessary)
93
+ def ptemplate
94
+ self.class.to_s.underscore
95
+ end
96
+
97
+ # copy an item and all it's children
98
+ def duplicate(page, parent = nil)
99
+ new_element = self.class.new(self.attributes.except('id', 'created_at', 'updated_at'))
100
+ case parent.class.to_s
101
+ when Fe::ChoiceField
102
+ new_element.conditional_id = parent.id
103
+ when Fe::QuestionGrid, Fe::QuestionGridWithTotal
104
+ new_element.question_grid_id = parent.id
105
+ end
106
+ new_element.save(:validate => false)
107
+ Fe::PageElement.create(:element => new_element, :page => page) unless parent
108
+
109
+ # duplicate children
110
+ if respond_to?(:elements) && elements.present?
111
+ elements.each {|e| e.duplicate(page, new_element)}
112
+ end
113
+
114
+ new_element
115
+ end
116
+
117
+ # include nested elements
118
+ def all_elements
119
+ if respond_to?(:elements)
120
+ (elements + elements.collect(&:all_elements)).flatten
121
+ else
122
+ []
123
+ end
124
+ end
125
+
126
+ def reuseable?
127
+ (self.is_a?(Question) || self.is_a?(Fe::QuestionGrid) || self.is_a?(Fe::QuestionGridWithTotal))
128
+ end
129
+
130
+ def self.max_label_length
131
+ @@max_label_length ||= Fe::Element.columns.find{ |c| c.name == "label" }.limit
132
+ end
133
+
134
+ protected
135
+ def set_defaults
136
+ if self.content.blank?
137
+ case self.class.to_s
138
+ when "Fe::ChoiceField" then self.content ||= "Choice One\nChoice Two\nChoice Three"
139
+ when "Fe::Paragraph" then self.content ||="Lorem ipsum..."
140
+ end
141
+ end
142
+
143
+ if self.style.blank?
144
+ case self.class.to_s
145
+ when 'Fe::TextField' then self.style ||= 'essay'
146
+ when "Fe::DateField" then self.style ||= "date"
147
+ when "Fe::FileField" then self.style ||= "file"
148
+ when "Fe::Paragraph" then self.style ||= "paragraph"
149
+ when "Fe::Section" then self.style ||= "section"
150
+ when "Fe::ChoiceField" then self.style = "checkbox"
151
+ when "Fe::QuestionGrid" then self.style ||= "grid"
152
+ when "Fe::QuestionGridWithTotal" then self.style ||= "grid_with_total"
153
+ when "Fe::SchoolPicker" then self.style ||= "school_picker"
154
+ when "Fe::ProjectPreference" then self.style ||= "project_preference"
155
+ when "Fe::StateChooser" then self.style ||= "state_chooser"
156
+ when "Fe::ReferenceQuestion" then self.style ||= "peer"
157
+ else
158
+ self.style ||= self.class.to_s.underscore
159
+ end
160
+ end
161
+ end
162
+
163
+ end
164
+ end
@@ -0,0 +1,7 @@
1
+ module Fe
2
+ class EmailTemplate < ActiveRecord::Base
3
+ self.table_name = self.table_name.sub('fe_', Fe.table_name_prefix)
4
+
5
+ validates_presence_of :name
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Fe
2
+ class Option
3
+ attr_reader :key
4
+ attr_reader :value
5
+ def initialize(key, value)
6
+ @key = key
7
+ @value = value
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Fe
2
+ class OptionGroup
3
+ attr_reader :label
4
+ attr_reader :group
5
+ def initialize(label, group)
6
+ @label = label
7
+ @group = group
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,101 @@
1
+ require 'acts_as_list'
2
+ module Fe
3
+ class Page < ActiveRecord::Base
4
+ self.table_name = self.table_name.sub('fe_', Fe.table_name_prefix)
5
+
6
+ belongs_to :question_sheet
7
+
8
+ has_many :page_elements, -> { order(:position) },
9
+ :dependent => :destroy
10
+
11
+ has_many :elements, -> { order(Fe::PageElement.table_name + '.position') },
12
+ :through => :page_elements
13
+
14
+ has_many :question_grid_with_totals, -> { where("kind = 'Fe::QuestionGridWithTotal'") },
15
+ :through => :page_elements,
16
+ :source => :element
17
+
18
+ has_many :questions, -> { where("kind NOT IN('Fe::Paragraph', 'Fe::Section', 'Fe::QuestionGrid', 'Fe::QuestionGridWithTotal')") },
19
+ :through => :page_elements,
20
+ :source => :element
21
+
22
+ has_many :question_grids, -> { where("kind = 'Fe::QuestionGrid'") },
23
+ :through => :page_elements,
24
+ :source => :element
25
+
26
+ # has_many :conditions, :class_name => "Condition", :foreign_key => "toggle_page_id", # conditions associated with page as a whole
27
+ # :conditions => 'toggle_id is NULL', :dependent => :nullify
28
+
29
+ acts_as_list :column => :number, :scope => :question_sheet_id
30
+
31
+ scope :visible, -> { where(:hidden => false) }
32
+
33
+ # callbacks
34
+ before_validation :set_default_label, :on => :create # Page x
35
+
36
+ # validation
37
+ validates_presence_of :label, :number
38
+ validates_length_of :label, :maximum => 100, :allow_nil => true
39
+
40
+ # validates_uniqueness_of :number, :scope => :question_sheet_id
41
+
42
+ validates_numericality_of :number, :only_integer => true
43
+
44
+ # a page is disabled if there is a condition, and that condition evaluates to false
45
+ # could set multiple conditions to influence this question, in which case all must be met
46
+ # def active?
47
+ # # find first condition that doesn't pass (nil if all pass)
48
+ # self.conditions.detect { |c| !c.evaluate? }.nil? # true if all pass
49
+ # end
50
+ #
51
+ # def question?
52
+ # false
53
+ # end
54
+
55
+ def questions_before_position(position)
56
+ self.elements.where(["#{Fe::PageElement.table_name}.position < ?", position])
57
+ end
58
+
59
+ # Include nested elements
60
+ def all_elements
61
+ (elements + elements.collect(&:all_elements)).flatten
62
+ end
63
+
64
+ def copy_to(question_sheet)
65
+ new_page = Fe::Page.new(self.attributes)
66
+ new_page.question_sheet_id = question_sheet.id
67
+ new_page.save(:validate => false)
68
+ self.elements.each do |element|
69
+ if !question_sheet.archived? && element.reuseable?
70
+ Fe::PageElement.create(:element => element, :page => new_page)
71
+ else
72
+ element.duplicate(new_page)
73
+ end
74
+ end
75
+ end
76
+
77
+ def complete?(answer_sheet)
78
+ all_elements.all? {|e| !e.required?(answer_sheet) || e.has_response?(answer_sheet)}
79
+ end
80
+
81
+ def started?(answer_sheet)
82
+ all_elements.any? {|e| e.has_response?(answer_sheet)}
83
+ end
84
+
85
+ def has_questions?
86
+ all_elements.any? {|e| e.is_a?(Question)}
87
+ end
88
+
89
+ private
90
+
91
+ # next unused label with "Page" prefix
92
+ def set_default_label
93
+ self.label = Fe.next_label("Page", Fe::Page.untitled_labels(self.question_sheet)) if self.label.blank?
94
+ end
95
+
96
+ def self.untitled_labels(sheet)
97
+ sheet ? sheet.pages.where("label like 'Page %'").map {|p| p.label} : []
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,9 @@
1
+ require 'acts_as_list'
2
+ module Fe
3
+ class PageElement < ActiveRecord::Base
4
+ self.table_name = self.table_name.sub('fe_', Fe.table_name_prefix)
5
+ acts_as_list :scope => :page_id
6
+ belongs_to :page
7
+ belongs_to :element
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # represents a link to a page for the page_list sidebar or next page links
2
+ module Fe
3
+ class PageLink
4
+ attr_accessor :dom_id, :label, :load_path, :page
5
+ attr_accessor :save_path # to save current page
6
+
7
+ def initialize(label, load_path, dom_id, page)
8
+ @label = label
9
+ @load_path = load_path
10
+ @dom_id = dom_id
11
+ @page = page
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # Paragraph
2
+ # - Represents a block of content
3
+ #
4
+ # :kind - 'Fe::Paragraph' for single table inheritance (STI)
5
+ # :content - instructions, agreements, etc. to display
6
+ module Fe
7
+ class Paragraph < Element
8
+ validates_presence_of :content, :on => :update
9
+ end
10
+ end
@@ -0,0 +1,52 @@
1
+ module Fe
2
+ class Payment < ActiveRecord::Base
3
+ self.table_name = self.table_name.sub('fe_', Fe.table_name_prefix)
4
+
5
+ attr_accessor :first_name, :last_name, :address, :city, :state, :zip, :card_number, :payment_type,
6
+ :expiration_month, :expiration_year, :security_code, :staff_first, :staff_last, :card_type
7
+
8
+ scope :non_denied, -> { where("(status <> 'Denied' AND status <> 'Errored') OR status is null") }
9
+
10
+ belongs_to :answer_sheet, class_name: Fe.answer_sheet_class
11
+
12
+ after_save :check_answer_sheet_complete
13
+
14
+ def validate
15
+ if credit?
16
+ errors.add_on_empty([:first_name, :last_name, :address, :city, :state, :zip, :card_number,
17
+ :expiration_month, :expiration_year, :security_code])
18
+ errors.add(:card_number, "is invalid.") if get_card_type.nil?
19
+ end
20
+ end
21
+
22
+ def check_answer_sheet_complete
23
+ if self.approved?
24
+ self.answer_sheet.complete
25
+ end
26
+ end
27
+
28
+ def credit?
29
+ self.payment_type == 'Credit Card'
30
+ end
31
+
32
+ def staff?
33
+ self.payment_type == 'Staff'
34
+ end
35
+
36
+ def approved?
37
+ self.status == 'Approved'
38
+ end
39
+
40
+ def approve!
41
+ self.status = 'Approved'
42
+ self.save!
43
+ end
44
+
45
+ def get_card_type
46
+ card = ActiveMerchant::Billing::CreditCard.new(:number => card_number)
47
+ card.valid?
48
+ card.type
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,22 @@
1
+ module Fe
2
+ class PaymentQuestion < Question
3
+
4
+ def response(answer_sheet=nil)
5
+ return Payment.new unless answer_sheet
6
+ answer_sheet.payments || [Payment.new(:answer_sheetlication_id => answer_sheet.id) ]
7
+ end
8
+
9
+ def display_response(answer_sheet=nil)
10
+ response(answer_sheet).to_s
11
+ end
12
+
13
+ def has_response?(answer_sheet = nil)
14
+ if answer_sheet
15
+ answer_sheet.payments.length > 0
16
+ else
17
+ false
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ # based on http://blog.caboo.se/articles/2007/8/23/simple-presenters
2
+ module Fe
3
+ class Presenter
4
+ include ActionView::Helpers::TagHelper # link_to
5
+ # include ActionView::Helpers::UrlHelper # url_for
6
+ include ActionController::UrlFor # named routes
7
+ include ActionController::RecordIdentifier # dom_id
8
+ include Rails.application.routes.url_helpers
9
+ attr_accessor :controller # so we can be lazy
10
+
11
+ def initialize(controller)
12
+ @controller = controller
13
+ end
14
+
15
+ def request
16
+ @controller.request
17
+ end
18
+
19
+ def env
20
+ @controller.env
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,256 @@
1
+ # Question
2
+ # - An individual question element
3
+ # - children: TextField, ChoiceField, DateField, FileField
4
+
5
+ # :kind - 'TextField', 'ChoiceField', 'DateField' for single table inheritance (STI)
6
+ # :label - label for the question, such as "First name"
7
+ # :style - essay|phone|email|numeric|currency|simple, selectbox|radio, checkbox, my|mdy
8
+ # :required - is this question itself required or optional?
9
+ # :content - choices (one per line) for choice field
10
+
11
+ module Fe
12
+ class Question < Element
13
+ include ActionController::RecordIdentifier # dom_id
14
+ has_many :conditions,
15
+ :class_name => "Condition",
16
+ :foreign_key => "toggle_id",
17
+ :dependent => :nullify
18
+
19
+ has_many :dependents,
20
+ :class_name => "Condition",
21
+ :foreign_key => "trigger_id",
22
+ :dependent => :nullify
23
+
24
+ has_many :sheet_answers,
25
+ :class_name => "Answer",
26
+ :foreign_key => "question_id",
27
+ :dependent => :destroy
28
+
29
+ belongs_to :related_question_sheet,
30
+ :class_name => "QuestionSheet",
31
+ :foreign_key => "related_question_sheet_id"
32
+
33
+ # validates_inclusion_of :required, :in => [false, true]
34
+
35
+ validates_format_of :slug, :with => /\A[a-z_][a-z0-9_]*\z/,
36
+ :allow_nil => true, :if => Proc.new { |q| !q.slug.blank? },
37
+ :message => 'may only contain lowercase letters, digits and underscores; and cannot begin with a digit.' # enforcing lowercase because javascript is case-sensitive
38
+ validates_length_of :slug, :in => 4..36,
39
+ :allow_nil => true, :if => Proc.new { |q| !q.slug.blank? }
40
+ validates_uniqueness_of :slug,
41
+ :allow_nil => true, :if => Proc.new { |q| !q.slug.blank? },
42
+ :message => 'must be unique.'
43
+
44
+ # a question has one response per AnswerSheet (that is, an instance of a user filling out the question)
45
+ # generally the response is a single answer
46
+ # however, "Choose Many" (checkbox) questions have multiple answers in a single response
47
+
48
+ attr_accessor :answers
49
+
50
+ # @answers = nil # one or more answers in response to this question
51
+ # @mark_for_destroy = nil # when something is unchecked, there are less answers to the question than before
52
+
53
+
54
+ # a question is disabled if there is a condition, and that condition evaluates to false
55
+ # could set multiple conditions to influence this question, in which case all must be met
56
+ # def active?
57
+ # # find first condition that doesn't pass (nil if all pass)
58
+ # self.conditions.find(:all).find { |c| !c.evaluate? }.nil? # true if all pass
59
+ # end
60
+
61
+ # def conditions_attributes=(new_conditions)
62
+ # conditions.collect(&:destroy)
63
+ # conditions.reload
64
+ # (0..(new_conditions.length - 1)).each do |i|
65
+ # i = i.to_s
66
+ # expression = new_conditions[i]["expression"]
67
+ # trigger_id = new_conditions[i]["trigger_id"].to_i
68
+ # unless expression.blank? || !page.questions.collect(&:id).include?(trigger_id) || conditions.collect(&:trigger_id).include?(trigger_id)
69
+ # conditions.create(:question_sheet_id => question_sheet_id, :trigger_id => trigger_id,
70
+ # :expression => expression, :toggle_page_id => page_id,
71
+ # :toggle_id => self.id)
72
+ # end
73
+ # end
74
+ # end
75
+
76
+ # element view provides the element label with required indicator
77
+ def default_label?
78
+ true
79
+ end
80
+
81
+ def locked?(params, answer_sheet, presenter)
82
+ return true unless params['controller'] == 'answer_pages' && params['action'] == 'edit'
83
+ if self.object_name == 'person.current_address' && ['address1','address2','city','zip','email','state','country'].include?(self.attribute_name)
84
+ # Billing Address
85
+ return false
86
+ elsif self.object_name == 'person.emergency_address' && ['address1','address2','city','zip','email','state','country','contactName','homePhone','workPhone'].include?(self.attribute_name)
87
+ # Emergency Contact
88
+ return false
89
+ elsif self.label == 'Relationship To You' || self.style == "country" || (self.style == "email" && self.label == "Confirm Email")
90
+ # Relationship & Country & Email Address
91
+ return false
92
+ else
93
+ return answer_sheet.frozen? && !presenter.reference?
94
+ end
95
+ end
96
+
97
+ # css class names for javascript-based validation
98
+ def validation_class(answer_sheet = nil)
99
+ if required?(answer_sheet)
100
+ ' required '
101
+ else
102
+ ''
103
+ end
104
+ end
105
+
106
+ # just in case something slips through client-side validation?
107
+ # def valid_response?
108
+ # if self.required? && !self.has_response? then
109
+ # false
110
+ # else
111
+ # # other validations
112
+ # true
113
+ # end
114
+ # end
115
+
116
+ # just in case something slips through client-side validation?
117
+ # def valid_response_for_answer_sheet?(answers)
118
+ # return true if !self.required?
119
+ # answer = answers.detect {|a| a.question_id == self.id}
120
+ # return answer && answer.value.present?
121
+ # # raise answer.inspect
122
+ # end
123
+
124
+ # shortcut to return first answer
125
+ def response(answer_sheet)
126
+ responses(answer_sheet).first.to_s
127
+ end
128
+
129
+ def display_response(answer_sheet)
130
+ r = responses(answer_sheet)
131
+ if r.blank?
132
+ ""
133
+ else
134
+ r.join(", ")
135
+ end
136
+ end
137
+
138
+ def responses(answer_sheet)
139
+ return [] unless answer_sheet
140
+
141
+ # try to find answer from external object
142
+ if !object_name.blank? and !attribute_name.blank?
143
+ obj = object_name == 'answer_sheet' ? answer_sheet : eval("answer_sheet." + object_name)
144
+ if obj.nil? or eval("obj." + attribute_name + ".nil?")
145
+ []
146
+ else
147
+ [eval("obj." + attribute_name)]
148
+ end
149
+ else
150
+ #answer_sheet.answers_by_question[id] || []
151
+ Fe::Answer.where(:answer_sheet_id => answer_sheet.id, :question_id => self.id)
152
+ end
153
+ end
154
+
155
+ # set answers from posted response
156
+ def set_response(values, answer_sheet)
157
+ values = Array.wrap(values)
158
+ if !object_name.blank? and !attribute_name.blank?
159
+ # if eval("answer_sheet." + object_name).present?
160
+ object = object_name == 'answer_sheet' ? answer_sheet : eval("answer_sheet." + object_name)
161
+ unless object.present?
162
+ if object_name.include?('.')
163
+ objects = object_name.split('.')
164
+ object = eval("answer_sheet." + objects[0..-2].join('.') + ".create_" + objects.last)
165
+ eval("answer_sheet." + objects[0..-2].join('.')).reload
166
+ end
167
+ end
168
+ unless responses(answer_sheet) == values
169
+ value = values.first
170
+ if self.is_a?(Fe::DateField) && value.present?
171
+ begin
172
+ value = Date.strptime(value, (I18n.t 'date.formats.default'))
173
+ rescue
174
+ raise "invalid date - " + value.inspect
175
+ end
176
+ end
177
+ object.update_attribute(attribute_name, value)
178
+ end
179
+ # else
180
+ # raise object_name.inspect + ' == ' + attribute_name.inspect
181
+ # end
182
+ else
183
+ @answers ||= []
184
+ @mark_for_destroy ||= []
185
+ # go through existing answers (in reverse order, as we delete)
186
+ (@answers.length - 1).downto(0) do |index|
187
+ # reject: skip over responses that are unchanged
188
+ unless values.reject! {|value| value == @answers[index]}
189
+ # remove any answers that don't match the posted values
190
+ @mark_for_destroy << @answers[index] # destroy from database later
191
+ @answers.delete_at(index)
192
+ end
193
+ end
194
+
195
+ # insert any new answers
196
+ for value in values
197
+ if @mark_for_destroy.empty?
198
+ answer = Fe::Answer.new(:question_id => self.id)
199
+ else
200
+ # re-use marked answers (an update vs. a delete+insert)
201
+ answer = @mark_for_destroy.pop
202
+ end
203
+ answer.set(value)
204
+ @answers << answer
205
+ end
206
+ end
207
+ end
208
+
209
+ def save_file(answer_sheet, file)
210
+ @answers.collect(&:destroy) if @answers
211
+ Fe::Answer.create!(:question_id => self.id, :answer_sheet_id => answer_sheet.id, :attachment => file)
212
+ end
213
+
214
+ # save this question's @answers to database
215
+ def save_response(answer_sheet)
216
+ unless @answers.nil?
217
+ for answer in @answers
218
+ if answer.is_a?(Fe::Answer)
219
+ answer.answer_sheet_id = answer_sheet.id
220
+ answer.save!
221
+ end
222
+ end
223
+ end
224
+
225
+ # remove others
226
+ unless @mark_for_destroy.nil?
227
+ for answer in @mark_for_destroy
228
+ answer.destroy
229
+ end
230
+ @mark_for_destroy.clear
231
+ end
232
+ rescue TypeError
233
+ raise answer.inspect
234
+ end
235
+
236
+ # has any sort of non-empty response?
237
+ def has_response?(answer_sheet = nil)
238
+ if answer_sheet.present?
239
+ answers = responses(answer_sheet)
240
+ else
241
+ answers = Fe::Answer.where(:question_id => self.id)
242
+ end
243
+ return false if answers.length == 0
244
+ answers.each do |answer| # loop through Answers
245
+ value = answer.is_a?(Fe::Answer) ? answer.value : answer
246
+ return true if (value.is_a?(FalseClass) && value === false) || value.present?
247
+ end
248
+ false
249
+ end
250
+
251
+ def required?(answer_sheet = nil)
252
+ super || (answer_sheet && choice_field && choice_field.has_answer?('1', answer_sheet))
253
+ end
254
+
255
+ end
256
+ end