dynamic_fieldsets 0.0.3 → 0.0.4

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 (89) hide show
  1. data/CHANGELOG +7 -0
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +1 -0
  4. data/README.rdoc +15 -2
  5. data/Rakefile +24 -4
  6. data/VERSION +1 -1
  7. data/app/controllers/dynamic_fieldsets/fields_controller.rb +25 -0
  8. data/app/controllers/dynamic_fieldsets/fieldset_children_controller.rb +57 -0
  9. data/app/controllers/dynamic_fieldsets/fieldsets_controller.rb +91 -5
  10. data/app/helpers/dynamic_fieldsets/fields_helper.rb +1 -15
  11. data/app/helpers/dynamic_fieldsets/fieldset_children_helper.rb +5 -0
  12. data/app/helpers/dynamic_fieldsets/nested_model_helper.rb +18 -0
  13. data/app/helpers/dynamic_fieldsets_helper.rb +70 -44
  14. data/app/models/dynamic_fieldsets/dependency.rb +76 -0
  15. data/app/models/dynamic_fieldsets/dependency_clause.rb +32 -0
  16. data/app/models/dynamic_fieldsets/dependency_group.rb +108 -0
  17. data/app/models/dynamic_fieldsets/field.rb +17 -12
  18. data/app/models/dynamic_fieldsets/field_default.rb +27 -1
  19. data/app/models/dynamic_fieldsets/field_record.rb +19 -4
  20. data/app/models/dynamic_fieldsets/fieldset.rb +35 -30
  21. data/app/models/dynamic_fieldsets/fieldset_associator.rb +52 -8
  22. data/app/models/dynamic_fieldsets/fieldset_child.rb +148 -0
  23. data/app/views/dynamic_fieldsets/fields/_disable_field_form.html.erb +4 -0
  24. data/app/views/dynamic_fieldsets/fields/_field_default_fields.html.erb +1 -1
  25. data/app/views/dynamic_fieldsets/fields/_form.html.erb +9 -27
  26. data/app/views/dynamic_fieldsets/fields/index.html.erb +8 -5
  27. data/app/views/dynamic_fieldsets/fields/new.html.erb +5 -1
  28. data/app/views/dynamic_fieldsets/fields/show.html.erb +1 -13
  29. data/app/views/dynamic_fieldsets/fieldset_children/_dependency_clause_fields.html.erb +25 -0
  30. data/app/views/dynamic_fieldsets/fieldset_children/_dependency_fields.html.erb +12 -0
  31. data/app/views/dynamic_fieldsets/fieldset_children/_dependency_group_fields.html.erb +16 -0
  32. data/app/views/dynamic_fieldsets/fieldset_children/_form.html.erb +34 -0
  33. data/app/views/dynamic_fieldsets/fieldset_children/edit.html.erb +6 -0
  34. data/app/views/dynamic_fieldsets/fieldsets/_associate_child.html.erb +4 -0
  35. data/app/views/dynamic_fieldsets/fieldsets/_child.html.erb +41 -0
  36. data/app/views/dynamic_fieldsets/fieldsets/_form.html.erb +2 -8
  37. data/app/views/dynamic_fieldsets/fieldsets/children.html.erb +47 -39
  38. data/app/views/dynamic_fieldsets/fieldsets/index.html.erb +4 -4
  39. data/app/views/dynamic_fieldsets/fieldsets/new.html.erb +5 -1
  40. data/app/views/dynamic_fieldsets/fieldsets/reorder.html.erb +4 -0
  41. data/app/views/dynamic_fieldsets/fieldsets/show.html.erb +1 -12
  42. data/app/views/dynamic_fieldsets/shared/_javascript_watcher.html.erb +255 -0
  43. data/app/views/dynamic_fieldsets/shared/_nested_model_javascript.html.erb +35 -0
  44. data/config/.routes.rb.swp +0 -0
  45. data/config/routes.rb +11 -0
  46. data/dynamic_fieldsets.gemspec +41 -4
  47. data/lib/dynamic_fieldsets/dynamic_fieldsets_in_model.rb +141 -14
  48. data/lib/generators/dynamic_fieldsets/templates/migrations/install_migration.rb +39 -5
  49. data/spec/dummy/app/controllers/information_forms_controller.rb +2 -1
  50. data/spec/dummy/app/models/information_form.rb +1 -1
  51. data/spec/dummy/app/views/information_forms/dynamic_view.html.erb +18 -0
  52. data/spec/dummy/app/views/information_forms/show.html.erb +1 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +9 -1
  54. data/spec/dummy/config/routes.rb +1 -1
  55. data/spec/dummy/db/migrate/{20110726215814_create_dynamic_fieldsets_tables.rb → 20110809161724_create_dynamic_fieldsets_tables.rb} +39 -5
  56. data/spec/dummy/db/schema.rb +40 -11
  57. data/spec/dummy/features/field.feature +17 -2
  58. data/spec/dummy/features/fieldset.feature +1 -21
  59. data/spec/dummy/features/fieldset_children.feature +50 -0
  60. data/spec/dummy/features/javascript_tests.feature +91 -0
  61. data/spec/dummy/features/step_definitions/field_steps.rb +9 -5
  62. data/spec/dummy/features/step_definitions/fieldset_associator_steps.rb +3 -1
  63. data/spec/dummy/features/step_definitions/fieldset_children_steps.rb +65 -0
  64. data/spec/dummy/features/step_definitions/fieldset_steps.rb +0 -27
  65. data/spec/dummy/features/step_definitions/javascript_steps.rb +208 -0
  66. data/spec/dummy/features/step_definitions/web_steps.rb +5 -0
  67. data/spec/dummy/features/support/paths.rb +10 -1
  68. data/spec/dummy/features/support/selectors.rb +2 -0
  69. data/spec/dummy/public/javascripts/jquery-1.6.2.min.js +18 -0
  70. data/spec/dummy/public/javascripts/jquery-ui-1.8.15.custom.min.js +111 -0
  71. data/spec/dummy/public/javascripts/jquery-ui-nestedSortable.js +356 -0
  72. data/spec/dummy/public/stylesheets/scaffold.css +101 -0
  73. data/spec/dynamic_fieldsets_helper_spec.rb +236 -55
  74. data/spec/dynamic_fieldsets_in_model_spec.rb +122 -4
  75. data/spec/models/dependency_clause_spec.rb +55 -0
  76. data/spec/models/dependency_group_spec.rb +237 -0
  77. data/spec/models/dependency_spec.rb +173 -0
  78. data/spec/models/field_default_spec.rb +49 -0
  79. data/spec/models/field_record_spec.rb +11 -2
  80. data/spec/models/field_spec.rb +30 -6
  81. data/spec/models/fieldset_associator_spec.rb +138 -25
  82. data/spec/models/fieldset_child_spec.rb +122 -0
  83. data/spec/models/fieldset_spec.rb +78 -80
  84. data/spec/support/dependency_group_helper.rb +9 -0
  85. data/spec/support/dependency_helper.rb +13 -0
  86. data/spec/support/field_helper.rb +0 -2
  87. data/spec/support/fieldset_child_helper.rb +10 -0
  88. data/spec/support/fieldset_helper.rb +2 -18
  89. metadata +51 -5
