discerner 1.1.18

Sign up to get free protection for your applications and to get access to all the features.
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