discerner 1.1.18

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 (167) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +97 -0
  4. data/Rakefile +29 -0
  5. data/app/assets/images/discerner/add.png +0 -0
  6. data/app/assets/images/discerner/ajax-loader.gif +0 -0
  7. data/app/assets/images/discerner/bar.gif +0 -0
  8. data/app/assets/images/discerner/bullet_arrow_down.png +0 -0
  9. data/app/assets/images/discerner/bullet_arrow_up.png +0 -0
  10. data/app/assets/images/discerner/cog.png +0 -0
  11. data/app/assets/images/discerner/delete.png +0 -0
  12. data/app/assets/images/discerner/edit.png +0 -0
  13. data/app/assets/images/discerner/excel.png +0 -0
  14. data/app/assets/images/discerner/greencheck.gif +0 -0
  15. data/app/assets/images/discerner/rails.png +0 -0
  16. data/app/assets/images/discerner/show.png +0 -0
  17. data/app/assets/images/discerner/switch_minus.gif +0 -0
  18. data/app/assets/images/discerner/switch_plus.gif +0 -0
  19. data/app/assets/javascripts/discerner/application.js +20 -0
  20. data/app/assets/javascripts/discerner/combobox.js +301 -0
  21. data/app/assets/javascripts/discerner/discerner.js +19 -0
  22. data/app/assets/javascripts/discerner/export_parameters.js +13 -0
  23. data/app/assets/javascripts/discerner/jquery/jquery.blockUI.js +576 -0
  24. data/app/assets/javascripts/discerner/jquery/jquery.form.js +1074 -0
  25. data/app/assets/javascripts/discerner/nested_attributes.js +69 -0
  26. data/app/assets/javascripts/discerner/search_combinations.js +49 -0
  27. data/app/assets/javascripts/discerner/search_parameter.js +131 -0
  28. data/app/assets/javascripts/discerner/search_parameter_value.js +172 -0
  29. data/app/assets/javascripts/discerner/searches.js +93 -0
  30. data/app/assets/javascripts/discerner/url.js +42 -0
  31. data/app/assets/stylesheets/discerner/application.css +16 -0
  32. data/app/assets/stylesheets/discerner/buttons.sass +13 -0
  33. data/app/assets/stylesheets/discerner/categorized_autocompleter.sass +53 -0
  34. data/app/assets/stylesheets/discerner/discerner.sass +51 -0
  35. data/app/assets/stylesheets/discerner/export_parameters.sass +43 -0
  36. data/app/assets/stylesheets/discerner/links.sass +29 -0
  37. data/app/assets/stylesheets/discerner/searches.sass +205 -0
  38. data/app/assets/stylesheets/discerner/tables.sass +32 -0
  39. data/app/controllers/discerner/application_controller.rb +2 -0
  40. data/app/controllers/discerner/export_parameters_controller.rb +5 -0
  41. data/app/controllers/discerner/parameters_controller.rb +5 -0
  42. data/app/controllers/discerner/searches_controller.rb +5 -0
  43. data/app/helpers/discerner/application_helper.rb +4 -0
  44. data/app/helpers/discerner/parameters_helper.rb +5 -0
  45. data/app/helpers/discerner/searches_helper.rb +5 -0
  46. data/app/models/discerner/dictionary.rb +5 -0
  47. data/app/models/discerner/export_parameter.rb +5 -0
  48. data/app/models/discerner/operator.rb +5 -0
  49. data/app/models/discerner/parameter.rb +5 -0
  50. data/app/models/discerner/parameter_category.rb +5 -0
  51. data/app/models/discerner/parameter_type.rb +5 -0
  52. data/app/models/discerner/parameter_value.rb +5 -0
  53. data/app/models/discerner/parameter_value_categorization.rb +5 -0
  54. data/app/models/discerner/parameter_value_category.rb +5 -0
  55. data/app/models/discerner/search.rb +5 -0
  56. data/app/models/discerner/search_combination.rb +5 -0
  57. data/app/models/discerner/search_parameter.rb +5 -0
  58. data/app/models/discerner/search_parameter_value.rb +5 -0
  59. data/app/views/discerner/export_parameters/index.html.haml +55 -0
  60. data/app/views/discerner/parameters/_values_autocompleter.html.haml +49 -0
  61. data/app/views/discerner/parameters/values.html.haml +1 -0
  62. data/app/views/discerner/searches/_form.html.haml +21 -0
  63. data/app/views/discerner/searches/_form_combined_searches.html.haml +32 -0
  64. data/app/views/discerner/searches/_form_controls.html.haml +8 -0
  65. data/app/views/discerner/searches/_form_header.html.haml +39 -0
  66. data/app/views/discerner/searches/_form_search_parameters.html.haml +28 -0
  67. data/app/views/discerner/searches/_list.html.haml +17 -0
  68. data/app/views/discerner/searches/_search_combination_fields.html.haml +22 -0
  69. data/app/views/discerner/searches/_search_parameter_fields.html.haml +48 -0
  70. data/app/views/discerner/searches/_search_parameter_value_fields.html.haml +62 -0
  71. data/app/views/discerner/searches/_summary.html.haml +25 -0
  72. data/app/views/discerner/searches/edit.html.haml +8 -0
  73. data/app/views/discerner/searches/index.html.haml +13 -0
  74. data/app/views/discerner/searches/index.js.haml +1 -0
  75. data/app/views/discerner/searches/new.html.haml +3 -0
  76. data/app/views/discerner/searches/rename.html.haml +6 -0
  77. data/app/views/discerner/searches/update.js.haml +7 -0
  78. data/app/views/discerner/shared/_categorized_autocompleter_items.html.haml +15 -0
  79. data/app/views/discerner/shared/_error_messages.html.haml +5 -0
  80. data/app/views/layouts/discerner/searches.html.erb +17 -0
  81. data/config/cucumber.yml +8 -0
  82. data/config/routes.rb +17 -0
  83. data/db/migrate/20121004040716_create_discerner_dictionaries.rb +11 -0
  84. data/db/migrate/20121004153043_create_discerner_parameter_categories.rb +12 -0
  85. data/db/migrate/20121005194843_create_discerner_parameter_types.rb +11 -0
  86. data/db/migrate/20121005203223_create_discerner_parameters.rb +14 -0
  87. data/db/migrate/20121008154855_create_discerner_operators.rb +13 -0
  88. data/db/migrate/20121008160313_create_discerner_operators_parameter_types.rb +8 -0
  89. data/db/migrate/20121008161455_create_discerner_parameter_values.rb +13 -0
  90. data/db/migrate/20121008180829_create_discerner_searches.rb +11 -0
  91. data/db/migrate/20121008182443_create_discerner_search_parameters.rb +11 -0
  92. data/db/migrate/20121011205130_create_discerner_search_parameter_values.rb +15 -0
  93. data/db/migrate/20121211213215_add_dictionary_id_to_discerner_searches.rb +5 -0
  94. data/db/migrate/20130205193602_create_discerner_search_combinations.rb +12 -0
  95. data/db/migrate/20130211230636_create_discerner_export_parameters.rb +9 -0
  96. data/db/migrate/20130213185818_add_search_columns_to_discerner_parameters.rb +38 -0
  97. data/db/migrate/20130213205255_rename_database_name_to_search_value_in_discerner_parameter_values.rb +5 -0
  98. data/db/migrate/20130215165416_add_searchable_to_discerner_parameters.rb +5 -0
  99. data/db/migrate/20130218230257_add_exclusive_to_discerner_parameters.rb +5 -0
  100. data/db/migrate/20130220163015_replace_integer_type_with_numeric.rb +22 -0
  101. data/db/migrate/20130221172826_add_unique_identifier_to_discerner_parameters.rb +5 -0
  102. data/db/migrate/20130222052924_change_search_attribute_to_method.rb +29 -0
  103. data/db/migrate/20130222070959_add_export_columns_to_discerner_parameters.rb +6 -0
  104. data/db/migrate/20130227031747_add_unique_identifier_to_discerner_operators.rb +16 -0
  105. data/db/migrate/20130306015019_change_discerner_parameter_values_name.rb +9 -0
  106. data/db/migrate/20130306212430_add_deleted_at_to_discerner_search_parameters.rb +5 -0
  107. data/db/migrate/20130306212504_add_deleted_at_to_discerner_search_parameter_values.rb +5 -0
  108. data/db/migrate/20130306212527_add_deleted_at_to_discerner_search_combinations.rb +5 -0
  109. data/db/migrate/20130306212818_add_deleted_at_to_discerner_export_parameters.rb +5 -0
  110. data/db/migrate/20130311190717_add_operator_type_to_discerner_operators.rb +5 -0
  111. data/db/migrate/20131212175110_remove_extra_search_parameter_values.rb +10 -0
  112. data/db/migrate/20140204170625_create_discerner_parameter_value_categories.rb +16 -0
  113. data/db/migrate/20140204170646_create_discerner_parameter_value_categorizations.rb +12 -0
  114. data/db/migrate/20140227191827_remove_blank_parameter_values_from_export_parameters.rb +10 -0
  115. data/db/seeds.rb +0 -0
  116. data/lib/discerner/engine.rb +16 -0
  117. data/lib/discerner/methods/controllers/export_parameters_controller.rb +38 -0
  118. data/lib/discerner/methods/controllers/parameters_controller.rb +30 -0
  119. data/lib/discerner/methods/controllers/searches_controller.rb +204 -0
  120. data/lib/discerner/methods/helpers/searches_helper.rb +145 -0
  121. data/lib/discerner/methods/models/dictionary.rb +54 -0
  122. data/lib/discerner/methods/models/export_parameter.rb +35 -0
  123. data/lib/discerner/methods/models/operator.rb +42 -0
  124. data/lib/discerner/methods/models/parameter.rb +90 -0
  125. data/lib/discerner/methods/models/parameter_category.rb +60 -0
  126. data/lib/discerner/methods/models/parameter_type.rb +33 -0
  127. data/lib/discerner/methods/models/parameter_value.rb +87 -0
  128. data/lib/discerner/methods/models/parameter_value_categorization.rb +21 -0
  129. data/lib/discerner/methods/models/parameter_value_category.rb +45 -0
  130. data/lib/discerner/methods/models/search.rb +147 -0
  131. data/lib/discerner/methods/models/search_combination.rb +50 -0
  132. data/lib/discerner/methods/models/search_parameter.rb +128 -0
  133. data/lib/discerner/methods/models/search_parameter_value.rb +130 -0
  134. data/lib/discerner/methods/models/soft_delete.rb +35 -0
  135. data/lib/discerner/methods/models/warning.rb +15 -0
  136. data/lib/discerner/parser.rb +497 -0
  137. data/lib/discerner/version.rb +3 -0
  138. data/lib/discerner.rb +4 -0
  139. data/lib/generators/discerner/dictionary/dictionary_generator.rb +38 -0
  140. data/lib/generators/discerner/dictionary/templates/model.rb +15 -0
  141. data/lib/generators/discerner/dictionary/templates/show.xls.erb +37 -0
  142. data/lib/generators/discerner/dictionary/templates/view.html.haml +2 -0
  143. data/lib/generators/discerner/install/install_generator.rb +153 -0
  144. data/lib/generators/discerner/install/templates/controllers/export_parameters_controller.rb +20 -0
  145. data/lib/generators/discerner/install/templates/controllers/parameters_controller.rb +15 -0
  146. data/lib/generators/discerner/install/templates/controllers/searches_controller.rb +39 -0
  147. data/lib/generators/discerner/install/templates/dictionaries.yml +236 -0
  148. data/lib/generators/discerner/install/templates/helpers/searches_helper.rb +9 -0
  149. data/lib/generators/discerner/install/templates/models/book.rb +15 -0
  150. data/lib/generators/discerner/install/templates/models/dictionary.rb +9 -0
  151. data/lib/generators/discerner/install/templates/models/export_parameter.rb +9 -0
  152. data/lib/generators/discerner/install/templates/models/operator.rb +9 -0
  153. data/lib/generators/discerner/install/templates/models/parameter.rb +10 -0
  154. data/lib/generators/discerner/install/templates/models/parameter_category.rb +9 -0
  155. data/lib/generators/discerner/install/templates/models/parameter_type.rb +9 -0
  156. data/lib/generators/discerner/install/templates/models/parameter_value.rb +9 -0
  157. data/lib/generators/discerner/install/templates/models/parameter_value_categorization.rb +9 -0
  158. data/lib/generators/discerner/install/templates/models/parameter_value_category.rb +9 -0
  159. data/lib/generators/discerner/install/templates/models/search.rb +9 -0
  160. data/lib/generators/discerner/install/templates/models/search_combination.rb +9 -0
  161. data/lib/generators/discerner/install/templates/models/search_parameter.rb +9 -0
  162. data/lib/generators/discerner/install/templates/models/search_parameter_value.rb +9 -0
  163. data/lib/generators/discerner/install/templates/views/layouts/searches.html.erb +17 -0
  164. data/lib/setup/operators.yml +91 -0
  165. data/lib/tasks/cucumber.rake +65 -0
  166. data/lib/tasks/discerner_tasks.rake +30 -0
  167. metadata +531 -0
