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 CHANGED
@@ -6,3 +6,4 @@ scoped_search-*.gem
6
6
  /coverage
7
7
  /classes
8
8
  /files
9
+ /spec/database.yml
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 scoped_search in your project is adding the scoped_search gem to your environment. Add the following code to your Rails configuration in <tt>config/environment.rb</tt>:
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 'wvanbergen-scoped_search', :lib => 'scoped_search',
21
- :source => 'http://gems.github.com/'
40
+ config.gem 'scoped_search'
22
41
  end
23
42
 
24
- Run <tt>sudo rake gems:install</tt> to install the gem.
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/master
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://wvanbergen.github.com/scoped_search
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.1"
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, :fields, :unique_fields
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 = 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.named_scope(:search_for, lambda { |*args| ScopedSearch::QueryBuilder.build_query(args[1] || self, args[0]) })
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, query)
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(builder, operator = nil, &block) # :yields: finder_option_type, value
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
- "(#{fragments.join(' OR ')})"
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
- "(NOT(#{rhs.to_sql(builder, definition, &block)}) OR #{rhs.to_sql(builder, definition, &block)} IS NULL)"
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
- fragments.empty? ? nil : "(#{fragments.join(' OR ')})"
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 : "(#{fragments.join(" #{operator.to_s.upcase} ")})"
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
@@ -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.1"
7
- s.date = "2009-10-02"
6
+ s.version = "2.2.0"
7
+ s.date = "2010-05-26"
8
8
 
9
- s.summary = "A Rails plugin to search your models with a simple query language, implemented using a named_scope"
10
- s.description = <<EOS
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@vanbergen.org', 'weshays@gbdev.com']
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 spec/integration/relation_querying_spec.rb .gitignore spec/lib/mocks.rb scoped_search.gemspec lib/scoped_search/query_language/parser.rb LICENSE spec/lib/matchers.rb lib/scoped_search/definition.rb init.rb spec/unit/tokenizer_spec.rb spec/unit/parser_spec.rb spec/unit/ast_spec.rb lib/scoped_search/query_language/ast.rb spec/lib/database.rb Rakefile tasks/github-gem.rake spec/unit/query_builder_spec.rb lib/scoped_search/query_language.rb lib/scoped_search/query_builder.rb README.rdoc spec/unit/definition_spec.rb spec/database.yml spec/integration/api_spec.rb spec/integration/ordinal_querying_spec.rb lib/scoped_search/query_language/tokenizer.rb lib/scoped_search.rb)
31
- s.test_files = %w(spec/integration/string_querying_spec.rb spec/integration/relation_querying_spec.rb spec/unit/tokenizer_spec.rb spec/unit/parser_spec.rb spec/unit/ast_spec.rb spec/unit/query_builder_spec.rb spec/unit/definition_spec.rb spec/integration/api_spec.rb spec/integration/ordinal_querying_spec.rb)
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
- user: "root"
16
+ username: "root"
17
17
  password:
18
18
  database: "scoped_search_test"
19
19
 
20
- postgresql:
21
- adapter: "postgresql"
22
- host: "localhost"
23
- username: "scoped_search"
24
- password: "scoped_search"
25
- database: "scoped_search_test"
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
- it "should return an ActiveRecord::NamedScope::Scope when :search_for is called" do
48
- @class.search_for('query').class.should eql(ActiveRecord::NamedScope::Scope)
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 => 'temp 3', :explicit => nil)
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 opther two records using NOT with an exact string match" do
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(3).item
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(3).item
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
- it "should create the named scope when" do
14
- @klass.should_receive(:named_scope).with(:search_for, instance_of(Proc))
15
- ScopedSearch::Definition.new(@klass)
16
- end
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
- it "should not create the named scope if it already exists" do
19
- @klass.stub!(:search_for)
20
- @klass.should_not_receive(:named_scope)
21
- ScopedSearch::Definition.new(@klass)
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
@@ -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 verison of the gem"
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
- task(:check_rubyforge) { check_rubyforge_task }
134
- task(:rubyforge_release) { rubyforge_release_task }
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
- # Loads the latest version number using the created tags
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
- # Login no longer necessary when using rubyforge 2.0.0 gem
221
- # raise "Could not login on rubyforge!" unless `rubyforge login 2>&1`.strip.empty?
222
- output = `rubyforge names`.split("\n")
223
- raise "Rubyforge group not found!" unless output.any? { |line| %r[^groups\s*\:.*\b#{Regexp.quote(gemspec.rubyforge_project)}\b.*] =~ line }
224
- raise "Rubyforge package not found!" unless output.any? { |line| %r[^packages\s*\:.*\b#{Regexp.quote(gemspec.name)}\b.*] =~ line }
225
- end
226
-
227
- # Task to release the .gem file toRubyforge.
228
- def rubyforge_release_task
229
- sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version.to_s, "pkg/#{gemspec.name}-#{gemspec.version}.gem"
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
- version: 2.0.1
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: 2009-10-02 00:00:00 +02:00
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
- type: :runtime
19
- version_requirement:
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
- version:
33
+ type: :runtime
34
+ version_requirements: *id001
26
35
  - !ruby/object:Gem::Dependency
27
36
  name: rspec
28
- type: :development
29
- version_requirement:
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
- version:
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@vanbergen.org
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
- - spec/integration/relation_querying_spec.rb
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
- - lib/scoped_search/definition.rb
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/unit/ast_spec.rb
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.yml
71
- - spec/integration/api_spec.rb
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.5
120
+ rubygems_version: 1.3.6
105
121
  signing_key:
106
122
  specification_version: 3
107
- summary: A Rails plugin to search your models with a simple query language, implemented using a named_scope
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/integration/relation_querying_spec.rb
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