@@ -0,0 +1,76 @@
1
+ module DynamicFieldsets
2
+
3
+ # Logical atom for dealing with dependency clauses
4
+ #
5
+ # @author John "hex" Carter
6
+ class Dependency < ActiveRecord::Base
7
+
8
+ RELATIONSHIP_LIST = ["equals","not equals","includes","not includes","blank","not blank"]
9
+
10
+ # Relations
11
+
12
+ belongs_to :fieldset_child
13
+ belongs_to :dependency_clause
14
+
15
+ # Validations
16
+ validates_presence_of :fieldset_child_id
17
+
18
+ # This validation should really be on
19
+ # It is off due to a bug (?) in the nested attributes
20
+ # where the dependency clause id is not set when it creates the dependency
21
+ # validates_presence_of :dependency_clause_id
22
+
23
+ validates_inclusion_of :relationship, :in => RELATIONSHIP_LIST
24
+
25
+ # Returns a full list of the options for relationship_list
26
+ #
27
+ # @params [None]
28
+ # @returns [Array] The array of the constant RELATIONSHIP_LIST
29
+ #
30
+ def relationship_list
31
+ return RELATIONSHIP_LIST
32
+ end
33
+
34
+ # Returns true or false based on whether the value pushed in matches the logical relationship between it and the dependency's value
35
+ #
36
+ # @params [String] input_value - The value to be tested
37
+ # @returns [Boolean] Logical response to relationship between the dependency value and the pushed value
38
+ #
39
+ def process_relationship(input_value)
40
+ case self.relationship
41
+ when "equals"
42
+ return self.value == input_value
43
+ when "not equals"
44
+ return self.value != input_value
45
+ when "includes"
46
+ return input_value.include?(self.value)
47
+ when "not includes"
48
+ return !input_value.include?(self.value)
49
+ when "blank"
50
+ return input_value == ""
51
+ when "not blank"
52
+ return input_value != ""
53
+ else
54
+ return false
55
+ end
56
+ end
57
+
58
+ # Looks through the input_hash for the fieldset_child that matches the one belonging to this dependency and compares the values through process_relationship
59
+ #
60
+ # @params [Hash] input_hash - Key is the id of the fieldset_child to be tested, value is the input given by the user
61
+ # @returns [Boolean] returns the value of process_relationship unless the input doesn't contain the fieldset_child of the dependency, which also returns false
62
+ #
63
+ def evaluate(input_hash)
64
+ input_value = input_hash[self.fieldset_child_id]
65
+ unless input_value.nil?
66
+ return self.process_relationship(input_value)
67
+ else
68
+ return false
69
+ end
70
+ end
71
+
72
+ def to_hash
73
+ return { "id" => self.id, "fieldset_child_id" => self.fieldset_child_id, "value" => self.value, "relationship" => self.relationship, "dependency_clause_id" => self.dependency_clause_id }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,32 @@
1
+ module DynamicFieldsets
2
+ # A clause in CNF expression for question dependencies
3
+ #
4
+ # @author Jeremiah Hemphill
5
+ class DependencyClause < ActiveRecord::Base
6
+ belongs_to :dependency_group
7
+
8
+ has_many :dependencies
9
+ accepts_nested_attributes_for :dependencies, :allow_destroy => true
10
+
11
+ validates_presence_of :dependency_group_id
12
+
13
+ # Evaluates the depdendencies in the claus by ORing them together
14
+ # Short circuit evaluation returns true as soon as possible
15
+ #
16
+ # @param [Hash] input_values A hash of fieldset_child_id:value pairs to test against
17
+ # @return [Boolean] True if one of the dependencies is true
18
+ def evaluate(input_values)
19
+ self.dependencies.each do |dependency|
20
+ if dependency.evaluate
21
+ return true
22
+ end
23
+ end
24
+ return false
25
+ end
26
+
27
+ def to_hash
28
+ return { "id" => self.id, "dependency_group_id" => self.dependency_group_id }
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,108 @@
1
+ module DynamicFieldsets
2
+ class DependencyGroup < ActiveRecord::Base
3
+ belongs_to :fieldset_child
4
+
5
+ has_many :dependency_clauses
6
+ accepts_nested_attributes_for :dependency_clauses, :allow_destroy => true
7
+
8
+ # List of allowable actions for the group
9
+ # Success and failure options are returned by the get_action method
10
+ Action_list = { "show" => { :success => "show", :failure => "hide" }, "enable" => { :success => "enable", :failure => "disable" } }
11
+
12
+ validates_presence_of :fieldset_child_id
13
+ validates_presence_of :action
14
+ validate :action_in_action_list
15
+
16
+ # adds an error if the action isn't in the action list
17
+ def action_in_action_list
18
+ if !action_list.keys.include?(self.action)
19
+ self.errors.add(:action, "The action must be set to one of the provided values.")
20
+ end
21
+ end
22
+
23
+ # @return [Hash] The action list hash
24
+ def action_list
25
+ return Action_list
26
+ end
27
+
28
+ # Returns all fieldset children included in this dependency group
29
+ # Not sure if it will be useful, it would probably be called in evaluate
30
+ # I think it will be faster to just pass the input values array all the way down
31
+ # when evaluate is called.
32
+ #
33
+ # @returns [Array] An array of included fieldset children ids
34
+ def dependent_fieldset_children
35
+ children = []
36
+ self.dependency_clauses.each do |clause|
37
+ clause.dependencies.each do |dep|
38
+ children.push(dep.fieldset_child_id)
39
+ end
40
+ end
41
+ return children
42
+ end
43
+
44
+ # Parses the dependnecy_group's fieldset_child and ones inherited through dependency clause and creates a
45
+ # hash where the key is the dependency_group's fieldset_child and the value is an array of all fieldset
46
+ # children dependent on this group. This is returned as a JSON object
47
+ #
48
+ # @returns [JSON] A hash inside a Json object
49
+ def dependency_group_fieldset_children
50
+ return { self.fieldset_child_id => self.dependent_fieldset_children }.to_json
51
+ end
52
+
53
+ # Returns the success or failure action depending on what evaluate returns
54
+ #
55
+ # @param [Hash] input_values A hash of fieldset_child_id:value pairs to test against
56
+ # @return [String] The success or failure action
57
+ def get_action(input_values)
58
+ if evaluate(input_values)
59
+ return Action_list[self.action][:success]
60
+ else
61
+ return Action_list[self.action][:failure]
62
+ end
63
+ end
64
+
65
+ # Evaluates the clauses by ANDing them together
66
+ # Short circuit evaluation returns false as soon as possible
67
+ #
68
+ # @param [Hash] values A hash of fieldset_child_id:value pairs to test against
69
+ # @return [Boolean] True if all of the clauses are true
70
+ def evaluate(values)
71
+ self.dependency_clauses.each do |clause|
72
+ if !clause.evaluate(values)
73
+ return false
74
+ end
75
+ end
76
+ return true
77
+ end
78
+
79
+ # Creates a nested hash that has information on the dependency_group as well
80
+ # as all dependency_clauses and nested dependencies within it. To be used
81
+ # when capturing dependencies for interacting with jQuery.
82
+ #
83
+ # @param [None]
84
+ # @return [Hash] A nested hash of the dependency group and related dependency_clauses and dependencies
85
+ def to_hash
86
+ dependency_group_hash =
87
+ {
88
+ "action" => self.action,
89
+ "fieldset_child_id" => self.fieldset_child_id,
90
+ "field_id" => self.fieldset_child.child_id,
91
+ "clause" => {}
92
+ }
93
+ for dependency_clause in self.dependency_clauses
94
+ dependency_group_hash["clause"][dependency_clause.id] = {}
95
+ for dependency in dependency_clause.dependencies
96
+ dependency_group_hash["clause"][dependency_clause.id][dependency.id] =
97
+ {
98
+ "fieldset_child_id" => dependency.fieldset_child.id,
99
+ "relationship" => dependency.relationship,
100
+ "value" => dependency.value
101
+ }
102
+ end
103
+ end
104
+ dependency_group_hash
105
+ end
106
+
107
+ end
108
+ end
@@ -4,7 +4,11 @@ module DynamicFieldsets
4
4
  # @author Jeremiah Hemphill, Ethan Pemble
