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.
- data/CHANGELOG +7 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +1 -0
- data/README.rdoc +15 -2
- data/Rakefile +24 -4
- data/VERSION +1 -1
- data/app/controllers/dynamic_fieldsets/fields_controller.rb +25 -0
- data/app/controllers/dynamic_fieldsets/fieldset_children_controller.rb +57 -0
- data/app/controllers/dynamic_fieldsets/fieldsets_controller.rb +91 -5
- data/app/helpers/dynamic_fieldsets/fields_helper.rb +1 -15
- data/app/helpers/dynamic_fieldsets/fieldset_children_helper.rb +5 -0
- data/app/helpers/dynamic_fieldsets/nested_model_helper.rb +18 -0
- data/app/helpers/dynamic_fieldsets_helper.rb +70 -44
- data/app/models/dynamic_fieldsets/dependency.rb +76 -0
- data/app/models/dynamic_fieldsets/dependency_clause.rb +32 -0
- data/app/models/dynamic_fieldsets/dependency_group.rb +108 -0
- data/app/models/dynamic_fieldsets/field.rb +17 -12
- data/app/models/dynamic_fieldsets/field_default.rb +27 -1
- data/app/models/dynamic_fieldsets/field_record.rb +19 -4
- data/app/models/dynamic_fieldsets/fieldset.rb +35 -30
- data/app/models/dynamic_fieldsets/fieldset_associator.rb +52 -8
- data/app/models/dynamic_fieldsets/fieldset_child.rb +148 -0
- data/app/views/dynamic_fieldsets/fields/_disable_field_form.html.erb +4 -0
- data/app/views/dynamic_fieldsets/fields/_field_default_fields.html.erb +1 -1
- data/app/views/dynamic_fieldsets/fields/_form.html.erb +9 -27
- data/app/views/dynamic_fieldsets/fields/index.html.erb +8 -5
- data/app/views/dynamic_fieldsets/fields/new.html.erb +5 -1
- data/app/views/dynamic_fieldsets/fields/show.html.erb +1 -13
- data/app/views/dynamic_fieldsets/fieldset_children/_dependency_clause_fields.html.erb +25 -0
- data/app/views/dynamic_fieldsets/fieldset_children/_dependency_fields.html.erb +12 -0
- data/app/views/dynamic_fieldsets/fieldset_children/_dependency_group_fields.html.erb +16 -0
- data/app/views/dynamic_fieldsets/fieldset_children/_form.html.erb +34 -0
- data/app/views/dynamic_fieldsets/fieldset_children/edit.html.erb +6 -0
- data/app/views/dynamic_fieldsets/fieldsets/_associate_child.html.erb +4 -0
- data/app/views/dynamic_fieldsets/fieldsets/_child.html.erb +41 -0
- data/app/views/dynamic_fieldsets/fieldsets/_form.html.erb +2 -8
- data/app/views/dynamic_fieldsets/fieldsets/children.html.erb +47 -39
- data/app/views/dynamic_fieldsets/fieldsets/index.html.erb +4 -4
- data/app/views/dynamic_fieldsets/fieldsets/new.html.erb +5 -1
- data/app/views/dynamic_fieldsets/fieldsets/reorder.html.erb +4 -0
- data/app/views/dynamic_fieldsets/fieldsets/show.html.erb +1 -12
- data/app/views/dynamic_fieldsets/shared/_javascript_watcher.html.erb +255 -0
- data/app/views/dynamic_fieldsets/shared/_nested_model_javascript.html.erb +35 -0
- data/config/.routes.rb.swp +0 -0
- data/config/routes.rb +11 -0
- data/dynamic_fieldsets.gemspec +41 -4
- data/lib/dynamic_fieldsets/dynamic_fieldsets_in_model.rb +141 -14
- data/lib/generators/dynamic_fieldsets/templates/migrations/install_migration.rb +39 -5
- data/spec/dummy/app/controllers/information_forms_controller.rb +2 -1
- data/spec/dummy/app/models/information_form.rb +1 -1
- data/spec/dummy/app/views/information_forms/dynamic_view.html.erb +18 -0
- data/spec/dummy/app/views/information_forms/show.html.erb +1 -0
- data/spec/dummy/app/views/layouts/application.html.erb +9 -1
- data/spec/dummy/config/routes.rb +1 -1
- data/spec/dummy/db/migrate/{20110726215814_create_dynamic_fieldsets_tables.rb → 20110809161724_create_dynamic_fieldsets_tables.rb} +39 -5
- data/spec/dummy/db/schema.rb +40 -11
- data/spec/dummy/features/field.feature +17 -2
- data/spec/dummy/features/fieldset.feature +1 -21
- data/spec/dummy/features/fieldset_children.feature +50 -0
- data/spec/dummy/features/javascript_tests.feature +91 -0
- data/spec/dummy/features/step_definitions/field_steps.rb +9 -5
- data/spec/dummy/features/step_definitions/fieldset_associator_steps.rb +3 -1
- data/spec/dummy/features/step_definitions/fieldset_children_steps.rb +65 -0
- data/spec/dummy/features/step_definitions/fieldset_steps.rb +0 -27
- data/spec/dummy/features/step_definitions/javascript_steps.rb +208 -0
- data/spec/dummy/features/step_definitions/web_steps.rb +5 -0
- data/spec/dummy/features/support/paths.rb +10 -1
- data/spec/dummy/features/support/selectors.rb +2 -0
- data/spec/dummy/public/javascripts/jquery-1.6.2.min.js +18 -0
- data/spec/dummy/public/javascripts/jquery-ui-1.8.15.custom.min.js +111 -0
- data/spec/dummy/public/javascripts/jquery-ui-nestedSortable.js +356 -0
- data/spec/dummy/public/stylesheets/scaffold.css +101 -0
- data/spec/dynamic_fieldsets_helper_spec.rb +236 -55
- data/spec/dynamic_fieldsets_in_model_spec.rb +122 -4
- data/spec/models/dependency_clause_spec.rb +55 -0
- data/spec/models/dependency_group_spec.rb +237 -0
- data/spec/models/dependency_spec.rb +173 -0
- data/spec/models/field_default_spec.rb +49 -0
- data/spec/models/field_record_spec.rb +11 -2
- data/spec/models/field_spec.rb +30 -6
- data/spec/models/fieldset_associator_spec.rb +138 -25
- data/spec/models/fieldset_child_spec.rb +122 -0
- data/spec/models/fieldset_spec.rb +78 -80
- data/spec/support/dependency_group_helper.rb +9 -0
- data/spec/support/dependency_helper.rb +13 -0
- data/spec/support/field_helper.rb +0 -2
- data/spec/support/fieldset_child_helper.rb +10 -0
- data/spec/support/fieldset_helper.rb +2 -18
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 :
|
|
5
|
+
belongs_to :fieldset_child
|
|
6
6
|
belongs_to :fieldset_associator
|
|
7
7
|
|
|
8
|
-
validates_presence_of :
|
|
9
|
-
validates_exclusion_of :value, :in => [nil]
|
|
10
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
all.
|
|
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
|
|
34
|
+
return parent.nil?
|
|
44
35
|
end
|
|
45
36
|
|
|
46
|
-
#
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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[
|
|
68
|
+
output[child_id] = record.value.to_i
|
|
67
69
|
else
|
|
68
70
|
# note record.value
|
|
69
|
-
output[
|
|
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
|