wvanbergen-scoped_search 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/README.rdoc +48 -32
  2. data/Rakefile +1 -3
  3. data/lib/scoped_search.rb +45 -95
  4. data/lib/scoped_search/adapters.rb +41 -0
  5. data/lib/scoped_search/definition.rb +122 -0
  6. data/lib/scoped_search/query_builder.rb +213 -0
  7. data/lib/scoped_search/query_language.rb +30 -0
  8. data/lib/scoped_search/query_language/ast.rb +141 -0
  9. data/lib/scoped_search/query_language/parser.rb +115 -0
  10. data/lib/scoped_search/query_language/tokenizer.rb +62 -0
  11. data/{test → spec}/database.yml +0 -0
  12. data/spec/integration/api_spec.rb +82 -0
  13. data/spec/integration/ordinal_querying_spec.rb +153 -0
  14. data/spec/integration/relation_querying_spec.rb +258 -0
  15. data/spec/integration/string_querying_spec.rb +187 -0
  16. data/spec/lib/database.rb +44 -0
  17. data/spec/lib/matchers.rb +40 -0
  18. data/spec/lib/mocks.rb +19 -0
  19. data/spec/spec_helper.rb +21 -0
  20. data/spec/unit/ast_spec.rb +197 -0
  21. data/spec/unit/definition_spec.rb +24 -0
  22. data/spec/unit/parser_spec.rb +105 -0
  23. data/spec/unit/query_builder_spec.rb +5 -0
  24. data/spec/unit/tokenizer_spec.rb +97 -0
  25. data/tasks/database_tests.rake +5 -5
  26. data/tasks/github-gem.rake +8 -3
  27. metadata +39 -23
  28. data/lib/scoped_search/query_conditions_builder.rb +0 -209
  29. data/lib/scoped_search/query_language_parser.rb +0 -117
  30. data/lib/scoped_search/reg_tokens.rb +0 -51
  31. data/tasks/documentation.rake +0 -33
  32. data/test/integration/api_test.rb +0 -53
  33. data/test/lib/test_models.rb +0 -148
  34. data/test/lib/test_schema.rb +0 -68
  35. data/test/test_helper.rb +0 -44
  36. data/test/unit/query_conditions_builder_test.rb +0 -410
  37. data/test/unit/query_language_test.rb +0 -155
  38. data/test/unit/search_for_test.rb +0 -124
@@ -1,10 +1,10 @@
1
1
  require 'yaml' unless Object::const_defined?('YAML')
2
2
 
3
- namespace :test do
3
+ namespace :spec do
4
4
 
5
- databases = YAML.load(File.read(File.dirname(__FILE__) + '/../test/database.yml'))
5
+ databases = YAML.load(File.read(File.dirname(__FILE__) + '/../spec/database.yml'))
6
6
 
7
- desc "Run testsuite on all configured databases in test/database.yml"
7
+ desc "Run testsuite on all configured databases in spec/database.yml"
8
8
  task(:all => databases.keys.map { |db| db.to_sym }) do
9
9
  puts "\nFinished testing on all configured databases!"
10
10
  puts "(Configure databases by adjusting test/database.yml)"
@@ -13,8 +13,8 @@ namespace :test do
13
13
  databases.each do |database, config|
14
14
  desc "Run testsuite on #{database} database."
15
15
  task database.to_sym do
16
- puts "Running testsuite on #{database} database...\n\n"
17
- sh "rake test DATABASE=#{database}"
16
+ puts "Running specs for #{database} database...\n\n"
17
+ sh "rake spec DATABASE=#{database}"
18
18
  end
19
19
  end
20
20
  end
@@ -97,7 +97,7 @@ module Rake
97
97
  protected
98
98
 
99
99
  def has_rdoc?
100
- @specification.has_rdoc
100
+ branch_exist?('gh-pages')
101
101
  end
102
102
 
103
103
  def has_specs?
@@ -168,6 +168,11 @@ module Rake
168
168
  run_command('git branch').detect { |line| /^\* (.+)/ =~ line }
