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 ADDED
@@ -0,0 +1,9 @@
1
+ if Gem.available?("autotest-fsevent")
2
+ require 'autotest/fsevent'
3
+ end
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.add_exception(%r{log\/})
7
+ at.add_exception(%r{db\/})
8
+ at.add_exception(%r{config\/})
9
+ end
data/Gemfile CHANGED
@@ -9,6 +9,9 @@ source "http://rubygems.org"
9
9
  gem "thinking-sphinx", "2.0.3"
10
10
 
11
11
  group :development do
12
+ gem "ruby-debug"
13
+ gem "mysql2", "~> 0.2.7"
14
+ gem "geokit"
12
15
  gem "autotest"
13
16
  gem "autotest-growl"
14
17
  gem "rspec", "~> 2.3.0"
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/thiking_sphinx"
7
-
8
- class class Offer
9
- include Supernova::ThinkingSphinx
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
- include Supernova::ThinkingSphinx
28
-
29
- named_search_scope :popular do
30
- order("popularity desc")
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
- named_search_scope :for_artists do |ids|
34
- with(:artist_id => ids)
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
- 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
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 Tobias Schwab. See LICENSE.txt for
58
+ Copyright (c) 2011 Dynport GmbH. See LICENSE.txt for
54
59
  further details.
55
60
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
data/lib/supernova.rb CHANGED
@@ -16,5 +16,6 @@ module Supernova
16
16
  end
17
17
  end
18
18
 
19
+ require "supernova/numeric_extensions"
19
20
  require "supernova/criteria"
20
21
  require "supernova/thinking_sphinx"
@@ -1,5 +1,5 @@
1
1
  class Supernova::Criteria
2
- attr_accessor :filters, :options, :clazz
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.options = {}
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
- merge_options :order, order_option
30
+ merge_search_options :order, order_option
31
31
  end
32
32
 
33
33
  def limit(limit_option)
34
- merge_options :limit, limit_option
34
+ merge_search_options :limit, limit_option
35
35
  end
36
36
 
37
37
  def group_by(group_option)
38
- merge_options :group_by, group_option
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
- merge_options :select, fields
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(options)
58
- merge_options :pagination, options
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
- merge_filters_or_options(self.filters, key, value)
90
+ merge_filters_or_search_options(self.filters, key, value)
63
91
  end
64
92
 
65
- def merge_options(key, value)
66
- merge_filters_or_options(self.options, key, value)
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 merge_filters_or_options(reference, key, value)
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.options.each do |key, value|
96
- self.merge_options(key, value)
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.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)
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
- ThinkingSphinx.search_for_ids(*self.to_params)
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
- class Offer
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").options[:order].should == "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").options[:group_by].should == "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).options[:limit].should == 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)).options[:select].should == %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).options[:pagination].should == { :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).options[:order].should == "popularity asc"
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!(: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
+ 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
@@ -48,7 +48,7 @@ describe Supernova do
48
48
  end
49
49
 
50
50
  it "sets the correct order option" do
51
- clazz.popular.options[:order].should == "popularity desc"
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.options[:order].should == "popularity desc"
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.0"
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: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 0
10
- version: 0.1.0
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: autotest
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: *id003
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: &id004 !ruby/object:Gem::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: *id004
120
+ version_requirements: *id007
77
121
  name: rspec
78
122
  prerelease: false
79
123
  type: :development
80
124
  - !ruby/object:Gem::Dependency
81
- requirement: &id005 !ruby/object:Gem::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: *id005
136
+ version_requirements: *id008
93
137
  name: bundler
94
138
  prerelease: false
95
139
  type: :development
96
140
  - !ruby/object:Gem::Dependency
97
- requirement: &id006 !ruby/object:Gem::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: *id006
152
+ version_requirements: *id009
109
153
  name: jeweler
110
154
  prerelease: false
111
155
  type: :development
112
156
  - !ruby/object:Gem::Dependency
113
- requirement: &id007 !ruby/object:Gem::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: *id007
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