5
5
  class Field < ActiveRecord::Base
6
6
  # Relations
7
- belongs_to :fieldset
7
+
8
+ # parents
9
+ has_many :fieldset_children, :dependent => :destroy, :as => :child
10
+ has_many :parent_fieldsets, :source => :fieldset, :foreign_key => "fieldset_id", :through => :fieldset_children, :class_name => "Fieldset"
11
+
8
12
  has_many :field_options
9
13
  accepts_nested_attributes_for :field_options, :allow_destroy => true
10
14
 
@@ -14,11 +18,13 @@ module DynamicFieldsets
14
18
  has_many :field_html_attributes
15
19
  accepts_nested_attributes_for :field_html_attributes, :allow_destroy => true
16
20
 
21
+ #has_many :dependency_groups
22
+ #accepts_nested_attributes_for :dependency_groups, :allow_destroy => true
23
+
17
24
  # Validations
18
25
  validates_presence_of :name
19
26
  validates_presence_of :label
20
27
  validates_presence_of :field_type
21
- validates_presence_of :order_num
22
28
  validates_inclusion_of :enabled, :in => [true, false]
23
29
  validates_inclusion_of :required, :in => [true, false]
24
30
  validate :has_field_options, :field_type_in_field_types
@@ -65,20 +71,19 @@ module DynamicFieldsets
65
71
 