169
169
  raise "You are currently not working in the master branch!" unless branch == $1
170
170
  end
171
+
172
+ def branch_exist?(branch)
173
+ run_command('git branch').any? { |line| line.split(' ').last == branch }
174
+ end
175
+
171
176
 
172
177
  def verify_clean_status(on_branch = nil)
173
178
  sh "git fetch"
@@ -232,10 +237,10 @@ module Rake
232
237
 
233
238
  def create_version_tag!
234
239
  # commit the gemspec file
235
- git_commit_file(gemspec_file, "Released #{@name} version #{@specification.version}") if git_modified?(gemspec_file)
240
+ git_commit_file(gemspec_file, "Updated #{gemspec_file} for release of version #{@specification.version}") if git_modified?(gemspec_file)
236
241
 
237
242
  # create tag and push changes
238
- git_create_tag("#{@name}-#{@specification.version}", "Tagged #{@name} version #{@specification.version}")
243
+ git_create_tag("#{@name}-#{@specification.version}", "Tagged version #{@specification.version}")
239
244
  git_push('origin', 'master', [:tags])
240
245
  end
241
246
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wvanbergen-scoped_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-06-18 00:00:00 -07:00
13
+ date: 2009-08-26 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -32,27 +32,38 @@ files:
32
32
  - lib
33
33
  - lib/scoped_search
34
34
  - lib/scoped_search.rb
35
- - lib/scoped_search/query_conditions_builder.rb
36
- - lib/scoped_search/query_language_parser.rb
37
- - lib/scoped_search/reg_tokens.rb
35
+ - lib/scoped_search/adapters.rb
36
+ - lib/scoped_search/definition.rb
37
+ - lib/scoped_search/query_builder.rb
38
+ - lib/scoped_search/query_language
39
+ - lib/scoped_search/query_language.rb
40
+ - lib/scoped_search/query_language/ast.rb
41
+ - lib/scoped_search/query_language/parser.rb
42
+ - lib/scoped_search/query_language/tokenizer.rb
43
+ - spec
44
+ - spec/database.yml
45
+ - spec/integration
46
+ - spec/integration/api_spec.rb
47
+ - spec/integration/ordinal_querying_spec.rb
48
+ - spec/integration/relation_querying_spec.rb
49
+ - spec/integration/string_querying_spec.rb
50
+ - spec/lib
51
+ - spec/lib/database.rb
52
+ - spec/lib/matchers.rb
53
+ - spec/lib/mocks.rb
54
+ - spec/spec_helper.rb
55
+ - spec/unit
56
+ - spec/unit/ast_spec.rb
57
+ - spec/unit/definition_spec.rb
58
+ - spec/unit/parser_spec.rb
59
+ - spec/unit/query_builder_spec.rb
60
+ - spec/unit/tokenizer_spec.rb
38
61
  - tasks
39
62
  - tasks/database_tests.rake
40
- - tasks/documentation.rake
41
63
  - tasks/github-gem.rake
42
- - test
43
- - test/database.yml
44
- - test/integration
45
- - test/integration/api_test.rb
46
- - test/lib
47
- - test/lib/test_models.rb
48
- - test/lib/test_schema.rb
49
- - test/test_helper.rb
50
- - test/unit
51
- - test/unit/query_conditions_builder_test.rb
52
- - test/unit/query_language_test.rb
53
- - test/unit/search_for_test.rb
54
64
  has_rdoc: true
55
65
  homepage: http://wiki.github.com/wvanbergen/scoped_search
66
+ licenses:
56
67
  post_install_message:
57
68
  rdoc_options:
58
69
  - --title
@@ -78,12 +89,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
89
  requirements: []
79
90
 
80
91
  rubyforge_project:
81
- rubygems_version: 1.2.0
92
+ rubygems_version: 1.3.5
82
93
  signing_key:
83
94
  specification_version: 2
84
95
  summary: A Rails plugin to search your models using a named_scope
85
96
  test_files:
