question_chain 0.0.1

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 (94) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +18 -0
  3. data/Licence.txt +20 -0
  4. data/README.rdoc +34 -0
  5. data/Rakefile +2 -0
  6. data/config/initializers/mustache.rb +2 -0
  7. data/lib/question_chain/answerable.rb +142 -0
  8. data/lib/question_chain/answers.rb +290 -0
  9. data/lib/question_chain/models/answers/question_view.rb +21 -0
  10. data/lib/question_chain/models/answers/ui_object_view.rb +67 -0
  11. data/lib/question_chain/models/answers/ui_objects_check_box_view.rb +17 -0
  12. data/lib/question_chain/models/answers/ui_objects_drop_down_view.rb +16 -0
  13. data/lib/question_chain/models/answers/ui_objects_hidden_field_view.rb +8 -0
  14. data/lib/question_chain/models/answers/ui_objects_object_reference_drop_down_view.rb +18 -0
  15. data/lib/question_chain/models/answers/ui_objects_object_search_view.rb +22 -0
  16. data/lib/question_chain/models/answers/ui_objects_relatable_category_drop_down_view.rb +18 -0
  17. data/lib/question_chain/models/answers/ui_objects_text_field_view.rb +8 -0
  18. data/lib/question_chain/models/chain_template.rb +43 -0
  19. data/lib/question_chain/models/question.rb +37 -0
  20. data/lib/question_chain/models/relatable_category_filter.rb +15 -0
  21. data/lib/question_chain/models/rule.rb +27 -0
  22. data/lib/question_chain/models/rules/attribute_change.rb +28 -0
  23. data/lib/question_chain/models/rules/choice_genenerator.rb +5 -0
  24. data/lib/question_chain/models/rules/populate_drop_down.rb +30 -0
  25. data/lib/question_chain/models/rules/search.rb +19 -0
  26. data/lib/question_chain/models/rules/value_change.rb +22 -0
  27. data/lib/question_chain/models/ui_group.rb +71 -0
  28. data/lib/question_chain/models/ui_object.rb +92 -0
  29. data/lib/question_chain/models/ui_object_answer.rb +17 -0
  30. data/lib/question_chain/models/ui_objects/check_box.rb +8 -0
  31. data/lib/question_chain/models/ui_objects/drop_down.rb +17 -0
  32. data/lib/question_chain/models/ui_objects/hidden_field.rb +5 -0
  33. data/lib/question_chain/models/ui_objects/object_reference_drop_down.rb +67 -0
  34. data/lib/question_chain/models/ui_objects/object_search.rb +44 -0
  35. data/lib/question_chain/models/ui_objects/radio_button.rb +5 -0
  36. data/lib/question_chain/models/ui_objects/radio_button_group.rb +5 -0
  37. data/lib/question_chain/models/ui_objects/relatable_category_drop_down.rb +70 -0
  38. data/lib/question_chain/models/ui_objects/text_field.rb +11 -0
  39. data/lib/question_chain/mongo_serialization.rb +62 -0
  40. data/lib/question_chain/mustache_handler.rb +16 -0
  41. data/lib/question_chain/mustache_rails.rb +50 -0
  42. data/lib/question_chain/state_machine.rb +29 -0
  43. data/lib/question_chain/stored_template.rb +30 -0
  44. data/lib/question_chain/version.rb +3 -0
  45. data/lib/question_chain/views/answers/_edit.html.haml +62 -0
  46. data/lib/question_chain/views/answers/_new.html.haml +62 -0
  47. data/lib/question_chain/views/answers/_question.html.mustache +11 -0
  48. data/lib/question_chain/views/answers/_ui_objects_check_box.html.mustache +19 -0
  49. data/lib/question_chain/views/answers/_ui_objects_drop_down.html.mustache +26 -0
  50. data/lib/question_chain/views/answers/_ui_objects_hidden_field.html.mustache +3 -0
  51. data/lib/question_chain/views/answers/_ui_objects_object_reference_drop_down.html.mustache +27 -0
  52. data/lib/question_chain/views/answers/_ui_objects_object_search.html.mustache +20 -0
  53. data/lib/question_chain/views/answers/_ui_objects_relatable_category_drop_down.html.mustache +26 -0
  54. data/lib/question_chain/views/answers/_ui_objects_text_field.html.mustache +19 -0
  55. data/lib/question_chain/views/layouts/application.html.haml +10 -0
  56. data/lib/question_chain.rb +35 -0
  57. data/question_chain.gemspec +31 -0
  58. data/test_app/.gitignore +4 -0
  59. data/test_app/Gemfile +29 -0
  60. data/test_app/Rakefile +16 -0
  61. data/test_app/app/controllers/answers_controller.rb +13 -0
  62. data/test_app/app/controllers/application_controller.rb +4 -0
  63. data/test_app/app/models/container.rb +10 -0
  64. data/test_app/app/models/flight.rb +20 -0
  65. data/test_app/app/views/answers/edit.html.haml +1 -0
  66. data/test_app/app/views/answers/new.html.haml +1 -0
  67. data/test_app/config/application.rb +18 -0
  68. data/test_app/config/boot.rb +13 -0
  69. data/test_app/config/database.yml +15 -0
  70. data/test_app/config/environment.rb +5 -0
  71. data/test_app/config/initializers/app.rb +5 -0
  72. data/test_app/config/initializers/cookie_verification_secret.rb +7 -0
  73. data/test_app/config/initializers/mongodb.rb +2 -0
  74. data/test_app/config/initializers/session_store.rb +3 -0
  75. data/test_app/config/routes.rb +30 -0
  76. data/test_app/config.ru +2 -0
  77. data/test_app/environments/development.rb +19 -0
  78. data/test_app/environments/test.rb +30 -0
  79. data/test_app/lib/tasks/rspec.rake +69 -0
  80. data/test_app/lib/tasks/yard.rake +4 -0
  81. data/test_app/public/.gitkeep +0 -0
  82. data/test_app/script/rails +10 -0
  83. data/test_app/spec/acceptance/new_spec +30 -0
  84. data/test_app/spec/factories.rb +81 -0
  85. data/test_app/spec/models/chain_template_spec.rb +53 -0
  86. data/test_app/spec/models/flight_spec.rb +101 -0
  87. data/test_app/spec/models/question_spec.rb +13 -0
  88. data/test_app/spec/models/rules/value_change_spec.rb +31 -0
  89. data/test_app/spec/models/ui_group_spec.rb +55 -0
  90. data/test_app/spec/models/ui_object_spec.rb +33 -0
  91. data/test_app/spec/models/ui_objects/drop_down_spec.rb +28 -0
  92. data/test_app/spec/models/ui_objects/relatable_category_drop_down_spec.rb +13 -0
  93. data/test_app/spec/spec_helper.rb +25 -0
  94. metadata +325 -0
