searchgasm 0.9.6 → 0.9.7

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 (66) hide show
  1. data/CHANGELOG +2 -0
  2. data/Manifest +34 -21
  3. data/{README.mdown → README.rdoc} +96 -63
  4. data/Rakefile +1 -1
  5. data/examples/README.rdoc +4 -0
  6. data/lib/searchgasm/active_record/associations.rb +40 -42
  7. data/lib/searchgasm/active_record/base.rb +75 -61
  8. data/lib/searchgasm/condition/base.rb +127 -0
  9. data/lib/searchgasm/condition/begins_with.rb +20 -0
  10. data/lib/searchgasm/condition/child_of.rb +11 -0
  11. data/lib/searchgasm/condition/contains.rb +20 -0
  12. data/lib/searchgasm/condition/descendant_of.rb +24 -0
  13. data/lib/searchgasm/condition/does_not_equal.rb +28 -0
  14. data/lib/searchgasm/condition/ends_with.rb +20 -0
  15. data/lib/searchgasm/condition/equals.rb +20 -0
  16. data/lib/searchgasm/condition/greater_than.rb +25 -0
  17. data/lib/searchgasm/condition/greater_than_or_equal_to.rb +20 -0
  18. data/lib/searchgasm/condition/inclusive_descendant_of.rb +13 -0
  19. data/lib/searchgasm/condition/keywords.rb +33 -0
  20. data/lib/searchgasm/condition/less_than.rb +25 -0
  21. data/lib/searchgasm/condition/less_than_or_equal_to.rb +20 -0
  22. data/lib/searchgasm/condition/sibling_of.rb +16 -0
  23. data/lib/searchgasm/condition/tree.rb +16 -0
  24. data/lib/searchgasm/conditions/base.rb +221 -0
  25. data/lib/searchgasm/conditions/protection.rb +30 -0
  26. data/lib/searchgasm/config.rb +137 -0
  27. data/lib/searchgasm/helpers/form_helper.rb +159 -0
  28. data/lib/searchgasm/helpers/search_helper.rb +178 -0
  29. data/lib/searchgasm/helpers/utilities_helper.rb +125 -0
  30. data/lib/searchgasm/search/base.rb +73 -179
  31. data/lib/searchgasm/search/conditions.rb +42 -166
  32. data/lib/searchgasm/search/ordering.rb +149 -0
  33. data/lib/searchgasm/search/pagination.rb +69 -0
  34. data/lib/searchgasm/search/protection.rb +61 -0
  35. data/lib/searchgasm/utilities.rb +30 -0
  36. data/lib/searchgasm/version.rb +44 -47
  37. data/lib/searchgasm.rb +57 -21
  38. data/searchgasm.gemspec +71 -46
  39. data/test/test_active_record_associations.rb +1 -1
  40. data/test/test_active_record_base.rb +4 -4
  41. data/test/test_condition.rb +143 -0
  42. data/test/{test_searchgasm_conditions.rb → test_conditions_base.rb} +43 -33
  43. data/test/test_search_base.rb +189 -0
  44. data/test/test_search_ordering.rb +91 -0
  45. data/test/test_search_pagination.rb +56 -0
  46. data/test/test_search_protection.rb +35 -0
  47. metadata +70 -45
  48. data/lib/searchgasm/search/condition.rb +0 -105
  49. data/lib/searchgasm/search/condition_types/begins_with_condition.rb +0 -26
  50. data/lib/searchgasm/search/condition_types/child_of_condition.rb +0 -17
  51. data/lib/searchgasm/search/condition_types/contains_condition.rb +0 -26
  52. data/lib/searchgasm/search/condition_types/descendant_of_condition.rb +0 -30
  53. data/lib/searchgasm/search/condition_types/does_not_equal_condition.rb +0 -34
  54. data/lib/searchgasm/search/condition_types/ends_with_condition.rb +0 -26
  55. data/lib/searchgasm/search/condition_types/equals_condition.rb +0 -26
  56. data/lib/searchgasm/search/condition_types/greater_than_condition.rb +0 -31
  57. data/lib/searchgasm/search/condition_types/greater_than_or_equal_to_condition.rb +0 -26
  58. data/lib/searchgasm/search/condition_types/inclusive_descendant_of_condition.rb +0 -19
  59. data/lib/searchgasm/search/condition_types/keywords_condition.rb +0 -39
  60. data/lib/searchgasm/search/condition_types/less_than_condition.rb +0 -31
  61. data/lib/searchgasm/search/condition_types/less_than_or_equal_to_condition.rb +0 -26
  62. data/lib/searchgasm/search/condition_types/sibling_of_condition.rb +0 -22
  63. data/lib/searchgasm/search/condition_types/tree_condition.rb +0 -20
  64. data/lib/searchgasm/search/utilities.rb +0 -34
  65. data/test/test_searchgasm_base.rb +0 -185
  66. data/test/test_searchgasm_condition_types.rb +0 -143