86
- - test/integration/api_test.rb
87
- - test/unit/query_conditions_builder_test.rb
88
- - test/unit/query_language_test.rb
89
- - test/unit/search_for_test.rb
97
+ - spec/integration/api_spec.rb
98
+ - spec/integration/ordinal_querying_spec.rb
99
+ - spec/integration/relation_querying_spec.rb
100
+ - spec/integration/string_querying_spec.rb
101
+ - spec/unit/ast_spec.rb
102
+ - spec/unit/definition_spec.rb
103
+ - spec/unit/parser_spec.rb
104
+ - spec/unit/query_builder_spec.rb
105
+ - spec/unit/tokenizer_spec.rb
@@ -1,209 +0,0 @@
1
- module ScopedSearch
2
-
3
- class QueryConditionsBuilder
4
-
5
- # Builds the query string by calling the build method on a new instances of QueryConditionsBuilder.
6
- def self.build_query(search_conditions, query_fields)
7
- self.new.build(search_conditions, query_fields)
8
- end
9
-
10
- # Initializes the default class variables.
11
- def initialize
12
- @query_fields = nil
13
- @query_params = {}
14
-
15
- @sql_like = 'LIKE'
16
-
17
- if ActiveRecord::Base.connected? and ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
18
- @sql_like = 'ILIKE'
19
- end
20
- end
21
-
22
-
23
- # Build the query based on the search conditions and the fields to query.
24
- #
25
- # Hash query_options : A hash of fields and field types.
26
- #
27
- # Example:
28
- #
29
- # search_conditions = [["Wes", :like], ["Hays", :not], ["Hello World", :like], ["Goodnight Moon", :not],
30
- # ["Bob OR Wes", :or], ["Happy cow OR Sad Frog", :or], ["Man made OR Dogs", :or],
31
- # ["Cows OR Frog Toys", :or], ['9/28/1980, :datetime]]
32
- # query_fields = {:first_name => :string, :created_at => :datetime}
33
- #
34
- # Exceptons :
35
- # 1) If search_conditions is not an array
36
- # 2) If query_fields is not a Hash
37
- def build(search_conditions, query_fields)
38
- raise 'search_conditions must be a hash' unless search_conditions.class.to_s == 'Array'
39
- raise 'query_fields must be a hash' unless query_fields.class.to_s == 'Hash'
40
- @query_fields = query_fields
41
-
42
- conditions = []
43
-
44
- search_conditions.each_with_index do |search_condition, index|
45
- keyword_name = "keyword_#{index}".to_sym
46
- conditions << case search_condition.last
47
- # :like also handles integers
48
- when :like then like_condition(keyword_name, search_condition.first)
49
- when :not then not_like_condition(keyword_name, search_condition.first)
50
-
51
- when :or then or_condition(keyword_name, search_condition.first)
52
-
53
- when :less_than_date then less_than_date(keyword_name, search_condition.first)
54
- when :less_than_or_equal_to_date then less_than_or_equal_to_date(keyword_name, search_condition.first)
55
- when :as_of_date then as_of_date(keyword_name, search_condition.first)
56
- when :greater_than_date then greater_than_date(keyword_name, search_condition.first)
57
- when :greater_than_or_equal_to_date then greater_than_or_equal_to_date(keyword_name, search_condition.first)
58
-
59
- when :between_dates then between_dates(keyword_name, search_condition.first)
60
- end
61
- end
62
-
63
- [conditions.compact.join(' AND '), @query_params]
64
- end
65
-
66
-
67
- private
68
-
69
- def like_condition(keyword_name, value)
70
- retVal = []
71
- @query_fields.each do |field, field_type| #|key,value|
72
- if field_type == :string or field_type == :text
73
- @query_params[keyword_name] = "%#{value}%"
74
- retVal << "#{field} #{@sql_like} :#{keyword_name.to_s}"
75
- end
76
- if value.strip =~ /^[0-9]+$/ and (field_type == :int or field_type == :integer)
77
- qkey = "#{keyword_name}_#{value.strip}"
78
- @query_params[qkey.to_sym] = value.strip.to_i
79
- retVal << "#{field} = :#{qkey}"
80
- end
81
- end
82
- "(#{retVal.join(' OR ')})"
83
- end
84
-
85
- def not_like_condition(keyword_name, value)
86
- @query_params[keyword_name] = "%#{value}%"
87
- retVal = []
88
- @query_fields.each do |field, field_type| #|key,value|
89
- if field_type == :string or field_type == :text
90
- retVal << "(#{field} NOT #{@sql_like} :#{keyword_name.to_s} OR #{field} IS NULL)"
91
- end
92
- end
93
- "(#{retVal.join(' AND ')})"
94
- end
95
-
96
- def or_condition(keyword_name, value)
97
- retVal = []
98
- word1, word2 = value.split(' OR ')
99
- keyword_name_a = "#{keyword_name.to_s}a".to_sym
100
- keyword_name_b = "#{keyword_name.to_s}b".to_sym
101
- @query_params[keyword_name_a] = "%#{word1}%"
102
- @query_params[keyword_name_b] = "%#{word2}%"
103
- @query_fields.each do |field, field_type| #|key,value|
104
- if field_type == :string or field_type == :text
105
- retVal << "(#{field} #{@sql_like} :#{keyword_name_a.to_s} OR #{field} #{@sql_like} :#{keyword_name_b.to_s})"
106
- end
107
- if (word1.strip =~ /^[0-9]+$/ and word2.strip =~ /^[0-9]+$/) and (field_type == :int or field_type == :integer)
108
- qkeya = "#{keyword_name}_a_#{word1.strip}"
109
- qkeyb = "#{keyword_name}_b_#{word2.strip}"
110
- @query_params[qkeya] = word1.strip.to_i
111
- @query_params[qkeyb] = word2.strip.to_i
112
- retVal << "(#{field} = :#{qkeya} OR #{field} = :#{qkeyb})"
113
- elsif (word1.strip =~ /^[0-9]+$/ or word2.strip =~ /^[0-9]+$/) and (field_type == :int or field_type == :integer)
114
- num_word = word1.strip =~ /^[0-9]+$/ ? word1.strip.to_i : word2.strip.to_i
115
- qkey = "#{keyword_name}_#{num_word}"
116
- @query_params[qkey.to_sym] = num_word
117
- retVal << "(#{field} = :#{qkey})"
118
- end
119
- end
120
- "(#{retVal.join(' OR ')})"
121
- end
122
-
123
- def less_than_date(keyword_name, value)
124
- helper_date_operation('<', keyword_name, value)
125
- end
126
-
127
- def less_than_or_equal_to_date(keyword_name, value)
128
- helper_date_operation('<=', keyword_name, value)
129
- end
130
-
131
- def as_of_date(keyword_name, value)
132
- retVal = []
133
- begin
134
- dt = Date.parse(value) # This will throw an exception if it is not valid
135
- @query_params[keyword_name] = dt.to_s
136
- @query_fields.each do |field, field_type| #|key,value|
137
- if field_type == :date or field_type == :datetime or field_type == :timestamp
138
- retVal << "#{field} = :#{keyword_name.to_s}"
139
- end
140
- end
141
- rescue
142
- # do not search on any date columns since the date is invalid
143
- retVal = [] # Reset just in case
144
- end
145
-
146
- # Search the text fields for the date as well as it could be in text.
147
- # Also still search on the text columns for an invalid date as it could
148
- # have a different meaning.
149
- found_text_fields_to_search = false
150
- keyword_name_b = "#{keyword_name}b".to_sym
151
- @query_fields.each do |field, field_type| #|key,value|
152
- if field_type == :string or field_type == :text
153
- found_text_fields_to_search = true
154
- retVal << "#{field} #{@sql_like} :#{keyword_name_b.to_s}"
155
- end
156
- end
157
- if found_text_fields_to_search
158
- @query_params[keyword_name_b] = "%#{value}%"
159
- end
160
-
161
- retVal.empty? ? '' : "(#{retVal.join(' OR ')})"
162
- end
163
-
164
- def greater_than_date(keyword_name, value)
165
- helper_date_operation('>', keyword_name, value)
166
- end
167
-
168
- def greater_than_or_equal_to_date(keyword_name, value)
169
- helper_date_operation('>=', keyword_name, value)
170
- end
171
-
172
- def between_dates(keyword_name, value)
173
- date1, date2 = value.split(' TO ')
174
- dt1 = Date.parse(date1) # This will throw an exception if it is not valid
175
- dt2 = Date.parse(date2) # This will throw an exception if it is not valid
176
- keyword_name_a = "#{keyword_name.to_s}a".to_sym
177
- keyword_name_b = "#{keyword_name.to_s}b".to_sym
178
- @query_params[keyword_name_a] = dt1.to_s
179
- @query_params[keyword_name_b] = dt2.to_s
180
-
181
- retVal = []
182
- @query_fields.each do |field, field_type| #|key,value|
183
- if field_type == :date or field_type == :datetime or field_type == :timestamp
184
- retVal << "(#{field} BETWEEN :#{keyword_name_a.to_s} AND :#{keyword_name_b.to_s})"
185
- end
186
- end
187
- "(#{retVal.join(' OR ')})"
188
- rescue
189
- # The date is not valid so just ignore it
190
- return nil
191
- end
192
-
193
-
194
- def helper_date_operation(operator, keyword_name, value)
195
- dt = Date.parse(value) # This will throw an exception if it is not valid
196
- @query_params[keyword_name] = dt.to_s
197
- retVal = []
198
- @query_fields.each do |field, field_type| #|key,value|
199
- if field_type == :date or field_type == :datetime or field_type == :timestamp
200
- retVal << "#{field} #{operator} :#{keyword_name.to_s}"
201
- end
202
- end
203
- "(#{retVal.join(' OR ')})"
204
- rescue
205
- # The date is not valid so just ignore it
206
- return nil
207
- end
208
- end
209
- end
@@ -1,117 +0,0 @@
1
- module ScopedSearch
2
-
3
- # Used to parse and build the conditions tree for the search.
4
- class QueryLanguageParser
5
-
6
- # Parses the query string by calling the parse_query method on a new instances of QueryLanguageParser.
7
- #
8
- # query:: The query string to parse. If nil the all values will be returned (default is nil)
9
- # If the query string is longer then 300 characters then it will be truncated to a
10
- # length of 300.
11
- def self.parse(query)
12
- self.new.parse_query(query)
13
- end
14
-
15
- # Parse the query string.
16
- #
17
- # query:: The query string to parse. If nil the all values will be returned (default is nil)
18
- # If the query string is longer then 300 characters then it will be truncated to a
19
- # length of 300.
20
- def parse_query(query = nil)
21
- # truncate query string at 300 characters
22
- query = query[0...300] if query.length > 300
23
- return build_conditions_tree(tokenize(query))
24
- end
25
-
26
- protected
27
-
28
- # Build the conditions tree based on the tokens found.
29
- #
30
- # tokens:: An array of tokens.
31
- def build_conditions_tree(tokens)
32
- conditions_tree = []
33
-
34
- negate = false
35
- tokens.each do |item|
36
- case item
37
- when :not
38
- negate = true
39
- else
40
- if /^.+[ ]OR[ ].+$/ =~ item
41
- conditions_tree << [item, :or]
42
- elsif /^#{RegTokens::BetweenDateFormatMMDDYYYY}$/ =~ item or
43
- /^#{RegTokens::BetweenDateFormatYYYYMMDD}$/ =~ item or
44
- /^#{RegTokens::BetweenDatabaseFormat}$/ =~ item
45
- conditions_tree << [item, :between_dates]
46
- elsif /^#{RegTokens::GreaterThanOrEqualToDateFormatMMDDYYYY}$/ =~ item or
47
- /^#{RegTokens::GreaterThanOrEqualToDateFormatYYYYMMDD}$/ =~ item or
48
- /^#{RegTokens::GreaterThanOrEqualToDatabaseFormat}$/ =~ item
49
- conditions_tree << [item, :greater_than_or_equal_to_date]
50
- elsif /^#{RegTokens::LessThanOrEqualToDateFormatMMDDYYYY}$/ =~ item or
51
- /^#{RegTokens::LessThanOrEqualToDateFormatYYYYMMDD}$/ =~ item or
52
- /^#{RegTokens::LessThanOrEqualToDatabaseFormat}$/ =~ item
53
- conditions_tree << [item, :less_than_or_equal_to_date]
54
- elsif /^#{RegTokens::GreaterThanDateFormatMMDDYYYY}$/ =~ item or
55
- /^#{RegTokens::GreaterThanDateFormatYYYYMMDD}$/ =~ item or
56
- /^#{RegTokens::GreaterThanDatabaseFormat}$/ =~ item
57
- conditions_tree << [item, :greater_than_date]
58
- elsif /^#{RegTokens::LessThanDateFormatMMDDYYYY}$/ =~ item or
59
- /^#{RegTokens::LessThanDateFormatYYYYMMDD}$/ =~ item or
60
- /^#{RegTokens::LessThanDatabaseFormat}$/ =~ item
61
- conditions_tree << [item, :less_than_date]
62
- elsif /^#{RegTokens::DateFormatMMDDYYYY}$/ =~ item or
63
- /^#{RegTokens::DateFormatYYYYMMDD}$/ =~ item or
64
- /^#{RegTokens::DatabaseFormat}$/ =~ item
65
- conditions_tree << [item, :as_of_date]
66
- else
67
- conditions_tree << (negate ? [item, :not] : [item, :like])
68
- negate = false
69
- end
70
- end
71
- end
72
-
73
- return conditions_tree
74
- end
75
-
76
- # Tokenize the query based on the different RegTokens.
77
- def tokenize(query)
78
- pattern = [RegTokens::BetweenDateFormatMMDDYYYY,
79
- RegTokens::BetweenDateFormatYYYYMMDD,
80
- RegTokens::BetweenDatabaseFormat,
81
- RegTokens::GreaterThanOrEqualToDateFormatMMDDYYYY,
82
- RegTokens::GreaterThanOrEqualToDateFormatYYYYMMDD,
83
- RegTokens::GreaterThanOrEqualToDatabaseFormat,
84
- RegTokens::LessThanOrEqualToDateFormatMMDDYYYY,
85
- RegTokens::LessThanOrEqualToDateFormatYYYYMMDD,
86
- RegTokens::LessThanOrEqualToDatabaseFormat,
87
- RegTokens::GreaterThanDateFormatMMDDYYYY,
88
- RegTokens::GreaterThanDateFormatYYYYMMDD,
89
- RegTokens::GreaterThanDatabaseFormat,
90
- RegTokens::LessThanDateFormatMMDDYYYY,
91
- RegTokens::LessThanDateFormatYYYYMMDD,
92
- RegTokens::LessThanDatabaseFormat,
93
- RegTokens::DateFormatMMDDYYYY,
94
- RegTokens::DateFormatYYYYMMDD,
95
- RegTokens::DatabaseFormat,
96
- RegTokens::WordOrWord,
97
- RegTokens::WordOrString,
98
- RegTokens::StringOrWord,
99
- RegTokens::StringOrString,
100
- RegTokens::PossiblyNegatedWord,
101
- RegTokens::PossiblyNegatedString]
102
- pattern = Regexp.new(pattern.join('|'))
103
-
104
- tokens = []
105
- matches = query.scan(pattern).flatten.compact
106
- matches.each { |match|
107
- tokens << :not if match.index('-') == 0
108
- # Remove any escaped quotes
109
- # Remove any dashes preceded by a space or at the beginning of a token
110
- # Remove any additional spaces - more that one.
111
- cleaned_token = match.gsub(/"/,'').gsub(/^-| -/,'').gsub(/[ ]{2,}/, ' ')
112
- tokens << cleaned_token if cleaned_token.length > 0
113
- }
114
- return tokens
115
- end
116
- end
117
- end