66
72
  # @return [Array] Alias for field_defaults
67
73
  def defaults
68
- if options?
69
- return self.field_defaults
70
- else
71
- return nil
72
- end
74
+ return self.field_defaults if options?
75
+ return nil
73
76
  end
74
77
 
75
78
  # @return [String] Alias for field_defaults.first
76
79
  def default
77
- if options?
78
- return nil
79
- else
80
- return self.field_defaults.first
81
- end
80
+ return nil if options?
81
+ return self.field_defaults.first
82
+ end
83
+
84
+ # @return [Boolean] True if there are any field records for the field or if it is in any fieldsets
85
+ def in_use?
86
+ self.fieldset_children.count { |child| !child.fieldset_id.nil? || !child.field_records.empty? } > 0
82
87
  end
83
88
 
84
89
  end
@@ -10,5 +10,31 @@ module DynamicFieldsets
10
10
 
11
11
  #validations
12
12
  validates_presence_of :value
13
+
14
+ before_save :convert_option_name_to_id
15
+
16
+ # When the field type is an option type, the saved value should be converted into an id
17
+ # This needs to happen because the value field normally stores a string but sometimes stores a field option id
18
+ #
19
+ # In some cases, the field_option instance is not set before this, no idea what happens then
20
+ #
21
+ # http://www.youtube.com/watch?v=BeP6CpUnfc0
22
+ def convert_option_name_to_id
23
+ if Field.option_field_types.include?(self.field.field_type)
24
+ option = FieldOption.find_by_name(self.value)
25
+ self.value = option.id unless option.nil?
26
+ end
27
+ end
28
+
29
+ # @return [String] Either the value or the name of the field option reference by the value
30
+ def pretty_value
31
+ if !self.field.nil? && Field.option_field_types.include?(self.field.field_type)
32
+ option = FieldOption.find_by_id(self.value)
33
+ if !option.nil?
34
+ return option.name
35
+ end
36
+ end
37
+ return self.value
38
+ end
13
39
  end
