supernova 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+
9
+ gem "thinking-sphinx", "2.0.3"
10
+
11
+ group :development do
12
+ gem "autotest"
13
+ gem "autotest-growl"
14
+ gem "rspec", "~> 2.3.0"
15
+ gem "bundler", "~> 1.0.0"
16
+ gem "jeweler", "~> 1.6.0"
17
+ gem "rcov", ">= 0"
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,53 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ ZenTest (4.5.0)
5
+ activemodel (3.0.8)
6
+ activesupport (= 3.0.8)
7
+ builder (~> 2.1.2)
8
+ i18n (~> 0.5.0)
9
+ activerecord (3.0.8)
10
+ activemodel (= 3.0.8)
11
+ activesupport (= 3.0.8)
12
+ arel (~> 2.0.10)
13
+ tzinfo (~> 0.3.23)
14
+ activesupport (3.0.8)
15
+ arel (2.0.10)
16
+ autotest (4.4.6)
17
+ ZenTest (>= 4.4.1)
18
+ autotest-growl (0.2.9)
19
+ builder (2.1.2)
20
+ diff-lcs (1.1.2)
21
+ git (1.2.5)
22
+ i18n (0.5.0)
23
+ jeweler (1.6.2)
24
+ bundler (~> 1.0)
25
+ git (>= 1.2.5)
26
+ rake
27
+ rake (0.9.2)
28
+ rcov (0.9.9)
29
+ riddle (1.3.3)
30
+ rspec (2.3.0)
31
+ rspec-core (~> 2.3.0)
32
+ rspec-expectations (~> 2.3.0)
33
+ rspec-mocks (~> 2.3.0)
34
+ rspec-core (2.3.1)
35
+ rspec-expectations (2.3.0)
36
+ diff-lcs (~> 1.1.2)
37
+ rspec-mocks (2.3.0)
38
+ thinking-sphinx (2.0.3)
39
+ activerecord (>= 3.0.3)
40
+ riddle (>= 1.2.2)
41
+ tzinfo (0.3.27)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ autotest
48
+ autotest-growl
49
+ bundler (~> 1.0.0)
50
+ jeweler (~> 1.6.0)
51
+ rcov
52
+ rspec (~> 2.3.0)
53
+ thinking-sphinx (= 2.0.3)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Tobias Schwab
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,55 @@
1
+ = supernova
2
+
3
+ == Setup
4
+
5
+ require "supernova"
6
+ require "supernova/thiking_sphinx"
7
+
8
+ class class Offer
9
+ include Supernova::ThinkingSphinx
10
+ end
11
+
12
+ === Anonymous Scopes
13
+
14
+ scope = Offer.order("popularity desc").with(:city => "Hamburg").paginate(:page => 2, :per_page => 10)
15
+
16
+ # no query sent yet
17
+ # query is sent e.g. when
18
+
19
+ scope.each { |offer| puts offer.city } # objects of current page are iterated
20
+ scope.total_entries # no objects loaded though
21
+ scope.ids # no objects loaded, only ids are returned
22
+ scope.first or scope.last # all objects are loaded, only first or last in "page" is returned
23
+
24
+ === Named Scopes
25
+
26
+ class Offer
27
+ include Supernova::ThinkingSphinx
28
+
29
+ named_search_scope :popular do
30
+ order("popularity desc")
31
+ end
32
+
33
+ named_search_scope :for_artists do |ids|
34
+ with(:artist_id => ids)
35
+ end
36
+ end
37
+
38
+ Offer.popular.for_artists([1, 2, 3]) # various named scopes can be combined
39
+ Offer.popular.for_artists([1, 2, 3]).with(:available => true) # named scopes can also be combined with anonymous scopes
40
+
41
+ == Contributing to search_scopes
42
+
43
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
44
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
45
+ * Fork the project
46
+ * Start a feature/bugfix branch
47
+ * Commit and push until you are happy with your contribution
48
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
49
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
50
+
51
+ == Copyright
52
+
53
+ Copyright (c) 2011 Tobias Schwab. See LICENSE.txt for
54
+ further details.
55
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "supernova"
18
+ # gem.homepage = "http://github.com/dynport/supernova"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Unified search scopes}
21
+ gem.description = %Q{Unified search scopes}
22
+ gem.email = "tobias.schwab@dynport.de"
23
+ gem.authors = ["Tobias Schwab"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "search_scope #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
data/lib/supernova.rb ADDED
@@ -0,0 +1,20 @@
1
+ module Supernova
2
+ module ClassMethods
3
+ attr_accessor :criteria_class, :defined_named_search_scopes
4
+
5
+ def search_scope
6
+ self.criteria_class.new(self)
7
+ end
8
+
9
+ def named_search_scope(name, &block)
10
+ self.class.send(:define_method, name) do |*args|
11
+ self.search_scope.instance_exec(*args, &block)
12
+ end
13
+ self.defined_named_search_scopes ||= []
14
+ self.defined_named_search_scopes << name
15
+ end
16
+ end
17
+ end
18
+
19
+ require "supernova/criteria"
20
+ require "supernova/thinking_sphinx"
@@ -0,0 +1,114 @@
1
+ class Supernova::Criteria
2
+ attr_accessor :filters, :options, :clazz
3
+
4
+ class << self
5
+ def method_missing(*args)
6
+ scope = self.new
7
+ if scope.respond_to?(args.first)
8
+ scope.send(*args)
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ def select(*args)
15
+ self.new.send(:select, *args)
16
+ end
17
+ end
18
+
19
+ def initialize(clazz = nil)
20
+ self.clazz = clazz
21
+ self.filters = {}
22
+ self.options = {}
23
+ end
24
+
25
+ def for_classes(clazzes)
26
+ merge_filters :classes, [clazzes].flatten
27
+ end
28
+
29
+ def order(order_option)
30
+ merge_options :order, order_option
31
+ end
32
+
33
+ def limit(limit_option)
34
+ merge_options :limit, limit_option
35
+ end
36
+
37
+ def group_by(group_option)
38
+ merge_options :group_by, group_option
39
+ end
40
+
41
+ def search(query)
42
+ merge_filters :search, query
43
+ end
44
+
45
+ def with(filters)
46
+ merge_filters :with, filters
47
+ end
48
+
49
+ def select(fields)
50
+ merge_options :select, fields
51
+ end
52
+
53
+ def conditions(filters)
54
+ merge_filters :conditions, filters
55
+ end
56
+
57
+ def paginate(options)
58
+ merge_options :pagination, options
59
+ end
60
+
61
+ def merge_filters(key, value)
62
+ merge_filters_or_options(self.filters, key, value)
63
+ end
64
+
65
+ def merge_options(key, value)
66
+ merge_filters_or_options(self.options, key, value)
67
+ end
68
+
69
+ def merge_filters_or_options(reference, key, value)
70
+ if value.is_a?(Hash)
71
+ reference[key] ||= Hash.new
72
+ reference[key].merge!(value)
73
+ else
74
+ reference[key] = value
75
+ end
76
+ self
77
+ end
78
+
79
+ def to_parameters
80
+ implement_in_subclass
81
+ end
82
+
83
+ def to_a
84
+ implement_in_subclass
85
+ end
86
+
87
+ def implement_in_subclass
88
+ raise "implement in subclass"
89
+ end
90
+
91
+ def merge(other_criteria)
92
+ other_criteria.filters.each do |key, value|
93
+ self.merge_filters(key, value)
94
+ end
95
+ other_criteria.options.each do |key, value|
96
+ self.merge_options(key, value)
97
+ end
98
+ self
99
+ end
100
+
101
+ def method_missing(*args, &block)
102
+ if args.length == 1 && Array.new.respond_to?(args.first)
103
+ to_a.send(args.first, &block)
104
+ elsif self.named_scope_defined?(args.first)
105
+ self.merge(self.clazz.send(*args)) # merge named scope and current criteria
106
+ else
107
+ super
108
+ end
109
+ end
110
+
111
+ def named_scope_defined?(name)
112
+ self.clazz && self.clazz.respond_to?(:defined_named_search_scopes) && clazz.defined_named_search_scopes.respond_to?(:include?) && clazz.defined_named_search_scopes.include?(name)
113
+ end
114
+ end
@@ -0,0 +1,8 @@
1
+ require "supernova/thinking_sphinx_criteria"
2
+
3
+ module Supernova::ThinkingSphinx
4
+ def self.included(base)
5
+ base.extend(Supernova::ClassMethods)
6
+ base.criteria_class = Supernova::ThinkingSphinxCriteria
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+ require "thinking_sphinx"
2
+
3
+ class Supernova::ThinkingSphinxCriteria < Supernova::Criteria
4
+ def self.index_statement_for(field_name, column = nil)
5
+ column ||= field_name
6
+ [%(CONCAT("#{field_name}_", #{column})), { :as => :"indexed_#{field_name}" }]
7
+ end
8
+
9
+ def to_params
10
+ sphinx_options = { :match_mode => :boolean, :with => {}, :conditions => {} }
11
+ sphinx_options[:order] = self.options[:order] if self.options[:order]
12
+ sphinx_options[:limit] = self.options[:limit] if self.options[:limit]
13
+ sphinx_options[:select] = self.options[:select] if self.options[:select]
14
+ sphinx_options[:group_by] = self.options[:group_by] if self.options[:group_by]
15
+ sphinx_options.merge!(self.options[:pagination]) if self.options[:pagination].is_a?(Hash)
16
+ sphinx_options[:classes] = self.filters[:classes] if self.filters[:classes]
17
+ sphinx_options[:conditions].merge!(self.filters[:conditions]) if self.filters[:conditions]
18
+ [self.filters[:search], sphinx_options]
19
+ end
20
+
21
+ def to_a
22
+ ThinkingSphinx.search(*self.to_params)
23
+ end
24
+
25
+ def ids
26
+ ThinkingSphinx.search_for_ids(*self.to_params)
27
+ end
28
+
29
+ def total_entries
30
+ ids.total_entries
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'supernova'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
13
+
14
+
15
+ class Offer
16
+ end
17
+
18
+ class Host
19
+ end
@@ -0,0 +1,245 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+
3
+ describe "Supernova::Criteria" do
4
+ let(:scope) { Supernova::Criteria.new }
5
+
6
+
7
+ describe "#initialize" do
8
+ it "can be initialized" do
9
+ Supernova::Criteria.new.should be_an_instance_of(Supernova::Criteria)
10
+ end
11
+
12
+ it "sets the clazz_name" do
13
+ Supernova::Criteria.new(Offer).clazz.should == Offer
14
+ end
15
+ end
16
+
17
+ [
18
+ [:order, "popularity desc"],
19
+ [:group_by, "title"],
20
+ [:search, "query"],
21
+ [:limit, 10],
22
+ [:with, { :stars => 2 }],
23
+ [:conditions, { :stars => 2 }],
24
+ [:paginate, { :stars => 2 }],
25
+ [:select, %w(stars)]
26
+ ].each do |args|
27
+ it "returns the scope itself for #{args.first}" do
28
+ scope.send(*args).should == scope
29
+ end
30
+
31
+ it "delegates all methods to the instance when responding to" do
32
+ scope_double = Supernova::Criteria.new
33
+ Supernova::Criteria.should_receive(:new).and_return scope_double
34
+ scope_double.should_receive(args.first).with(*args[1..-1])
35
+ Supernova::Criteria.send(*args)
36
+ end
37
+ end
38
+
39
+ describe "#order" do
40
+ it "sets the order statement" do
41
+ scope.order("popularity desc").options[:order].should == "popularity desc"
42
+ end
43
+ end
44
+
45
+ describe "#group_by" do
46
+ it "sets the group option" do
47
+ scope.group_by("name").options[:group_by].should == "name"
48
+ end
49
+ end
50
+
51
+ it "sets the limit option" do
52
+ scope.limit(77).options[:limit].should == 77
53
+ end
54
+
55
+ describe "#search" do
56
+ it "sets the query" do
57
+ scope.search("title").filters[:search].should == "title"
58
+ end
59
+ end
60
+
61
+ describe "#for_classes" do
62
+ it "sets the correct classes" do
63
+ scope.for_classes([Offer, Host]).filters[:classes].should == [Offer, Host]
64
+ end
65
+
66
+ it "also sets single classes" do
67
+ scope.for_classes(Offer).filters[:classes].should == [Offer]
68
+ end
69
+ end
70
+
71
+ [:with, :conditions].each do |method|
72
+ describe "##{method}" do
73
+ it "adds all filters to the #{method} block" do
74
+ scope.send(method, { :length => 3, :height => 99 }).filters[method].should == { :length => 3, :height => 99 }
75
+ end
76
+
77
+ it "overwrites before set filters" do
78
+ scope.send(method, { :length => 3, :height => 88 }).send(method, { :length => 4 }).filters[method].should == { :length => 4, :height => 88 }
79
+ end
80
+ end
81
+ end
82
+
83
+ it "sets select option" do
84
+ scope.select(%w(a b)).options[:select].should == %w(a b)
85
+ end
86
+
87
+ it "sets the correct pagination fields" do
88
+ scope.paginate(:page => 9, :per_page => 2).options[:pagination].should == { :page => 9, :per_page => 2 }
89
+ end
90
+
91
+ it "to_parameters raises an implement in subclass error" do
92
+ lambda {
93
+ scope.to_parameters
94
+ }.should raise_error("implement in subclass")
95
+ end
96
+
97
+ it "to_a raises an implement in subclass error" do
98
+ lambda {
99
+ scope.to_a
100
+ }.should raise_error("implement in subclass")
101
+ end
102
+
103
+ describe "with to_a stubbed" do
104
+ let(:array_double) { double("array") }
105
+
106
+ before(:each) do
107
+ scope.stub!(:to_a).and_return array_double
108
+ end
109
+
110
+ [ :first, :each, :count, :last ].each do |method|
111
+ it "forwards #{method} to array" do
112
+ ret = double("ret")
113
+ array_double.should_receive(method).and_return ret
114
+ scope.send(method)
115
+ end
116
+ end
117
+
118
+ it "hands given blocks in" do
119
+ array = [1, 2, 3]
120
+ scope.stub!(:to_a).and_return array
121
+ called = []
122
+ scope.each do |i|
123
+ called << i
124
+ end
125
+ called.should == array
126
+ end
127
+
128
+ it "does raise errors when array does not respond" do
129
+ lambda {
130
+ scope.rgne
131
+ }.should raise_error(NoMethodError)
132
+
133
+ end
134
+ end
135
+
136
+ describe "#method_missing" do
137
+ it "raises a no method error when methd not defined" do
138
+ lambda {
139
+ scope.method_missing(:rgne)
140
+ }.should raise_error(NoMethodError)
141
+ end
142
+
143
+ it "calls named_scope_defined" do
144
+ scope.should_receive(:named_scope_defined?).with(:rgne).and_return false
145
+ scope.method_missing(:rgne) rescue nil
146
+ end
147
+
148
+ it "does not call named scopes when named_scope_defined? returns false" do
149
+ clazz = double("clazz")
150
+ scope = Supernova::Criteria.new(clazz)
151
+ scope.stub(:named_scope_defined?).and_return false
152
+ clazz.should_not_receive(:rgne)
153
+ end
154
+
155
+ it "it calls merge with self and returned scope" do
156
+ clazz = double("clazz")
157
+ scope = Supernova::Criteria.new(clazz)
158
+ scope.stub(:named_scope_defined?).and_return true
159
+ rge_scope = double("rgne_scope")
160
+ scope_ret = double("ret")
161
+ clazz.should_receive(:rgne).with(1, 2).and_return scope_ret
162
+ merge_ret = double("merge_ret")
163
+ scope.should_receive(:merge).with(scope_ret).and_return merge_ret
164
+ scope.method_missing(:rgne, 1, 2).should == merge_ret
165
+ end
166
+ end
167
+
168
+ describe "#merge" do
169
+ let(:criteria) { Supernova::Criteria.new.order("popularity asc").with(:a => 1).conditions(:b => 2).search("New Search") }
170
+ let(:new_crit) { Supernova::Criteria.new.order("popularity desc").with(:c => 8).conditions(:e => 9).search("Search") }
171
+
172
+ it "it returns the original criteria" do
173
+ new_crit.merge(criteria).should == new_crit
174
+ end
175
+
176
+ it "merges e.g. the order" do
177
+ new_crit.merge(criteria).options[:order].should == "popularity asc"
178
+ end
179
+
180
+ it "merges e.g. the with filters" do
181
+ new_crit.merge(criteria).filters[:with].should == { :c => 8, :a => 1 }
182
+ end
183
+
184
+ it "merges e.g. the conditions filters" do
185
+ new_crit.merge(criteria).filters[:conditions].should == { :b => 2, :e => 9 }
186
+ end
187
+
188
+ it "merges search search" do
189
+ new_crit.merge(criteria).filters[:search].should == "New Search"
190
+ end
191
+
192
+ it "calls merge on options" do
193
+ criteria.stub!(:options).and_return({ :x => 2, :y => 9 })
194
+ new_crit.stub!(:options).and_return({ :z => 3, :c => 1 })
195
+ new_crit.should_receive(:merge_options).with(:x, 2)
196
+ new_crit.should_receive(:merge_options).with(:y, 9)
197
+ new_crit.merge(criteria)
198
+ end
199
+
200
+ it "calls merge filters on all filters" do
201
+ criteria.stub!(:filters).and_return({ :a => 1, :c => 3 })
202
+ new_crit.stub!(:filters).and_return({ :b => 2, :e => 1 })
203
+ new_crit.should_receive(:merge_filters).with(:a, 1)
204
+ new_crit.should_receive(:merge_filters).with(:c, 3)
205
+ new_crit.merge(criteria)
206
+ end
207
+ end
208
+
209
+ describe "#named_scope_defined?" do
210
+ it "returns false when clazz is nil" do
211
+ Supernova::Criteria.new.should_not be_named_scope_defined(:rgne)
212
+ end
213
+
214
+ it "returns false when clazz is present but not responding to defined_search_scopes" do
215
+ Supernova::Criteria.new("test").should_not be_named_scope_defined(:rgne)
216
+ end
217
+
218
+ it "returns false when clazz is responding to defined_search_scopes but empty" do
219
+ clazz = Class.new
220
+ class << clazz
221
+ attr_accessor :defined_named_search_scopes
222
+ end
223
+ clazz.defined_named_search_scopes = nil
224
+ Supernova::Criteria.new(clazz).should_not be_named_scope_defined(:rgne)
225
+ end
226
+
227
+ it "returns false when clazz is responding to defined_search_scopes but not included" do
228
+ clazz = Class.new
229
+ class << clazz
230
+ attr_accessor :defined_named_search_scopes
231
+ end
232
+ clazz.defined_named_search_scopes = [:some_other]
233
+ Supernova::Criteria.new(clazz).should_not be_named_scope_defined(:rgne)
234
+ end
235
+
236
+ it "returns true when clazz is responding to defined_search_scopes and included" do
237
+ clazz = Class.new
238
+ class << clazz
239
+ attr_accessor :defined_named_search_scopes
240
+ end
241
+ clazz.defined_named_search_scopes = [:rgne]
242
+ Supernova::Criteria.new(clazz).should be_named_scope_defined(:rgne)
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Supernova::ThinkingSphinxCriteria" do
4
+ let(:scope) { Supernova::ThinkingSphinxCriteria.new }
5
+
6
+ describe "#to_params" do
7
+ it "returns an array" do
8
+ scope.to_params.should be_an_instance_of(Array)
9
+ end
10
+
11
+ it "sets the correct order query" do
12
+ scope.order("title desc").to_params.at(1)[:order].should == "title desc"
13
+ end
14
+
15
+ it "sets the correct group_by statement" do
16
+ scope.group_by("title").to_params.at(1)[:group_by].should == "title"
17
+ end
18
+
19
+ it "sets the match_mode to boolean" do
20
+ scope.to_params.at(1)[:match_mode].should == :boolean
21
+ end
22
+
23
+ it "does not set the classes field by default" do
24
+ scope.to_params.at(1).should_not have_key(:classes)
25
+ end
26
+
27
+ it "sets the classes field when classes set" do
28
+ scope.for_classes(Offer).to_params.at(1)[:classes].should == [Offer]
29
+ end
30
+
31
+ it "sets the search query when present" do
32
+ scope.search("some test").to_params.at(0).should == "some test"
33
+ end
34
+
35
+ it "sets a set limit" do
36
+ scope.limit(88).to_params.at(1)[:limit].should == 88
37
+ end
38
+
39
+ it "calls sphinx with select fields" do
40
+ scope.select(%w(id title name)).to_params.at(1)[:select].should == %w(id title name)
41
+ end
42
+
43
+ { :page => 8, :per_page => 1 }.each do |key, value|
44
+ it "sets pagination pagination #{key} to #{value}" do
45
+ scope.paginate(key => value).to_params.at(1)[key].should == value
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "with to_params mockes" do
51
+ let(:query) { double("query") }
52
+ let(:options) { double("options") }
53
+ let(:sphinx_response) { double("sphinx_respons") }
54
+
55
+ before(:each) do
56
+ scope.stub!(:to_params).and_return([query, options])
57
+ end
58
+
59
+ describe "#to_a" do
60
+ it "returns the sphinx search" do
61
+ ThinkingSphinx.stub!(:search).and_return sphinx_response
62
+ scope.to_a.should == sphinx_response
63
+ end
64
+
65
+ it "calls ThinkingSphinx with what to_params returns" do
66
+ ThinkingSphinx.should_receive(:search).with(query, options).and_return sphinx_response
67
+ scope.to_a.should == sphinx_response
68
+ end
69
+ end
70
+
71
+ it "forwards ids to search_for_ids" do
72
+ ids_response = double("id response")
73
+ ThinkingSphinx.should_receive(:search_for_ids).with(query, options).and_return ids_response
74
+ scope.ids
75
+ end
76
+
77
+ it "forwards total_entries to search_for_ids" do
78
+ ids_response = double("id response")
79
+ ThinkingSphinx.should_receive(:search_for_ids).with(query, options).and_return ids_response
80
+ ids_response.should_receive(:total_entries).and_return 88
81
+ scope.total_entries.should == 88
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe Supernova do
4
+ let(:clazz) do
5
+ clazz = Class.new
6
+ clazz.send(:include, Supernova::ThinkingSphinx)
7
+ end
8
+
9
+ describe "#including" do
10
+ it "can be includes" do
11
+ Class.new.send(:include, Supernova::ThinkingSphinx)
12
+ end
13
+
14
+ it "defines a named_search_scope method" do
15
+ clazz.send(:include, Supernova)
16
+ clazz.should be_respond_to(:named_search_scope)
17
+ end
18
+ end
19
+
20
+ describe "#search_scope" do
21
+ it "returns a new criteria" do
22
+ clazz.search_scope.should be_an_instance_of(Supernova::ThinkingSphinxCriteria)
23
+ end
24
+
25
+ it "sets the correct clazz" do
26
+ clazz.search_scope.clazz.should == clazz
27
+ end
28
+ end
29
+
30
+ describe "#named_search_scope" do
31
+ before(:each) do
32
+ clazz.named_search_scope :popular do
33
+ order("popularity desc")
34
+ end
35
+ end
36
+
37
+ describe "without parameters" do
38
+ it "defines a new method" do
39
+ clazz.should respond_to(:popular)
40
+ end
41
+
42
+ it "returns a new criteria" do
43
+ clazz.popular.should be_an_instance_of(Supernova::ThinkingSphinxCriteria)
44
+ end
45
+
46
+ it "sets the clazz attribute" do
47
+ clazz.popular.clazz.should == clazz
48
+ end
49
+
50
+ it "sets the correct order option" do
51
+ clazz.popular.options[:order].should == "popularity desc"
52
+ end
53
+
54
+ it "adds the name of the scope to the defined_named_search_scopes array" do
55
+ clazz.defined_named_search_scopes.should == [:popular]
56
+ end
57
+ end
58
+
59
+ describe "chaining" do
60
+ it "calls merge with both scopes" do
61
+ scope = Supernova::ThinkingSphinxCriteria.new(clazz)
62
+ scope.should_receive(:merge).with(instance_of(Supernova::ThinkingSphinxCriteria))
63
+ scope.popular
64
+ end
65
+ # Supernova::ThinkingSphinxCriteria.new(clazz).popular.should be_an_instance_of()
66
+ end
67
+
68
+ describe "with parameters" do
69
+ before(:each) do
70
+ clazz.named_search_scope :for_artists do |artist_ids|
71
+ with(:artist_id => artist_ids)
72
+ end
73
+ clazz.named_search_scope :popular do
74
+ order("popularity desc")
75
+ end
76
+ end
77
+
78
+ it "sets the correct filters" do
79
+ clazz.for_artists(%w(1 3 2)).filters[:with][:artist_id].should == %w(1 3 2)
80
+ end
81
+
82
+ it "allows chaining of named_search_scopes" do
83
+ clazz.for_artists(%w(1 3 2)).popular.options[:order].should == "popularity desc"
84
+ end
85
+ end
86
+ end
87
+ end
data/supernova.gemspec ADDED
@@ -0,0 +1,74 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{supernova}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Tobias Schwab"]
12
+ s.date = %q{2011-06-08}
13
+ s.description = %q{Unified search scopes}
14
+ s.email = %q{tobias.schwab@dynport.de}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "autotest/discover.rb",
29
+ "lib/supernova.rb",
30
+ "lib/supernova/criteria.rb",
31
+ "lib/supernova/thinking_sphinx.rb",
32
+ "lib/supernova/thinking_sphinx_criteria.rb",
33
+ "spec/spec_helper.rb",
34
+ "spec/supernova/criteria_spec.rb",
35
+ "spec/supernova/thinking_sphinx_criteria_spec.rb",
36
+ "spec/supernova_spec.rb",
37
+ "supernova.gemspec"
38
+ ]
39
+ s.licenses = ["MIT"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.7.2}
42
+ s.summary = %q{Unified search scopes}
43
+
44
+ if s.respond_to? :specification_version then
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
48
+ s.add_runtime_dependency(%q<thinking-sphinx>, ["= 2.0.3"])
49
+ s.add_development_dependency(%q<autotest>, [">= 0"])
50
+ s.add_development_dependency(%q<autotest-growl>, [">= 0"])
51
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
52
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
53
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
54
+ s.add_development_dependency(%q<rcov>, [">= 0"])
55
+ else
56
+ s.add_dependency(%q<thinking-sphinx>, ["= 2.0.3"])
57
+ s.add_dependency(%q<autotest>, [">= 0"])
58
+ s.add_dependency(%q<autotest-growl>, [">= 0"])
59
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
60
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
61
+ s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
62
+ s.add_dependency(%q<rcov>, [">= 0"])
63
+ end
64
+ else
65
+ s.add_dependency(%q<thinking-sphinx>, ["= 2.0.3"])
66
+ s.add_dependency(%q<autotest>, [">= 0"])
67
+ s.add_dependency(%q<autotest-growl>, [">= 0"])
68
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
69
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
70
+ s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
71
+ s.add_dependency(%q<rcov>, [">= 0"])
72
+ end
73
+ end
74
+
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: supernova
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Tobias Schwab
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-08 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ requirement: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - "="
25
+ - !ruby/object:Gem::Version
26
+ hash: 9
27
+ segments:
28
+ - 2
29
+ - 0
30
+ - 3
31
+ version: 2.0.3
32
+ version_requirements: *id001
33
+ name: thinking-sphinx
34
+ prerelease: false
35
+ type: :runtime
36
+ - !ruby/object:Gem::Dependency
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ version_requirements: *id002
47
+ name: autotest
48
+ prerelease: false
49
+ type: :development
50
+ - !ruby/object:Gem::Dependency
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ version_requirements: *id003
61
+ name: autotest-growl
62
+ prerelease: false
63
+ type: :development
64
+ - !ruby/object:Gem::Dependency
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 2
73
+ - 3
74
+ - 0
75
+ version: 2.3.0
76
+ version_requirements: *id004
77
+ name: rspec
78
+ prerelease: false
79
+ type: :development
80
+ - !ruby/object:Gem::Dependency
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ~>
85
+ - !ruby/object:Gem::Version
86
+ hash: 23
87
+ segments:
88
+ - 1
89
+ - 0
90
+ - 0
91
+ version: 1.0.0
92
+ version_requirements: *id005
93
+ name: bundler
94
+ prerelease: false
95
+ type: :development
96
+ - !ruby/object:Gem::Dependency
97
+ requirement: &id006 !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ hash: 15
103
+ segments:
104
+ - 1
105
+ - 6
106
+ - 0
107
+ version: 1.6.0
108
+ version_requirements: *id006
109
+ name: jeweler
110
+ prerelease: false
111
+ type: :development
112
+ - !ruby/object:Gem::Dependency
113
+ requirement: &id007 !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ version_requirements: *id007
123
+ name: rcov
124
+ prerelease: false
125
+ type: :development
126
+ description: Unified search scopes
127
+ email: tobias.schwab@dynport.de
128
+ executables: []
129
+
130
+ extensions: []
131
+
132
+ extra_rdoc_files:
133
+ - LICENSE.txt
134
+ - README.rdoc
135
+ files:
136
+ - .document
137
+ - .rspec
138
+ - Gemfile
139
+ - Gemfile.lock
140
+ - LICENSE.txt
141
+ - README.rdoc
142
+ - Rakefile
143
+ - VERSION
144
+ - autotest/discover.rb
145
+ - lib/supernova.rb
146
+ - lib/supernova/criteria.rb
147
+ - lib/supernova/thinking_sphinx.rb
148
+ - lib/supernova/thinking_sphinx_criteria.rb
149
+ - spec/spec_helper.rb
150
+ - spec/supernova/criteria_spec.rb
151
+ - spec/supernova/thinking_sphinx_criteria_spec.rb
152
+ - spec/supernova_spec.rb
153
+ - supernova.gemspec
154
+ homepage:
155
+ licenses:
156
+ - MIT
157
+ post_install_message:
158
+ rdoc_options: []
159
+
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ none: false
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ hash: 3
168
+ segments:
169
+ - 0
170
+ version: "0"
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ none: false
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ hash: 3
177
+ segments:
178
+ - 0
179
+ version: "0"
180
+ requirements: []
181
+
182
+ rubyforge_project:
183
+ rubygems_version: 1.7.2
184
+ signing_key:
185
+ specification_version: 3
186
+ summary: Unified search scopes
187
+ test_files: []
188
+