jnewland-scoped_search 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ module ScopedSearch
2
+
3
+ class QueryLanguageParser
4
+
5
+ def parse_query(query = nil)
6
+ return build_conditions_tree(tokenize(query))
7
+ end
8
+
9
+ def self.parse(query)
10
+ self.new.parse_query(query)
11
+ end
12
+
13
+ protected
14
+
15
+ def build_conditions_tree(tokens)
16
+ conditions_tree = []
17
+
18
+ negate = false
19
+ tokens.each do |item|
20
+ case item
21
+ when :not
22
+ negate = true
23
+ else
24
+ if /^.+[ ]OR[ ].+$/ =~ item
25
+ conditions_tree << [item, :or]
26
+ elsif /^#{RegTokens::BetweenDateFormatMMDDYYYY}$/ =~ item or
27
+ /^#{RegTokens::BetweenDateFormatYYYYMMDD}$/ =~ item or
28
+ /^#{RegTokens::BetweenDatabaseFormat}$/ =~ item
29
+ conditions_tree << [item, :between_dates]
30
+ elsif /^#{RegTokens::GreaterThanOrEqualToDateFormatMMDDYYYY}$/ =~ item or
31
+ /^#{RegTokens::GreaterThanOrEqualToDateFormatYYYYMMDD}$/ =~ item or
32
+ /^#{RegTokens::GreaterThanOrEqualToDatabaseFormat}$/ =~ item
33
+ conditions_tree << [item, :greater_than_or_equal_to_date]
34
+ elsif /^#{RegTokens::LessThanOrEqualToDateFormatMMDDYYYY}$/ =~ item or
35
+ /^#{RegTokens::LessThanOrEqualToDateFormatYYYYMMDD}$/ =~ item or
36
+ /^#{RegTokens::LessThanOrEqualToDatabaseFormat}$/ =~ item
37
+ conditions_tree << [item, :less_than_or_equal_to_date]
38
+ elsif /^#{RegTokens::GreaterThanDateFormatMMDDYYYY}$/ =~ item or
39
+ /^#{RegTokens::GreaterThanDateFormatYYYYMMDD}$/ =~ item or
40
+ /^#{RegTokens::GreaterThanDatabaseFormat}$/ =~ item
41
+ conditions_tree << [item, :greater_than_date]
42
+ elsif /^#{RegTokens::LessThanDateFormatMMDDYYYY}$/ =~ item or
43
+ /^#{RegTokens::LessThanDateFormatYYYYMMDD}$/ =~ item or
44
+ /^#{RegTokens::LessThanDatabaseFormat}$/ =~ item
45
+ conditions_tree << [item, :less_than_date]
46
+ elsif /^#{RegTokens::DateFormatMMDDYYYY}$/ =~ item or
47
+ /^#{RegTokens::DateFormatYYYYMMDD}$/ =~ item or
48
+ /^#{RegTokens::DatabaseFormat}$/ =~ item
49
+ conditions_tree << [item, :as_of_date]
50
+ else
51
+ conditions_tree << (negate ? [item, :not] : [item, :like])
52
+ negate = false
53
+ end
54
+ end
55
+ end
56
+
57
+ return conditions_tree
58
+ end
59
+
60
+ def tokenize(query)
61
+ pattern = [RegTokens::BetweenDateFormatMMDDYYYY,
62
+ RegTokens::BetweenDateFormatYYYYMMDD,
63
+ RegTokens::BetweenDatabaseFormat,
64
+ RegTokens::GreaterThanOrEqualToDateFormatMMDDYYYY,
65
+ RegTokens::GreaterThanOrEqualToDateFormatYYYYMMDD,
66
+ RegTokens::GreaterThanOrEqualToDatabaseFormat,
67
+ RegTokens::LessThanOrEqualToDateFormatMMDDYYYY,
68
+ RegTokens::LessThanOrEqualToDateFormatYYYYMMDD,
69
+ RegTokens::LessThanOrEqualToDatabaseFormat,
70
+ RegTokens::GreaterThanDateFormatMMDDYYYY,
71
+ RegTokens::GreaterThanDateFormatYYYYMMDD,
72
+ RegTokens::GreaterThanDatabaseFormat,
73
+ RegTokens::LessThanDateFormatMMDDYYYY,
74
+ RegTokens::LessThanDateFormatYYYYMMDD,
75
+ RegTokens::LessThanDatabaseFormat,
76
+ RegTokens::DateFormatMMDDYYYY,
77
+ RegTokens::DateFormatYYYYMMDD,
78
+ RegTokens::DatabaseFormat,
79
+ RegTokens::WordOrWord,
80
+ RegTokens::WordOrString,
81
+ RegTokens::StringOrWord,
82
+ RegTokens::StringOrString,
83
+ RegTokens::PossiblyNegatedWord,
84
+ RegTokens::PossiblyNegatedString]
85
+ pattern = Regexp.new(pattern.join('|'))
86
+
87
+ tokens = []
88
+ matches = query.scan(pattern).flatten.compact
89
+ matches.each { |match|
90
+ tokens << :not if match.index('-') == 0
91
+ # Remove any escaped quotes
92
+ # Remove any dashes preceded by a space or at the beginning of a token
93
+ # Remove any additional spaces - more that one.
94
+ cleaned_token = match.gsub(/"/,'').gsub(/^-| -/,'').gsub(/[ ]{2,}/, ' ')
95
+ tokens << cleaned_token if cleaned_token.length > 0
96
+ }
97
+ return tokens
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,51 @@
1
+ # Regular expression tokens to be used for parsing.
2
+ module RegTokens
3
+
4
+ WORD = '[\w-]+'
5
+ SPACE = '[ ]'
6
+ STRING = '["][\w ]+["]'
7
+ OR = 'OR'
8
+ POSSIBLY_NEGATED = '[-]?'
9
+ MONTH = '[\d]{1,2}'
10
+ DAY = '[\d]{1,2}'
11
+ FULL_YEAR = '[\d]{4}'
12
+ LESS_THAN = '[<][ ]'
13
+ GREATER_THAN = '[>][ ]'
14
+ LESS_THAN_OR_EQUAL_TO = '[<][=][ ]'
15
+ GREATER_THAN_OR_EQUAL_TO = '[>][=][ ]'
16
+ TO = 'TO'
17
+
18
+ WordOrWord = "(#{WORD}#{SPACE}#{OR}#{SPACE}#{WORD})"
19
+ WordOrString = "(#{WORD}#{SPACE}#{OR}#{SPACE}#{STRING})"
20
+ StringOrWord = "(#{STRING}#{SPACE}#{OR}#{SPACE}#{WORD})"
21
+ StringOrString = "(#{STRING}#{SPACE}#{OR}#{SPACE}#{STRING})"
22
+ PossiblyNegatedWord = "(#{POSSIBLY_NEGATED}#{WORD})"
23
+ PossiblyNegatedString = "(#{POSSIBLY_NEGATED}#{STRING})"
24
+
25
+ DateFormatMMDDYYYY = "(#{MONTH}/#{DAY}/#{FULL_YEAR})" # This would be the same for DD/MM/YYYY
26
+ DateFormatYYYYMMDD = "(#{FULL_YEAR}/#{MONTH}/#{DAY})"
27
+ DatabaseFormat = "(#{FULL_YEAR}-#{MONTH}-#{DAY})"
28
+
29
+ LessThanDateFormatMMDDYYYY = "(#{LESS_THAN}#{MONTH}/#{DAY}/#{FULL_YEAR})"
30
+ LessThanDateFormatYYYYMMDD = "(#{LESS_THAN}#{FULL_YEAR}/#{MONTH}/#{DAY})"
31
+ LessThanDatabaseFormat = "(#{LESS_THAN}#{FULL_YEAR}-#{MONTH}-#{DAY})"
32
+
33
+ GreaterThanDateFormatMMDDYYYY = "(#{GREATER_THAN}#{MONTH}/#{DAY}/#{FULL_YEAR})"
34
+ GreaterThanDateFormatYYYYMMDD = "(#{GREATER_THAN}#{FULL_YEAR}/#{MONTH}/#{DAY})"
35
+ GreaterThanDatabaseFormat = "(#{GREATER_THAN}#{FULL_YEAR}-#{MONTH}-#{DAY})"
36
+
37
+ LessThanOrEqualToDateFormatMMDDYYYY = "(#{LESS_THAN_OR_EQUAL_TO}#{MONTH}/#{DAY}/#{FULL_YEAR})"
38
+ LessThanOrEqualToDateFormatYYYYMMDD = "(#{LESS_THAN_OR_EQUAL_TO}#{FULL_YEAR}/#{MONTH}/#{DAY})"
39
+ LessThanOrEqualToDatabaseFormat = "(#{LESS_THAN_OR_EQUAL_TO}#{FULL_YEAR}-#{MONTH}-#{DAY})"
40
+
41
+ GreaterThanOrEqualToDateFormatMMDDYYYY = "(#{GREATER_THAN_OR_EQUAL_TO}#{MONTH}/#{DAY}/#{FULL_YEAR})"
42
+ GreaterThanOrEqualToDateFormatYYYYMMDD = "(#{GREATER_THAN_OR_EQUAL_TO}#{FULL_YEAR}/#{MONTH}/#{DAY})"
43
+ GreaterThanOrEqualToDatabaseFormat = "(#{GREATER_THAN_OR_EQUAL_TO}#{FULL_YEAR}-#{MONTH}-#{DAY})"
44
+
45
+ BetweenDateFormatMMDDYYYY = "(#{MONTH}/#{DAY}/#{FULL_YEAR}#{SPACE}#{TO}#{SPACE}#{MONTH}/#{DAY}/#{FULL_YEAR})"
46
+ BetweenDateFormatYYYYMMDD = "(#{FULL_YEAR}/#{MONTH}/#{DAY}#{SPACE}#{TO}#{SPACE}#{FULL_YEAR}/#{MONTH}/#{DAY})"
47
+ BetweenDatabaseFormat = "(#{FULL_YEAR}-#{MONTH}-#{DAY}#{SPACE}#{TO}#{SPACE}#{FULL_YEAR}-#{MONTH}-#{DAY})"
48
+ end
49
+
50
+
51
+
@@ -0,0 +1,114 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class QueryConditionsBuilderTest < Test::Unit::TestCase
4
+
5
+ # change this function if you switch to another query language parser
6
+ def build_query(search_conditions, query_fields)
7
+ ScopedSearch::QueryConditionsBuilder.build_query(search_conditions, query_fields)
8
+ end
9
+
10
+ # ** Single query search tests **
11
+ def test_like_search_condition
12
+ search_conditions = [["Wes", :like]]
13
+ query_fields = {'some_table.first_name' => :string}
14
+ conditions = build_query(search_conditions, query_fields)
15
+
16
+ assert_equal '(some_table.first_name LIKE :keyword_0)', conditions.first
17
+ assert_equal '%Wes%', conditions.last[:keyword_0]
18
+ end
19
+
20
+ def test_not_like_search_condition
21
+ search_conditions = [["Wes", :not]]
22
+ query_fields = {'some_table.first_name' => :string}
23
+ conditions = build_query(search_conditions, query_fields)
24
+
25
+ assert_equal '((some_table.first_name NOT LIKE :keyword_0 OR some_table.first_name IS NULL))', conditions.first
26
+ assert_equal '%Wes%', conditions.last[:keyword_0]
27
+ end
28
+
29
+ def test_or_search_condition
30
+ search_conditions = [["Wes OR Hays", :or]]
31
+ query_fields = {'some_table.first_name' => :string}
32
+ conditions = build_query(search_conditions, query_fields)
33
+ regExs = build_regex_for_or(['first_name'], 'keyword_0')
34
+ assert_match /^#{regExs}$/, conditions.first
35
+ assert_equal '%Wes%', conditions.last[:keyword_0a]
36
+ assert_equal '%Hays%', conditions.last[:keyword_0b]
37
+ end
38
+
39
+ # def test_date_search_condition
40
+ # search_conditions = [["09/27/1980", :as_of_date]]
41
+ # query_fields = {'some_table.event_date' => :datetime}
42
+ # conditions = build_query(search_conditions, query_fields)
43
+ # regExs = build_regex_for_date(['event_date'], 'keyword_0')
44
+ # assert_match /^#{regExs}$/, conditions.first
45
+ # assert_equal '09/27/1980', conditions.last[:keyword_0a]
46
+ # end
47
+
48
+
49
+ # ** Multi query search tests **
50
+ def test_like_two_search_condition
51
+ search_conditions = [["Wes", :like],["Hays", :like]]
52
+ query_fields = {'some_table.first_name' => :string,'some_table.last_name' => :string}
53
+ conditions = build_query(search_conditions, query_fields)
54
+
55
+ fields = ['first_name','last_name']
56
+ regExs = [build_regex_for_like(fields,'keyword_0'),
57
+ build_regex_for_like(fields,'keyword_1')].join('[ ]AND[ ]')
58
+
59
+ assert_match /^#{regExs}$/, conditions.first
60
+ assert_equal '%Wes%', conditions.last[:keyword_0]
61
+ assert_equal '%Hays%', conditions.last[:keyword_1]
62
+ end
63
+
64
+ def test_like_two_search_conditions_with_one_not
65
+ search_conditions = [["Wes", :like],["Hays", :not]]
66
+ query_fields = {'some_table.first_name' => :string,'some_table.last_name' => :string}
67
+ conditions = build_query(search_conditions, query_fields)
68
+
69
+ fields = ['first_name','last_name']
70
+ regExs = [build_regex_for_like(fields,'keyword_0'),
71
+ build_regex_for_not_like(fields,'keyword_1')].join('[ ]AND[ ]')
72
+
73
+ assert_match /^#{regExs}$/, conditions.first
74
+ assert_equal '%Wes%', conditions.last[:keyword_0]
75
+ assert_equal '%Hays%', conditions.last[:keyword_1]
76
+ end
77
+
78
+
79
+
80
+ # ** Helper methods **
81
+ def build_regex_for_like(fields,keyword)
82
+ orFields = fields.join('|')
83
+ regParts = fields.collect { |field|
84
+ "some_table.(#{orFields}) LIKE :#{keyword}"
85
+ }.join('[ ]OR[ ]')
86
+ "[\(]#{regParts}[\)]"
87
+ end
88
+
89
+ def build_regex_for_not_like(fields,keyword)
90
+ orFields = fields.join('|')
91
+ regParts = fields.collect { |field|
92
+ "[\(]some_table.(#{orFields}) NOT LIKE :#{keyword} OR some_table.(#{orFields}) IS NULL[\)]"
93
+ }.join('[ ]AND[ ]')
94
+
95
+ "[\(]#{regParts}[\)]"
96
+ end
97
+
98
+ def build_regex_for_or(fields,keyword)
99
+ orFields = fields.join('|')
100
+ regParts = fields.collect { |field|
101
+ "[\(]some_table.(#{orFields}) LIKE :#{keyword}a OR some_table.(#{orFields}) LIKE :#{keyword}b[\)]"
102
+ }.join('[ ]OR[ ]')
103
+
104
+ "[\(]#{regParts}[\)]"
105
+ end
106
+
107
+ def build_regex_for_date(fields,keyword)
108
+ orFields = fields.join('|')
109
+ regParts = fields.collect { |field|
110
+ "some_table.(#{orFields}) = :#{keyword}"
111
+ }.join('[ ]OR[ ]')
112
+ "[\(]#{regParts}[\)]"
113
+ end
114
+ end
@@ -0,0 +1,172 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class QueryLanguageTest < Test::Unit::TestCase
4
+
5
+ # change this function if you switch to another query language parser
6
+ def parse_query(query)
7
+ ScopedSearch::QueryLanguageParser.parse(query)
8
+ end
9
+
10
+ def test_empty_search_query
11
+ parsed = parse_query('')
12
+ assert_equal 0, parsed.length
13
+
14
+ parsed = parse_query("\t \n")
15
+ assert_equal 0, parsed.length
16
+ end
17
+
18
+ def test_single_keyword
19
+ parsed = parse_query('hallo')
20
+ assert_equal 1, parsed.length
21
+ assert_equal 'hallo', parsed.first.first
22
+
23
+ parsed = parse_query(' hallo ')
24
+ assert_equal 1, parsed.length
25
+ assert_equal 'hallo', parsed.first.first
26
+ end
27
+
28
+ def test_multiple_keywords
29
+ parsed = parse_query(' hallo willem')
30
+ assert_equal 2, parsed.length
31
+ assert_equal 'willem', parsed.last.first
32
+
33
+ parsed = parse_query(" hallo willem van\tbergen ")
34
+ assert_equal 4, parsed.length
35
+ assert_equal 'hallo', parsed[0].first
36
+ assert_equal 'willem', parsed[1].first
37
+ assert_equal 'van', parsed[2].first
38
+ assert_equal 'bergen', parsed[3].first
39
+ end
40
+
41
+ def test_quoted_keywords
42
+ parsed = parse_query(' "hallo"')
43
+ assert_equal 1, parsed.length
44
+ assert_equal 'hallo', parsed.first.first
45
+
46
+ parsed = parse_query(' "hallo willem"')
47
+ assert_equal 1, parsed.length
48
+ assert_equal 'hallo willem', parsed.first.first
49
+
50
+ parsed = parse_query(' "hallo willem')
51
+ assert_equal 2, parsed.length
52
+ assert_equal 'hallo', parsed[0].first
53
+ assert_equal 'willem', parsed[1].first
54
+
55
+ parsed = parse_query(' "hallo wi"llem"')
56
+ assert_equal 2, parsed.length
57
+ assert_equal 'hallo wi', parsed[0].first
58
+ assert_equal 'llem', parsed[1].first
59
+ end
60
+
61
+ def test_quote_escaping
62
+ parsed = parse_query(' "hallo wi\\"llem"')
63
+ assert_equal 3, parsed.length
64
+ assert_equal 'hallo', parsed[0].first
65
+ assert_equal 'wi', parsed[1].first
66
+ assert_equal 'llem', parsed[2].first
67
+
68
+ parsed = parse_query('"\\"hallo willem\\""')
69
+ assert_equal 2, parsed.length
70
+ assert_equal 'hallo', parsed[0].first
71
+ assert_equal 'willem', parsed[1].first
72
+ end
73
+
74
+ def test_negation
75
+ parsed = parse_query('-willem')
76
+ assert_equal 1, parsed.length
77
+ assert_equal 'willem', parsed[0].first
78
+ assert_equal :not, parsed[0].last
79
+
80
+ parsed = parse_query('hallo-world')
81
+ assert_equal 1, parsed.length
82
+ assert_equal 'hallo-world', parsed.first.first
83
+
84
+ parsed = parse_query('hallo -world')
85
+ assert_equal 2, parsed.length
86
+ assert_equal 'hallo', parsed.first.first
87
+ assert_equal 'world', parsed.last.first
88
+ assert_equal :not, parsed.last.last
89
+
90
+ parsed = parse_query('123 -"456 789"')
91
+ assert_equal 2, parsed.length
92
+ assert_equal '123', parsed[0].first
93
+ assert_equal :like, parsed[0].last
94
+
95
+ assert_equal '456 789', parsed[1].first
96
+ assert_equal :not, parsed[1].last
97
+ end
98
+
99
+ def test_or
100
+ parsed = parse_query('Wes OR Hays')
101
+ assert_equal 1, parsed.length
102
+ assert_equal 'Wes OR Hays', parsed[0][0]
103
+ assert_equal :or, parsed[0][1]
104
+
105
+ parsed = parse_query('"Man made" OR Dogs')
106
+ assert_equal 1, parsed.length
107
+ assert_equal 'Man made OR Dogs', parsed[0][0]
108
+ assert_equal :or, parsed[0][1]
109
+
110
+ parsed = parse_query('Cows OR "Frog Toys"')
111
+ assert_equal 1, parsed.length
112
+ assert_equal 'Cows OR Frog Toys', parsed[0][0]
113
+ assert_equal :or, parsed[0][1]
114
+
115
+ parsed = parse_query('"Happy cow" OR "Sad Frog"')
116
+ assert_equal 1, parsed.length
117
+ assert_equal 'Happy cow OR Sad Frog', parsed[0][0]
118
+ assert_equal :or, parsed[0][1]
119
+ end
120
+
121
+ def test_as_of_date
122
+ # parsed = parse_query('9/27/1980')
123
+ # assert_equal 1, parsed.length
124
+ # assert_equal '9/27/1980', parsed[0][0]
125
+ # assert_equal :as_of, parsed[0][1]
126
+
127
+ # parsed = parse_query('"Man made" OR Dogs')
128
+ # assert_equal 1, parsed.length
129
+ # assert_equal 'Man made OR Dogs', parsed[0][0]
130
+ # assert_equal :or, parsed[0][1]
131
+ #
132
+ # parsed = parse_query('Cows OR "Frog Toys"')
133
+ # assert_equal 1, parsed.length
134
+ # assert_equal 'Cows OR Frog Toys', parsed[0][0]
135
+ # assert_equal :or, parsed[0][1]
136
+ #
137
+ # parsed = parse_query('"Happy cow" OR "Sad Frog"')
138
+ # assert_equal 1, parsed.length
139
+ # assert_equal 'Happy cow OR Sad Frog', parsed[0][0]
140
+ # assert_equal :or, parsed[0][1]
141
+ end
142
+
143
+ def test_long_string
144
+ str = 'Wes -Hays "Hello World" -"Goodnight Moon" Bob OR Wes "Happy cow" OR "Sad Frog" "Man made" OR Dogs Cows OR "Frog Toys"'
145
+ parsed = parse_query(str)
146
+ assert_equal 8, parsed.length
147
+
148
+ assert_equal 'Wes', parsed[0].first
149
+ assert_equal :like, parsed[0].last
150
+
151
+ assert_equal 'Hays', parsed[1].first
152
+ assert_equal :not, parsed[1].last
153
+
154
+ assert_equal 'Hello World', parsed[2].first
155
+ assert_equal :like, parsed[2].last
156
+
157
+ assert_equal 'Goodnight Moon', parsed[3].first
158
+ assert_equal :not, parsed[3].last
159
+
160
+ assert_equal 'Bob OR Wes', parsed[4].first
161
+ assert_equal :or, parsed[4].last
162
+
163
+ assert_equal 'Happy cow OR Sad Frog', parsed[5].first
164
+ assert_equal :or, parsed[5].last
165
+
166
+ assert_equal 'Man made OR Dogs', parsed[6].first
167
+ assert_equal :or, parsed[6].last
168
+
169
+ assert_equal 'Cows OR Frog Toys', parsed[7].first
170
+ assert_equal :or, parsed[7].last
171
+ end
172
+ end
@@ -0,0 +1,159 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ScopedSearchTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ case ENV['DATABASE']
7
+ when 'mysql'
8
+ create_mysql_connection
9
+ when 'postgresql'
10
+ create_postgresql_connection
11
+ else 'sqlite3'
12
+ create_sqlite3_connection
13
+ end
14
+ InitialSchema.up
15
+ SearchTestModel.create_corpus!
16
+ Group.create_corpus!
17
+ Location.create_corpus!
18
+ Address.create_corpus!
19
+ User.create_corpus!
20
+ Client.create_corpus!
21
+ Office.create_corpus!
22
+ Note.create_corpus!
23
+ end
24
+
25
+ def teardown
26
+ InitialSchema.down
27
+ end
28
+
29
+ def test_enabling
30
+ assert !SearchTestModel.respond_to?(:search_for)
31
+ SearchTestModel.searchable_on :string_field, :text_field, :date_field
32
+ assert SearchTestModel.respond_to?(:search_for)
33
+
34
+ assert_equal ActiveRecord::NamedScope::Scope, SearchTestModel.search_for('test').class
35
+ end
36
+
37
+ def test_search_only_fields
38
+ SearchTestModel.searchable_on :only => [:string_field, :text_field, :date_field]
39
+ assert SearchTestModel.respond_to?(:search_for)
40
+ assert_equal SearchTestModel.scoped_search_fields.size, 3
41
+ assert SearchTestModel.scoped_search_fields.include?(:string_field)
42
+ assert SearchTestModel.scoped_search_fields.include?(:text_field)
43
+ assert SearchTestModel.scoped_search_fields.include?(:date_field)
44
+ end
45
+
46
+ def test_search_except_fields
47
+ SearchTestModel.searchable_on :except => [:id, :ignored_field, :created_at, :updated_at]
48
+ assert SearchTestModel.respond_to?(:search_for)
49
+ assert_equal SearchTestModel.scoped_search_fields.size, 3
50
+ assert SearchTestModel.scoped_search_fields.include?(:string_field)
51
+ assert SearchTestModel.scoped_search_fields.include?(:text_field)
52
+ assert SearchTestModel.scoped_search_fields.include?(:date_field)
53
+ end
54
+
55
+ def test_search_with_only_and_except
56
+ # :except should be ignored if :only is specified.
57
+ SearchTestModel.searchable_on({:only => [:text_field], :except => [:text_field]})
58
+ assert SearchTestModel.respond_to?(:search_for)
59
+ assert_equal SearchTestModel.scoped_search_fields.size, 1
60
+ assert SearchTestModel.scoped_search_fields.include?(:text_field), ':except should be ignored if :only is specified'
61
+ end
62
+
63
+ def test_search
64
+ SearchTestModel.searchable_on :string_field, :text_field, :date_field
65
+
66
+ assert_equal SearchTestModel.count, SearchTestModel.search_for('').count
67
+ assert_equal 0, SearchTestModel.search_for('456').count
68
+ assert_equal 2, SearchTestModel.search_for('hays').count
69
+ assert_equal 1, SearchTestModel.search_for('hay ob').count
70
+ assert_equal 15, SearchTestModel.search_for('o').count
71
+ assert_equal 2, SearchTestModel.search_for('-o').count
72
+ assert_equal 15, SearchTestModel.search_for('-Jim').count
73
+ assert_equal 1, SearchTestModel.search_for('Jim -Bush').count
74
+ assert_equal 1, SearchTestModel.search_for('"Hello World" -"Goodnight Moon"').count
75
+ assert_equal 2, SearchTestModel.search_for('Wes OR Bob').count
76
+ assert_equal 3, SearchTestModel.search_for('"Happy cow" OR "Sad Frog"').count
77
+ assert_equal 3, SearchTestModel.search_for('"Man made" OR Dogs').count
78
+ assert_equal 2, SearchTestModel.search_for('Cows OR "Frog Toys"').count
79
+
80
+ # ** DATES **
81
+ #
82
+ # The next two dates are invalid therefore it will be ignored.
83
+ # Since it is just a date being searched for it will also
84
+ # be searched for in text fields regardless of whether or
85
+ # not it is a valid date.
86
+ assert_equal 0, SearchTestModel.search_for('2/30/1980').count
87
+ assert_equal 0, SearchTestModel.search_for('99/99/9999').count
88
+
89
+ assert_equal 1, SearchTestModel.search_for('9/27/1980').count
90
+ assert_equal 1, SearchTestModel.search_for('hays 9/27/1980').count
91
+ assert_equal 0, SearchTestModel.search_for('hays 2/30/1980').count
92
+
93
+ assert_equal 1, SearchTestModel.search_for('< 12/01/1980').count
94
+ assert_equal 6, SearchTestModel.search_for('> 2006/1/1').count
95
+
96
+ assert_equal 5, SearchTestModel.search_for('< 12/26/2002').count
97
+ assert_equal 6, SearchTestModel.search_for('<= 12/26/2002').count
98
+
99
+ assert_equal 6, SearchTestModel.search_for('> 2/5/2005').count
100
+ assert_equal 7, SearchTestModel.search_for('>= 2/5/2005').count
101
+
102
+ assert_equal 3, SearchTestModel.search_for('1/1/2005 TO 1/1/2007').count
103
+
104
+ assert_equal 2, SearchTestModel.search_for('Happy 1/1/2005 TO 1/1/2007').count
105
+
106
+ # This should return one with a date of 7/15/2006 found in the text.
107
+ assert_equal 2, SearchTestModel.search_for('7/15/2006').count
108
+ end
109
+
110
+ def test_search_belongs_to_association
111
+ User.searchable_on :first_name, :last_name, :group_name
112
+
113
+ assert_equal User.count, User.search_for('').count
114
+ assert_equal 1, User.search_for('Wes').count
115
+ assert_equal 2, User.search_for('System Administrator').count
116
+ assert_equal 2, User.search_for('Managers').count
117
+ end
118
+
119
+ def test_search_has_many_association
120
+ User.searchable_on :first_name, :last_name, :notes_title, :notes_content
121
+
122
+ assert_equal User.count, User.search_for('').count
123
+ assert_equal 2, User.search_for('Router').count
124
+ assert_equal 1, User.search_for('milk').count
125
+ assert_equal 1, User.search_for('"Spec Tests"').count
126
+ assert_equal 0, User.search_for('Wes "Spec Tests"').count
127
+ end
128
+
129
+ def test_search_has_many_through_association
130
+ User.searchable_on :first_name, :last_name, :clients_first_name, :clients_last_name
131
+
132
+ assert_equal User.count, User.search_for('').count
133
+ assert_equal 2, User.search_for('Smith').count
134
+ assert_equal 1, User.search_for('Sam').count
135
+ assert_equal 1, User.search_for('Johnson').count
136
+ end
137
+
138
+ def test_search_has_one_association
139
+ User.searchable_on :first_name, :last_name, :address_street, :address_city, :address_state, :address_postal_code
140
+
141
+ assert_equal User.count, User.search_for('').count
142
+ assert_equal 1, User.search_for('Fernley').count
143
+ assert_equal 4, User.search_for('NV').count
144
+ assert_equal 1, User.search_for('Haskell').count
145
+ assert_equal 2, User.search_for('89434').count
146
+ end
147
+
148
+ def test_search_has_and_belongs_to_many_association
149
+ User.searchable_on :first_name, :last_name, :locations_name
150
+
151
+ assert_equal User.count, User.search_for('').count
152
+ assert_equal 2, User.search_for('Office').count
153
+ assert_equal 1, User.search_for('Store').count
154
+ assert_equal 1, User.search_for('John Office').count
155
+ end
156
+
157
+ end
158
+
159
+