14
- end
40
+ end
@@ -2,10 +2,25 @@ module DynamicFieldsets
2
2
  # Stores a single record's answer to a field in a fieldset
3
3
  # Fields with multiple answers should have multiple records in this model
4
4
  class FieldRecord < ActiveRecord::Base
5
- belongs_to :field
5
+ belongs_to :fieldset_child
6
6
  belongs_to :fieldset_associator
7
7
 
8
- validates_presence_of :field, :fieldset_associator
9
- validates_exclusion_of :value, :in => [nil]
10
- end
8
+ validates_presence_of :fieldset_child, :fieldset_associator
9
+ validates_exclusion_of :value, :in => [nil]
10
+ validate :type_of_fieldset_child
11
+
12
+ # make sure the fieldset child has the type field
13
+ # does not explicitly check to make sure the fieldset_child exists, still have to validate presence
14
+ def type_of_fieldset_child
15
+ if self.fieldset_child && !self.fieldset_child.child.is_a?(DynamicFieldsets::Field)
16
+ self.errors.add(:fieldset_child, "The fieldset child must refer to a Field object")
17
+ end
18
+ end
19
+
20
+ # @return [Field] Alias for fieldset_child.child.
21
+ # A record can only be associated with Field children
22
+ def field
23
+ self.fieldset_child.child
24
+ end
25
+ end
11
26
  end
@@ -5,53 +5,58 @@ module DynamicFieldsets
5
5
  class Fieldset < ActiveRecord::Base
6
6
  # Relations
7
7
  has_many :fieldset_associators
8
- belongs_to :parent_fieldset, :class_name => "Fieldset", :foreign_key => "parent_fieldset_id"
9
- has_many :child_fieldsets, :class_name => "Fieldset", :foreign_key => "parent_fieldset_id"
10
- has_many :fields
8
+
9
+ # parents
10
+ has_one :fieldset_parent, :dependent => :destroy, :class_name => "FieldsetChild", :as => :child
11
+ has_one :parent, :source => :fieldset, :foreign_key => "fieldset_id", :through => :fieldset_parent, :class_name => "DynamicFieldsets::Fieldset"
12
+ # children
13
+ has_many :fieldset_children, :dependent => :destroy, :foreign_key => "fieldset_id", :class_name => "FieldsetChild"
14
+ has_many :child_fields, :source => :child, :through => :fieldset_children, :source_type => "DynamicFieldsets::Field", :class_name => "DynamicFieldsets::Field"
15
+ has_many :child_fieldsets, :source => :child, :through => :fieldset_children, :source_type => "DynamicFieldsets::Fieldset", :class_name => "DynamicFieldsets::Fieldset"
16
+
11
17
 
12
18
  # Validations
13
19
  validates_presence_of :name
14
20
  validates_presence_of :description
15
21
  validates_presence_of :nkey
16
22
  validates_uniqueness_of :nkey
