searchgasm 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/Manifest +17 -4
- data/README.mdown +19 -16
- data/lib/searchgasm/search/base.rb +2 -4
- data/lib/searchgasm/search/condition_types/begins_with_condition.rb +28 -0
- data/lib/searchgasm/search/condition_types/child_of_condition.rb +17 -0
- data/lib/searchgasm/search/condition_types/condition.rb +107 -0
- data/lib/searchgasm/search/condition_types/contains_condition.rb +26 -0
- data/lib/searchgasm/search/condition_types/descendant_of_condition.rb +30 -0
- data/lib/searchgasm/search/condition_types/does_not_equal_condition.rb +34 -0
- data/lib/searchgasm/search/condition_types/ends_with_condition.rb +26 -0
- data/lib/searchgasm/search/condition_types/equals_condition.rb +26 -0
- data/lib/searchgasm/search/condition_types/greater_than_condition.rb +31 -0
- data/lib/searchgasm/search/condition_types/greater_than_or_equal_to_condition.rb +26 -0
- data/lib/searchgasm/search/condition_types/inclusive_descendant_of_condition.rb +19 -0
- data/lib/searchgasm/search/condition_types/keywords_condition.rb +39 -0
- data/lib/searchgasm/search/condition_types/less_than_condition.rb +31 -0
- data/lib/searchgasm/search/condition_types/less_than_or_equal_to_condition.rb +26 -0
- data/lib/searchgasm/search/condition_types/sibling_of_condition.rb +22 -0
- data/lib/searchgasm/search/condition_types/tree_condition.rb +20 -0
- data/lib/searchgasm/search/conditions.rb +55 -82
- data/lib/searchgasm/version.rb +1 -1
- data/lib/searchgasm.rb +23 -2
- data/searchgasm.gemspec +36 -10
- data/test/test_helper.rb +1 -0
- data/test/test_searchgasm_base.rb +32 -0
- data/test/test_searchgasm_condition_types.rb +143 -0
- data/test/test_searchgasm_conditions.rb +21 -29
- metadata +35 -9
- data/lib/searchgasm/helpers.rb +0 -100
- data/lib/searchgasm/search/condition.rb +0 -168
- data/test/test_active_record_protection.rb +0 -0
- data/test/test_searchgasm_condition.rb +0 -149
data/CHANGELOG
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
v0.9.3. Changed structure of conditions to have their own class. Making it easier to add your own conditions.
|
2
|
+
|
1
3
|
v0.9.2. Enhanced protection
|
2
4
|
|
3
5
|
v0.9.1. Added aliases for datetime, date, time, and timestamp attrs. You could call created_at_after, mow you can also call created_after.
|
data/Manifest
CHANGED
@@ -2,9 +2,23 @@ CHANGELOG
|
|
2
2
|
init.rb
|
3
3
|
lib/searchgasm/active_record/associations.rb
|
4
4
|
lib/searchgasm/active_record/base.rb
|
5
|
-
lib/searchgasm/helpers.rb
|
6
5
|
lib/searchgasm/search/base.rb
|
7
|
-
lib/searchgasm/search/
|
6
|
+
lib/searchgasm/search/condition_types/begins_with_condition.rb
|
7
|
+
lib/searchgasm/search/condition_types/child_of_condition.rb
|
8
|
+
lib/searchgasm/search/condition_types/condition.rb
|
9
|
+
lib/searchgasm/search/condition_types/contains_condition.rb
|
10
|
+
lib/searchgasm/search/condition_types/descendant_of_condition.rb
|
11
|
+
lib/searchgasm/search/condition_types/does_not_equal_condition.rb
|
12
|
+
lib/searchgasm/search/condition_types/ends_with_condition.rb
|
13
|
+
lib/searchgasm/search/condition_types/equals_condition.rb
|
14
|
+
lib/searchgasm/search/condition_types/greater_than_condition.rb
|
15
|
+
lib/searchgasm/search/condition_types/greater_than_or_equal_to_condition.rb
|
16
|
+
lib/searchgasm/search/condition_types/inclusive_descendant_of_condition.rb
|
17
|
+
lib/searchgasm/search/condition_types/keywords_condition.rb
|
18
|
+
lib/searchgasm/search/condition_types/less_than_condition.rb
|
19
|
+
lib/searchgasm/search/condition_types/less_than_or_equal_to_condition.rb
|
20
|
+
lib/searchgasm/search/condition_types/sibling_of_condition.rb
|
21
|
+
lib/searchgasm/search/condition_types/tree_condition.rb
|
8
22
|
lib/searchgasm/search/conditions.rb
|
9
23
|
lib/searchgasm/search/utilities.rb
|
10
24
|
lib/searchgasm/version.rb
|
@@ -20,8 +34,7 @@ test/libs/acts_as_tree.rb
|
|
20
34
|
test/libs/rexml_fix.rb
|
21
35
|
test/test_active_record_associations.rb
|
22
36
|
test/test_active_record_base.rb
|
23
|
-
test/test_active_record_protection.rb
|
24
37
|
test/test_helper.rb
|
25
38
|
test/test_searchgasm_base.rb
|
26
|
-
test/
|
39
|
+
test/test_searchgasm_condition_types.rb
|
27
40
|
test/test_searchgasm_conditions.rb
|
data/README.mdown
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Searchgasm is orgasmic. Maybe not orgasmic, but you will get aroused. So go grab a towel and let's dive in.
|
4
4
|
|
5
|
-
Searchgasm was a super secret tool of mine until I decided to share with the world. It has saved me tons of time, allowed me to write less code, made searching painless, and kept my controllers DRY.
|
5
|
+
Searchgasm was a super secret tool of mine until I decided to share with the world. It has saved me tons of time, allowed me to write less code, made searching painless, and kept my controllers DRY and simple.
|
6
6
|
|
7
7
|
It originated to satisfy a VERY simple need: so that I could use my form builder when making search forms. Sounds simple right? The goal was to use an object, that represents a search, just like an ActiveRecord object in form\_for and fields\_for.
|
8
8
|
|
9
|
-
I'm a big fan of understanding what I'm using, so here's a quick explanation: The design behind this plugin is pretty simple. The search object "santiizes" down into the options passed into ActiveRecord::Base.find(). It basically serves as a transparent filter between you and ActiveRecord::Base.find(). This filter provides "enhancements" that get translated into options that ActiveRecord::Base.find() can understand.
|
9
|
+
I'm a big fan of understanding what I'm using, so here's a quick explanation: The design behind this plugin is pretty simple. The search object "santiizes" down into the options passed into ActiveRecord::Base.find(). It basically serves as a transparent filter between you and ActiveRecord::Base.find(). This filter provides "enhancements" that get translated into options that ActiveRecord::Base.find() can understand. It doesn't dig into the ActiveRecord internals, it only uses what is publicly available. It jumps in and helps out only when needed, otherwise it sits back and lets ActiveRecord do all of the work. Between that and the extensive tests this is a solid plugin.
|
10
10
|
|
11
11
|
Here's where you get aroused...
|
12
12
|
|
@@ -18,7 +18,7 @@ Here's where you get aroused...
|
|
18
18
|
@users, @users_count = @search.all, @search.count
|
19
19
|
end
|
20
20
|
|
21
|
-
Now your view
|
21
|
+
Now your view, isn't it nice to use form builder again?
|
22
22
|
|
23
23
|
# app/views/users/index.html.erb
|
24
24
|
<%= form_for :conditions, @search.conditions, :url => users_path do |f| %>
|
@@ -63,7 +63,9 @@ Now go into your console and try out any of these example with your own models.
|
|
63
63
|
:page => 3 # offset 60
|
64
64
|
)
|
65
65
|
|
66
|
-
|
66
|
+
Instead of using the "all" method you could use any search method: first, find(:all), find(:first), count, sum, average, etc
|
67
|
+
|
68
|
+
## Detailed Example w/ object based searching (great for form\_for for fields\_for)
|
67
69
|
|
68
70
|
# Instantiate
|
69
71
|
@search = User.new_search(
|
@@ -137,7 +139,7 @@ If you want to be hardcore:
|
|
137
139
|
search.per_page = 20
|
138
140
|
search.all
|
139
141
|
|
140
|
-
## Search with conditions only
|
142
|
+
## Search with conditions only
|
141
143
|
|
142
144
|
conditions = User.new_conditions(:age_gt => 18)
|
143
145
|
conditions.first_name_contains = "Ben"
|
@@ -177,16 +179,15 @@ For tree data structures you get a few nifty methods. Let's assume Users is a tr
|
|
177
179
|
User.all(:conditions => {:sibling_of => User.roots.first})
|
178
180
|
User.all(:conditions => {:sibling_of => User.roots.first.id})
|
179
181
|
|
180
|
-
#
|
181
|
-
User.all(:conditions => {:
|
182
|
-
User.all(:conditions => {:
|
182
|
+
# Descendant of (includes all recursive children: children, grand children, great grand children, etc)
|
183
|
+
User.all(:conditions => {:descendant_of => User.roots.first})
|
184
|
+
User.all(:conditions => {:descendant_of => User.roots.first.id})
|
183
185
|
|
184
|
-
# Inclusive
|
185
|
-
User.all(:conditions => {:
|
186
|
-
User.all(:conditions => {:
|
186
|
+
# Inclusive descendant_of. Same as above but includes the root
|
187
|
+
User.all(:conditions => {:inclusive_descendant_of => User.roots.first})
|
188
|
+
User.all(:conditions => {:inclusive_descendant_of => User.roots.first.id})
|
187
189
|
|
188
190
|
|
189
|
-
|
190
191
|
## Available anywhere (relationships & named scopes)
|
191
192
|
|
192
193
|
Not only can you use searchgasm when searching, but you can use it when setting up relationships or named scopes:
|
@@ -198,7 +199,7 @@ Not only can you use searchgasm when searching, but you can use it when setting
|
|
198
199
|
|
199
200
|
## Always use protection...against SQL injections
|
200
201
|
|
201
|
-
If there is one thing we all know, it's to always use protection. That's why searchgasm protects you by default. The new\_search and new\_conditions methods are protected by default. This means that various checks are done to ensure it is not possible to perform any type of SQL injection. But this also limits how you can search, meaning you can't write raw SQL. If you want to be daring and search without protection prepare to accept the consequences. All that you have to do is add ! to the end of the methods: new\_search! and new\_conditions!.
|
202
|
+
If there is one thing we all know, it's to always use protection. That's why searchgasm protects you by default. The new\_search and new\_conditions methods are protected by default. This means that various checks are done to ensure it is not possible to perform any type of SQL injection. But this also limits how you can search, meaning you can't write raw SQL. If you want to be daring and search without protection prepare to accept the consequences. All that you have to do is add ! to the end of the methods: new\_search! and new\_conditions!.
|
202
203
|
|
203
204
|
### Protected from SQL injections
|
204
205
|
|
@@ -229,16 +230,18 @@ Depending on the type, each column comes preloaded with a bunch of nifty conditi
|
|
229
230
|
=> :greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to
|
230
231
|
|
231
232
|
tree data structures (see above "searching trees")
|
232
|
-
=> :child_of, :sibling_of, :
|
233
|
+
=> :child_of, :sibling_of, :descendant_of, :inclusive_descendant_of
|
233
234
|
|
234
235
|
Some of these conditions come with aliases, so you have your choice how to call the conditions. For example you can use "greater\_than" or "gt":
|
235
236
|
|
236
237
|
:equals; => :is
|
237
238
|
:does_not_equal => :is_not, :not
|
238
|
-
:begins_with => :starts_with
|
239
|
-
:contains => :like
|
239
|
+
:begins_with => :starts_with, :bw, :start
|
240
|
+
:contains => :like, :has
|
241
|
+
:ends_with => :ew, :ends, :end
|
240
242
|
:greater_than => :gt, :after
|
241
243
|
:greater_than_or_equal_to => :at_least, :gte
|
244
|
+
:keywords => :kwords, :kw
|
242
245
|
:less_than => :lt, :before
|
243
246
|
:less_than_or_equal_to => :at_most, :lte
|
244
247
|
|
@@ -20,12 +20,10 @@ module BinaryLogic
|
|
20
20
|
end
|
21
21
|
|
22
22
|
(::ActiveRecord::Base.valid_find_options - [:conditions]).each do |option|
|
23
|
-
|
23
|
+
class_eval <<-end_eval
|
24
24
|
def #{option}(sanitize = false); options[:#{option}]; end
|
25
25
|
def #{option}=(value); self.options[:#{option}] = value; end
|
26
|
-
|
27
|
-
|
28
|
-
class_eval src
|
26
|
+
end_eval
|
29
27
|
end
|
30
28
|
|
31
29
|
alias_method :per_page, :limit
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BinaryLogic
|
2
|
+
module Searchgasm
|
3
|
+
module Search
|
4
|
+
module ConditionTypes
|
5
|
+
class BeginsWithCondition < 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}_bw", "#{column.name}_starts_with", "#{column.name}_start"]
|
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::BeginsWithCondition)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
BinaryLogic::Searchgasm::Search::Conditions.register_condition(BinaryLogic::Searchgasm::Search::ConditionTypes::BeginsWithCondition)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module BinaryLogic
|
2
|
+
module Searchgasm
|
3
|
+
module Search
|
4
|
+
module ConditionTypes
|
5
|
+
class ChildOfCondition < TreeCondition
|
6
|
+
def to_conditions(value)
|
7
|
+
parent_association = klass.reflect_on_association(:parent)
|
8
|
+
foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
|
9
|
+
["#{quoted_table_name}.#{quote_column_name(foreign_key_name)} = ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Conditions.register_condition(ConditionTypes::ChildOfCondition)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module BinaryLogic
|
2
|
+
module Searchgasm
|
3
|
+
module Search
|
4
|
+
module ConditionTypes
|
5
|
+
class Condition
|
6
|
+
include Utilities
|
7
|
+
|
8
|
+
attr_accessor :column, :klass
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def condition_name
|
13
|
+
name.split("::").last.scan(/(.*)Condition/)[0][0].underscore
|
14
|
+
end
|
15
|
+
|
16
|
+
def name_for_column(column)
|
17
|
+
"#{column.name}_#{condition_name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def aliases_for_column(column)
|
21
|
+
[]
|
22
|
+
end
|
23
|
+
|
24
|
+
def name_for_klass(klass)
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def aliases_for_klass(klass)
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
|
32
|
+
def string_column?(column)
|
33
|
+
[:string, :text].include?(column.type)
|
34
|
+
end
|
35
|
+
|
36
|
+
def comparable_column?(column)
|
37
|
+
[:integer, :float, :decimal, :datetime, :timestamp, :time, :date].include?(column.type)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(klass, column = nil)
|
42
|
+
self.klass = klass
|
43
|
+
self.column = column.is_a?(String) ? klass.columns_hash[column] : column
|
44
|
+
end
|
45
|
+
|
46
|
+
def explicitly_set_value=(value)
|
47
|
+
@explicitly_set_value = value
|
48
|
+
end
|
49
|
+
|
50
|
+
# Need this if someone wants to actually use nil in a meaningful way
|
51
|
+
def explicitly_set_value?
|
52
|
+
@explicitly_set_value == true
|
53
|
+
end
|
54
|
+
|
55
|
+
def ignore_blanks?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def name
|
60
|
+
column ? self.class.name_for_column(column) : self.class.name_for_klass(klass)
|
61
|
+
end
|
62
|
+
|
63
|
+
def condition_name
|
64
|
+
self.class.condition_name
|
65
|
+
end
|
66
|
+
|
67
|
+
def quote_column_name(column_name)
|
68
|
+
klass.connection.quote_column_name(column_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def quoted_column_name
|
72
|
+
quote_column_name(column.name)
|
73
|
+
end
|
74
|
+
|
75
|
+
def quote_table_name(table_name)
|
76
|
+
klass.connection.quote_table_name(table_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def quoted_table_name
|
80
|
+
quote_table_name(klass.table_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def sanitize(alt_value = nil)
|
84
|
+
return unless explicitly_set_value?
|
85
|
+
v = alt_value || value
|
86
|
+
if v.is_a?(Array) && !["equals", "does_not_equal"].include?(condition_name)
|
87
|
+
merge_conditions(*v.collect { |i| sanitize(i) })
|
88
|
+
else
|
89
|
+
v = v.utc if column && [:time, :timestamp, :datetime].include?(column.type) && klass.time_zone_aware_attributes && !klass.skip_time_zone_conversion_for_attributes.include?(column.name.to_sym)
|
90
|
+
to_conditions(v)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def value
|
95
|
+
@value.is_a?(String) ? column.type_cast(@value) : @value
|
96
|
+
end
|
97
|
+
|
98
|
+
def value=(v)
|
99
|
+
return if ignore_blanks? && v.blank?
|
100
|
+
self.explicitly_set_value = true
|
101
|
+
@value = v
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
@@ -0,0 +1,30 @@
|
|
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
|
@@ -0,0 +1,34 @@
|
|
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
|
@@ -0,0 +1,26 @@
|
|
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
|
@@ -0,0 +1,26 @@
|
|
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
|
@@ -0,0 +1,31 @@
|
|
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
|
@@ -0,0 +1,26 @@
|
|
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
|
@@ -0,0 +1,19 @@
|
|
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
|
@@ -0,0 +1,39 @@
|
|
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
|
+
search_parts.each do |search_part|
|
25
|
+
strs << "#{quoted_table_name}.#{quoted_column_name} LIKE ?"
|
26
|
+
subs << "%#{search_part}%"
|
27
|
+
end
|
28
|
+
|
29
|
+
return if strs.blank?
|
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
|
@@ -0,0 +1,31 @@
|
|
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
|
@@ -0,0 +1,26 @@
|
|
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
|
@@ -0,0 +1,22 @@
|
|
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
|