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,128 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module SearchParameter
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+ base.send :include, Warning
8
+
9
+ # Associations
10
+ base.send :belongs_to, :search, :inverse_of => :search_parameters
11
+ base.send :belongs_to, :parameter, :inverse_of => :search_parameters
12
+ base.send :has_many, :search_parameter_values, :dependent => :destroy, :inverse_of => :search_parameter
13
+
14
+ # Scopes
15
+ base.send(:scope, :by_parameter_category, ->(parameter_category) { base.includes(:parameter).where('discerner_parameters.parameter_category_id' => parameter_category.id) unless parameter_category.blank?})
16
+ base.send(:scope, :ordered_by_display_order, -> { base.order('discerner_search_parameters.display_order ASC') })
17
+ base.send(:scope, :ordered, -> { base.order('discerner_search_parameters.id ASC') })
18
+
19
+ #Validations
20
+ base.send :validates_presence_of, :search, :parameter
21
+
22
+ # Nested attributes
23
+ base.send :accepts_nested_attributes_for, :search_parameter_values, :allow_destroy => true
24
+
25
+ # Hooks
26
+ base.send :after_commit, :update_associations, :on => :update, :if => Proc.new { |record| record.previous_changes.include?('deleted_at') }
27
+
28
+ # Whitelisting attributes
29
+ base.send :attr_accessible, :search, :search_id, :parameter, :parameter_id, :search_parameter_values_attributes, :display_order
30
+ end
31
+
32
+ # Instance Methods
33
+ def initialize(*args)
34
+ super(*args)
35
+ end
36
+
37
+ def check_search_parameters
38
+ if self.search_parameters.size < 1 || self.search_parameters.all?{|search_parameter| search_parameter.marked_for_destruction? }
39
+ errors.add(:base,"Search should have at least one search criteria.")
40
+ end
41
+ end
42
+
43
+ def parameterized_name
44
+ name.blank? ? 'no_name_specified' : name.parameterize.underscore
45
+ end
46
+
47
+ def prepare_sql
48
+ sql = {}
49
+ parameter_type = parameter.parameter_type.name
50
+ case parameter_type
51
+ when 'list'
52
+ values = [search_parameter_values.chosen.map { |spv| spv.parameter_value.search_value }]
53
+ predicate = "#{parameter.search_method} in (?)"
54
+ when 'combobox'
55
+ values = [search_parameter_values.map { |spv| spv.parameter_value.search_value unless spv.parameter_value.nil? }.compact]
56
+ predicate = "#{parameter.search_method} in (?)" unless values.blank?
57
+ else # 'numeric','date', 'text', 'string
58
+ spvs = []
59
+ values = []
60
+ search_parameter_values.map {|spv| spvs << spv.to_sql}
61
+
62
+ predicates = spvs.map { |s| s[:predicates] }.join(' or ')
63
+ predicate = "(#{predicates})"
64
+
65
+ spvs.each do |spv|
66
+ if spv[:values].is_a?(Array)
67
+ spv[:values].each do |v|
68
+ values << v
69
+ end
70
+ else
71
+ values << spv[:values] unless spv[:values].blank?
72
+ end
73
+ end
74
+ end
75
+ sql[:predicates] = predicate
76
+ sql[:values] = values
77
+ sql
78
+ end
79
+
80
+ def search_model_class
81
+ return if parameter.search_model.blank? || parameter.search_method.blank?
82
+ search_model_class = parameter.search_model.safe_constantize
83
+ raise "Search model #{parameter.search_model} could not be found" if search_model_class.blank?
84
+ search_model_class
85
+ end
86
+
87
+ def search_model_attribute_method?
88
+ search_model_class && search_model_class.attribute_method?(parameter.search_method)
89
+ end
90
+
91
+ def to_sql
92
+ sql = prepare_sql
93
+ if search_model_class && !search_model_attribute_method?
94
+ raise "Search model #{parameter.search_model} does not respond to search method #{parameter.search_method}" unless search_model_class.respond_to?(parameter.search_method)
95
+ sql = search_model_class.send(parameter.search_method, sql)
96
+ end
97
+ sql
98
+ end
99
+
100
+ def disabled?
101
+ return false unless persisted?
102
+ if parameter.blank?
103
+ warnings.add(:base, "Parameter has to be selected")
104
+ return true
105
+ elsif parameter.deleted?
106
+ warnings.add(:base, "Parameter has been deleted and has to be removed from the search")
107
+ return true
108
+ elsif search_parameter_values.blank? || (parameter.parameter_type.name == 'list' && search_parameter_values.select{|spv| spv.chosen?}.empty?)
109
+ warnings.add(:base, "Parameter value has to be selected")
110
+ return true
111
+ elsif deleted? || search_parameter_values.select{ |spv| spv.disabled?}.any?
112
+ return true
113
+ end
114
+ end
115
+
116
+
117
+
118
+ private
119
+ def update_associations
120
+ search_parameter_values.each do |r|
121
+ r.deleted_at = Time.now
122
+ r.save
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,130 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module SearchParameterValue
5
+ def self.included(base)
6
+ base.send :include, SoftDelete
7
+ base.send :include, Warning
8
+
9
+ # Associations
10
+ base.send :belongs_to, :search_parameter, :inverse_of => :search_parameter_values
11
+ base.send :belongs_to, :parameter_value, :inverse_of => :search_parameter_values
12
+ base.send :belongs_to, :operator, :inverse_of => :search_parameter_values
13
+
14
+ # Scopes
15
+ base.send(:scope, :chosen, -> { base.where(:chosen => true) })
16
+ base.send(:scope, :ordered_by_display_order, -> { base.order('discerner_search_parameter_values.display_order ASC') })
17
+
18
+ #Validations
19
+ base.send :validate, :validate_operator
20
+ base.send :validates, :search_parameter, :presence => true
21
+
22
+ # Hooks
23
+ base.send :before_validation, :cleanup_parameter_values
24
+ base.send :after_commit, :destroy_if_deleted_parameter_value, :on => :update
25
+
26
+ # Whitelisting attributes
27
+ base.send :attr_accessible, :additional_value, :chosen, :display_order, :operator_id, :parameter_value_id, :search_parameter_id, :value, :parameter_value, :operator, :search_parameter
28
+ end
29
+
30
+ # Instance Methods
31
+ def initialize(*args)
32
+ super(*args)
33
+ end
34
+
35
+ def to_sql
36
+ sql = {}
37
+ raise "Search operator has to be defined in order to run 'to_sql' method on search_parameter_value" if operator.blank?
38
+ case operator.operator_type
39
+ when 'comparison'
40
+ sql[:predicates] = "#{search_parameter.parameter.search_method} #{operator.symbol} ?"
41
+ sql[:values] = formatted_values.first
42
+ when 'range'
43
+ sql[:predicates] = "#{search_parameter.parameter.search_method} #{operator.symbol} ? and ?"
44
+ sql[:values] = formatted_values
45
+ when 'text_comparison'
46
+ sql[:predicates] = "#{search_parameter.parameter.search_method} #{operator.symbol} ?"
47
+ sql[:values] = "%#{formatted_values.first}%"
48
+ when 'presence'
49
+ sql[:predicates] = "#{search_parameter.parameter.search_method} #{operator.symbol}"
50
+ end
51
+ sql
52
+ end
53
+
54
+ def formatted_values
55
+ all_values = [value, additional_value].compact
56
+ case search_parameter.parameter.parameter_type.name
57
+ when 'date'
58
+ all_values.map{|v| v.to_date}
59
+ when 'numeric'
60
+ all_values.map{|v| v.to_f}
61
+ else
62
+ all_values
63
+ end
64
+ end
65
+
66
+ def disabled?
67
+ return false unless persisted?
68
+ if parameter_value.blank? && value.blank? && operator && operator.operator_type != 'presence'
69
+ warnings.add(:base, "Parameter value has to be selected")
70
+ return true
71
+ end
72
+ if chosen? && parameter_value.blank? || parameter_value && parameter_value.deleted?
73
+ warnings.add(:base, "Parameter value has been deleted and has to be removed from the search")
74
+ return true
75
+ end
76
+ if search_parameter && search_parameter.parameter && search_parameter.parameter.parameter_type.name == 'date' && !validate_dates_format
77
+ warnings.add(:base, "Provided date is not valid")
78
+ return true
79
+ end
80
+ if parameter_value.blank? && search_parameter.parameter.parameter_type.name == 'combobox'
81
+ warnings.add(:base, "Parameter value has to be selected")
82
+ return true
83
+ end
84
+ warnings.clear
85
+ return false
86
+ end
87
+
88
+ def validate_dates_format
89
+ validate_date(value) && validate_date(additional_value)
90
+ end
91
+
92
+ private
93
+ def destroy_if_deleted_parameter_value
94
+ return if parameter_value.blank? || search_parameter.blank? || search_parameter.parameter.blank? || search_parameter.parameter.parameter_type.blank?
95
+ #return unless ['list', 'combobox'].include?(search_parameter.parameter.parameter_type.name)
96
+ destroy if parameter_value.deleted? && search_parameter.parameter.parameter_type.name == 'list' && !chosen?
97
+ end
98
+
99
+ def validate_date(date)
100
+ begin
101
+ unless date.blank?
102
+ parsed_date = date.to_date
103
+ #http://www.karaszi.com/sqlserver/info_datetime.asp#Why1753
104
+ return false if parsed_date.year < 1753
105
+ end
106
+ rescue => e
107
+ return false
108
+ end
109
+ return true
110
+ end
111
+
112
+ def cleanup_parameter_values
113
+ if operator && operator.operator_type == 'presence'
114
+ self.value = nil
115
+ self.additional_value = nil
116
+ self.parameter_value = nil
117
+ end
118
+ end
119
+
120
+ def validate_operator
121
+ if search_parameter && search_parameter.parameter && search_parameter.parameter.parameter_type && ['list', 'combobox'].include?(search_parameter.parameter.parameter_type.name)
122
+ self.operator = nil
123
+ else
124
+ errors.add(:base, "Operator has to be selected for parameter values that do not belong to list or combobox") if operator.blank?
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,35 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module SoftDelete
5
+ def self.included(base)
6
+ attr_accessor :soft_delete
7
+ base.send(:scope, :not_deleted, -> { base.where(:deleted_at => nil) })
8
+ end
9
+
10
+ def process_soft_delete
11
+ self.deleted_at = Time.zone.now
12
+ end
13
+
14
+ def soft_delete=(removed)
15
+ if (removed.is_a?(TrueClass) || removed.to_s == 't' || removed.to_s == '1')
16
+ process_soft_delete
17
+ end
18
+ end
19
+
20
+ def soft_delete
21
+ !self.deleted_at.blank?
22
+ end
23
+
24
+ def soft_delete!
25
+ process_soft_delete
26
+ save!
27
+ end
28
+
29
+ def deleted?
30
+ !deleted_at.blank?
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ module Discerner
2
+ module Methods
3
+ module Models
4
+ module Warning
5
+ def self.included(base)
6
+ attr_accessor :force_save
7
+ end
8
+
9
+ def warnings
10
+ @warnings ||= ActiveModel::Errors.new(self)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end