17
- validates_presence_of :order_num, :if => lambda { !self.root? }
18
- validate :cannot_be_own_parent
19
-
20
- # looks recursively up the parent_fieldset value to check if it sees itself
21
- def cannot_be_own_parent
22
- parent = self.parent_fieldset
23
- while !parent.nil?
24
- if parent == self
25
- self.errors.add(:parent_fieldset, "Parent fieldsets must not create a cycle.")
26
- parent = nil
27
- else
28
- parent = parent.parent_fieldset
29
- end
30
- end
31
- end
32
23
 
33
24
  # @return [Array] Scope: parent-less fieldsets
34
- scope :roots, :conditions => ["parent_fieldset_id IS NULL"]
35
-
36
- # @return [Array] An array of name, id pairs to be used in select tags
37
- def self.parent_fieldset_list
38
- all.collect { |f| [f.name, f.id] }
25
+ def self.roots
26
+ # the old method used a scope and the old table definition
27
+ # scope :roots, :conditions => ["parent_fieldset_id IS NULL"]
28
+ # the new method doesn't use a scope because I am bad at them
29
+ all.select { |fs| fs.parent.nil? }
39
30
  end
40
31
 
41
32
  # @return [Boolean] True if fieldset has no parent
42
33
  def root?
43
- return parent_fieldset.nil?
34
+ return parent.nil?
44
35
  end
45
36
 
46
- # The collected descendents of a fieldset. This group is sorted first by order number,
47
- # then alphabetically by name in the case of duplicate order numbers.
37
+ # @return [Array] An array of name, id pairs to be used in select tags
38
+ def self.parent_fieldset_list
39
+ all.collect { |f| [f.name, f.id] }
40
+ end
41
+
42
+ # The collected descendents of a fieldset.
43
+ # This group is sorted by order number on the fieldsetchild model
48
44
  # @return [Array] Ordered collection of descendent fields and fieldsets.
49
45
  def children
50
46
  collected_children = []
51
- fields.reject{|f| !f.enabled}.each{ |field| collected_children.push field }
52
- child_fieldsets.each{ |fieldset| collected_children.push fieldset }
53
- return collected_children.sort_by{ |child| [child.order_num, child.name] }
47
+ self.fieldset_children.sort_by(&:order_num).each do |fs_child|
48
+ child = fs_child.child_type.constantize.find_by_id fs_child.child_id
49
+ if child.respond_to? :enabled? and child.enabled?
50
+ collected_children.push child
51
+ elsif !child.respond_to? :enabled?
52
+ collected_children.push child
53
+ end
54
+ end
55
+ return collected_children
56
+ end
57
+
58
+ def has_children?
59
+ return !self.fieldset_children.empty?
54
60
  end
55
-
56
61
  end
57
62
  end
@@ -1,8 +1,9 @@
1
1
  module DynamicFieldsets
2
2
  class FieldsetAssociator < ActiveRecord::Base
3
3
  belongs_to :fieldset
4
+ belongs_to :fieldset_model, :polymorphic => true
4
5
  has_many :field_records
5
-
6
+
6
7
  validates_presence_of :fieldset_id, :fieldset_model_id, :fieldset_model_type, :fieldset_model_name
7
8
  validate :unique_fieldset_model_name_per_polymorphic_fieldset_model
8
9
 
@@ -56,21 +57,64 @@ module DynamicFieldsets
56
57
  def field_values
57
58
  output = {}
58
59
  self.field_records.each do |record|
59
- if record.field.field_type == "checkbox" || record.field.field_type == "multiple_select"
60
- output[record.field.id] = [] unless output[record.field.id].is_a?(Array)
60
+ fieldtype = record.fieldset_child.child.field_type
61
+ child_id = record.fieldset_child.id
62
+ if fieldtype == "checkbox" || fieldtype == "multiple_select"
63
+ output[child_id] = [] unless output[child_id].is_a? Array
61
64
  # note record.id array
62
- # collect?
63
- output[record.field.id].push record.value.to_i
64
- elsif record.field.field_type == "radio" || record.field.field_type == "select"
65
+ output[child_id].push record.value.to_i
66
+ elsif fieldtype == "radio" || fieldtype == "select"
65
67
  # note record.id
66
- output[record.field.id] = record.value.to_i
68
+ output[child_id] = record.value.to_i
67
69
  else
68
70
  # note record.value
69
- output[record.field.id] = record.value
71
+ output[child_id] = record.value
70
72
  end
71
73
  end
72
74
  return output
73
75
  end
74
76
 