@@ -1,26 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class ContainsCondition < Condition
6
- class << self
7
- def name_for_column(column)
8
- return unless string_column?(column)
9
- super
10
- end
11
-
12
- def aliases_for_column(column)
13
- ["#{column.name}_like", "#{column.name}_has"]
14
- end
15
- end
16
-
17
- def to_conditions(value)
18
- ["#{quoted_table_name}.#{quoted_column_name} LIKE ?", "%#{value}%"]
19
- end
20
- end
21
- end
22
-
23
- Conditions.register_condition(ConditionTypes::ContainsCondition)
24
- end
25
- end
26
- end
@@ -1,30 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class DescendantOfCondition < TreeCondition
6
- def to_conditions(value)
7
- # Wish I knew how to do this in SQL
8
- root = (value.is_a?(klass) ? value : klass.find(value)) rescue return
9
- strs = []
10
- subs = []
11
- all_children_ids(root).each do |child_id|
12
- strs << "#{quoted_table_name}.#{quote_column_name(klass.primary_key)} = ?"
13
- subs << child_id
14
- end
15
- [strs.join(" OR "), *subs]
16
- end
17
-
18
- private
19
- def all_children_ids(record)
20
- ids = record.children.collect { |child| child.send(klass.primary_key) }
21
- record.children.each { |child| ids += all_children_ids(child) }
22
- ids
23
- end
24
- end
25
- end
26
-
27
- Conditions.register_condition(ConditionTypes::DescendantOfCondition)
28
- end
29
- end
30
- end
@@ -1,34 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class DoesNotEqualCondition < Condition
6
- class << self
7
- def aliases_for_column(column)
8
- ["#{column.name}_is_not", "#{column.name}_not"]
9
- end
10
- end
11
-
12
- def ignore_blanks?
13
- false
14
- end
15
-
16
- def to_conditions(value)
17
- # Delegate to equals and then change
18
- condition = EqualsCondition.new(klass, column)
19
- condition.value = value
20
-
21
- sql = condition.sanitize
22
- sql.gsub!(/ IS /, " IS NOT ")
23
- sql.gsub!(/ BETWEEN /, " NOT BETWEEN ")
24
- sql.gsub!(/ IN /, " NOT IN ")
25
- sql.gsub!(/=/, "!=")
26
- sql
27
- end
28
- end
29
- end
30
-
31
- Conditions.register_condition(ConditionTypes::DoesNotEqualCondition)
32
- end
33
- end
34
- end
@@ -1,26 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class EndsWithCondition < Condition
6
- class << self
7
- def name_for_column(column)
8
- return unless string_column?(column)
9
- super
10
- end
11
-
12
- def aliases_for_column(column)
13
- ["#{column.name}_ew", "#{column.name}_ends", "#{column.name}_end"]
14
- end
15
- end
16
-
17
- def to_conditions(value)
18
- ["#{quoted_table_name}.#{quoted_column_name} LIKE ?", "%#{value}"]
19
- end
20
- end
21
- end
22
-
23
- Conditions.register_condition(ConditionTypes::EndsWithCondition)
24
- end
25
- end
26
- end
@@ -1,26 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class EqualsCondition < Condition
6
- class << self
7
- def aliases_for_column(column)
8
- ["#{column.name}", "#{column.name}_is"]
9
- end
10
- end
11
-
12
- def ignore_blanks?
13
- false
14
- end
15
-
16
- def to_conditions(value)
17
- # Let ActiveRecord handle this
18
- klass.send(:sanitize_sql_hash_for_conditions, {column.name => value})
19
- end
20
- end
21
- end
22
-
23
- Conditions.register_condition(ConditionTypes::EqualsCondition)
24
- end
25
- end
26
- end
@@ -1,31 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class GreaterThanCondition < Condition
6
- class << self
7
- def name_for_column(column)
8
- return unless comparable_column?(column)
9
- super
10
- end
11
-
12
- def aliases_for_column(column)
13
- column_names = [column.name]
14
- column_names << column.name.gsub(/_at$/, "") if [:datetime, :timestamp, :time, :date].include?(column.type) && column.name =~ /_at$/
15
-
16
- aliases = []
17
- column_names.each { |column_name| aliases += ["#{column_name}_gt", "#{column_name}_after"] }
18
- aliases
19
- end
20
- end
21
-
22
- def to_conditions(value)
23
- ["#{quoted_table_name}.#{quoted_column_name} > ?", value]
24
- end
25
- end
26
- end
27
-
28
- Conditions.register_condition(ConditionTypes::GreaterThanCondition)
29
- end
30
- end
31
- end
@@ -1,26 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class GreaterThanOrEqualToCondition < Condition
6
- class << self
7
- def name_for_column(column)
8
- return unless comparable_column?(column)
9
- super
10
- end
11
-
12
- def aliases_for_column(column)
13
- ["#{column.name}_gte", "#{column.name}_at_least"]
14
- end
15
- end
16
-
17
- def to_conditions(value)
18
- ["#{quoted_table_name}.#{quoted_column_name} >= ?", value]
19
- end
20
- end
21
- end
22
-
23
- Conditions.register_condition(ConditionTypes::GreaterThanOrEqualToCondition)
24
- end
25
- end
26
- end
@@ -1,19 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class InclusiveDescendantOfCondition < TreeCondition
6
- include Search::Utilities
7
-
8
- def to_conditions(value)
9
- condition = DescendantOfCondition.new(klass, column)
10
- condition.value = value
11
- merge_conditions(["#{quoted_table_name}.#{quote_column_name(klass.primary_key)} = ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)], condition.sanitize, :any => true)
12
- end
13
- end
14
- end
15
-
16
- Conditions.register_condition(ConditionTypes::InclusiveDescendantOfCondition)
17
- end
18
- end
19
- end
@@ -1,39 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class KeywordsCondition < Condition
6
- BLACKLISTED_WORDS = ('a'..'z').to_a + ["about", "an", "are", "as", "at", "be", "by", "com", "de", "en", "for", "from", "how", "in", "is", "it", "la", "of", "on", "or", "that", "the", "the", "this", "to", "und", "was", "what", "when", "where", "who", "will", "with", "www"] # from ranks.nl
7
-
8
- class << self
9
- def name_for_column(column)
10
- return unless string_column?(column)
11
- super
12
- end
13
-
14
- def aliases_for_column(column)
15
- ["#{column.name}_kwords", "#{column.name}_kw"]
16
- end
17
- end
18
-
19
- def to_conditions(value)
20
- strs = []
21
- subs = []
22
-
23
- search_parts = value.gsub(/,/, " ").split(/ /).collect { |word| word.downcase.gsub(/[^[:alnum:]]/, ''); }.uniq.select { |word| !BLACKLISTED_WORDS.include?(word.downcase) && !word.blank? }
24
- return if search_parts.blank?
25
-
26
- search_parts.each do |search_part|
27
- strs << "#{quoted_table_name}.#{quoted_column_name} LIKE ?"
28
- subs << "%#{search_part}%"
29
- end
30
-
31
- [strs.join(" AND "), *subs]
32
- end
33
- end
34
- end
35
-
36
- Conditions.register_condition(ConditionTypes::KeywordsCondition)
37
- end
38
- end
39
- end
@@ -1,31 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class LessThanCondition < Condition
6
- class << self
7
- def name_for_column(column)
8
- return unless comparable_column?(column)
9
- super
10
- end
11
-
12
- def aliases_for_column(column)
13
- column_names = [column.name]
14
- column_names << column.name.gsub(/_at$/, "") if [:datetime, :timestamp, :time, :date].include?(column.type) && column.name =~ /_at$/
15
-
16
- aliases = []
17
- column_names.each { |column_name| aliases += ["#{column_name}_lt", "#{column_name}_before"] }
18
- aliases
19
- end
20
- end
21
-
22
- def to_conditions(value)
23
- ["#{quoted_table_name}.#{quoted_column_name} < ?", value]
24
- end
25
- end
26
- end
27
-
28
- Conditions.register_condition(ConditionTypes::LessThanCondition)
29
- end
30
- end
31
- end
@@ -1,26 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class LessThanOrEqualToCondition < Condition
6
- class << self
7
- def name_for_column(column)
8
- return unless comparable_column?(column)
9
- super
10
- end
11
-
12
- def aliases_for_column(column)
13
- ["#{column.name}_lte", "#{column.name}_at_most"]
14
- end
15
- end
16
-
17
- def to_conditions(value)
18
- ["#{quoted_table_name}.#{quoted_column_name} <= ?", value]
19
- end
20
- end
21
- end
22
-
23
- Conditions.register_condition(ConditionTypes::LessThanOrEqualToCondition)
24
- end
25
- end
26
- end
@@ -1,22 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class SiblingOfCondition < TreeCondition
6
- include Search::Utilities
7
-
8
- def to_conditions(value)
9
- parent_association = klass.reflect_on_association(:parent)
10
- foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
11
- parent_id = (value.is_a?(klass) ? value : klass.find(value)).send(foreign_key_name)
12
- condition = ChildOfCondition.new(klass, column)
13
- condition.value = parent_id
14
- merge_conditions(["#{quoted_table_name}.#{quote_column_name(klass.primary_key)} != ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)], condition.sanitize)
15
- end
16
- end
17
- end
18
-
19
- Conditions.register_condition(ConditionTypes::SiblingOfCondition)
20
- end
21
- end
22
- end
@@ -1,20 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module ConditionTypes
5
- class TreeCondition < Condition
6
- class << self
7
- def name_for_column(column)
8
- nil
9
- end
10
-
11
- def name_for_klass(klass)
12
- return unless klass.reflect_on_association(:parent) && klass.reflect_on_association(:children)
13
- condition_name
14
- end
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,34 +0,0 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- module Utilities
5
- private
6
- def merge_conditions(*conditions)
7
- options = conditions.extract_options!
8
- conditions.delete_if { |condition| condition.blank? }
9
- return if conditions.blank?
10
- return conditions.first if conditions.size == 1
11
-
12
- conditions_strs = []
13
- conditions_subs = []
14
-
15
- conditions.each do |condition|
16
- next if condition.blank?
17
- arr_condition = [condition].flatten
18
- conditions_strs << arr_condition.first
19
- conditions_subs += arr_condition[1..-1]
20
- end
21
-
22
- return if conditions_strs.blank?
23
-
24
- join = options[:any] ? "OR" : "AND"
25
- conditions_str = "(#{conditions_strs.join(") #{join} (")})"
26
-
27
- return conditions_str if conditions_subs.blank?
28
-
29
- [conditions_str, *conditions_subs]
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,185 +0,0 @@
1
- require File.dirname(__FILE__) + '/test_helper.rb'
2
-
3
- class TestSearchgasmBase < Test::Unit::TestCase
4
- fixtures :accounts, :users, :orders
5
-
6
- def setup
7
- setup_db
8
- load_fixtures
9
- end
10
-
11
- def teardown
12
- teardown_db
13
- end
14
-
15
- def test_needed
16
- assert BinaryLogic::Searchgasm::Search::Base.needed?(Account, :page => 2, :conditions => {:name => "Ben"})
17
- assert !BinaryLogic::Searchgasm::Search::Base.needed?(Account, :conditions => {:name => "Ben"})
18
- assert BinaryLogic::Searchgasm::Search::Base.needed?(Account, :limit => 2, :conditions => {:name_contains => "Ben"})
19
- assert !BinaryLogic::Searchgasm::Search::Base.needed?(Account, :limit => 2)
20
- assert BinaryLogic::Searchgasm::Search::Base.needed?(Account, :per_page => 2)
21
- end
22
-
23
- def test_initialize
24
- assert_nothing_raised { BinaryLogic::Searchgasm::Search::Base.new(Account) }
25
- search = BinaryLogic::Searchgasm::Search::Base.new(Account, :conditions => {:name_like => "binary"}, :page => 2, :limit => 10, :readonly => true)
26
- assert_equal Account, search.klass
27
- assert_equal "binary", search.conditions.name_like
28
- assert_equal 2, search.page
29
- assert_equal 10, search.limit
30
- assert_equal true, search.readonly
31
- end
32
-
33
- def test_setting_first_level_options
34
- search = BinaryLogic::Searchgasm::Search::Base.new(Account)
35
-
36
- search.include = :users
37
- assert_equal :users, search.include
38
-
39
- search.joins = "test"
40
- assert_equal "test", search.joins
41
-
42
- search.page = 5
43
- assert_equal 1, search.page # haven't set a limit yet
44
- assert_equal nil, search.offset
45
-
46
- search.limit = 20
47
- assert_equal search.limit, 20
48
- assert_equal search.per_page, 20
49
- assert_equal search.page, 5
50
- assert_equal search.offset, 100
51
-
52
- search.offset = 50
53
- assert_equal search.offset, 50
54
- assert_equal search.page, 3
55
-
56
- search.per_page = 2
57
- assert_equal search.per_page, 2
58
- assert_equal search.limit, 2
59
- assert_equal search.page, 25
60
- assert_equal search.offset, 50
61
-
62
- search.order = "name ASC"
63
- assert_equal search.order, "name ASC"
64
-
65
- search.select = "name"
66
- assert_equal search.select, "name"
67
-
68
- search.readonly = true
69
- assert_equal search.readonly, true
70
-
71
- search.group = "name"
72
- assert_equal search.group, "name"
73
-
74
- search.from = "accounts"
75
- assert_equal search.from, "accounts"
76
-
77
- search.lock = true
78
- assert_equal search.lock, true
79
- end
80
-
81
- def test_conditions
82
- search = BinaryLogic::Searchgasm::Search::Base.new(Account)
83
- assert_kind_of BinaryLogic::Searchgasm::Search::Conditions, search.conditions
84
- assert_equal search.conditions.klass, Account
85
-
86
- search.conditions = {:name_like => "Binary"}
87
- assert_kind_of BinaryLogic::Searchgasm::Search::Conditions, search.conditions
88
-
89
- conditions = BinaryLogic::Searchgasm::Search::Conditions.new(Account, :id_greater_than => 8)
90
- search.conditions = conditions
91
- assert_equal conditions, search.conditions
92
- end
93
-
94
- def test_include
95
-
96
- end
97
-
98
- def test_limit
99
-
100
- end
101
-
102
- def test_options
103
-
104
- end
105
-
106
- def test_order_as
107
-
108
- end
109
-
110
- def test_order_by
111
-
112
- end
113
-
114
- def test_page
115
-
116
- end
117
-
118
- def test_sanitize
119
- search = BinaryLogic::Searchgasm::Search::Base.new(Account)
120
- search.per_page = 2
121
- search.conditions.name_like = "Binary"
122
- search.conditions.users.id_greater_than = 2
123
- search.page = 3
124
- search.readonly = true
125
- assert_equal search.sanitize, {:include => :users, :offset => 6, :readonly => true, :conditions => ["(\"accounts\".\"name\" LIKE ?) AND (\"users\".\"id\" > ?)", "%Binary%", 2], :limit => 2 }
126
- end
127
-
128
- def test_scope
129
-
130
- end
131
-
132
- def test_searching
133
- search = BinaryLogic::Searchgasm::Search::Base.new(Account)
134
- search.conditions.name_like = "Binary"
135
- assert_equal search.all, [Account.find(1), Account.find(3)]
136
- assert_equal search.find(:all), [Account.find(1), Account.find(3)]
137
- assert_equal search.first, Account.find(1)
138
- assert_equal search.find(:first), Account.find(1)
139
-
140
- search.per_page = 20
141
- search.page = 2
142
-
143
- assert_equal search.all, []
144
- assert_equal search.find(:all), []
145
- assert_equal search.first, nil
146
- assert_equal search.find(:first), nil
147
-
148
- search.per_page = 0
149
- search.page = nil
150
- search.conditions.users.first_name_contains = "Ben"
151
- search.conditions.users.orders.description_keywords = "products, &*ap#ple $%^&*"
152
- assert_equal search.all, [Account.find(1)]
153
- assert_equal search.find(:all), [Account.find(1)]
154
- assert_equal search.first, Account.find(1)
155
- assert_equal search.find(:first), Account.find(1)
156
- end
157
-
158
- def test_calculations
159
- search = BinaryLogic::Searchgasm::Search::Base.new(Account)
160
- search.conditions.name_like = "Binary"
161
- assert_equal 2, search.average('id')
162
- assert_equal 2, search.calculate(:avg, 'id')
163
- assert_equal 3, search.calculate(:max, 'id')
164
- assert_equal 2, search.count
165
- assert_equal 3, search.maximum('id')
166
- assert_equal 1, search.minimum('id')
167
- assert_equal 4, search.sum('id')
168
- end
169
-
170
- def test_protection
171
- assert_raise(ArgumentError) { Account.build_search(:conditions => "(DELETE FROM users)", :page => 2, :per_page => 15) }
172
- BinaryLogic::Searchgasm::Search::Base::VULNERABLE_OPTIONS.each { |option| assert_raise(ArgumentError) { Account.build_search(option => "(DELETE FROM users)") } }
173
-
174
- assert_nothing_raised { Account.build_search!(:conditions => "(DELETE FROM users)", :page => 2, :per_page => 15) }
175
- BinaryLogic::Searchgasm::Search::Base::VULNERABLE_OPTIONS.each { |option| assert_nothing_raised { Account.build_search!(option => "(DELETE FROM users)") } }
176
-
177
- account = Account.first
178
-
179
- assert_raise(ArgumentError) { account.users.build_search(:conditions => "(DELETE FROM users)", :page => 2, :per_page => 15) }
180
- BinaryLogic::Searchgasm::Search::Base::VULNERABLE_OPTIONS.each { |option| assert_raise(ArgumentError) { account.users.build_search(option => "(DELETE FROM users)") } }
181
-
182
- assert_nothing_raised { account.users.build_search!(:conditions => "(DELETE FROM users)", :page => 2, :per_page => 15) }
183
- BinaryLogic::Searchgasm::Search::Base::VULNERABLE_OPTIONS.each { |option| assert_nothing_raised { account.users.build_search!(option => "(DELETE FROM users)") } }
184
- end
185
- end