@@ -0,0 +1,35 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module ExportParameter
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+
8
+ # Associations
9
+ base.send :belongs_to, :parameter, :inverse_of => :export_parameters
10
+ base.send :belongs_to, :search, :inverse_of => :export_parameters
11
+
12
+ # Scopes
13
+ base.send(:scope, :ordered, -> { base.order('discerner_export_parameters.id ASC') })
14
+ base.send(:scope, :by_parameter_category, ->(parameter_category) { base.includes(:parameter).where('discerner_parameters.parameter_category_id' => parameter_category.id) unless parameter_category.blank?})
15
+
16
+ # Validations
17
+ base.send :validates, :parameter, :search, :presence => { :message => "for export parameter can't be blank" }
18
+
19
+ # Whitelisting attributes
20
+ base.send :attr_accessible, :parameter_id, :search_id, :parameter, :search
21
+ end
22
+
23
+ # Instance Methods
24
+ def initialize(*args)
25
+ super(*args)
26
+ end
27
+
28
+ def disabled?
29
+ return false unless persisted?
30
+ deleted? || parameter.blank? || parameter.deleted?
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module Operator
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+
8
+ # Associations
9
+ base.send :has_many, :search_parameter_values, :inverse_of => :operator
10
+ base.send :has_many, :search_combinations, :inverse_of => :operator
11
+ base.send :has_and_belongs_to_many, :parameter_types, :join_table => :discerner_operators_parameter_types
12
+
13
+ # Validations
14
+ base.send :validates, :symbol, :presence => true, :uniqueness => {:message => "for operator has already been taken"}
15
+ base.send :validates, :operator_type, :presence => true
16
+ base.send :validate, :type_supported?
17
+
18
+ # Whitelisting attributes
19
+ base.send :attr_accessible, :operator_type, :symbol, :text, :deleted_at
20
+ end
21
+
22
+ # Instance Methods
23
+ def initialize(*args)
24
+ super(*args)
25
+ end
26
+
27
+ def css_class_name
28
+ css_class = parameter_types.map{ |t| t.name }
29
+ css_class << operator_type unless operator_type.blank?
30
+ css_class.join(' ')
31
+ end
32
+
33
+ private
34
+ def type_supported?
35
+ return if self.operator_type.blank?
36
+ supported_types = ['comparison', 'text_comparison', 'range', 'list', 'presence']
37
+ errors.add(:base,"Operator type '#{self.operator_type}' is not supported, please use one of the following types: #{supported_types.join(', ')}") unless supported_types.include?(self.operator_type)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,90 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module Parameter
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+
8
+ # Associations
9
+ base.send :belongs_to, :parameter_category, :inverse_of => :parameters
10
+ base.send :belongs_to, :parameter_type, :inverse_of => :parameters
11
+ base.send :has_many, :parameter_values, :inverse_of => :parameter, :dependent => :destroy
12
+ base.send :has_many, :search_parameters, :inverse_of => :parameter, :dependent => :destroy
13
+ base.send :has_many, :export_parameters, :inverse_of => :parameter, :dependent => :destroy
14
+ base.send :has_many, :parameter_value_categories, :inverse_of => :parameter, :dependent => :destroy
15
+
16
+ #Validations
17
+ base.send :validates, :name, :unique_identifier, :parameter_category, :presence => { :message => "for parameter can't be blank" }
18
+ base.send :validate, :validate_unique_identifier
19
+ base.send :validate, :validate_search_attributes
20
+ base.send :validate, :validate_export_attributes
21
+
22
+ # Scopes
23
+ base.send(:scope, :searchable, -> {base.not_deleted.where('search_model is not null and search_method is not null')})
24
+ base.send(:scope, :exportable, -> {base.not_deleted.where('export_model is not null and export_method is not null')})
25
+
26
+ # Hooks
27
+ base.send :after_commit, :cascade_delete_parameter_values, :on => :update, :if => Proc.new { |record| record.previous_changes.include?('deleted_at') }
28
+
29
+ # Whitelisting attributes
30
+ base.send :attr_accessible, :name, :parameter_category, :parameter_category_id, :parameter_type, :parameter_type_id,
31
+ :search_model, :search_method, :unique_identifier, :export_model, :export_method
32
+ end
33
+
34
+ # Instance Methods
35
+ def initialize(*args)
36
+ super(*args)
37
+ end
38
+
39
+ def used_in_search?
40
+ search_parameters.any? || export_parameters.any?
41
+ end
42
+
43
+ def display_name
44
+ "#{parameter_category.name} - #{name}"
45
+ end
46
+
47
+ def css_class_name
48
+ css_class_array = [parameter_type.name, parameter_category.css_class_name, parameter_category.dictionary.css_class_name]
49
+ css_class_array << 'exclusive' if exclusive
50
+ css_class_array.join(' ')
51
+ end
52
+
53
+ private
54
+ def validate_search_parameters
55
+ errors.add(:base,"Search should have at least one search criteria.") if
56
+ self.search_parameters.size < 1 || self.search_parameters.all?{|search_parameter| search_parameter.marked_for_destruction? }
57
+ end
58
+
59
+ def validate_unique_identifier
60
+ return if self.parameter_category.blank?
61
+ existing_parameters = Discerner::Parameter.
62
+ joins({ :parameter_category => :dictionary }).
63
+ where('discerner_dictionaries.id = ? and discerner_parameters.unique_identifier = ?', self.parameter_category.dictionary.id, self.unique_identifier)
64
+ existing_parameters = existing_parameters.where('discerner_parameters.id != ?', self.id) unless self.id.blank?
65
+ errors.add(:base,"Unique identifier has to be unique within dictionary.") if existing_parameters.any?
66
+ end
67
+
68
+ def validate_search_attributes
69
+ unless self.search_model.blank? && self.search_method.blank?
70
+ errors.add(:base,"Searchable parameter should have search model, search method and parameter_type defined.") if self.search_model.blank? || self.search_method.blank? || self.parameter_type.blank?
71
+ end
72
+ end
73
+
74
+ def validate_export_attributes
75
+ unless self.export_model.blank? && self.export_method.blank?
76
+ errors.add(:base,"Exportable parameter should have export model and search method defined.") if self.export_model.blank? || self.export_method.blank?
77
+ end
78
+ end
79
+
80
+ def cascade_delete_parameter_values
81
+ return unless deleted?
82
+ parameter_values.each do |pv|
83
+ pv.deleted_at = Time.now
84
+ pv.save
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,60 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module ParameterCategory
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+
8
+ # Associations
9
+ base.send :belongs_to, :dictionary, :inverse_of => :parameter_categories
10
+ base.send :has_many, :parameters, :inverse_of => :parameter_category, :dependent => :destroy
11
+
12
+ # Validations
13
+ base.send :validates, :name, :presence => true, :uniqueness => { :scope => :dictionary_id, :message => "for parameter category has already been taken"}
14
+ base.send :validates, :dictionary, :presence => { :message => "for parameter category can't be blank" }
15
+
16
+ # Scopes
17
+ base.send(:scope, :searchable, -> {base.includes(:parameters).where('discerner_parameters.search_model is not null and discerner_parameters.search_method is not null and discerner_parameters.deleted_at is null')})
18
+ base.send(:scope, :exportable, -> {base.includes(:parameters).where('discerner_parameters.export_model is not null and discerner_parameters.export_method is not null and discerner_parameters.deleted_at is null')})
19
+ base.send(:scope, :ordered_by_name, -> {base.order('discerner_parameter_categories.name ASC')})
20
+
21
+ # Hooks
22
+ base.send :after_commit, :cascade_delete_parameters, :on => :update, :if => Proc.new { |record| record.previous_changes.include?('deleted_at') }
23
+
24
+ # Whitelisting attributes
25
+ base.send :attr_accessible, :deleted_at, :dictionary, :dictionary_id, :name
26
+ end
27
+
28
+ # Instance Methods
29
+ def initialize(*args)
30
+ super(*args)
31
+ end
32
+
33
+ def parameterized_name
34
+ name.parameterize.underscore
35
+ end
36
+
37
+ def searchable_parameters
38
+ parameters.searchable
39
+ end
40
+
41
+ def exportable_parameters
42
+ parameters.exportable
43
+ end
44
+
45
+ def css_class_name
46
+ "parameter_category_#{parameterized_name}"
47
+ end
48
+
49
+ private
50
+ def cascade_delete_parameters
51
+ return unless deleted?
52
+ parameters.each do |p|
53
+ p.deleted_at = Time.now
54
+ p.save
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module ParameterType
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+
8
+ # Associations
9
+ base.send :has_many, :parameters, :inverse_of => :parameter_type
10
+ base.send :has_and_belongs_to_many, :operators, :join_table => :discerner_operators_parameter_types
11
+
12
+ # Validations
13
+ base.send :validates, :name, :presence => true, :uniqueness => {:message => "for parameter type has already been taken"}
14
+ base.send :validate, :name_supported?
15
+
16
+ # Whitelisting attributes
17
+ base.send :attr_accessible, :deleted_at, :name
18
+ end
19
+
20
+ # Instance Methods
21
+ def initialize(*args)
22
+ super(*args)
23
+ end
24
+
25
+ def name_supported?
26
+ return if self.name.blank?
27
+ supported_types = ['numeric', 'date', 'list', 'combobox', 'text', 'string', 'search']
28
+ errors.add(:base,"Parameter type '#{self.name}' is not supported, please use one of the following types: #{supported_types.join(', ')}") unless supported_types.include?(self.name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,87 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module ParameterValue
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+
8
+ # Associations
9
+ base.send :belongs_to, :parameter, :inverse_of => :parameter_values
10
+ base.send :has_many, :search_parameter_values, :inverse_of => :parameter_value, :dependent => :destroy
11
+ base.send :has_one, :parameter_value_categorization, :inverse_of => :parameter_value, :dependent => :destroy
12
+ base.send :has_one, :parameter_value_category, :through=> :parameter_value_categorization
13
+
14
+ # Scopes
15
+ base.send(:scope, :ordered_by_name, -> { base.order('discerner_parameter_values.name ASC') })
16
+ base.send(:scope, :ordered_by_parameter_and_name, -> { base.order('discerner_parameter_values.parameter_id ASC, discerner_parameter_values.name ASC') })
17
+
18
+ #Validations
19
+ base.send :validates, :parameter, :presence => true
20
+ base.send :validates, :search_value, :length => { :maximum => 1000 }, :uniqueness => {:scope => :parameter_id, :message => "for parameter value has already been taken"}
21
+ base.send :validates, :name, :presence => true, :length => { :maximum => 1000 }
22
+ base.send :validate, :parameter_category_belongs_to_parameter
23
+
24
+ # Hooks
25
+ base.send :after_commit, :create_search_parameter_values, :on => :create
26
+ base.send :after_commit, :update_search_parameter_values, :on => :update, :if => Proc.new { |record| record.previous_changes.include?('deleted_at') }
27
+ base.send :scope, :categorized, -> {base.joins(:parameter_value_category)}
28
+ base.send :scope, :uncategorized, -> {base.includes(:parameter_value_category).where(:discerner_parameter_value_categories => {:name => nil})}
29
+
30
+ # Whitelisting attributes
31
+ base.send :attr_accessible, :search_value, :name, :parameter, :parameter_id
32
+ end
33
+
34
+ # Instance Methods
35
+ def initialize(*args)
36
+ super(*args)
37
+ end
38
+
39
+ def used_in_search?
40
+ if parameter.parameter_type.name == 'list'
41
+ search_parameter_values.chosen.any?
42
+ else
43
+ search_parameter_values.any?
44
+ end
45
+ end
46
+
47
+ def display_name
48
+ if parameter_value_category
49
+ "#{parameter_value_category.name} - #{name} "
50
+ else
51
+ name
52
+ end
53
+ end
54
+
55
+ private
56
+ def parameter_category_belongs_to_parameter
57
+ unless parameter_value_category.blank?
58
+ errors.add(:base,"Parameter category #{parameter_value_category.name} does not belong to parameter #{parameter.name}") unless parameter_value_category.parameter_id == parameter.id
59
+ end
60
+ end
61
+
62
+ def create_search_parameter_values
63
+ # create additional search_parameter_values for list search_parameters so they can be dislayed as nested attribures
64
+ return if parameter.blank? || parameter.parameter_type.blank?
65
+ if parameter.parameter_type.name == 'list'
66
+ parameter.search_parameters.each do |sp|
67
+ if sp.search_parameter_values.where(:parameter_value_id => id).blank?
68
+ max_display_order = sp.search_parameter_values.ordered_by_display_order.last.display_order || -1
69
+ sp.search_parameter_values.build(:parameter_value_id => id, :display_order => max_display_order + 1)
70
+ sp.save
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def update_search_parameter_values
77
+ create_search_parameter_values
78
+ # destroy search_parameter_values that reference this value but are not chosen (list options)
79
+ return unless deleted?
80
+ search_parameter_values.each do |spv|
81
+ spv.destroy if parameter.parameter_type.name == 'list' && !spv.chosen
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,21 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module ParameterValueCategorization
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+
8
+ # Associations
9
+ base.send :belongs_to, :parameter_value_category, :inverse_of => :parameter_value_categorizations
10
+ base.send :belongs_to, :parameter_value, :inverse_of => :parameter_value_categorization
11
+
12
+ # Validations
13
+ base.send :validates_presence_of, :parameter_value_category, :parameter_value
14
+
15
+ # Whitelisting attributes
16
+ base.send :attr_accessible, :parameter_value_category_id, :parameter_value_id, :parameter_value_category, :parameter_value
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,45 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module ParameterValueCategory
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+
8
+ # Associations
9
+ base.send :belongs_to, :parameter, :inverse_of => :parameter_value_categories
10
+ base.send :has_many, :parameter_value_categorizations, :dependent => :destroy
11
+ base.send :has_many, :parameter_values, :through => :parameter_value_categorizations
12
+
13
+ # Scopes
14
+ base.send(:scope, :ordered_by_name, -> { base.order('discerner_parameter_value_categories.name ASC') })
15
+ base.send(:scope, :ordered_by_display_order_and_name, -> { base.order('discerner_parameter_value_categories.display_order ASC, discerner_parameter_value_categories.name ASC') })
16
+
17
+ # Validations
18
+ base.send :validates_presence_of, :parameter, :unique_identifier, :name
19
+ base.send :validates, :unique_identifier, :uniqueness => {:scope => [:parameter_id, :deleted_at], :message => "for parameter value category has already been taken"}
20
+ base.send :validate, :parameter_value_belongs_to_parameter
21
+
22
+ # Whitelisting attributes
23
+ base.send :attr_accessible, :parameter, :parameter_id, :unique_identifier, :name
24
+ end
25
+
26
+ def css_class
27
+ "parameter_value_category_#{parameterized_name}"
28
+ end
29
+
30
+ def parameterized_name
31
+ name.parameterize.underscore
32
+ end
33
+
34
+ private
35
+ def parameter_value_belongs_to_parameter
36
+ unless parameter.blank?
37
+ parameter_values.each do |parameter_value|
38
+ errors.add(:base,"Parameter value #{parameter_value.name} does not belong to parameter #{parameter.name}") unless parameter_value.parameter_id == parameter.id
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,147 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module Search
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+ base.send :include, Warning
8
+
9
+ # Associations
10
+ base.send :belongs_to, :dictionary
11
+ base.send :has_many, :search_parameters, :inverse_of => :search
12
+ base.send :has_many, :search_combinations, :inverse_of => :search
13
+ base.send :has_many, :combined_searches, :through => :search_combinations
14
+ base.send :has_many, :export_parameters, :inverse_of => :search
15
+
16
+ # Validations
17
+ base.send :validates, :dictionary, :presence => { :message => "for search can't be blank" }
18
+ base.send :validate, :validate_search_parameters
19
+
20
+ # Scopes
21
+ base.send(:scope, :by_user, ->(username) { base.where(:username => username) unless username.blank?})
22
+
23
+ # Nested attributes
24
+ base.send :accepts_nested_attributes_for, :search_parameters, :allow_destroy => true,
25
+ :reject_if => proc { |attributes| attributes['parameter_id'].blank? && attributes['parameter'].blank? }
26
+
27
+ base.send :accepts_nested_attributes_for, :search_combinations, :allow_destroy => true,
28
+ :reject_if => proc { |attributes| attributes['combined_search_id'].blank? && attributes['combined_search'].blank? }
29
+
30
+ # Hooks
31
+ base.send :after_commit, :update_associations, :on => :update, :if => Proc.new { |record| record.previous_changes.include?('deleted_at') }
32
+
33
+ # Whitelisting attributes
34
+ base.send :attr_accessible, :deleted_at, :name, :username, :search_parameters, :search_parameters_attributes,
35
+ :dictionary, :dictionary_id, :search_combinations_attributes
36
+ end
37
+
38
+ # Instance Methods
39
+ def initialize(*args)
40
+ super(*args)
41
+ end
42
+
43
+ def display_name
44
+ name.blank? ? "[No name specified]" : name
45
+ end
46
+
47
+ def parameterized_name
48
+ display_name.parameterize.underscore
49
+ end
50
+
51
+ def traverse
52
+ return unless combined_searches.any?
53
+ searches = []
54
+ combined_searches.each do |s|
55
+ searches << s
56
+ nested_searches = s.traverse
57
+ searches << nested_searches unless nested_searches.blank?
58
+ end
59
+ searches
60
+ end
61
+
62
+ def nested_searches
63
+ nested_searches = traverse || []
64
+ nested_searches.flatten.compact
65
+ end
66
+
67
+ def parameter_categories
68
+ search_parameters.map{|p| p.parameter.parameter_category unless p.parameter.blank?}.uniq
69
+ end
70
+
71
+ def to_conditions
72
+ search_models = {}
73
+ all_search_parameters = nested_searches.map { |ns| ns.search_parameters }.flatten
74
+ all_search_parameters.concat(search_parameters).flatten!
75
+
76
+ all_search_parameters.each do |search_parameter|
77
+ search_models[search_parameter.parameter.search_model] = { :search_parameters => [], :conditions => nil } unless search_models.has_key?(search_parameter.parameter.search_model)
78
+ search_models[search_parameter.parameter.search_model][:search_parameters] << search_parameter
79
+ end
80
+
81
+ search_models.each do |k,v|
82
+ predicates = []
83
+ arguments = []
84
+
85
+ v[:search_parameters].each do |search_parameter|
86
+ sql = search_parameter.to_sql unless search_parameter.search_parameter_values.empty?
87
+ unless sql.nil?
88
+ predicates << sql[:predicates]
89
+ arguments << sql[:values] unless sql[:values].blank?
90
+ end
91
+ end
92
+
93
+ search_models[k][:conditions] = [predicates.join(' and '), *flatten_arguments(arguments)]
94
+ end
95
+ search_models
96
+ end
97
+
98
+ def flatten_arguments(arguments)
99
+ args = []
100
+ arguments.each do |a|
101
+ a.each do |b|
102
+ args << b
103
+ end
104
+ end
105
+ args
106
+ end
107
+
108
+ def disabled?
109
+ return false unless persisted?
110
+ if export_parameters.select{|ep| ep.disabled?}.any?
111
+ warnings.add(:base, "Search uses deleted export parameters")
112
+ return true
113
+ end
114
+ deleted? ||
115
+ dictionary.deleted? ||
116
+ search_parameters.select{|sp| sp.disabled?}.any? ||
117
+ search_combinations.select{|sc| sc.disabled?}.any?
118
+ end
119
+
120
+ def searched_model?(model_name)
121
+ conditions.has_key?(model_name)
122
+ end
123
+
124
+ def conditions(force=false)
125
+ @conditions = self.to_conditions if @conditions.blank? || force
126
+ @conditions
127
+ end
128
+
129
+ private
130
+ def validate_search_parameters
131
+ if self.search_parameters.size < 1 || self.search_parameters.all?{|search_parameter| search_parameter.marked_for_destruction? }
132
+ errors.add(:base,"Search should have at least one search criteria.")
133
+ end
134
+ end
135
+
136
+ def update_associations
137
+ [search_parameters, export_parameters, search_combinations].each do |accociated_records|
138
+ accociated_records.each do |r|
139
+ r.deleted_at = Time.now
140
+ r.save
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,50 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module SearchCombination
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+ base.send :include, Warning
8
+
9
+ # Associations
10
+ base.send :belongs_to, :operator, :inverse_of => :search_combinations
11
+ base.send :belongs_to, :search, :inverse_of => :search_combinations, :foreign_key => :search_id
12
+ base.send :belongs_to, :combined_search, :foreign_key => :combined_search_id, :class_name => 'Discerner::Search'
13
+
14
+ # Scopes
15
+ base.send(:scope, :ordered_by_display_order, -> { base.order('discerner_search_combinations.display_order ASC') })
16
+
17
+ # Validations
18
+ base.send :validates_presence_of, :search, :combined_search
19
+ base.send :validate, :validate_searches
20
+
21
+ # Whitelisting attributes
22
+ base.send :attr_accessible, :combined_search_id, :display_order, :operator_id, :search_id, :search, :combined_search
23
+ end
24
+
25
+ # Instance Methods
26
+ def initialize(*args)
27
+ super(*args)
28
+ end
29
+
30
+ def validate_searches
31
+ return if self.search_id.blank? || self.combined_search_id.blank?
32
+ errors.add(:base,"Search cannot be combined with itself.") if self.search_id == self.combined_search_id
33
+ end
34
+
35
+ def disabled?
36
+ return false unless persisted?
37
+ return true if deleted?
38
+ if combined_search.deleted?
39
+ warnings.add(:base, "Combined search has been deleted and has to be removed from the search")
40
+ return true
41
+ elsif combined_search.disabled?
42
+ warnings.add(:base, "Combined search has been disabled and has to be removed from the search")
43
+ return true
44
+ end
45
+ return false
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end