77
+
78
+ # OMG COMMENT
79
+ #
80
+ # TODO: Fill in actual comments
81
+ #
82
+ # @params - stuff
83
+ # @returns - other stuff
84
+ def dependency_child_hash
85
+ @fieldset_child_collection = []
86
+ look_for_dependents(self.fieldset)
87
+
88
+ output = {}
89
+ for fieldset_child in @fieldset_child_collection
90
+ output[fieldset_child.id] = {}
91
+ for dependency in fieldset_child.dependencies
92
+ dependency_group = dependency.dependency_clause.dependency_group
93
+ output[fieldset_child.id][dependency_group.id] = dependency_group.to_hash
94
+ end
95
+ end
96
+ output
97
+ end
98
+
99
+
100
+ # OMG COMMENT
101
+ #
102
+ # TODO: Fill in actual comments
103
+ #
104
+ # @params - stuff
105
+ # @returns - other stuff
106
+ def look_for_dependents(parent_fieldset)
107
+ for fieldset_child in parent_fieldset.fieldset_children
108
+ if (fieldset_child.child_type == "DynamicFieldsets::Field") && (!fieldset_child.dependencies.empty?)
109
+ @fieldset_child_collection.push(fieldset_child)
110
+ return
111
+ elsif (fieldset_child.child_type == "DynamicFieldsets::Field") && (fieldset_child.dependencies.empty?)
112
+ return
113
+ else
114
+ look_for_dependents(fieldset_child.child)
115
+ end
116
+ end
117
+ end
118
+
75
119
  end
76
120
  end
