scoped_search 2.0.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.rdoc +25 -8
- data/lib/scoped_search.rb +1 -1
- data/lib/scoped_search/definition.rb +38 -5
- data/lib/scoped_search/query_builder.rb +41 -16
- data/scoped_search.gemspec +8 -8
- data/spec/database.yml +7 -7
- data/spec/integration/api_spec.rb +12 -2
- data/spec/integration/profile_querying_spec.rb +61 -0
- data/spec/integration/string_querying_spec.rb +8 -4
- data/spec/lib/mocks.rb +2 -0
- data/spec/unit/definition_spec.rb +26 -8
- data/spec/unit/query_builder_spec.rb +2 -0
- data/tasks/github-gem.rake +69 -21
- metadata +48 -31
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -10,20 +10,37 @@ and build a query based on the search string they enter. If you want to build a
|
|
10
10
|
more complex search form with multiple fields, searchlogic (see
|
11
11
|
http://github.com/binarylogic/searchlogic) may be a good choice for you.
|
12
12
|
|
13
|
+
=== Usage
|
14
|
+
|
15
|
+
If you include a :profile parameter in scoped_search, the fields specified will only
|
16
|
+
be search when you pass :profile into the search_for command:
|
17
|
+
|
18
|
+
class User < ActiveRecord::Base
|
19
|
+
scoped_search :on => :public_information
|
20
|
+
scoped_search :on => :private_information, :profile => :members
|
21
|
+
end
|
22
|
+
|
23
|
+
This will only search the :public_information column:
|
24
|
+
|
25
|
+
User.search_for('blah blah blah')
|
26
|
+
|
27
|
+
And this will only search the :private_information column:
|
28
|
+
|
29
|
+
User.search_for('blah blah blah', :profile => :members)
|
13
30
|
|
14
31
|
== Installing
|
15
32
|
|
16
|
-
The recommended method to enable
|
33
|
+
The recommended method to enable scoped search in your project is adding the
|
34
|
+
scoped_search gem to your environment. Add the following code to your Rails
|
35
|
+
configuration in <tt>config/environment.rb</tt>, and run <tt>rake
|
36
|
+
gems:install</tt> to install the gem.:
|
17
37
|
|
18
38
|
Rails::Initializer.run do |config|
|
19
39
|
...
|
20
|
-
config.gem '
|
21
|
-
:source => 'http://gems.github.com/'
|
40
|
+
config.gem 'scoped_search'
|
22
41
|
end
|
23
42
|
|
24
|
-
|
25
|
-
|
26
|
-
Alternatively, install scoped_search as a Rails plugin:
|
43
|
+
Alternatively, install scoped search as a Rails plugin (*deprecated*):
|
27
44
|
|
28
45
|
script/plugin install git://github.com/wvanbergen/scoped_search.git
|
29
46
|
|
@@ -95,9 +112,9 @@ For more info, see the the project wiki: http://wiki.github.com/wvanbergen/scope
|
|
95
112
|
|
96
113
|
== Additional resources
|
97
114
|
|
98
|
-
* Source code: http://github.com/wvanbergen/scoped_search/tree
|
115
|
+
* Source code: http://github.com/wvanbergen/scoped_search/tree
|
99
116
|
* Project wiki: http://wiki.github.com/wvanbergen/scoped_search
|
100
|
-
* RDoc documentation: http://
|
117
|
+
* RDoc documentation: http://rdoc.info/projects/wvanbergen/scoped_search
|
101
118
|
* wvanbergen's blog posts: http://techblog.floorplanner.com/tag/scoped_search
|
102
119
|
|
103
120
|
== License
|
data/lib/scoped_search.rb
CHANGED
@@ -12,7 +12,7 @@ module ScopedSearch
|
|
12
12
|
|
13
13
|
# The current scoped_search version. Do not change thisvalue by hand,
|
14
14
|
# because it will be updated automatically by the gem release script.
|
15
|
-
VERSION = "2.0
|
15
|
+
VERSION = "2.2.0"
|
16
16
|
|
17
17
|
# The ClassMethods module will be included into the ActiveRecord::Base class
|
18
18
|
# to add the <tt>ActiveRecord::Base.scoped_search</tt> method and the
|
@@ -74,6 +74,8 @@ module ScopedSearch
|
|
74
74
|
# scoped_search call on the ActiveRecord-based model class.
|
75
75
|
def initialize(definition, options = {})
|
76
76
|
@definition = definition
|
77
|
+
@definition.profile = options[:profile] if options[:profile]
|
78
|
+
|
77
79
|
case options
|
78
80
|
when Symbol, String
|
79
81
|
@field = field.to_sym
|
@@ -96,18 +98,32 @@ module ScopedSearch
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
99
|
-
attr_reader :klass
|
101
|
+
attr_reader :klass
|
100
102
|
|
101
103
|
# Initializes a ScopedSearch definition instance.
|
102
104
|
# This method will also setup a database adapter and create the :search_for
|
103
105
|
# named scope if it does not yet exist.
|
104
106
|
def initialize(klass)
|
105
|
-
@klass
|
106
|
-
@fields
|
107
|
-
@unique_fields
|
107
|
+
@klass = klass
|
108
|
+
@fields = {}
|
109
|
+
@unique_fields = []
|
110
|
+
@profile_fields = {:default => {}}
|
111
|
+
@profile_unique_fields = {:default => []}
|
108
112
|
|
109
113
|
register_named_scope! unless klass.respond_to?(:search_for)
|
110
114
|
end
|
115
|
+
|
116
|
+
attr_accessor :profile
|
117
|
+
|
118
|
+
def fields
|
119
|
+
@profile ||= :default
|
120
|
+
@profile_fields[@profile] ||= {}
|
121
|
+
end
|
122
|
+
|
123
|
+
def unique_fields
|
124
|
+
@profile ||= :default
|
125
|
+
@profile_unique_fields[@profile] ||= []
|
126
|
+
end
|
111
127
|
|
112
128
|
NUMERICAL_REGXP = /^\-?\d+(\.\d+)?$/
|
113
129
|
|
@@ -139,7 +155,24 @@ module ScopedSearch
|
|
139
155
|
|
140
156
|
# Registers the search_for named scope within the class that is used for searching.
|
141
157
|
def register_named_scope! # :nodoc
|
142
|
-
@klass.
|
158
|
+
if @klass.ancestors.include?(ActiveRecord::Base)
|
159
|
+
case ActiveRecord::VERSION::MAJOR
|
160
|
+
when 2
|
161
|
+
@klass.named_scope(:search_for, lambda { |*args| ScopedSearch::QueryBuilder.build_query(self, args[0], args[1]) })
|
162
|
+
when 3
|
163
|
+
@klass.scope(:search_for, lambda { |*args|
|
164
|
+
find_options = ScopedSearch::QueryBuilder.build_query(self, args[0], args[1])
|
165
|
+
search_scope = @klass.scoped
|
166
|
+
search_scope = search_scope.where(find_options[:conditions]) if find_options[:conditions]
|
167
|
+
search_scope = search_scope.includes(find_options[:include]) if find_options[:include]
|
168
|
+
search_scope
|
169
|
+
})
|
170
|
+
else
|
171
|
+
raise "This ActiveRecord version is currently not supported!"
|
172
|
+
end
|
173
|
+
else
|
174
|
+
raise "Currently, only ActiveRecord 2.1 or higher is supported!"
|
175
|
+
end
|
143
176
|
end
|
144
177
|
end
|
145
178
|
end
|
@@ -14,12 +14,15 @@ module ScopedSearch
|
|
14
14
|
# This method will parse the query string and build an SQL query using the search
|
15
15
|
# query. It will return an ampty hash if the search query is empty, in which case
|
16
16
|
# the scope call will simply return all records.
|
17
|
-
def self.build_query(definition,
|
17
|
+
def self.build_query(definition, *args)
|
18
|
+
query = args[0]
|
19
|
+
options = args[1] || {}
|
20
|
+
|
18
21
|
query_builder_class = self.class_for(definition)
|
19
22
|
if query.kind_of?(ScopedSearch::QueryLanguage::AST::Node)
|
20
|
-
return query_builder_class.new(definition, query).build_find_params
|
23
|
+
return query_builder_class.new(definition, query, options[:profile]).build_find_params
|
21
24
|
elsif query.kind_of?(String)
|
22
|
-
return query_builder_class.new(definition, ScopedSearch::QueryLanguage::Compiler.parse(query)).build_find_params
|
25
|
+
return query_builder_class.new(definition, ScopedSearch::QueryLanguage::Compiler.parse(query), options[:profile]).build_find_params
|
23
26
|
elsif query.nil?
|
24
27
|
return { }
|
25
28
|
else
|
@@ -36,8 +39,8 @@ module ScopedSearch
|
|
36
39
|
end
|
37
40
|
|
38
41
|
# Initializes the instance by setting the relevant parameters
|
39
|
-
def initialize(definition, ast)
|
40
|
-
@definition, @ast = definition, ast
|
42
|
+
def initialize(definition, ast, profile)
|
43
|
+
@definition, @ast, @definition.profile = definition, ast, profile
|
41
44
|
end
|
42
45
|
|
43
46
|
# Actually builds the find parameters hash that should be used in the search_for
|
@@ -63,7 +66,7 @@ module ScopedSearch
|
|
63
66
|
find_attributes = {}
|
64
67
|
find_attributes[:conditions] = [sql] + parameters unless sql.nil?
|
65
68
|
find_attributes[:include] = includes.uniq unless includes.empty?
|
66
|
-
find_attributes # Uncomment for debugging
|
69
|
+
# p find_attributes # Uncomment for debugging
|
67
70
|
return find_attributes
|
68
71
|
end
|
69
72
|
|
@@ -107,7 +110,7 @@ module ScopedSearch
|
|
107
110
|
# fall inside/outside the range of timestamps of that day.
|
108
111
|
yield(:parameter, timestamp)
|
109
112
|
yield(:parameter, timestamp + 1)
|
110
|
-
negate = (operator == :ne) ? 'NOT' : ''
|
113
|
+
negate = (operator == :ne) ? 'NOT ' : ''
|
111
114
|
field_sql = field.to_sql(operator, &block)
|
112
115
|
return "#{negate}(#{field_sql} >= ? AND #{field_sql} < ?)"
|
113
116
|
|
@@ -165,10 +168,10 @@ module ScopedSearch
|
|
165
168
|
# This function may yield an :include that should be used in the
|
166
169
|
# ActiveRecord::Base#find call, to make sure that the field is avalable
|
167
170
|
# for the SQL query.
|
168
|
-
def to_sql(
|
171
|
+
def to_sql(operator = nil, &block) # :yields: finder_option_type, value
|
169
172
|
yield(:include, relation) if relation
|
170
|
-
definition.klass.connection.quote_table_name(klass.table_name) + "." +
|
171
|
-
definition.klass.connection.quote_column_name(field)
|
173
|
+
definition.klass.connection.quote_table_name(klass.table_name.to_s) + "." +
|
174
|
+
definition.klass.connection.quote_column_name(field.to_s)
|
172
175
|
end
|
173
176
|
end
|
174
177
|
|
@@ -182,16 +185,21 @@ module ScopedSearch
|
|
182
185
|
fragments = definition.default_fields_for(value).map do |field|
|
183
186
|
builder.sql_test(field, field.default_operator, value, &block)
|
184
187
|
end
|
185
|
-
|
188
|
+
|
189
|
+
case fragments.length
|
190
|
+
when 0 then nil
|
191
|
+
when 1 then fragments.first
|
192
|
+
else "#{fragments.join(' OR ')}"
|
193
|
+
end
|
186
194
|
end
|
187
195
|
end
|
188
196
|
|
189
197
|
# Defines the to_sql method for AST operator nodes
|
190
198
|
module OperatorNode
|
191
199
|
|
192
|
-
# Returns a NOT(...) SQL fragment that negates the current AST node's children
|
200
|
+
# Returns a NOT (...) SQL fragment that negates the current AST node's children
|
193
201
|
def to_not_sql(builder, definition, &block)
|
194
|
-
"
|
202
|
+
"NOT COALESCE(#{rhs.to_sql(builder, definition, &block)}, 0)"
|
195
203
|
end
|
196
204
|
|
197
205
|
# Returns an IS (NOT) NULL SQL fragment
|
@@ -213,7 +221,11 @@ module ScopedSearch
|
|
213
221
|
fragments = definition.default_fields_for(rhs.value, operator).map { |field|
|
214
222
|
builder.sql_test(field, operator, rhs.value, &block) }.compact
|
215
223
|
|
216
|
-
|
224
|
+
case fragments.length
|
225
|
+
when 0 then nil
|
226
|
+
when 1 then fragments.first
|
227
|
+
else "#{fragments.join(' OR ')}"
|
228
|
+
end
|
217
229
|
end
|
218
230
|
|
219
231
|
# Explicit field name given, run the operator on the specified field only
|
@@ -246,8 +258,8 @@ module ScopedSearch
|
|
246
258
|
# Defines the to_sql method for AST AND/OR operators
|
247
259
|
module LogicalOperatorNode
|
248
260
|
def to_sql(builder, definition, &block)
|
249
|
-
fragments = children.map { |c| c.to_sql(builder, definition, &block) }.compact
|
250
|
-
fragments.empty? ? nil : "
|
261
|
+
fragments = children.map { |c| c.to_sql(builder, definition, &block) }.compact.map { |sql| "(#{sql})" }
|
262
|
+
fragments.empty? ? nil : "#{fragments.join(" #{operator.to_s.upcase} ")}"
|
251
263
|
end
|
252
264
|
end
|
253
265
|
end
|
@@ -284,6 +296,19 @@ module ScopedSearch
|
|
284
296
|
end
|
285
297
|
end
|
286
298
|
end
|
299
|
+
|
300
|
+
# The Oracle adapter also requires some tweaks to make the case insensitive LIKE work.
|
301
|
+
class OracleEnhancedAdapter < ScopedSearch::QueryBuilder
|
302
|
+
|
303
|
+
def sql_test(field, operator, value, &block) # :yields: finder_option_type, value
|
304
|
+
if field.textual? && [:like, :unlike].include?(operator)
|
305
|
+
yield(:parameter, (value !~ /^\%/ && value !~ /\%$/) ? "%#{value}%" : value)
|
306
|
+
return "LOWER(#{field.to_sql(operator, &block)}) #{self.sql_operator(operator, field)} LOWER(?)"
|
307
|
+
else
|
308
|
+
return super(field, operator, value, &block)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
287
312
|
end
|
288
313
|
|
289
314
|
# Include the modules into the corresponding classes
|
data/scoped_search.gemspec
CHANGED
@@ -3,20 +3,20 @@ 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.0
|
7
|
-
s.date = "
|
6
|
+
s.version = "2.2.0"
|
7
|
+
s.date = "2010-05-26"
|
8
8
|
|
9
|
-
s.summary = "
|
10
|
-
s.description =
|
9
|
+
s.summary = "Easily search you ActiveRecord models with a simple query language using a named scope."
|
10
|
+
s.description = <<-EOS
|
11
11
|
Scoped search makes it easy to search your ActiveRecord-based models.
|
12
12
|
It will create a named scope :search_for that can be called with a query string. It will build an SQL query using
|
13
13
|
the provided query string and a definition that specifies on what fields to search. Because the functionality is
|
14
14
|
built on named_scope, the result of the search_for call can be used like any other named_scope, so it can be
|
15
15
|
chained with another scope or combined with will_paginate."
|
16
|
-
EOS
|
16
|
+
EOS
|
17
17
|
|
18
18
|
s.authors = ['Willem van Bergen', 'Wes Hays']
|
19
|
-
s.email = ['willem@
|
19
|
+
s.email = ['willem@railsdoctors.com', 'weshays@gbdev.com']
|
20
20
|
s.homepage = 'http://wiki.github.com/wvanbergen/scoped_search'
|
21
21
|
|
22
22
|
s.add_runtime_dependency('activerecord', '>= 2.1.0')
|
@@ -27,6 +27,6 @@ EOS
|
|
27
27
|
|
28
28
|
# Do not change the files and test_files fields by hand. This will be done
|
29
29
|
# automatically by the gem release script.
|
30
|
-
s.files = %w(spec/spec_helper.rb spec/integration/string_querying_spec.rb
|
31
|
-
s.test_files = %w(spec/integration/string_querying_spec.rb spec/
|
30
|
+
s.files = %w(spec/spec_helper.rb spec/integration/string_querying_spec.rb lib/scoped_search/definition.rb spec/lib/mocks.rb scoped_search.gemspec lib/scoped_search/query_language/parser.rb spec/lib/matchers.rb .gitignore LICENSE spec/unit/ast_spec.rb spec/database.yml init.rb Rakefile spec/unit/tokenizer_spec.rb spec/unit/parser_spec.rb spec/integration/api_spec.rb lib/scoped_search/query_language/ast.rb lib/scoped_search/query_language.rb lib/scoped_search/query_builder.rb README.rdoc spec/unit/definition_spec.rb spec/lib/database.rb spec/integration/profile_querying_spec.rb tasks/github-gem.rake spec/unit/query_builder_spec.rb lib/scoped_search.rb spec/integration/relation_querying_spec.rb spec/integration/ordinal_querying_spec.rb lib/scoped_search/query_language/tokenizer.rb)
|
31
|
+
s.test_files = %w(spec/integration/string_querying_spec.rb spec/unit/ast_spec.rb spec/unit/tokenizer_spec.rb spec/unit/parser_spec.rb spec/integration/api_spec.rb spec/unit/definition_spec.rb spec/integration/profile_querying_spec.rb spec/unit/query_builder_spec.rb spec/integration/relation_querying_spec.rb spec/integration/ordinal_querying_spec.rb)
|
32
32
|
end
|
data/spec/database.yml
CHANGED
@@ -13,13 +13,13 @@ sqlite3:
|
|
13
13
|
mysql:
|
14
14
|
adapter: "mysql"
|
15
15
|
host: "localhost"
|
16
|
-
|
16
|
+
username: "root"
|
17
17
|
password:
|
18
18
|
database: "scoped_search_test"
|
19
19
|
|
20
|
-
postgresql:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
# postgresql:
|
21
|
+
# adapter: "postgresql"
|
22
|
+
# host: "localhost"
|
23
|
+
# username: "scoped_search"
|
24
|
+
# password: "scoped_search"
|
25
|
+
# database: "scoped_search_test"
|
@@ -44,8 +44,18 @@ describe ScopedSearch, "API" do
|
|
44
44
|
@class.should respond_to(:search_for)
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
if ActiveRecord::VERSION::MAJOR == 2
|
48
|
+
|
49
|
+
it "should return a ActiveRecord::NamedScope::Scope instance" do
|
50
|
+
@class.search_for('query').class.should eql(ActiveRecord::NamedScope::Scope)
|
51
|
+
end
|
52
|
+
|
53
|
+
elsif ActiveRecord::VERSION::MAJOR == 3
|
54
|
+
|
55
|
+
it "should return a ActiveRecord::Relation instance" do
|
56
|
+
@class.search_for('query').class.should eql(ActiveRecord::Relation)
|
57
|
+
end
|
58
|
+
|
49
59
|
end
|
50
60
|
end
|
51
61
|
|
@@ -0,0 +1,61 @@
|
|
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::Spec::Database.test_databases.each do |db|
|
6
|
+
|
7
|
+
describe ScopedSearch, "using a #{db} database" do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
ScopedSearch::Spec::Database.establish_named_connection(db)
|
11
|
+
|
12
|
+
@class = ScopedSearch::Spec::Database.create_model(:public => :string, :private => :string, :useless => :string) do |klass|
|
13
|
+
klass.scoped_search :on => :public
|
14
|
+
klass.scoped_search :on => :private, :profile => :private_profile
|
15
|
+
klass.scoped_search :on => :useless, :profile => :another_profile
|
16
|
+
end
|
17
|
+
|
18
|
+
@item1 = @class.create!(:public => 'foo', :private => 'bar', :useless => 'boo')
|
19
|
+
@item2 = @class.create!(:public => 'qwerty', :private => 'foo', :useless => 'cool')
|
20
|
+
@item3 = @class.create!(:public => 'asdf', :private => 'blargh', :useless => 'foo')
|
21
|
+
end
|
22
|
+
|
23
|
+
after(:all) do
|
24
|
+
ScopedSearch::Spec::Database.drop_model(@class)
|
25
|
+
ScopedSearch::Spec::Database.close_connection
|
26
|
+
end
|
27
|
+
|
28
|
+
context "searching without profile specified" do
|
29
|
+
before(:each) do
|
30
|
+
@results = @class.search_for('foo')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should find results on column specified" do
|
34
|
+
@results.should include(@item1)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not find results on columns only specified with a given profile" do
|
38
|
+
@results.should_not include(@item2)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "searching with profile specified" do
|
43
|
+
before(:each) do
|
44
|
+
@results = @class.search_for('foo', :profile => :private_profile)
|
45
|
+
end
|
46
|
+
|
47
|
+
# it "should find results on columns indexed w/o profile" do
|
48
|
+
# @results.should include(@item1)
|
49
|
+
# end
|
50
|
+
|
51
|
+
it "should find results on column indexed with specified profile" do
|
52
|
+
@results.should include(@item2)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not find results on a column indexed with a different profile" do
|
56
|
+
@results.should_not include(@item3)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -17,7 +17,7 @@ ScopedSearch::Spec::Database.test_databases.each do |db|
|
|
17
17
|
|
18
18
|
@class.create!(:string => 'foo', :another => 'temp 1', :explicit => 'baz')
|
19
19
|
@class.create!(:string => 'bar', :another => 'temp 2', :explicit => 'baz')
|
20
|
-
@class.create!(:string => 'baz', :another =>
|
20
|
+
@class.create!(:string => 'baz', :another => nil, :explicit => nil)
|
21
21
|
end
|
22
22
|
|
23
23
|
after(:all) do
|
@@ -30,7 +30,7 @@ ScopedSearch::Spec::Database.test_databases.each do |db|
|
|
30
30
|
@class.search_for('foo').should have(1).item
|
31
31
|
end
|
32
32
|
|
33
|
-
it "should find the
|
33
|
+
it "should find the other two records using NOT with an exact string match" do
|
34
34
|
@class.search_for('-foo').should have(2).item
|
35
35
|
end
|
36
36
|
|
@@ -133,11 +133,15 @@ ScopedSearch::Spec::Database.test_databases.each do |db|
|
|
133
133
|
end
|
134
134
|
|
135
135
|
it "should find a partial match when the like operator is given" do
|
136
|
-
@class.search_for('~ temp').should have(
|
136
|
+
@class.search_for('~ temp').should have(2).item
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should find a negation of partial match when the like operator is give with an explicit NOT operator" do
|
140
|
+
@class.search_for('!(~ temp)').should have(1).item
|
137
141
|
end
|
138
142
|
|
139
143
|
it "should find a partial match when the like operator and the field name is given" do
|
140
|
-
@class.search_for('another ~ temp').should have(
|
144
|
+
@class.search_for('another ~ temp').should have(2).item
|
141
145
|
end
|
142
146
|
end
|
143
147
|
|
data/spec/lib/mocks.rb
CHANGED
@@ -7,7 +7,9 @@ module ScopedSearch::Spec::Mocks
|
|
7
7
|
def mock_activerecord_class
|
8
8
|
ar_mock = mock('ActiveRecord::Base')
|
9
9
|
ar_mock.stub!(:named_scope).with(:search_for, anything)
|
10
|
+
ar_mock.stub!(:scope).with(:search_for, anything)
|
10
11
|
ar_mock.stub!(:connection).and_return(mock_database_connection)
|
12
|
+
ar_mock.stub!(:ancestors).and_return([ActiveRecord::Base])
|
11
13
|
return ar_mock
|
12
14
|
end
|
13
15
|
|
@@ -8,17 +8,35 @@ describe ScopedSearch::Definition do
|
|
8
8
|
@definition.stub!(:setup_adapter)
|
9
9
|
end
|
10
10
|
|
11
|
+
|
11
12
|
describe '#initialize' do
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
if ActiveRecord::VERSION::MAJOR == 2
|
15
|
+
|
16
|
+
it "should create the named scope when" do
|
17
|
+
@klass.should_receive(:named_scope).with(:search_for, instance_of(Proc))
|
18
|
+
ScopedSearch::Definition.new(@klass)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not create the named scope if it already exists" do
|
22
|
+
@klass.stub!(:search_for)
|
23
|
+
@klass.should_not_receive(:named_scope)
|
24
|
+
ScopedSearch::Definition.new(@klass)
|
25
|
+
end
|
26
|
+
|
27
|
+
elsif ActiveRecord::VERSION::MAJOR == 3
|
28
|
+
|
29
|
+
it "should create the named scope when" do
|
30
|
+
@klass.should_receive(:scope).with(:search_for, instance_of(Proc))
|
31
|
+
ScopedSearch::Definition.new(@klass)
|
32
|
+
end
|
17
33
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
34
|
+
it "should not create the named scope if it already exists" do
|
35
|
+
@klass.stub!(:search_for)
|
36
|
+
@klass.should_not_receive(:scope)
|
37
|
+
ScopedSearch::Definition.new(@klass)
|
38
|
+
end
|
39
|
+
|
22
40
|
end
|
23
41
|
end
|
24
42
|
end
|
@@ -5,6 +5,8 @@ describe ScopedSearch::QueryBuilder do
|
|
5
5
|
before(:each) do
|
6
6
|
@definition = mock('ScopedSearch::Definition')
|
7
7
|
@definition.stub!(:klass).and_return(Class.new(ActiveRecord::Base))
|
8
|
+
@definition.stub!(:profile).and_return(:default)
|
9
|
+
@definition.stub!(:profile=).and_return(true)
|
8
10
|
end
|
9
11
|
|
10
12
|
it "should return empty conditions if the search query is nil" do
|
data/tasks/github-gem.rake
CHANGED
@@ -119,23 +119,43 @@ module GithubGem
|
|
119
119
|
checks = [:check_current_branch, :check_clean_status, :check_not_diverged, :check_version]
|
120
120
|
checks.unshift('spec:basic') if has_specs?
|
121
121
|
checks.unshift('test:basic') if has_tests?
|
122
|
-
checks.push << [:check_rubyforge] if gemspec.rubyforge_project
|
122
|
+
# checks.push << [:check_rubyforge] if gemspec.rubyforge_project
|
123
123
|
|
124
124
|
desc "Perform all checks that would occur before a release"
|
125
125
|
task(:release_checks => checks)
|
126
126
|
|
127
|
-
release_tasks = [:release_checks, :set_version, :build, :github_release]
|
128
|
-
release_tasks << [:rubyforge_release] if gemspec.rubyforge_project
|
127
|
+
release_tasks = [:release_checks, :set_version, :build, :github_release, :gemcutter_release]
|
128
|
+
# release_tasks << [:rubyforge_release] if gemspec.rubyforge_project
|
129
129
|
|
130
|
-
desc "Release a new
|
130
|
+
desc "Release a new version of the gem using the VERSION environment variable"
|
131
131
|
task(:release => release_tasks) { release_task }
|
132
|
+
|
133
|
+
namespace(:release) do
|
134
|
+
desc "Release the next version of the gem, by incrementing the last version segment by 1"
|
135
|
+
task(:next => [:next_version] + release_tasks) { release_task }
|
132
136
|
|
133
|
-
|
134
|
-
|
137
|
+
desc "Release the next version of the gem, using a bump increment (0.0.1)"
|
138
|
+
task(:bump => [:next_bump_version] + release_tasks) { release_task }
|
139
|
+
|
140
|
+
desc "Release the next version of the gem, using a minor increment (0.1.0)"
|
141
|
+
task(:minor => [:next_minor_version] + release_tasks) { release_task }
|
142
|
+
|
143
|
+
desc "Release the next version of the gem, using a major increment (1.0.0)"
|
144
|
+
task(:major => [:next_major_version] + release_tasks) { release_task }
|
145
|
+
end
|
146
|
+
|
147
|
+
# task(:check_rubyforge) { check_rubyforge_task }
|
148
|
+
# task(:rubyforge_release) { rubyforge_release_task }
|
149
|
+
task(:gemcutter_release) { gemcutter_release_task }
|
135
150
|
task(:github_release => [:commit_modified_files, :tag_version]) { github_release_task }
|
136
151
|
task(:tag_version) { tag_version_task }
|
137
152
|
task(:commit_modified_files) { commit_modified_files_task }
|
138
153
|
|
154
|
+
task(:next_version) { next_version_task }
|
155
|
+
task(:next_bump_version) { next_version_task(:bump) }
|
156
|
+
task(:next_minor_version) { next_version_task(:minor) }
|
157
|
+
task(:next_major_version) { next_version_task(:major) }
|
158
|
+
|
139
159
|
desc "Updates the gem release tasks with the latest version on Github"
|
140
160
|
task(:update_tasks) { update_tasks_task }
|
141
161
|
end
|
@@ -159,6 +179,32 @@ module GithubGem
|
|
159
179
|
sh "mv #{gemspec.name}-#{gemspec.version}.gem pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
160
180
|
end
|
161
181
|
|
182
|
+
def newest_version
|
183
|
+
git.tags.map { |tag| tag.name.split('-').last }.compact.map { |v| Gem::Version.new(v) }.max || Gem::Version.new('0.0.0')
|
184
|
+
end
|
185
|
+
|
186
|
+
def next_version(increment = nil)
|
187
|
+
next_version = newest_version.segments
|
188
|
+
increment_index = case increment
|
189
|
+
when :micro then 3
|
190
|
+
when :bump then 2
|
191
|
+
when :minor then 1
|
192
|
+
when :major then 0
|
193
|
+
else next_version.length - 1
|
194
|
+
end
|
195
|
+
|
196
|
+
next_version[increment_index] ||= 0
|
197
|
+
next_version[increment_index] = next_version[increment_index].succ
|
198
|
+
((increment_index + 1)...next_version.length).each { |i| next_version[i] = 0 }
|
199
|
+
|
200
|
+
Gem::Version.new(next_version.join('.'))
|
201
|
+
end
|
202
|
+
|
203
|
+
def next_version_task(increment = nil)
|
204
|
+
ENV['VERSION'] = next_version(increment).version
|
205
|
+
puts "Releasing version #{ENV['VERSION']}..."
|
206
|
+
end
|
207
|
+
|
162
208
|
# Updates the version number in the gemspec file, the VERSION constant in the main
|
163
209
|
# include file and the contents of the VERSION file.
|
164
210
|
def version_task
|
@@ -172,9 +218,7 @@ module GithubGem
|
|
172
218
|
def check_version_task
|
173
219
|
raise "#{ENV['VERSION']} is not a valid version number!" if ENV['VERSION'] && !Gem::Version.correct?(ENV['VERSION'])
|
174
220
|
proposed_version = Gem::Version.new(ENV['VERSION'] || gemspec.version)
|
175
|
-
#
|
176
|
-
newest_version = git.tags.map { |tag| tag.name.split('-').last }.compact.map { |v| Gem::Version.new(v) }.max
|
177
|
-
raise "This version (#{proposed_version}) is not higher than the highest tagged version (#{newest_version})" if newest_version && newest_version >= proposed_version
|
221
|
+
raise "This version (#{proposed_version}) is not higher than the highest tagged version (#{newest_version})" if newest_version >= proposed_version
|
178
222
|
end
|
179
223
|
|
180
224
|
# Checks whether the current branch is not diverged from the remote branch
|
@@ -215,18 +259,22 @@ module GithubGem
|
|
215
259
|
git.push(remote, remote_branch, true)
|
216
260
|
end
|
217
261
|
|
218
|
-
# Checks whether Rubyforge is configured properly
|
219
|
-
def check_rubyforge_task
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
end
|
226
|
-
|
227
|
-
# Task to release the .gem file toRubyforge.
|
228
|
-
def rubyforge_release_task
|
229
|
-
|
262
|
+
# # Checks whether Rubyforge is configured properly
|
263
|
+
# def check_rubyforge_task
|
264
|
+
# # Login no longer necessary when using rubyforge 2.0.0 gem
|
265
|
+
# # raise "Could not login on rubyforge!" unless `rubyforge login 2>&1`.strip.empty?
|
266
|
+
# output = `rubyforge names`.split("\n")
|
267
|
+
# raise "Rubyforge group not found!" unless output.any? { |line| %r[^groups\s*\:.*\b#{Regexp.quote(gemspec.rubyforge_project)}\b.*] =~ line }
|
268
|
+
# raise "Rubyforge package not found!" unless output.any? { |line| %r[^packages\s*\:.*\b#{Regexp.quote(gemspec.name)}\b.*] =~ line }
|
269
|
+
# end
|
270
|
+
|
271
|
+
# # Task to release the .gem file toRubyforge.
|
272
|
+
# def rubyforge_release_task
|
273
|
+
# sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version.to_s, "pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
274
|
+
# end
|
275
|
+
|
276
|
+
def gemcutter_release_task
|
277
|
+
sh "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
230
278
|
end
|
231
279
|
|
232
280
|
# Gem release task.
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scoped_search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 2
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 2.2.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Willem van Bergen
|
@@ -10,32 +15,40 @@ autorequire:
|
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
17
|
|
13
|
-
date:
|
18
|
+
date: 2010-05-26 00:00:00 +02:00
|
14
19
|
default_executable:
|
15
20
|
dependencies:
|
16
21
|
- !ruby/object:Gem::Dependency
|
17
22
|
name: activerecord
|
18
|
-
|
19
|
-
|
20
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
21
25
|
requirements:
|
22
26
|
- - ">="
|
23
27
|
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 1
|
31
|
+
- 0
|
24
32
|
version: 2.1.0
|
25
|
-
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
26
35
|
- !ruby/object:Gem::Dependency
|
27
36
|
name: rspec
|
28
|
-
|
29
|
-
|
30
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
39
|
requirements:
|
32
40
|
- - ">="
|
33
41
|
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 1
|
44
|
+
- 1
|
45
|
+
- 4
|
34
46
|
version: 1.1.4
|
35
|
-
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
36
49
|
description: " Scoped search makes it easy to search your ActiveRecord-based models.\n It will create a named scope :search_for that can be called with a query string. It will build an SQL query using\n the provided query string and a definition that specifies on what fields to search. Because the functionality is\n built on named_scope, the result of the search_for call can be used like any other named_scope, so it can be\n chained with another scope or combined with will_paginate.\"\n"
|
37
50
|
email:
|
38
|
-
- willem@
|
51
|
+
- willem@railsdoctors.com
|
39
52
|
- weshays@gbdev.com
|
40
53
|
executables: []
|
41
54
|
|
@@ -46,32 +59,33 @@ extra_rdoc_files:
|
|
46
59
|
files:
|
47
60
|
- spec/spec_helper.rb
|
48
61
|
- spec/integration/string_querying_spec.rb
|
49
|
-
-
|
50
|
-
- .gitignore
|
62
|
+
- lib/scoped_search/definition.rb
|
51
63
|
- spec/lib/mocks.rb
|
52
64
|
- scoped_search.gemspec
|
53
65
|
- lib/scoped_search/query_language/parser.rb
|
54
|
-
- LICENSE
|
55
66
|
- spec/lib/matchers.rb
|
56
|
-
-
|
67
|
+
- .gitignore
|
68
|
+
- LICENSE
|
69
|
+
- spec/unit/ast_spec.rb
|
70
|
+
- spec/database.yml
|
57
71
|
- init.rb
|
72
|
+
- Rakefile
|
58
73
|
- spec/unit/tokenizer_spec.rb
|
59
74
|
- spec/unit/parser_spec.rb
|
60
|
-
- spec/
|
75
|
+
- spec/integration/api_spec.rb
|
61
76
|
- lib/scoped_search/query_language/ast.rb
|
62
|
-
- spec/lib/database.rb
|
63
|
-
- Rakefile
|
64
|
-
- tasks/github-gem.rake
|
65
|
-
- spec/unit/query_builder_spec.rb
|
66
77
|
- lib/scoped_search/query_language.rb
|
67
78
|
- lib/scoped_search/query_builder.rb
|
68
79
|
- README.rdoc
|
69
80
|
- spec/unit/definition_spec.rb
|
70
|
-
- spec/database.
|
71
|
-
- spec/integration/
|
81
|
+
- spec/lib/database.rb
|
82
|
+
- spec/integration/profile_querying_spec.rb
|
83
|
+
- tasks/github-gem.rake
|
84
|
+
- spec/unit/query_builder_spec.rb
|
85
|
+
- lib/scoped_search.rb
|
86
|
+
- spec/integration/relation_querying_spec.rb
|
72
87
|
- spec/integration/ordinal_querying_spec.rb
|
73
88
|
- lib/scoped_search/query_language/tokenizer.rb
|
74
|
-
- lib/scoped_search.rb
|
75
89
|
has_rdoc: true
|
76
90
|
homepage: http://wiki.github.com/wvanbergen/scoped_search
|
77
91
|
licenses: []
|
@@ -90,28 +104,31 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
104
|
requirements:
|
91
105
|
- - ">="
|
92
106
|
- !ruby/object:Gem::Version
|
107
|
+
segments:
|
108
|
+
- 0
|
93
109
|
version: "0"
|
94
|
-
version:
|
95
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
111
|
requirements:
|
97
112
|
- - ">="
|
98
113
|
- !ruby/object:Gem::Version
|
114
|
+
segments:
|
115
|
+
- 0
|
99
116
|
version: "0"
|
100
|
-
version:
|
101
117
|
requirements: []
|
102
118
|
|
103
119
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.3.
|
120
|
+
rubygems_version: 1.3.6
|
105
121
|
signing_key:
|
106
122
|
specification_version: 3
|
107
|
-
summary:
|
123
|
+
summary: Easily search you ActiveRecord models with a simple query language using a named scope.
|
108
124
|
test_files:
|
109
125
|
- spec/integration/string_querying_spec.rb
|
110
|
-
- spec/
|
126
|
+
- spec/unit/ast_spec.rb
|
111
127
|
- spec/unit/tokenizer_spec.rb
|
112
128
|
- spec/unit/parser_spec.rb
|
113
|
-
- spec/unit/ast_spec.rb
|
114
|
-
- spec/unit/query_builder_spec.rb
|
115
|
-
- spec/unit/definition_spec.rb
|
116
129
|
- spec/integration/api_spec.rb
|
130
|
+
- spec/unit/definition_spec.rb
|
131
|
+
- spec/integration/profile_querying_spec.rb
|
132
|
+
- spec/unit/query_builder_spec.rb
|
133
|
+
- spec/integration/relation_querying_spec.rb
|
117
134
|
- spec/integration/ordinal_querying_spec.rb
|