supernova 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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