@@ -0,0 +1,148 @@
1
+ module DynamicFieldsets
2
+
3
+ # FieldsetChild
4
+ # @author Jeremiah Hemphill, John "hex" Carter
5
+ class FieldsetChild < ActiveRecord::Base
6
+
7
+ # Constants
8
+
9
+ FIELDSET_CHILD_LIST = ["DynamicFieldsets::Field", "DynamicFieldsets::Fieldset"]
10
+
11
+ # Relationships
12
+
13
+ belongs_to :child, :polymorphic => true
14
+ belongs_to :fieldset
15
+
16
+ has_many :field_records
17
+
18
+ # this is a little confusing, so let's explain:
19
+ # a fieldset_child can have many dependencies when it is a part of several dependencies
20
+ # which each belong to a dependency_clause, which consequently belongs to a dependency_group.
21
+ # a fieldset_child belongs to a dependency_group when it is at the END of a dependency_clause
22
+ # statement:
23
+ # IF Field1 == A AND Field2 == B THEN show Field3
24
+ # In that example, Field1 and Field2 belong to dependencies and Field3 belongs to a dependency_group
25
+ has_one :dependency_group
26
+ accepts_nested_attributes_for :dependency_group, :allow_destroy => true
27
+
28
+ has_many :dependencies
29
+
30
+ # Validations
31
+
32
+ validates_presence_of :fieldset_id
33
+ validates_presence_of :child_id
34
+ validates_inclusion_of :child_type, :in => FIELDSET_CHILD_LIST
35
+ validates_presence_of :order_num
36
+ validate :no_duplicate_fields_in_fieldset_children
37
+ validate :cannot_be_own_parent
38
+ validate :no_parental_loop
39
+
40
+ before_validation :assign_order
41
+
42
+ # This method is called when FieldsetChildren is instantiated.
43
+ # It ensures that the Child has a valid order number.
44
+ def assign_order
45
+ self.order_num = self.last_order_num + 1 if self.order_num.nil?
46
+ end
47
+
48
+ # Methods
49
+
50
+ # Returns a list of all the values in the FIELDSET_CHILD_LIST constant
51
+ #
52
+ # @params [None]
53
+ # @returns [Array] An array of all the values stored in FIELDSET_CHILD_LIST
54
+ def fieldset_child_list
55
+ return FIELDSET_CHILD_LIST
56
+ end
57
+
58
+ # Sends a validation error if there are any duplicate pairings of fieldsets and fieldset children.
59
+ #
60
+ # @params [None]
61
+ # @returns [Errors] In the case a duplicate pairing fieldsets and fieldset children are found, it returns a validation error.
62
+ def no_duplicate_fields_in_fieldset_children
63
+ if self.fieldset && self.child
64
+ duplicate_children = FieldsetChild.where(:fieldset_id => self.fieldset.id, :child_id => self.child_id, :child_type => self.child_type).select { |fieldset_child| fieldset_child.id != self.id }
65
+ if duplicate_children.length > 0
66
+ self.errors.add(:child_id, "There is already a copy of this child in the fieldset.")
67
+ end
68
+ end
69
+ end
70
+
71
+ # Sends a validation error if there is an attempt to save a fieldset as its own parent.
72
+ #
73
+ # @params [None]
74
+ # @returns [Errors] In the case a fieldset is its own parent a validation error is returned.
75
+ def cannot_be_own_parent
76
+ if self.child_type == "DynamicFieldsets::Fieldset"
77
+ if self.fieldset_id == self.child_id
78
+ self.errors.add(:child_id, "A fieldset cannot have itself as a parent.")
79
+ end
80
+ end
81
+ end
82
+
83
+ # Recursively looks to see if there is an attempt to save a fieldset as a descendent of itself
84
+ #
85
+ # @params [None]
86
+ # @returns [Errors] In the case a fieldset contains itself as a parent, a validation error is returned.
87
+ def no_parental_loop
88
+ if (self.child_type == "DynamicFieldsets::Fieldset") && (self.has_loop?([self.fieldset_id]))
89
+ self.errors.add(:child_id, "There is a fieldset that contains itself as a parent.")
90
+ end
91
+ end
92
+
93
+ # Recursively calls itself to span entire hierarchy of a fieldset child family to see if any attempts to have a parental loop exist
94
+ #
95
+ # @params [Array] parent_array - A collection of all previously checked parents. If a repeat parent is found, it returns false, else true.
96
+ # @returns [Boolean] If there are no parental loops, it returns true, else it returns true
97
+ def has_loop?(parent_array)
98
+ return true if parent_array.include? self.child_id
99
+ parent_array.push self.child_id
100
+ return true if FieldsetChild.where(:fieldset_id => self.child_id).find { |f| f.has_loop? parent_array }
101
+ parent_array.pop
102
+ return false
103
+ end
104
+
105
+ scope :ordered, order( 'order_num asc' )
106
+
107
+ # @return [ActiveRecord::Relation] Collection of FieldsetChildren that are direct descendents; ascending order.
108
+ def children
109
+ FieldsetChild.where( fieldset_id: self.child_id ).ordered
110
+ end
111
+
112
+ # @return [ActiveRecord::Relation] Collection of FieldsetChildren that share the same parent; ascending order.
113
+ def siblings
114
+ sib = FieldsetChild.where( fieldset_id: self.fieldset_id ).ordered
115
+ sib.delete_if{ |child| child.id == self.id }
116
+ sib
117
+ end
118
+
119
+ # @return [Integer] The order number of the last sibling.
120
+ def last_order_num
121
+ return 0 if siblings.empty?
122
+ return self.siblings.last[:order_num]
123
+ end
124
+
125
+
126
+ def to_hash
127
+ return { "id" => self.id, "fieldset_id" => self.fieldset_id, "child_id" => self.child_id, "child_type" => self.child_type }
128
+ end
129
+
130
+ # Returns the root fieldset of the child
131
+ # Loops up the parent fieldset field until the parent is nil, then returns the child
132
+ #
133
+ # Important: This method is dependent on fieldsets not being reusable
134
+ #
135
+ # @return [Fieldset] The root fieldset of the fieldsetchild
136
+ def root_fieldset(fs = self.fieldset)
137
+ # whether the parent is a child
138
+ parent_as_a_child = FieldsetChild.where(:child_id => fs.id, :child_type => "DynamicFieldsets::Fieldset")
139
+ # my parent is nobody's child, it is the root
140
+ if parent_as_a_child.count == 0
141
+ return fs
142
+ else # if my parent is someone else's child, then
143
+ # look at her parent
144
+ return root_fieldset(parent_as_a_child.first.fieldset)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,4 @@
1
+ <%= form_for field, :method => :post, :url => dynamic_fieldsets_enable_field_path(field) do |f| %>
2
+ <%= f.hidden_field :enabled, :value => !field.enabled %>
3
+ <%= f.submit field.enabled ? "Disable" : "Enable" %>
4
+ <% end %>
@@ -1,6 +1,6 @@
1
1
  <p class="fields">
2
2
  <%= f.label :value, "Default field value" %>
3
- <%= f.text_field :value %>
3
+ <%= f.text_field :value, :value => obj.pretty_value %>
4
4
  <%= f.hidden_field :_destroy %>
5
5
  <%= link_to_function "remove", "remove_fields(this)" %>
6
6
  </p>