supernova 0.1.0 → 0.1.1
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/.autotest +9 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +12 -0
- data/README.rdoc +32 -27
- data/VERSION +1 -1
- data/lib/supernova.rb +1 -0
- data/lib/supernova/criteria.rb +42 -14
- data/lib/supernova/numeric_extensions.rb +30 -0
- data/lib/supernova/thinking_sphinx_criteria.rb +22 -6
- data/spec/database.sql +1 -0
- data/spec/integration/search_spec.rb +72 -0
- data/spec/spec_helper.rb +47 -1
- data/spec/supernova/criteria_spec.rb +57 -11
- data/spec/supernova/numeric_extensions_spec.rb +29 -0
- data/spec/supernova/thinking_sphinx_criteria_spec.rb +53 -0
- data/spec/supernova_spec.rb +2 -2
- data/supernova.gemspec +15 -1
- metadata +62 -13
data/.autotest
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -17,13 +17,17 @@ GEM
|
|
17
17
|
ZenTest (>= 4.4.1)
|
18
18
|
autotest-growl (0.2.9)
|
19
19
|
builder (2.1.2)
|
20
|
+
columnize (0.3.2)
|
20
21
|
diff-lcs (1.1.2)
|
22
|
+
geokit (1.6.0)
|
21
23
|
git (1.2.5)
|
22
24
|
i18n (0.5.0)
|
23
25
|
jeweler (1.6.2)
|
24
26
|
bundler (~> 1.0)
|
25
27
|
git (>= 1.2.5)
|
26
28
|
rake
|
29
|
+
linecache (0.43)
|
30
|
+
mysql2 (0.2.7)
|
27
31
|
rake (0.9.2)
|
28
32
|
rcov (0.9.9)
|
29
33
|
riddle (1.3.3)
|
@@ -35,6 +39,11 @@ GEM
|
|
35
39
|
rspec-expectations (2.3.0)
|
36
40
|
diff-lcs (~> 1.1.2)
|
37
41
|
rspec-mocks (2.3.0)
|
42
|
+
ruby-debug (0.10.4)
|
43
|
+
columnize (>= 0.1)
|
44
|
+
ruby-debug-base (~> 0.10.4.0)
|
45
|
+
ruby-debug-base (0.10.4)
|
46
|
+
linecache (>= 0.3)
|
38
47
|
thinking-sphinx (2.0.3)
|
39
48
|
activerecord (>= 3.0.3)
|
40
49
|
riddle (>= 1.2.2)
|
@@ -47,7 +56,10 @@ DEPENDENCIES
|
|
47
56
|
autotest
|
48
57
|
autotest-growl
|
49
58
|
bundler (~> 1.0.0)
|
59
|
+
geokit
|
50
60
|
jeweler (~> 1.6.0)
|
61
|
+
mysql2 (~> 0.2.7)
|
51
62
|
rcov
|
52
63
|
rspec (~> 2.3.0)
|
64
|
+
ruby-debug
|
53
65
|
thinking-sphinx (= 2.0.3)
|
data/README.rdoc
CHANGED
@@ -2,41 +2,46 @@
|
|
2
2
|
|
3
3
|
== Setup
|
4
4
|
|
5
|
-
require "supernova"
|
6
|
-
require "supernova/
|
7
|
-
|
8
|
-
class
|
9
|
-
|
10
|
-
end
|
5
|
+
require "supernova"
|
6
|
+
require "supernova/thinking_sphinx"
|
7
|
+
|
8
|
+
class Offer
|
9
|
+
include Supernova::ThinkingSphinx
|
10
|
+
end
|
11
11
|
|
12
12
|
=== Anonymous Scopes
|
13
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
|
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
23
|
|
24
24
|
=== Named Scopes
|
25
25
|
|
26
|
-
class Offer
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
31
36
|
end
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
end
|
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
|
37
40
|
|
38
|
-
|
39
|
-
Offer.
|
41
|
+
=== Geo Searches
|
42
|
+
Offer.search_scope.near(47.0, 11.0).within(1.kms)
|
43
|
+
Offer.search_scope.near(47.0, 11.0).within(100.meters)
|
44
|
+
Offer.search_scope.near(other_offer).within(100.meters)
|
40
45
|
|
41
46
|
== Contributing to search_scopes
|
42
47
|
|
@@ -50,6 +55,6 @@ Offer.popular.for_artists([1, 2, 3]).with(:available => true) # named scopes
|
|
50
55
|
|
51
56
|
== Copyright
|
52
57
|
|
53
|
-
Copyright (c) 2011
|
58
|
+
Copyright (c) 2011 Dynport GmbH. See LICENSE.txt for
|
54
59
|
further details.
|
55
60
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/supernova.rb
CHANGED
data/lib/supernova/criteria.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class Supernova::Criteria
|
2
|
-
attr_accessor :filters, :
|
2
|
+
attr_accessor :filters, :search_options, :clazz
|
3
3
|
|
4
4
|
class << self
|
5
5
|
def method_missing(*args)
|
@@ -19,7 +19,7 @@ class Supernova::Criteria
|
|
19
19
|
def initialize(clazz = nil)
|
20
20
|
self.clazz = clazz
|
21
21
|
self.filters = {}
|
22
|
-
self.
|
22
|
+
self.search_options = {}
|
23
23
|
end
|
24
24
|
|
25
25
|
def for_classes(clazzes)
|
@@ -27,15 +27,15 @@ class Supernova::Criteria
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def order(order_option)
|
30
|
-
|
30
|
+
merge_search_options :order, order_option
|
31
31
|
end
|
32
32
|
|
33
33
|
def limit(limit_option)
|
34
|
-
|
34
|
+
merge_search_options :limit, limit_option
|
35
35
|
end
|
36
36
|
|
37
37
|
def group_by(group_option)
|
38
|
-
|
38
|
+
merge_search_options :group_by, group_option
|
39
39
|
end
|
40
40
|
|
41
41
|
def search(query)
|
@@ -47,26 +47,54 @@ class Supernova::Criteria
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def select(fields)
|
50
|
-
|
50
|
+
merge_search_options :select, fields
|
51
51
|
end
|
52
52
|
|
53
53
|
def conditions(filters)
|
54
54
|
merge_filters :conditions, filters
|
55
55
|
end
|
56
56
|
|
57
|
-
def paginate(
|
58
|
-
|
57
|
+
def paginate(pagination_options)
|
58
|
+
merge_search_options :pagination, pagination_options
|
59
|
+
end
|
60
|
+
|
61
|
+
def near(*coordinates)
|
62
|
+
merge_search_options :geo_center, normalize_coordinates(*coordinates)
|
63
|
+
end
|
64
|
+
|
65
|
+
def within(distance)
|
66
|
+
merge_search_options :geo_distance, distance
|
67
|
+
end
|
68
|
+
|
69
|
+
def options(options_hash)
|
70
|
+
merge_search_options :custom_options, options_hash
|
71
|
+
end
|
72
|
+
|
73
|
+
def normalize_coordinates(*args)
|
74
|
+
flattened = args.flatten
|
75
|
+
if (lat = read_first_attribute(flattened.first, :lat, :latitude)) && (lng = read_first_attribute(flattened.first, :lng, :lon, :longitude))
|
76
|
+
{ :lat => lat.to_f, :lng => lng.to_f }
|
77
|
+
elsif flattened.length == 2
|
78
|
+
{ :lat => flattened.first.to_f, :lng => flattened.at(1).to_f }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def read_first_attribute(object, *keys)
|
83
|
+
keys.each do |key|
|
84
|
+
return object.send(key) if object.respond_to?(key)
|
85
|
+
end
|
86
|
+
nil
|
59
87
|
end
|
60
88
|
|
61
89
|
def merge_filters(key, value)
|
62
|
-
|
90
|
+
merge_filters_or_search_options(self.filters, key, value)
|
63
91
|
end
|
64
92
|
|
65
|
-
def
|
66
|
-
|
93
|
+
def merge_search_options(key, value)
|
94
|
+
merge_filters_or_search_options(self.search_options, key, value)
|
67
95
|
end
|
68
96
|
|
69
|
-
def
|
97
|
+
def merge_filters_or_search_options(reference, key, value)
|
70
98
|
if value.is_a?(Hash)
|
71
99
|
reference[key] ||= Hash.new
|
72
100
|
reference[key].merge!(value)
|
@@ -92,8 +120,8 @@ class Supernova::Criteria
|
|
92
120
|
other_criteria.filters.each do |key, value|
|
93
121
|
self.merge_filters(key, value)
|
94
122
|
end
|
95
|
-
other_criteria.
|
96
|
-
self.
|
123
|
+
other_criteria.search_options.each do |key, value|
|
124
|
+
self.merge_search_options(key, value)
|
97
125
|
end
|
98
126
|
self
|
99
127
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Numeric.class_eval do
|
2
|
+
KM_TO_METER = 1000.0
|
3
|
+
MILE_TO_METER = 1609.3472
|
4
|
+
DEG_TO_RADIAN = Math::PI / 180.0
|
5
|
+
RADIAN_TO_REG = 1 / DEG_TO_RADIAN
|
6
|
+
|
7
|
+
def km
|
8
|
+
self * KM_TO_METER
|
9
|
+
end
|
10
|
+
|
11
|
+
def meter
|
12
|
+
self.to_f
|
13
|
+
end
|
14
|
+
|
15
|
+
def mile
|
16
|
+
self * MILE_TO_METER
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_radians
|
20
|
+
self * DEG_TO_RADIAN
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_deg
|
24
|
+
self * RADIAN_TO_REG
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :miles, :mile
|
28
|
+
alias_method :kms, :km
|
29
|
+
alias_method :meters, :meter
|
30
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "thinking_sphinx"
|
2
|
+
require "zlib"
|
2
3
|
|
3
4
|
class Supernova::ThinkingSphinxCriteria < Supernova::Criteria
|
4
5
|
def self.index_statement_for(field_name, column = nil)
|
@@ -6,15 +7,29 @@ class Supernova::ThinkingSphinxCriteria < Supernova::Criteria
|
|
6
7
|
[%(CONCAT("#{field_name}_", #{column})), { :as => :"indexed_#{field_name}" }]
|
7
8
|
end
|
8
9
|
|
10
|
+
def normalize_with_filter(attributes)
|
11
|
+
attributes.inject({}) do |hash, (key, value)|
|
12
|
+
value = Zlib.crc32(value.to_s) if value.is_a?(String) || value.is_a?(Class)
|
13
|
+
hash.merge!(key => value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
def to_params
|
10
18
|
sphinx_options = { :match_mode => :boolean, :with => {}, :conditions => {} }
|
11
|
-
sphinx_options[:order] = self.
|
12
|
-
sphinx_options[:limit] = self.
|
13
|
-
sphinx_options[:select] = self.
|
14
|
-
sphinx_options[:group_by] = self.
|
15
|
-
sphinx_options.merge!(self.
|
19
|
+
sphinx_options[:order] = self.search_options[:order] if self.search_options[:order]
|
20
|
+
sphinx_options[:limit] = self.search_options[:limit] if self.search_options[:limit]
|
21
|
+
sphinx_options[:select] = self.search_options[:select] if self.search_options[:select]
|
22
|
+
sphinx_options[:group_by] = self.search_options[:group_by] if self.search_options[:group_by]
|
23
|
+
sphinx_options.merge!(self.search_options[:pagination]) if self.search_options[:pagination].is_a?(Hash)
|
16
24
|
sphinx_options[:classes] = self.filters[:classes] if self.filters[:classes]
|
25
|
+
sphinx_options[:classes] = [self.clazz] if self.clazz
|
17
26
|
sphinx_options[:conditions].merge!(self.filters[:conditions]) if self.filters[:conditions]
|
27
|
+
sphinx_options[:with].merge!(normalize_with_filter(self.filters[:with])) if self.filters[:with]
|
28
|
+
sphinx_options.merge!(self.search_options[:custom_options]) if self.search_options[:custom_options]
|
29
|
+
if self.search_options[:geo_center] && self.search_options[:geo_distance]
|
30
|
+
sphinx_options[:geo] = [self.search_options[:geo_center][:lat].to_radians, self.search_options[:geo_center][:lng].to_radians]
|
31
|
+
sphinx_options[:with]["@geodist"] = self.search_options[:geo_distance].is_a?(Range) ? self.search_options[:geo_distance] : Range.new(0.0, self.search_options[:geo_distance])
|
32
|
+
end
|
18
33
|
[self.filters[:search], sphinx_options]
|
19
34
|
end
|
20
35
|
|
@@ -23,7 +38,8 @@ class Supernova::ThinkingSphinxCriteria < Supernova::Criteria
|
|
23
38
|
end
|
24
39
|
|
25
40
|
def ids
|
26
|
-
|
41
|
+
params = *self.to_params
|
42
|
+
ThinkingSphinx.search_for_ids(*params)
|
27
43
|
end
|
28
44
|
|
29
45
|
def total_entries
|
data/spec/database.sql
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
create database supernova_test character set utf8;
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "active_record"
|
3
|
+
require "thinking_sphinx"
|
4
|
+
require "mysql2"
|
5
|
+
require "fileutils"
|
6
|
+
require "geokit"
|
7
|
+
|
8
|
+
describe "Search" do
|
9
|
+
let(:ts) { ThinkingSphinx::Configuration.instance }
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
ts.build
|
13
|
+
ts.controller.index
|
14
|
+
ts.controller.start
|
15
|
+
|
16
|
+
|
17
|
+
ThinkingSphinx.deltas_enabled = true
|
18
|
+
ThinkingSphinx.updates_enabled = true
|
19
|
+
ThinkingSphinx.suppress_delta_output = true
|
20
|
+
|
21
|
+
Offer.connection.execute "TRUNCATE offers"
|
22
|
+
root = Geokit::LatLng.new(47, 11)
|
23
|
+
endpoint = root.endpoint(90, 50, :units => :kms)
|
24
|
+
@offer1 = Offer.create!(:id => 1, :user_id => 1, :enabled => false, :text => "Hans Meyer", :popularity => 10, :lat => root.lat, :lng => root.lng)
|
25
|
+
@offer2 = Offer.create!(:id => 2, :user_id => 2, :enabled => true, :text => "Marek Mintal", :popularity => 1, :lat => endpoint.lat, :lng => endpoint.lng)
|
26
|
+
ts.controller.index
|
27
|
+
sleep 0.1
|
28
|
+
end
|
29
|
+
|
30
|
+
it "finds the correct objects" do
|
31
|
+
Offer.for_user_ids(2).to_a.to_a.should == [@offer2]
|
32
|
+
Offer.for_user_ids(2, 1).to_a.to_a.sort_by(&:id) == [@offer1, @offer2]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns the corect ids" do
|
36
|
+
Offer.for_user_ids(2).ids.to_a.to_a.should == [2]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "filters by enabled" do
|
40
|
+
Offer.search_scope.with(:enabled => true).to_a.to_a.should == [@offer2]
|
41
|
+
Offer.search_scope.with(:enabled => false).to_a.to_a.should == [@offer1]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "combines searches" do
|
45
|
+
Offer.search_scope.with(:enabled => false).with(:user_id => 2).to_a.should be_empty
|
46
|
+
end
|
47
|
+
|
48
|
+
it "searches for names" do
|
49
|
+
Offer.search_scope.search("Marek").map(&:id).should == [2]
|
50
|
+
Offer.search_scope.search("Hans").map(&:id).should == [1]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sorty by popularity" do
|
54
|
+
Offer.search_scope.order("popularity desc").map(&:id).should == [1, 2]
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "geo search" do
|
58
|
+
it "finds the correct offers" do
|
59
|
+
Offer.search_scope.near(47, 11).within(25.kms).to_a.to_a.should == [@offer1]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "finds more offers when radius is bigger" do
|
63
|
+
Offer.search_scope.near(47, 11).within(49.kms).to_a.should_not include(@offer2)
|
64
|
+
Offer.search_scope.near(47, 11).within(51.kms).to_a.should include(@offer2)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "finds offers around other offers" do
|
68
|
+
Offer.search_scope.near(@offer1).within(49.kms).to_a.to_a.should == [@offer1]
|
69
|
+
Offer.search_scope.near(@offer1).within(51.kms).order("@geodist desc").to_a.to_a.should == [@offer2, @offer1]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,6 +2,15 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
3
|
require 'rspec'
|
4
4
|
require 'supernova'
|
5
|
+
require "mysql2"
|
6
|
+
require "logger"
|
7
|
+
require "fileutils"
|
8
|
+
require "ruby-debug"
|
9
|
+
|
10
|
+
if defined?(Debugger) && Debugger.respond_to?(:settings)
|
11
|
+
Debugger.settings[:autolist] = 1
|
12
|
+
Debugger.settings[:autoeval] = true
|
13
|
+
end
|
5
14
|
|
6
15
|
# Requires supporting files with custom matchers and macros, etc,
|
7
16
|
# in ./support/ and its subdirectories.
|
@@ -11,8 +20,45 @@ RSpec.configure do |config|
|
|
11
20
|
|
12
21
|
end
|
13
22
|
|
23
|
+
ActiveRecord::Base.establish_connection(
|
24
|
+
:adapter => "mysql2",
|
25
|
+
:host => "localhost",
|
26
|
+
:database => "supernova_test",
|
27
|
+
:username => "root",
|
28
|
+
:encoding => "utf8"
|
29
|
+
)
|
30
|
+
|
31
|
+
PROJECT_ROOT = Pathname.new(File.expand_path("..", File.dirname(__FILE__)))
|
32
|
+
FileUtils.mkdir_p(PROJECT_ROOT.join("db/sphinx/development"))
|
33
|
+
FileUtils.mkdir_p(PROJECT_ROOT.join("log"))
|
34
|
+
FileUtils.mkdir_p(PROJECT_ROOT.join("config"))
|
35
|
+
|
36
|
+
ThinkingSphinx::ActiveRecord::LogSubscriber.logger = Logger.new(
|
37
|
+
open(File.expand_path("../log/active_record.log", File.dirname(__FILE__)), "a")
|
38
|
+
)
|
14
39
|
|
15
|
-
|
40
|
+
ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
|
41
|
+
|
42
|
+
ActiveRecord::Base.connection.execute("DROP TABLE offers")
|
43
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE offers (id SERIAL, text TEXT, user_id INTEGER, enabled BOOLEAN, popularity INTEGER, lat FLOAT, lng FLOAT)")
|
44
|
+
|
45
|
+
class Offer < ActiveRecord::Base
|
46
|
+
include Supernova::ThinkingSphinx
|
47
|
+
|
48
|
+
define_index do
|
49
|
+
indexes text
|
50
|
+
|
51
|
+
has :user_id
|
52
|
+
has :enabled
|
53
|
+
has :popularity, :sort => true
|
54
|
+
|
55
|
+
has "RADIANS(lat)", :as => :lat, :type => :float
|
56
|
+
has "RADIANS(lng)", :as => :lng, :type => :float
|
57
|
+
end
|
58
|
+
|
59
|
+
named_search_scope :for_user_ids do |*ids|
|
60
|
+
with(:user_id => ids.flatten)
|
61
|
+
end
|
16
62
|
end
|
17
63
|
|
18
64
|
class Host
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
2
|
+
require "ostruct"
|
2
3
|
|
3
4
|
describe "Supernova::Criteria" do
|
4
5
|
let(:scope) { Supernova::Criteria.new }
|
@@ -22,7 +23,10 @@ describe "Supernova::Criteria" do
|
|
22
23
|
[:with, { :stars => 2 }],
|
23
24
|
[:conditions, { :stars => 2 }],
|
24
25
|
[:paginate, { :stars => 2 }],
|
25
|
-
[:select, %w(stars)]
|
26
|
+
[:select, %w(stars)],
|
27
|
+
[:near, "test"],
|
28
|
+
[:within, 10],
|
29
|
+
[:options, {}]
|
26
30
|
].each do |args|
|
27
31
|
it "returns the scope itself for #{args.first}" do
|
28
32
|
scope.send(*args).should == scope
|
@@ -38,18 +42,18 @@ describe "Supernova::Criteria" do
|
|
38
42
|
|
39
43
|
describe "#order" do
|
40
44
|
it "sets the order statement" do
|
41
|
-
scope.order("popularity desc").
|
45
|
+
scope.order("popularity desc").search_options[:order].should == "popularity desc"
|
42
46
|
end
|
43
47
|
end
|
44
48
|
|
45
49
|
describe "#group_by" do
|
46
50
|
it "sets the group option" do
|
47
|
-
scope.group_by("name").
|
51
|
+
scope.group_by("name").search_options[:group_by].should == "name"
|
48
52
|
end
|
49
53
|
end
|
50
54
|
|
51
55
|
it "sets the limit option" do
|
52
|
-
scope.limit(77).
|
56
|
+
scope.limit(77).search_options[:limit].should == 77
|
53
57
|
end
|
54
58
|
|
55
59
|
describe "#search" do
|
@@ -81,11 +85,11 @@ describe "Supernova::Criteria" do
|
|
81
85
|
end
|
82
86
|
|
83
87
|
it "sets select option" do
|
84
|
-
scope.select(%w(a b)).
|
88
|
+
scope.select(%w(a b)).search_options[:select].should == %w(a b)
|
85
89
|
end
|
86
90
|
|
87
91
|
it "sets the correct pagination fields" do
|
88
|
-
scope.paginate(:page => 9, :per_page => 2).
|
92
|
+
scope.paginate(:page => 9, :per_page => 2).search_options[:pagination].should == { :page => 9, :per_page => 2 }
|
89
93
|
end
|
90
94
|
|
91
95
|
it "to_parameters raises an implement in subclass error" do
|
@@ -174,7 +178,7 @@ describe "Supernova::Criteria" do
|
|
174
178
|
end
|
175
179
|
|
176
180
|
it "merges e.g. the order" do
|
177
|
-
new_crit.merge(criteria).
|
181
|
+
new_crit.merge(criteria).search_options[:order].should == "popularity asc"
|
178
182
|
end
|
179
183
|
|
180
184
|
it "merges e.g. the with filters" do
|
@@ -190,10 +194,10 @@ describe "Supernova::Criteria" do
|
|
190
194
|
end
|
191
195
|
|
192
196
|
it "calls merge on options" do
|
193
|
-
criteria.stub!(:
|
194
|
-
new_crit.stub!(:
|
195
|
-
new_crit.should_receive(:
|
196
|
-
new_crit.should_receive(:
|
197
|
+
criteria.stub!(:search_options).and_return({ :x => 2, :y => 9 })
|
198
|
+
new_crit.stub!(:search_options).and_return({ :z => 3, :c => 1 })
|
199
|
+
new_crit.should_receive(:merge_search_options).with(:x, 2)
|
200
|
+
new_crit.should_receive(:merge_search_options).with(:y, 9)
|
197
201
|
new_crit.merge(criteria)
|
198
202
|
end
|
199
203
|
|
@@ -206,6 +210,48 @@ describe "Supernova::Criteria" do
|
|
206
210
|
end
|
207
211
|
end
|
208
212
|
|
213
|
+
describe "#near" do
|
214
|
+
it "sets the geo_center option" do
|
215
|
+
scope.near([47, 11]).search_options[:geo_center].should == { :lat => 47.0, :lng => 11.0 }
|
216
|
+
end
|
217
|
+
|
218
|
+
it "can be called without an array" do
|
219
|
+
scope.near(47, 11).search_options[:geo_center].should == { :lat => 47.0, :lng => 11.0 }
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "#within" do
|
224
|
+
it "sets the distance to a value in meters when numeric given" do
|
225
|
+
scope.within("test").search_options[:geo_distance].should == "test"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "#options" do
|
230
|
+
it "merges full hash into options" do
|
231
|
+
scope.order("popularity desc").options(:test => "out", :order => "popularity asc").search_options[:custom_options].should == { :test => "out", :order => "popularity asc" }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "normalize_coordinates" do
|
236
|
+
it "returns a hash when array given" do
|
237
|
+
scope.normalize_coordinates([47, 12]).should == { :lat => 47.0, :lng => 12.0 }
|
238
|
+
end
|
239
|
+
|
240
|
+
it "returns a hash when two parameters given" do
|
241
|
+
scope.normalize_coordinates(47, 12).should == { :lat => 47.0, :lng => 12.0 }
|
242
|
+
end
|
243
|
+
|
244
|
+
[
|
245
|
+
[:lat, :lng],
|
246
|
+
[:lat, :lon],
|
247
|
+
[:latitude, :longitude]
|
248
|
+
].each do |(lat_name, lng_name)|
|
249
|
+
it "returns a hash when object responding to lat and lng is given" do
|
250
|
+
scope.normalize_coordinates(OpenStruct.new(lat_name => 11, lng_name => 19)).should == { :lat => 11.0, :lng => 19.0 }
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
209
255
|
describe "#named_scope_defined?" do
|
210
256
|
it "returns false when clazz is nil" do
|
211
257
|
Supernova::Criteria.new.should_not be_named_scope_defined(:rgne)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "NumericExtensions" do
|
4
|
+
it "can be called with .meters as well" do
|
5
|
+
10.meters.should == 10
|
6
|
+
10.0.meters.should == 10
|
7
|
+
end
|
8
|
+
|
9
|
+
it "converts km to meters" do
|
10
|
+
100.0.km.should == 100_000.0
|
11
|
+
100.km.should == 100_000.0
|
12
|
+
|
13
|
+
100.0.kms.should == 100_000.0
|
14
|
+
100.kms.should == 100_000.0
|
15
|
+
end
|
16
|
+
|
17
|
+
it "converts miles to " do
|
18
|
+
100.mile.should == 160_934.72
|
19
|
+
100.miles.should == 160_934.72
|
20
|
+
end
|
21
|
+
|
22
|
+
it "converts deg to radians" do
|
23
|
+
90.to_radians.should == Math::PI / 2
|
24
|
+
end
|
25
|
+
|
26
|
+
it "converts radians to deg" do
|
27
|
+
(Math::PI / 2).to_deg.should == 90
|
28
|
+
end
|
29
|
+
end
|
@@ -28,6 +28,10 @@ describe "Supernova::ThinkingSphinxCriteria" do
|
|
28
28
|
scope.for_classes(Offer).to_params.at(1)[:classes].should == [Offer]
|
29
29
|
end
|
30
30
|
|
31
|
+
it "sets the classes to the clazz when intialized with the class" do
|
32
|
+
Supernova::ThinkingSphinxCriteria.new(Offer).to_params.at(1)[:classes].should == [Offer]
|
33
|
+
end
|
34
|
+
|
31
35
|
it "sets the search query when present" do
|
32
36
|
scope.search("some test").to_params.at(0).should == "some test"
|
33
37
|
end
|
@@ -40,11 +44,60 @@ describe "Supernova::ThinkingSphinxCriteria" do
|
|
40
44
|
scope.select(%w(id title name)).to_params.at(1)[:select].should == %w(id title name)
|
41
45
|
end
|
42
46
|
|
47
|
+
it "sets the correct with filters" do
|
48
|
+
scope.with(:a => 1).with(:b => 2).to_params.at(1)[:with].should == {
|
49
|
+
:a => 1,
|
50
|
+
:b => 2
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
it "uses the crc32 value of strings when used in with" do
|
55
|
+
scope.with(:a => Offer).to_params.at(1)[:with].should == {
|
56
|
+
:a => 3893864506, # crc32 of Offer string
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "sets the correct geo option filter" do
|
61
|
+
scope.near(53.5748, 10.0347).within(5.kms).to_params.at(1)[:geo].map(&:to_s).should == ["0.935056656097458", "0.175138554449875"]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "merges correct with options" do
|
65
|
+
scope.near(53.5748, 10.0347).within(5.kms).with(:filter => true).to_params.at(1)[:with].should == {
|
66
|
+
"@geodist" => Range.new(0.0, 5_000.0),
|
67
|
+
:filter => true
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
it "sets the correct geo distance filter when single value given" do
|
72
|
+
# @geodist
|
73
|
+
scope.near(53.5748, 10.0347).within(7.kms).to_params.at(1)[:with]["@geodist"].should == Range.new(0.0, 7_000.0)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "sets the correct geo distance filter when range given value given" do
|
77
|
+
# @geodist
|
78
|
+
scope.near(53.5748, 10.0347).within(7.kms..10.kms).to_params.at(1)[:with]["@geodist"].should == Range.new(7_000.0, 10_000.0)
|
79
|
+
end
|
80
|
+
|
43
81
|
{ :page => 8, :per_page => 1 }.each do |key, value|
|
44
82
|
it "sets pagination pagination #{key} to #{value}" do
|
45
83
|
scope.paginate(key => value).to_params.at(1)[key].should == value
|
46
84
|
end
|
47
85
|
end
|
86
|
+
|
87
|
+
it "adds custom options to the sphinx search" do
|
88
|
+
scope.options(:star => true).to_params.at(1)[:star].should == true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#normalize_with_filter" do
|
93
|
+
it "does not change booleans and integers" do
|
94
|
+
range = 1..10
|
95
|
+
scope.normalize_with_filter(:a => 1, :enabled => true, :range => range).should == { :a => 1, :enabled => true, :range => range }
|
96
|
+
end
|
97
|
+
|
98
|
+
it "uses crc32 of strings" do
|
99
|
+
scope.normalize_with_filter(:a => "Test").should == { :a => 2018365746 } # crc32 of "Test"
|
100
|
+
end
|
48
101
|
end
|
49
102
|
|
50
103
|
describe "with to_params mockes" do
|
data/spec/supernova_spec.rb
CHANGED
@@ -48,7 +48,7 @@ describe Supernova do
|
|
48
48
|
end
|
49
49
|
|
50
50
|
it "sets the correct order option" do
|
51
|
-
clazz.popular.
|
51
|
+
clazz.popular.search_options[:order].should == "popularity desc"
|
52
52
|
end
|
53
53
|
|
54
54
|
it "adds the name of the scope to the defined_named_search_scopes array" do
|
@@ -80,7 +80,7 @@ describe Supernova do
|
|
80
80
|
end
|
81
81
|
|
82
82
|
it "allows chaining of named_search_scopes" do
|
83
|
-
clazz.for_artists(%w(1 3 2)).popular.
|
83
|
+
clazz.for_artists(%w(1 3 2)).popular.search_options[:order].should == "popularity desc"
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
data/supernova.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{supernova}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Tobias Schwab"]
|
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
|
|
17
17
|
"README.rdoc"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
|
+
".autotest",
|
20
21
|
".document",
|
21
22
|
".rspec",
|
22
23
|
"Gemfile",
|
@@ -28,10 +29,14 @@ Gem::Specification.new do |s|
|
|
28
29
|
"autotest/discover.rb",
|
29
30
|
"lib/supernova.rb",
|
30
31
|
"lib/supernova/criteria.rb",
|
32
|
+
"lib/supernova/numeric_extensions.rb",
|
31
33
|
"lib/supernova/thinking_sphinx.rb",
|
32
34
|
"lib/supernova/thinking_sphinx_criteria.rb",
|
35
|
+
"spec/database.sql",
|
36
|
+
"spec/integration/search_spec.rb",
|
33
37
|
"spec/spec_helper.rb",
|
34
38
|
"spec/supernova/criteria_spec.rb",
|
39
|
+
"spec/supernova/numeric_extensions_spec.rb",
|
35
40
|
"spec/supernova/thinking_sphinx_criteria_spec.rb",
|
36
41
|
"spec/supernova_spec.rb",
|
37
42
|
"supernova.gemspec"
|
@@ -46,6 +51,9 @@ Gem::Specification.new do |s|
|
|
46
51
|
|
47
52
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
53
|
s.add_runtime_dependency(%q<thinking-sphinx>, ["= 2.0.3"])
|
54
|
+
s.add_development_dependency(%q<ruby-debug>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<mysql2>, ["~> 0.2.7"])
|
56
|
+
s.add_development_dependency(%q<geokit>, [">= 0"])
|
49
57
|
s.add_development_dependency(%q<autotest>, [">= 0"])
|
50
58
|
s.add_development_dependency(%q<autotest-growl>, [">= 0"])
|
51
59
|
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
@@ -54,6 +62,9 @@ Gem::Specification.new do |s|
|
|
54
62
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
55
63
|
else
|
56
64
|
s.add_dependency(%q<thinking-sphinx>, ["= 2.0.3"])
|
65
|
+
s.add_dependency(%q<ruby-debug>, [">= 0"])
|
66
|
+
s.add_dependency(%q<mysql2>, ["~> 0.2.7"])
|
67
|
+
s.add_dependency(%q<geokit>, [">= 0"])
|
57
68
|
s.add_dependency(%q<autotest>, [">= 0"])
|
58
69
|
s.add_dependency(%q<autotest-growl>, [">= 0"])
|
59
70
|
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
@@ -63,6 +74,9 @@ Gem::Specification.new do |s|
|
|
63
74
|
end
|
64
75
|
else
|
65
76
|
s.add_dependency(%q<thinking-sphinx>, ["= 2.0.3"])
|
77
|
+
s.add_dependency(%q<ruby-debug>, [">= 0"])
|
78
|
+
s.add_dependency(%q<mysql2>, ["~> 0.2.7"])
|
79
|
+
s.add_dependency(%q<geokit>, [">= 0"])
|
66
80
|
s.add_dependency(%q<autotest>, [">= 0"])
|
67
81
|
s.add_dependency(%q<autotest-growl>, [">= 0"])
|
68
82
|
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: supernova
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Tobias Schwab
|
@@ -44,11 +44,27 @@ dependencies:
|
|
44
44
|
- 0
|
45
45
|
version: "0"
|
46
46
|
version_requirements: *id002
|
47
|
-
name:
|
47
|
+
name: ruby-debug
|
48
48
|
prerelease: false
|
49
49
|
type: :development
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
51
|
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 25
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
- 2
|
60
|
+
- 7
|
61
|
+
version: 0.2.7
|
62
|
+
version_requirements: *id003
|
63
|
+
name: mysql2
|
64
|
+
prerelease: false
|
65
|
+
type: :development
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
68
|
none: false
|
53
69
|
requirements:
|
54
70
|
- - ">="
|
@@ -57,12 +73,40 @@ dependencies:
|
|
57
73
|
segments:
|
58
74
|
- 0
|
59
75
|
version: "0"
|
60
|
-
version_requirements: *
|
76
|
+
version_requirements: *id004
|
77
|
+
name: geokit
|
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: 3
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
version_requirements: *id005
|
91
|
+
name: autotest
|
92
|
+
prerelease: false
|
93
|
+
type: :development
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
version_requirements: *id006
|
61
105
|
name: autotest-growl
|
62
106
|
prerelease: false
|
63
107
|
type: :development
|
64
108
|
- !ruby/object:Gem::Dependency
|
65
|
-
requirement: &
|
109
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
66
110
|
none: false
|
67
111
|
requirements:
|
68
112
|
- - ~>
|
@@ -73,12 +117,12 @@ dependencies:
|
|
73
117
|
- 3
|
74
118
|
- 0
|
75
119
|
version: 2.3.0
|
76
|
-
version_requirements: *
|
120
|
+
version_requirements: *id007
|
77
121
|
name: rspec
|
78
122
|
prerelease: false
|
79
123
|
type: :development
|
80
124
|
- !ruby/object:Gem::Dependency
|
81
|
-
requirement: &
|
125
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
82
126
|
none: false
|
83
127
|
requirements:
|
84
128
|
- - ~>
|
@@ -89,12 +133,12 @@ dependencies:
|
|
89
133
|
- 0
|
90
134
|
- 0
|
91
135
|
version: 1.0.0
|
92
|
-
version_requirements: *
|
136
|
+
version_requirements: *id008
|
93
137
|
name: bundler
|
94
138
|
prerelease: false
|
95
139
|
type: :development
|
96
140
|
- !ruby/object:Gem::Dependency
|
97
|
-
requirement: &
|
141
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
98
142
|
none: false
|
99
143
|
requirements:
|
100
144
|
- - ~>
|
@@ -105,12 +149,12 @@ dependencies:
|
|
105
149
|
- 6
|
106
150
|
- 0
|
107
151
|
version: 1.6.0
|
108
|
-
version_requirements: *
|
152
|
+
version_requirements: *id009
|
109
153
|
name: jeweler
|
110
154
|
prerelease: false
|
111
155
|
type: :development
|
112
156
|
- !ruby/object:Gem::Dependency
|
113
|
-
requirement: &
|
157
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
114
158
|
none: false
|
115
159
|
requirements:
|
116
160
|
- - ">="
|
@@ -119,7 +163,7 @@ dependencies:
|
|
119
163
|
segments:
|
120
164
|
- 0
|
121
165
|
version: "0"
|
122
|
-
version_requirements: *
|
166
|
+
version_requirements: *id010
|
123
167
|
name: rcov
|
124
168
|
prerelease: false
|
125
169
|
type: :development
|
@@ -133,6 +177,7 @@ extra_rdoc_files:
|
|
133
177
|
- LICENSE.txt
|
134
178
|
- README.rdoc
|
135
179
|
files:
|
180
|
+
- .autotest
|
136
181
|
- .document
|
137
182
|
- .rspec
|
138
183
|
- Gemfile
|
@@ -144,10 +189,14 @@ files:
|
|
144
189
|
- autotest/discover.rb
|
145
190
|
- lib/supernova.rb
|
146
191
|
- lib/supernova/criteria.rb
|
192
|
+
- lib/supernova/numeric_extensions.rb
|
147
193
|
- lib/supernova/thinking_sphinx.rb
|
148
194
|
- lib/supernova/thinking_sphinx_criteria.rb
|
195
|
+
- spec/database.sql
|
196
|
+
- spec/integration/search_spec.rb
|
149
197
|
- spec/spec_helper.rb
|
150
198
|
- spec/supernova/criteria_spec.rb
|
199
|
+
- spec/supernova/numeric_extensions_spec.rb
|
151
200
|
- spec/supernova/thinking_sphinx_criteria_spec.rb
|
152
201
|
- spec/supernova_spec.rb
|
153
202
|
- supernova.gemspec
|