scoped_search 2.0.1 → 2.2.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 +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
|