searchgasm 0.9.6 → 0.9.7

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