@@ -0,0 +1,18 @@
1
+ module Answers
2
+ class UiObjectsRelatableCategoryDropDownView < Answers::UiObjectView
3
+
4
+ def prompt
5
+ ui_object.prompt
6
+ end
7
+
8
+ # @return [Array<Hash<value => value, name => name>>]
9
+ def options
10
+ if answer_params
11
+ ui_object.options.map{|option| option.merge!(:selected => option["value"].to_s == value.to_s)}
12
+ else
13
+ ui_object.options
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module Answers
2
+ class UiObjectsTextFieldView < Answers::UiObjectView
3
+
4
+ def value
5
+ super || default_value
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,43 @@
1
+ class ChainTemplate
2
+ include MongoMapper::Document
3
+ include MongoMapper::StateMachine
4
+
5
+ # == Keys
6
+ key :context, Hash # [Hash<content => [Array<MongoIds>]>]
7
+ key :account_id, ObjectId # used to override any specific question essentially
8
+ key :for_resource, String
9
+ key :parent_resource, String
10
+ key :active_at, Time
11
+
12
+ # == State Machine
13
+ state_machine :initial => :pending do
14
+ state :pending
15
+ state :active
16
+
17
+ event :activate do
18
+ transitions :to => :active, :from => [:pending], :on_transition => :set_active_at
19
+ end
20
+
21
+ event :deactivate do
22
+ transitions :to => :pending, :from => [:active]
23
+ end
24
+ end
25
+
26
+ # == Indexes
27
+ ensure_index :for_resource
28
+
29
+ # == Validations
30
+ validates_presence_of :for_resource
31
+ validates_presence_of :context
32
+ validates_true_for :context, :logic => Proc.new{ !context.empty?}
33
+
34
+ def self.attributes_for_api
35
+ %w(id name for_resource account_id parent_resource)
36
+ end
37
+
38
+ protected
39
+ def set_active_at
40
+ self.active_at = Time.now
41
+ self.save!
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ class Question
2
+ include MongoMapper::Document
3
+ include MongoMapper::Serialize
4
+
5
+ # == Keys
6
+ key :name, String
7
+ key :label, String
8
+ key :_type, String
9
+ key :description, String
10
+ key :calculator_id, ObjectId
11
+ key :computation_id, ObjectId
12
+ timestamps!
13
+
14
+ # == Indexes
15
+ ensure_index :names
16
+ ensure_index :label
17
+
18
+ # == Validations
19
+ validates_presence_of :name
20
+ validates_uniqueness_of :name
21
+
22
+ # == Associations
23
+ many :ui_groups, :order => :position.asc, :dependent => :destroy
24
+
25
+ # == Hooks
26
+ def self.attributes_for_api
27
+ %w(id name description computation_id ui_groups label calculator_id)
28
+ end
29
+
30
+ def to_json
31
+ attributes_for_api_resource.to_json
32
+ end
33
+
34
+ def to_hash
35
+ Hashie::Mash.new(attributes_for_api_resources)
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ class RelatableCategoryFilter
2
+ include MongoMapper::Document
3
+ include MongoMapper::Serialize
4
+
5
+ # == Keys
6
+ key :filters, Array
7
+ key :ui_group_id, ObjectId
8
+
9
+ # == Associations
10
+ belongs_to :ui_group
11
+
12
+ def self.attributes_for_api
13
+ %w(id filters ui_group_id)
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ class Rule
2
+ include MongoMapper::EmbeddedDocument
3
+ include MongoMapper::Serialize
4
+
5
+ # == Keys
6
+ key :fire_value, String
7
+ key :negate_value, :default => false
8
+ key :_type, String
9
+
10
+ def ui_object_id
11
+ _parent_document.id
12
+ end
13
+
14
+ # have to have this here to declear all subclass not being used?
15
+ def self.attributes_for_api
16
+ %w(id fire_value _type ui_object_id negate_value)
17
+ end
18
+
19
+ # this will manipulate the question hash with the updated
20
+ # attributes for the affecting ui objects that are
21
+ # attached to this rule
22
+ #
23
+ # @return [TrueClass] when the fire! is actually doing that
24
+ def fire!(value, question_hash = {})
25
+ raise NotImplementedError, "Need to set the fire! method"
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ module Rules
2
+ class AttributeChange < Rule
3
+ # {:id => {:atttibute_name => "new_state"}, :id => {:atttibute_name => "new_state"}}
4
+ key :affecting_ui_objects, Hash
5
+ key :compare_text_value, :default => false
6
+
7
+ def self.attributes_for_api
8
+ %w(id fire_value _type affecting_ui_objects ui_object_id negate_value compare_text_value)
9
+ end
10
+
11
+ # Checks to determine the value and the fire_value if cool
12
+ # then the ui objects are updated depending on the affecting values
13
+ #
14
+ # @param [UiObject] ui_object the ui object that this rul is observing
15
+ # @param [String] value the value that the ui_object has
16
+ # @param [Array<Hash>] ui_objects
17
+ def fire!(value, ui_objects)
18
+ if negate_value ? value != self.fire_value : value == self.fire_value
19
+ affecting_ui_objects.each_pair do |key, value|
20
+ if ui_object = ui_objects.detect{|ui| ui["id"] == key}
21
+ ui_object["ui_attributes"].merge!(value)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module Rules
2
+ class ChoiceGenerator < Rule
3
+
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ module Rules
2
+ class PopulateDropDown < Rule
3
+
4
+ # == Keys
5
+ key :ui_object_attribute_check, Hash, :default => nil
6
+
7
+ def fire!(value = nil, ui_objects_hash = {})
8
+ # does not matter what the value is in this instance
9
+ # it is used to get opions for the drop_down_target
10
+ end
11
+
12
+ def drop_down_target_id
13
+ _parent_document.drop_down_target_id
14
+ end
15
+
16
+ # ask the parent document to get the options
17
+ def get_options(object_ids = [])
18
+ options = []
19
+ _parent_document.get_target_drop_down_options(object_ids).each_pair do |key ,value|
20
+ options << {:name => value, :value => key}
21
+ end
22
+ options.sort_by{|option| option[:name]}
23
+ end
24
+
25
+ def self.attributes_for_api
26
+ %w(id fire_value _type ui_object_id drop_down_target_id ui_object_attribute_check negate_value)
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ module Rules
2
+ class Search < Rule
3
+
4
+ def fire!(value = nil, ui_objects_hash = {})
5
+ # does not matter what the value is in this instance
6
+ # it is used to get opions for the drop_down_target
7
+ end
8
+
9
+ # ask the parent document to get the options
10
+ def get_options(filter, relatable_category_names = [])
11
+ _parent_document.options(filter, relatable_category_names)
12
+ end
13
+
14
+ def self.attributes_for_api
15
+ %w(id fire_value _type ui_object_id negate_value)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module Rules
2
+ class ValueChange < Rule
3
+
4
+ # == Keys
5
+ key :affecting_ui_object_id, ObjectId
6
+ key :change_value, String
7
+
8
+ # == Validations
9
+ validates_presence_of :affecting_ui_object_id
10
+ validates_presence_of :change_value
11
+
12
+ def fire!(value = nil, ui_objects_hash = {})
13
+ # does not matter what the value is in this instance
14
+ # it is used to get opions for the drop_down_target
15
+ end
16
+
17
+ def self.attributes_for_api
18
+ %w(id fire_value _type ui_object_id affecting_ui_object_id negate_value)
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,71 @@
1
+ # holds a group of ui objects basically
2
+ # keep simple to start of with
3
+ class UiGroup
4
+ include MongoMapper::Document
5
+ include MongoMapper::Serialize
6
+
7
+ # == Keys
8
+ key :name, String
9
+ key :description, String
10
+ key :label, String
11
+ key :parent_id, ObjectId
12
+ key :parent_ids, Array
13
+ key :question_id, ObjectId
14
+ key :ui_attributes, Hash
15
+ key :css_classes, Array, :default => ["single"]
16
+ timestamps!
17
+
18
+ # == Indexes
19
+ ensure_index :parent_ids
20
+ ensure_index :name
21
+
22
+ # == Validations
23
+ validates_presence_of :name
24
+ validates_presence_of :label
25
+ validates_presence_of :question_id
26
+
27
+ # == Associations
28
+ belongs_to :parent
29
+ belongs_to :question
30
+ many :ui_objects, :order => :position.asc
31
+ many :object_searches, :class_name => "UiObjects::ObjectSearch"
32
+ many :text_fields, :class_name => "UiObjects::TextField"
33
+ many :drop_downs, :class_name => "UiObjects::DropDown"
34
+ many :checkboxes, :class_name => "UiObjects::CheckBox"
35
+ one :object_reference_drop_down, :class_name => "UiObjects::ObjectReferenceDropDown"
36
+ many :relatable_category_drop_downs, :class_name => "UiObjects::RelatableCategoryDropDown"
37
+ many :hidden_fields, :class_name => "UiObjects::HiddenField"
38
+ many :children, :class_name => 'UiGroup', :foreign_key => 'parent_id'
39
+ one :relatable_category_filter, :class_name => "RelatableCategoryFilter"
40
+
41
+ # thats right only ever one object_reference in a ui_group
42
+ # its a constraint we just need
43
+ one :object_reference_drop_down, :class_name => "UiObjects::ObjectReferenceDropDown"
44
+
45
+ # == hooks
46
+ before_save :set_parents
47
+
48
+ def self.attributes_for_api
49
+ %w(id name label question_id relatable_category_filter ui_objects ui_attributes default_styles css_classes)
50
+ end
51
+
52
+ # this should go into the mustache view basically
53
+ def default_styles
54
+ default_styles = ""
55
+ self.ui_attributes.each_pair do |key, value|
56
+ if key.to_s == "visible" && value == false
57
+ default_styles << "display:none;visibility:hidden;"
58
+ end
59
+ end
60
+ default_styles
61
+ end
62
+
63
+ def css_classes
64
+ read_attribute(:css_classes).join(" ")
65
+ end
66
+
67
+ protected
68
+ def set_parents
69
+ self.parent_ids = (parent.parent_ids || []) << parent_id if parent?
70
+ end
71
+ end
@@ -0,0 +1,92 @@
1
+ require "observer"
2
+ class UiObject
3
+ include ::Observable
4
+ include MongoMapper::Document
5
+ include MongoMapper::Serialize
6
+
7
+ # == Keys
8
+ key :_type, String
9
+ key :name, String
10
+ key :label, String
11
+ key :description, String
12
+ key :default_value, String
13
+ key :ui_attributes, Hash
14
+ key :ui_group_id, ObjectId
15
+ key :position, Integer
16
+ key :extra_info, String
17
+ key :per_page, Integer, :default => 50
18
+ key :css_classes, Array, :default => ["single"]
19
+ timestamps!
20
+
21
+ # == Indexes
22
+ ensure_index :label
23
+ ensure_index "rules.name"
24
+
25
+ # == Attrs
26
+ attr_accessor :value
27
+ class_inheritable_accessor :default_values
28
+
29
+ # Class set values
30
+ self.default_values = {:visible => true, :enabled => true}
31
+
32
+ # == Validations
33
+ validates_presence_of :ui_group_id
34
+ validates_presence_of :label
35
+ validates_presence_of :ui_attributes
36
+ validates_true_for :ui_attributes, :logic => Proc.new{
37
+ !ui_attributes.empty?
38
+ }
39
+ validates_associated :rules
40
+
41
+ # == Associations
42
+ belongs_to :ui_group
43
+ many :rules
44
+
45
+ # == Hooks
46
+ before_save :add_rule_observers!
47
+ before_validation_on_create :set_default_attributes
48
+
49
+ def self.attributes_for_api
50
+ %w(id name _type label description default_value ui_attributes ui_group_id rules extra_info css_classes)
51
+ end
52
+
53
+ # == need as added a little late in the day basically
54
+ def per_page
55
+ read_attribute(:per_page) || 50
56
+ end
57
+
58
+ def initialize(*args)
59
+ super
60
+ add_rule_observers!
61
+ end
62
+
63
+ # fires the rules that are attached the ui object
64
+ def change_value!(ui_objects = [])
65
+ if ui_object = ui_objects.detect{|ui| ui["id"] == self.id.to_s}
66
+ value = ui_object["ui_attributes"] && ui_object["ui_attributes"]["value"] || ""
67
+ @observer_state = true
68
+ notify_observers(value, ui_objects)
69
+ end
70
+ end
71
+
72
+ def visible?
73
+ ui_attributes[:visible]
74
+ end
75
+
76
+ def enabled?
77
+ ui_attributes[:enabled]
78
+ end
79
+
80
+ protected
81
+ def set_default_attributes
82
+ self.ui_attributes = self.class.default_values.merge(:value => self.default_value || "").merge(self.ui_attributes || {})
83
+ end
84
+
85
+ # Adding the obervers so we can fire rules
86
+ def add_rule_observers!
87
+ rules.each do |rule|
88
+ self.add_observer(rule, :fire!)
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,17 @@
1
+ class UiObjectAnswer
2
+
3
+ # @todo refactor make not as ugly as a bulldog eating a wasp
4
+ # @param [String] json
5
+ # @return [String] json formatted String manipulate from ui changes
6
+ def self.update_answers!(answer_json = "")
7
+ @answer_hash = JSON.parse(answer_json)
8
+ if @ui_objects = @answer_hash.delete("ui_objects")
9
+ uis = UiObject.find(ui_object_ids)
10
+ uis.each do |ui_object|
11
+ ui_object.change_value!(@ui_objects)
12
+ end
13
+ end
14
+ @answer_hash["ui_objects"] = @ui_objects
15
+ answer_json = @answer_hash.to_json
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module UiObjects
2
+ class CheckBox < UiObject
3
+
4
+ # == Added defaults
5
+ self.default_values = self.default_values.merge!(:checked => false)
6
+
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ module UiObjects
2
+ class DropDown < UiObject
3
+
4
+ # == keys
5
+ key :options, Array
6
+ key :prompt, String
7
+ key :max_options, Integer, :default => 20
8
+ key :populate, Boolean, :default => true
9
+ key :order, String
10
+ key :filter, Set
11
+
12
+ def self.attributes_for_api
13
+ %w(id name _type populate label order filter description default_value ui_attributes options prompt max_options rules extra_info css_classes)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ module UiObjects
2
+ class HiddenField < UiObject
3
+ key :name, String, :default => "computation_id"
4
+ end
5
+ end
@@ -0,0 +1,67 @@
1
+ module UiObjects
2
+ class ObjectReferenceDropDown < UiObject
3
+
4
+ # == Keys
5
+ key :prompt, String
6
+ key :max_options, Integer, :default => 20
7
+
8
+ # :id => :attribute_name # not quite sure about the option trackers
9
+ key :object_name, String
10
+ key :filters, Set # filters
11
+ key :filter_attribute, String
12
+ key :filter_value, String
13
+ key :order, String
14
+ key :drop_down_target_id, ObjectId
15
+ key :drop_down_target_options_filters, Array # == Array of filters for formula inputs
16
+ key :populate, Boolean, :default => true
17
+ key :relatable_options, Boolean, :default => false
18
+ key :attribute_for_value, String, :default => "id"
19
+ key :attribute_for_display, String, :default => "identifier"
20
+ key :external, Boolean, :default => true
21
+
22
+ # == Indexes
23
+ ensure_index :object_name
24
+
25
+ # == Validations
26
+ validates_presence_of :object_name
27
+
28
+ def options
29
+ return [] if !populate
30
+ @options ||= get_options(object_name, filters || [])
31
+ end
32
+
33
+ def self.attributes_for_api
34
+ %w(id name _type label description rules default_value ui_attributes options prompt max_options object_name drop_down_target_id extra_info css_classes)
35
+ end
36
+
37
+ # object_reference drop down can only target its formula units basically
38
+ # only 1 object in this instance
39
+ def get_target_drop_down_options(object_ids = [])
40
+ if object_id = object_ids.first
41
+ formula_inputs = QuestionChain.calculated_session.formula_inputs_for_generic_object(object_id.to_s)
42
+ if respond_to?(:drop_down_target_options_filters) && !drop_down_target_options_filters.empty?
43
+ selected_formula_inputs = formula_inputs.select{|f| drop_down_target_options_filters.map(&:downcase).include?(f.name.downcase)}
44
+ selected_formula_inputs.inject({}){|var, fi| var.merge!({fi.name => fi.label_input_units}); var}
45
+ else
46
+ formula_inputs.inject({}){|var, fi| var.merge!({fi.name => fi.label_input_units}); var}
47
+ end
48
+ end
49
+ end
50
+
51
+ protected
52
+ # Based on the fact we dont know what co2_platform
53
+ # we are using how can the options be found!
54
+ #
55
+ # @return [Array<Array<String, String>>] tuples [value, display]
56
+ def get_options(name, filters = [])
57
+ options = {:per_page => per_page}
58
+ options.merge!(:filter_attribute => self.filter_attribute, :filter_value => self.filter_value) if self.filter_attribute && self.filter_value
59
+ response = QuestionChain.calculated_session.generic_objects_for_object_template(name, options)
60
+ options = response.generic_objects.select{|obj| (filters.empty? || (!filters.empty? && filters.include?(obj.id.to_s)))}.map do |obj|
61
+ {:value => obj.send(self.attribute_for_value), :name => obj.send(self.attribute_for_display)}
62
+ end
63
+ options.sort_by{|obj| obj[:name]}
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,44 @@
1
+ module UiObjects
2
+ class ObjectSearch < UiObject
3
+
4
+ # == keys
5
+ key :object_name, String
6
+ key :max_options, Integer, :default => 20
7
+ key :prompt, String
8
+ key :attribute_for_value, String, :default => "id"
9
+ key :attribute_for_display, String, :default => "identifier"
10
+
11
+ # == Validations
12
+ validates_presence_of :object_name
13
+
14
+ # == Hooks
15
+ after_create :add_default_rule
16
+
17
+ # == Attrs
18
+ attr_accessor_with_default :default_rule, true
19
+
20
+ def self.attributes_for_api
21
+ %w(id name _type label position description rules ui_attributes prompt max_options object_name extra_info css_classes)
22
+ end
23
+
24
+ def options(filter, relatable_category_names = [])
25
+ @options ||= get_options(object_name, filter || "", relatable_category_names)
26
+ end
27
+
28
+ protected
29
+ def get_options(object_name, filter, relatable_category_names)
30
+ response = QuestionChain::calculated_session.generic_objects_for_object_template_with_filter(object_name, filter, :relatable_category_values => relatable_category_names, :per_page => per_page)
31
+ response.generic_objects.map do |obj|
32
+ {:value => obj.send(self.attribute_for_value), :name => obj.identifier}
33
+ end
34
+ end
35
+
36
+ def add_default_rule
37
+ if default_rule
38
+ self.rules << Rules::Search.new
39
+ self.save
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ module UiObjects
2
+ class RadioButton < UiObject
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module UiObjects
2
+ class RadioGroup < UiObject
3
+
4
+ end
5
+ end
@@ -0,0 +1,70 @@
1
+ module UiObjects
2
+ class RelatableCategoryDropDown < UiObject
3
+
4
+ # == Keys
5
+ key :related_attribute, String
6
+ key :object_name, String
7
+ key :filters, Set
8
+ key :order, String
9
+ key :prompt, String
10
+ key :max_options, Integer, :default => 20
11
+ key :drop_down_target_id, ObjectId
12
+ key :drop_down_target_is_relatable, Boolean, :default => false
13
+ key :populate, Boolean, :default => true
14
+ key :attribute_for_value, String, :default => "id"
15
+ key :attribute_for_display, String, :default => "label"
16
+ key :external, Boolean, :default => true
17
+
18
+ # == Indexes
19
+ ensure_index :filters
20
+ ensure_index :object_name
21
+ ensure_index :related_attribute
22
+
23
+ # == Validations
24
+ validates_presence_of :related_attribute
25
+ validates_presence_of :object_name
26
+
27
+ # == Associations
28
+ belongs_to :drop_down_target, :class_name => "UiObject", :foreign_key => "drop_down_target_id"
29
+
30
+ # == Hooks
31
+ after_create :add_default_rule
32
+
33
+ # == Attrs
34
+ attr_accessor_with_default :default_rule, true
35
+
36
+ def options
37
+ return [] if !populate
38
+ @options ||= relatable_categories.select{|obj| (filters.empty? || (!filters.empty? && filters.include?(obj.id.to_s)))}.map do |obj|
39
+ {:value => obj.id, :name => obj.name}
40
+ end.sort_by{|option| option[:name]}
41
+ end
42
+
43
+ def get_target_drop_down_options(relatable_category_ids = [])
44
+ if drop_down_target_is_relatable
45
+ id = relatable_category_ids.shift
46
+ QuestionChain.calculated_session.related_categories_from_relatable_category(id.to_s, drop_down_target.related_attribute, {:relatable_category_ids => relatable_category_ids, :per_page => per_page})
47
+ else
48
+ QuestionChain.calculated_session.related_objects_from_relatable_categories(self.object_name, relatable_category_ids, :per_page => per_page)
49
+ end
50
+ end
51
+
52
+ def self.attributes_for_api
53
+ %w(id name _type label populate drop_down_target_is_relatable related_attribute order rules filters description drop_down_target_id default_value ui_attributes options prompt max_options object_name order extra_info css_classes)
54
+ end
55
+
56
+ protected
57
+ def relatable_categories(related_attribute = self.related_attribute)
58
+ @relatable_categories ||= QuestionChain.calculated_session.relatable_categories_for_object_template(object_name, related_attribute, :per_page => per_page).relatable_categories
59
+ end
60
+
61
+ # @todo move to after save; check for existing
62
+ def add_default_rule
63
+ if default_rule
64
+ self.rules << Rules::PopulateDropDown.new
65
+ self.save
66
+ end
67
+ end
68
+
69
+ end
70
+ end