scoped_search 2.2.1 → 2.3.0
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.
- data/.gitignore +4 -1
- data/.infinity_test +8 -0
- data/Gemfile +15 -0
- data/lib/scoped_search/auto_complete_builder.rb +251 -0
- data/lib/scoped_search/definition.rb +90 -10
- data/lib/scoped_search/query_builder.rb +209 -45
- data/lib/scoped_search/query_language/parser.rb +4 -2
- data/lib/scoped_search/query_language/tokenizer.rb +3 -3
- data/lib/scoped_search/rails_helper.rb +210 -0
- data/lib/scoped_search.rb +9 -1
- data/scoped_search.gemspec +7 -5
- data/spec/database.yml +13 -6
- data/spec/integration/api_spec.rb +1 -1
- data/spec/integration/auto_complete_spec.rb +140 -0
- data/spec/integration/key_value_querying_spec.rb +87 -0
- data/spec/integration/ordinal_querying_spec.rb +105 -10
- data/spec/integration/profile_querying_spec.rb +1 -1
- data/spec/integration/relation_querying_spec.rb +186 -194
- data/spec/integration/set_query_spec.rb +76 -0
- data/spec/integration/string_querying_spec.rb +19 -2
- data/spec/lib/matchers.rb +10 -0
- data/spec/spec_helper.rb +2 -4
- data/spec/unit/ast_spec.rb +1 -1
- data/spec/unit/auto_complete_builder_spec.rb +20 -0
- data/spec/unit/definition_spec.rb +1 -1
- data/spec/unit/parser_spec.rb +1 -1
- data/spec/unit/query_builder_spec.rb +2 -1
- data/spec/unit/tokenizer_spec.rb +1 -1
- data/tasks/github-gem.rake +18 -14
- metadata +33 -7
@@ -0,0 +1,210 @@
|
|
1
|
+
module ScopedSearch
|
2
|
+
module RailsHelper
|
3
|
+
# Creates a link that alternates between ascending and descending.
|
4
|
+
#
|
5
|
+
# Examples:
|
6
|
+
#
|
7
|
+
# sort @search, :by => :username
|
8
|
+
# sort @search, :by => :created_at, :as => "Created"
|
9
|
+
#
|
10
|
+
# This helper accepts the following options:
|
11
|
+
#
|
12
|
+
# * <tt>:by</tt> - the name of the named scope. This helper will prepend this value with "ascend_by_" and "descend_by_"
|
13
|
+
# * <tt>:as</tt> - the text used in the link, defaults to whatever is passed to :by
|
14
|
+
def sort(field, options = {}, html_options = {})
|
15
|
+
|
16
|
+
unless options[:as]
|
17
|
+
id = field.to_s.downcase == "id"
|
18
|
+
options[:as] = id ? field.to_s.upcase : field.to_s.humanize
|
19
|
+
end
|
20
|
+
|
21
|
+
ascend = "#{field} ASC"
|
22
|
+
descend = "#{field} DESC"
|
23
|
+
|
24
|
+
ascending = params[:order] == ascend
|
25
|
+
new_sort = ascending ? descend : ascend
|
26
|
+
selected = [ascend, descend].include?(params[:order])
|
27
|
+
|
28
|
+
if selected
|
29
|
+
css_classes = html_options[:class] ? html_options[:class].split(" ") : []
|
30
|
+
if ascending
|
31
|
+
options[:as] = "▲ #{options[:as]}"
|
32
|
+
css_classes << "ascending"
|
33
|
+
else
|
34
|
+
options[:as] = "▼ #{options[:as]}"
|
35
|
+
css_classes << "descending"
|
36
|
+
end
|
37
|
+
html_options[:class] = css_classes.join(" ")
|
38
|
+
end
|
39
|
+
|
40
|
+
url_options = params.merge(:order => new_sort)
|
41
|
+
|
42
|
+
options[:as] = raw(options[:as]) if defined?(RailsXss)
|
43
|
+
|
44
|
+
a_link(options[:as], html_escape(url_for(url_options)),html_options)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Adds AJAX auto complete functionality to the text input field with the
|
48
|
+
# DOM ID specified by +field_id+.
|
49
|
+
#
|
50
|
+
# Required +options+ is:
|
51
|
+
# <tt>:url</tt>:: URL to call for auto completion results
|
52
|
+
# in url_for format.
|
53
|
+
#
|
54
|
+
# Additional +options+ are:
|
55
|
+
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
|
56
|
+
# innerHTML should be updated with the auto complete
|
57
|
+
# entries returned by the AJAX request.
|
58
|
+
# Defaults to <tt>field_id</tt> + '_auto_complete'
|
59
|
+
# <tt>:with</tt>:: A JavaScript expression specifying the
|
60
|
+
# parameters for the XMLHttpRequest. This defaults
|
61
|
+
# to 'fieldname=value'.
|
62
|
+
# <tt>:frequency</tt>:: Determines the time to wait after the last keystroke
|
63
|
+
# for the AJAX request to be initiated.
|
64
|
+
# <tt>:indicator</tt>:: Specifies the DOM ID of an element which will be
|
65
|
+
# displayed while auto complete is running.
|
66
|
+
# <tt>:tokens</tt>:: A string or an array of strings containing
|
67
|
+
# separator tokens for tokenized incremental
|
68
|
+
# auto completion. Example: <tt>:tokens => ','</tt> would
|
69
|
+
# allow multiple auto completion entries, separated
|
70
|
+
# by commas.
|
71
|
+
# <tt>:min_chars</tt>:: The minimum number of characters that should be
|
72
|
+
# in the input field before an Ajax call is made
|
73
|
+
# to the server.
|
74
|
+
# <tt>:on_hide</tt>:: A Javascript expression that is called when the
|
75
|
+
# auto completion div is hidden. The expression
|
76
|
+
# should take two variables: element and update.
|
77
|
+
# Element is a DOM element for the field, update
|
78
|
+
# is a DOM element for the div from which the
|
79
|
+
# innerHTML is replaced.
|
80
|
+
# <tt>:on_show</tt>:: Like on_hide, only now the expression is called
|
81
|
+
# then the div is shown.
|
82
|
+
# <tt>:after_update_element</tt>:: A Javascript expression that is called when the
|
83
|
+
# user has selected one of the proposed values.
|
84
|
+
# The expression should take two variables: element and value.
|
85
|
+
# Element is a DOM element for the field, value
|
86
|
+
# is the value selected by the user.
|
87
|
+
# <tt>:select</tt>:: Pick the class of the element from which the value for
|
88
|
+
# insertion should be extracted. If this is not specified,
|
89
|
+
# the entire element is used.
|
90
|
+
# <tt>:method</tt>:: Specifies the HTTP verb to use when the auto completion
|
91
|
+
# request is made. Defaults to POST.
|
92
|
+
def auto_complete_field(field_id, options = {})
|
93
|
+
function = "var #{field_id}_auto_completer = new Ajax.Autocompleter("
|
94
|
+
function << "'#{field_id}', "
|
95
|
+
function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', "
|
96
|
+
function << "'#{url_for(options[:url])}'"
|
97
|
+
|
98
|
+
js_options = {}
|
99
|
+
js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens]
|
100
|
+
js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with]
|
101
|
+
js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator]
|
102
|
+
js_options[:select] = "'#{options[:select]}'" if options[:select]
|
103
|
+
js_options[:paramName] = "'#{options[:param_name]}'" if options[:param_name]
|
104
|
+
js_options[:frequency] = "#{options[:frequency]}" if options[:frequency]
|
105
|
+
js_options[:method] = "'#{options[:method].to_s}'" if options[:method]
|
106
|
+
|
107
|
+
{ :after_update_element => :afterUpdateElement,
|
108
|
+
:on_show => :onShow, :on_hide => :onHide, :min_chars => :minChars }.each do |k,v|
|
109
|
+
js_options[v] = options[k] if options[k]
|
110
|
+
end
|
111
|
+
|
112
|
+
function << (', ' + options_for_javascript(js_options) + ')')
|
113
|
+
|
114
|
+
javascript_tag(function)
|
115
|
+
end
|
116
|
+
|
117
|
+
def auto_complete_field_jquery(method, url, options = {})
|
118
|
+
function = <<-EOF
|
119
|
+
$.widget( "custom.catcomplete", $.ui.autocomplete, {
|
120
|
+
_renderMenu: function( ul, items ) {
|
121
|
+
var self = this,
|
122
|
+
currentCategory = "";
|
123
|
+
$.each( items, function( index, item ) {
|
124
|
+
if ( item.category != undefined && item.category != currentCategory ) {
|
125
|
+
ul.append( "<li class='ui-autocomplete-category'>" + item.category + "</li>" );
|
126
|
+
currentCategory = item.category;
|
127
|
+
}
|
128
|
+
if ( item.error != undefined ) {
|
129
|
+
ul.append( "<li class='ui-autocomplete-error'>" + item.error + "</li>" );
|
130
|
+
}
|
131
|
+
if( item.completed != undefined ) {
|
132
|
+
$( "<li></li>" ).data( "item.autocomplete", item )
|
133
|
+
.append( "<a>" + "<strong class='ui-autocomplete-completed'>" + item.completed + "</strong>" + item.part + "</a>" )
|
134
|
+
.appendTo( ul );
|
135
|
+
} else {
|
136
|
+
self._renderItem( ul, item );
|
137
|
+
}
|
138
|
+
});
|
139
|
+
}
|
140
|
+
});
|
141
|
+
|
142
|
+
$("##{method}")
|
143
|
+
.catcomplete({
|
144
|
+
source: function( request, response ) { $.getJSON( "#{url}", { #{method}: request.term }, response ); },
|
145
|
+
minLength: #{options[:min_length] || 0},
|
146
|
+
delay: #{options[:delay] || 200},
|
147
|
+
select: function(event, ui) { $( this ).catcomplete( "search" , ui.item.value); },
|
148
|
+
search: function(event, ui) { $(".auto_complete_clear").hide(); },
|
149
|
+
open: function(event, ui) { $(".auto_complete_clear").show(); }
|
150
|
+
});
|
151
|
+
|
152
|
+
$("##{method}").bind( "focus", function( event ) {
|
153
|
+
if( $( this )[0].value == "" ) {
|
154
|
+
$( this ).catcomplete( "search" );
|
155
|
+
}
|
156
|
+
});
|
157
|
+
|
158
|
+
EOF
|
159
|
+
|
160
|
+
|
161
|
+
javascript_tag(function)
|
162
|
+
end
|
163
|
+
|
164
|
+
def auto_complete_clear_value_button(field_id)
|
165
|
+
html_options = {:tabindex => '-1',:class=>"auto_complete_clear",:title =>'Clear Search', :onclick=>"document.getElementById('#{field_id}').value = '';"}
|
166
|
+
a_link("", "#", html_options)
|
167
|
+
end
|
168
|
+
|
169
|
+
def a_link(name, href, html_options)
|
170
|
+
tag_options = tag_options(html_options)
|
171
|
+
link = "<a href=\"#{href}\"#{tag_options}>#{name}</a>"
|
172
|
+
return link.respond_to?(:html_safe) ? link.html_safe : link
|
173
|
+
end
|
174
|
+
|
175
|
+
# Use this method in your view to generate a return for the AJAX auto complete requests.
|
176
|
+
#
|
177
|
+
# The auto_complete_result can of course also be called from a view belonging to the
|
178
|
+
# auto_complete action if you need to decorate it further.
|
179
|
+
def auto_complete_result(entries, phrase = nil)
|
180
|
+
return unless entries
|
181
|
+
items = entries.map { |entry| content_tag("li", phrase ? highlight(entry, phrase) : h(entry)) }
|
182
|
+
content_tag("ul", items)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Wrapper for text_field with added AJAX auto completion functionality.
|
186
|
+
#
|
187
|
+
# In your controller, you'll need to define an action called
|
188
|
+
# auto_complete_method to respond the AJAX calls,
|
189
|
+
def auto_complete_field_tag(method, val,tag_options = {}, completion_options = {})
|
190
|
+
auto_completer_options = { :url => { :action => "auto_complete_#{method}" } }.update(completion_options)
|
191
|
+
|
192
|
+
text_field_tag(method, val,tag_options.merge(:class => "auto_complete_input")) +
|
193
|
+
auto_complete_clear_value_button(method) +
|
194
|
+
content_tag("div", "", :id => "#{method}_auto_complete", :class => "auto_complete") +
|
195
|
+
auto_complete_field(method, auto_completer_options)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Wrapper for text_field with added JQuery auto completion functionality.
|
199
|
+
#
|
200
|
+
# In your controller, you'll need to define an action called
|
201
|
+
# auto_complete_method to respond the JQuery calls,
|
202
|
+
def auto_complete_field_tag_jquery(method, val,tag_options = {}, completion_options = {})
|
203
|
+
url = url_for(:action => "auto_complete_#{method}")
|
204
|
+
options = tag_options.merge(:class => "auto_complete_input")
|
205
|
+
text_field_tag(method, val, options) + auto_complete_clear_value_button(method) +
|
206
|
+
auto_complete_field_jquery(method, url, completion_options)
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
data/lib/scoped_search.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
1
3
|
# ScopedSearch is the base module for the scoped_search plugin. This file
|
2
4
|
# defines some modules and exception classes, loads the necessary files, and
|
3
5
|
# installs itself in ActiveRecord.
|
@@ -12,7 +14,7 @@ module ScopedSearch
|
|
12
14
|
|
13
15
|
# The current scoped_search version. Do not change thisvalue by hand,
|
14
16
|
# because it will be updated automatically by the gem release script.
|
15
|
-
VERSION = "2.
|
17
|
+
VERSION = "2.3.0"
|
16
18
|
|
17
19
|
# The ClassMethods module will be included into the ActiveRecord::Base class
|
18
20
|
# to add the <tt>ActiveRecord::Base.scoped_search</tt> method and the
|
@@ -85,7 +87,13 @@ end
|
|
85
87
|
require 'scoped_search/definition'
|
86
88
|
require 'scoped_search/query_language'
|
87
89
|
require 'scoped_search/query_builder'
|
90
|
+
require 'scoped_search/auto_complete_builder'
|
88
91
|
|
89
92
|
# Import the search_on method in the ActiveReocrd::Base class
|
90
93
|
ActiveRecord::Base.send(:extend, ScopedSearch::ClassMethods)
|
91
94
|
ActiveRecord::Base.send(:extend, ScopedSearch::BackwardsCompatibility)
|
95
|
+
|
96
|
+
if defined?(ActionController)
|
97
|
+
require "scoped_search/rails_helper"
|
98
|
+
ActionController::Base.helper(ScopedSearch::RailsHelper)
|
99
|
+
end
|
data/scoped_search.gemspec
CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
|
|
3
3
|
|
4
4
|
# Do not change the version and date fields by hand. This will be done
|
5
5
|
# automatically by the gem release script.
|
6
|
-
s.version = "2.
|
7
|
-
s.date = "
|
6
|
+
s.version = "2.3.0"
|
7
|
+
s.date = "2011-05-16"
|
8
8
|
|
9
9
|
s.summary = "Easily search you ActiveRecord models with a simple query language using a named scope."
|
10
10
|
s.description = <<-EOS
|
@@ -26,12 +26,14 @@ Gem::Specification.new do |s|
|
|
26
26
|
|
27
27
|
s.add_runtime_dependency('activerecord', '>= 2.1.0')
|
28
28
|
s.add_development_dependency('rspec', '~> 2.0')
|
29
|
+
|
30
|
+
s.add_development_dependency('sqlite3-ruby')
|
29
31
|
|
30
32
|
s.rdoc_options << '--title' << s.name << '--main' << 'README.rdoc' << '--line-numbers' << '--inline-source'
|
31
33
|
s.extra_rdoc_files = ['README.rdoc']
|
32
34
|
|
33
35
|
# Do not change the files and test_files fields by hand. This will be done
|
34
36
|
# automatically by the gem release script.
|
35
|
-
s.files = %w(.gitignore LICENSE README.rdoc Rakefile init.rb lib/scoped_search.rb lib/scoped_search/definition.rb lib/scoped_search/query_builder.rb lib/scoped_search/query_language.rb lib/scoped_search/query_language/ast.rb lib/scoped_search/query_language/parser.rb lib/scoped_search/query_language/tokenizer.rb scoped_search.gemspec spec/database.yml spec/integration/api_spec.rb spec/integration/ordinal_querying_spec.rb spec/integration/profile_querying_spec.rb spec/integration/relation_querying_spec.rb spec/integration/string_querying_spec.rb spec/lib/database.rb spec/lib/matchers.rb spec/lib/mocks.rb spec/spec_helper.rb spec/unit/ast_spec.rb spec/unit/definition_spec.rb spec/unit/parser_spec.rb spec/unit/query_builder_spec.rb spec/unit/tokenizer_spec.rb tasks/github-gem.rake)
|
36
|
-
s.test_files = %w(spec/integration/api_spec.rb spec/integration/ordinal_querying_spec.rb spec/integration/profile_querying_spec.rb spec/integration/relation_querying_spec.rb spec/integration/string_querying_spec.rb spec/unit/ast_spec.rb spec/unit/definition_spec.rb spec/unit/parser_spec.rb spec/unit/query_builder_spec.rb spec/unit/tokenizer_spec.rb)
|
37
|
-
end
|
37
|
+
s.files = %w(.gitignore .infinity_test Gemfile LICENSE README.rdoc Rakefile init.rb lib/scoped_search.rb lib/scoped_search/auto_complete_builder.rb lib/scoped_search/definition.rb lib/scoped_search/query_builder.rb lib/scoped_search/query_language.rb lib/scoped_search/query_language/ast.rb lib/scoped_search/query_language/parser.rb lib/scoped_search/query_language/tokenizer.rb lib/scoped_search/rails_helper.rb scoped_search.gemspec spec/database.yml spec/integration/api_spec.rb spec/integration/auto_complete_spec.rb spec/integration/key_value_querying_spec.rb spec/integration/ordinal_querying_spec.rb spec/integration/profile_querying_spec.rb spec/integration/relation_querying_spec.rb spec/integration/set_query_spec.rb spec/integration/string_querying_spec.rb spec/lib/database.rb spec/lib/matchers.rb spec/lib/mocks.rb spec/spec_helper.rb spec/unit/ast_spec.rb spec/unit/auto_complete_builder_spec.rb spec/unit/definition_spec.rb spec/unit/parser_spec.rb spec/unit/query_builder_spec.rb spec/unit/tokenizer_spec.rb tasks/github-gem.rake)
|
38
|
+
s.test_files = %w(spec/integration/api_spec.rb spec/integration/auto_complete_spec.rb spec/integration/key_value_querying_spec.rb spec/integration/ordinal_querying_spec.rb spec/integration/profile_querying_spec.rb spec/integration/relation_querying_spec.rb spec/integration/set_query_spec.rb spec/integration/string_querying_spec.rb spec/unit/ast_spec.rb spec/unit/auto_complete_builder_spec.rb spec/unit/definition_spec.rb spec/unit/parser_spec.rb spec/unit/query_builder_spec.rb spec/unit/tokenizer_spec.rb)
|
39
|
+
end
|
data/spec/database.yml
CHANGED
@@ -7,13 +7,20 @@
|
|
7
7
|
# to you.
|
8
8
|
|
9
9
|
|
10
|
-
<% if Gem.available?('sqlite3-ruby') %>
|
11
|
-
sqlite3:
|
12
|
-
|
13
|
-
|
14
|
-
<% end %>
|
10
|
+
# <% if Gem.available?('sqlite3-ruby') %>
|
11
|
+
# sqlite3:
|
12
|
+
# adapter: "sqlite3"
|
13
|
+
# database: ":memory:"
|
14
|
+
# <% end %>
|
15
15
|
|
16
|
-
<% if Gem.available?('
|
16
|
+
<% if Gem.available?('mysql2') %>
|
17
|
+
mysql2:
|
18
|
+
adapter: "mysql2"
|
19
|
+
host: "localhost"
|
20
|
+
username: "root"
|
21
|
+
password:
|
22
|
+
database: "scoped_search_test"
|
23
|
+
<% elsif Gem.available?('mysql') %>
|
17
24
|
mysql:
|
18
25
|
adapter: "mysql"
|
19
26
|
host: "localhost"
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../spec_helper"
|
2
|
+
|
3
|
+
# These specs will run on all databases that are defined in the spec/database.yml file.
|
4
|
+
# Comment out any databases that you do not have available for testing purposes if needed.
|
5
|
+
ScopedSearch::RSpec::Database.test_databases.each do |db|
|
6
|
+
|
7
|
+
describe ScopedSearch, "using a #{db} database" do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
ScopedSearch::RSpec::Database.establish_named_connection(db)
|
11
|
+
|
12
|
+
## The related class
|
13
|
+
ActiveRecord::Migration.drop_table(:bars) rescue nil
|
14
|
+
ActiveRecord::Migration.create_table(:bars) { |t| t.string :related; t.integer :foo_id }
|
15
|
+
class ::Bar < ActiveRecord::Base; belongs_to :foo; end
|
16
|
+
|
17
|
+
::Foo = ScopedSearch::RSpec::Database.create_model(:string => :string, :another => :string, :explicit => :string, :int => :integer, :date => :date, :unindexed => :integer) do |klass|
|
18
|
+
klass.has_many :bars
|
19
|
+
|
20
|
+
klass.scoped_search :on => [:string, :int, :date]
|
21
|
+
klass.scoped_search :on => :another, :default_operator => :eq, :alias => :alias
|
22
|
+
klass.scoped_search :on => :explicit, :only_explicit => true, :complete_value => true
|
23
|
+
klass.scoped_search :on => :related, :in => :bars, :rename => 'bars.related'.to_sym
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
@foo_1 = Foo.create!(:string => 'foo', :another => 'temp 1', :explicit => 'baz', :int => 9 , :date => 'February 8, 20011' , :unindexed => 10)
|
28
|
+
Foo.create!(:string => 'bar', :another => 'temp 2', :explicit => 'baz', :int => 9 , :date => 'February 10, 20011', :unindexed => 10)
|
29
|
+
Foo.create!(:string => 'baz', :another => nil, :explicit => nil , :int => nil, :date => nil , :unindexed => nil)
|
30
|
+
|
31
|
+
Bar.create!(:related => 'lala', :foo => @foo_1)
|
32
|
+
Bar.create!(:related => 'another lala', :foo => @foo_1)
|
33
|
+
end
|
34
|
+
|
35
|
+
after(:all) do
|
36
|
+
ScopedSearch::RSpec::Database.drop_model(Foo)
|
37
|
+
ScopedSearch::RSpec::Database.drop_model(Bar)
|
38
|
+
ScopedSearch::RSpec::Database.close_connection
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'basic auto completer' do
|
42
|
+
it "should complete the field name" do
|
43
|
+
Foo.complete_for('str').should =~ ([' string '])
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should not complete the logical operators at the beginning" do
|
47
|
+
Foo.complete_for('a').should_not contain(['and'])
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should complete the string comparators" do
|
51
|
+
Foo.complete_for('string ').should =~ (["string != ", "string !~ ", "string = ", "string ~ "])
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should complete the numerical comparators" do
|
55
|
+
Foo.complete_for('int ').should =~ (["int != ", "int < ", "int <= ", "int = ", "int > ", "int >= "])
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should complete the temporal (date) comparators" do
|
59
|
+
Foo.complete_for('date ').should =~ (["date = ", "date < ", "date > "])
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should raise error for unindexed field" do
|
63
|
+
lambda { Foo.complete_for('unindexed = 10 ')}.should raise_error(ScopedSearch::QueryNotSupported)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should raise error for unknown field" do
|
67
|
+
lambda {Foo.complete_for('unknown = 10 ')}.should raise_error(ScopedSearch::QueryNotSupported)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should complete logical comparators" do
|
71
|
+
Foo.complete_for('string ~ fo ').should contain("string ~ fo and", "string ~ fo or")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should complete prefix operators" do
|
75
|
+
Foo.complete_for(' ').should contain(" has", " not")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should not complete logical infix operators" do
|
79
|
+
Foo.complete_for(' ').should_not contain(" and", " or")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should not repeat logical operators" do
|
83
|
+
Foo.complete_for('string = foo and ').should_not contain("string = foo and and", "string = foo and or")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'using an aliased field' do
|
88
|
+
it "should complete an explicit match using its alias" do
|
89
|
+
Foo.complete_for('al').should contain(' alias ')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'value auto complete' do
|
94
|
+
it "should complete values list of values " do
|
95
|
+
Foo.complete_for('explicit = ').should have(1).item
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should complete values should contain baz" do
|
99
|
+
Foo.complete_for('explicit = ').should contain('explicit = baz')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'auto complete relations' do
|
104
|
+
it "should complete related object name" do
|
105
|
+
Foo.complete_for('ba').should contain(' bars.related ')
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should complete related object name with field name" do
|
109
|
+
Foo.complete_for('bars.').should contain(' bars.related ')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'using null prefix operators queries' do
|
114
|
+
|
115
|
+
it "should complete has operator" do
|
116
|
+
Foo.complete_for('has strin').should eql(['has string '])
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should complete null? operator" do
|
120
|
+
Foo.complete_for('null? st').should eql(['null? string '])
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should complete set? operator" do
|
124
|
+
Foo.complete_for('set? exp').should eql(['set? explicit '])
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should complete null? operator for explicit field" do
|
128
|
+
Foo.complete_for('null? explici').should eql(['null? explicit '])
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should not complete comparators after prefix statement" do
|
132
|
+
Foo.complete_for('has string ').should_not contain(['has string = '])
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should not complete infix operator" do
|
136
|
+
Foo.complete_for('has string ').should_not contain('has string = ')
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../spec_helper"
|
2
|
+
|
3
|
+
# These specs will run on all databases that are defined in the spec/database.yml file.
|
4
|
+
# Comment out any databases that you do not have available for testing purposes if needed.
|
5
|
+
ScopedSearch::RSpec::Database.test_databases.each do |db|
|
6
|
+
|
7
|
+
describe ScopedSearch, "using a #{db} database" do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
ScopedSearch::RSpec::Database.establish_named_connection(db)
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:all) do
|
14
|
+
ScopedSearch::RSpec::Database.close_connection
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'querying a key-value schema' do
|
18
|
+
|
19
|
+
before(:all) do
|
20
|
+
ActiveRecord::Migration.create_table(:keys) { |t| t.string :name }
|
21
|
+
class ::Key < ActiveRecord::Base; has_many :facts; end
|
22
|
+
|
23
|
+
ActiveRecord::Migration.create_table(:facts) { |t| t.string :value; t.integer :key_id; t.integer :bar_id }
|
24
|
+
class ::Fact < ActiveRecord::Base; belongs_to :key; belongs_to :bar; end
|
25
|
+
|
26
|
+
# The class that will run the queries
|
27
|
+
::Bar = ScopedSearch::RSpec::Database.create_model(:name => :string) do |klass|
|
28
|
+
klass.has_many :facts
|
29
|
+
klass.has_many :keys, :through => :facts
|
30
|
+
klass.scoped_search :in => :facts, :on => :value, :rename => :facts, :in_key => :keys, :on_key => :name, :complete_value => true
|
31
|
+
end
|
32
|
+
|
33
|
+
@key1 = Key.create!(:name => 'color')
|
34
|
+
@key2 = Key.create!(:name => 'size')
|
35
|
+
|
36
|
+
|
37
|
+
@bar1 = Bar.create!(:name => 'bar')
|
38
|
+
@bar2 = Bar.create!(:name => 'barbary')
|
39
|
+
|
40
|
+
Fact.create!(:value => 'green', :key => @key1, :bar => @bar1)
|
41
|
+
Fact.create!(:value => 'gold' , :key => @key1, :bar => @bar2)
|
42
|
+
Fact.create!(:value => '5' , :key => @key2, :bar => @bar1)
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
after(:all) do
|
47
|
+
ScopedSearch::RSpec::Database.drop_model(Bar)
|
48
|
+
ScopedSearch::RSpec::Database.drop_model(Fact)
|
49
|
+
ScopedSearch::RSpec::Database.drop_model(Key)
|
50
|
+
Object.send :remove_const, :Fact
|
51
|
+
Object.send :remove_const, :Key
|
52
|
+
Object.send :remove_const, :Bar
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should find all bars with a fact name color and fact value green" do
|
56
|
+
Bar.search_for('facts.color = green').should have(1).items
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should find all bars with a fact name size and fact value 5" do
|
60
|
+
Bar.search_for('facts.size = 5').should have(1).items
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should find all bars with a fact color green and fact size 5" do
|
64
|
+
Bar.search_for('facts.color = green and facts.size = 5').should have(1).items
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should find all bars that has size value" do
|
68
|
+
Bar.search_for('has facts.size').should have(1).items
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should find all bars that has color value" do
|
72
|
+
Bar.search_for('has facts.color').should have(2).items
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should complete facts names" do
|
76
|
+
Bar.complete_for('facts.').should have(2).items
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should complete values for fact name = color" do
|
80
|
+
Bar.complete_for('facts.color = ').should have(2).items
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|