sander6-enygma 0.0.7 → 0.1.0
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/Rakefile +19 -0
- data/lib/api/sphinx/client.rb +1093 -0
- data/lib/api/sphinx/request.rb +50 -0
- data/lib/api/sphinx/response.rb +69 -0
- data/lib/api/sphinx.rb +6 -0
- data/lib/enygma/adapters/abstract_adapter.rb +31 -0
- data/lib/enygma/adapters/active_record.rb +26 -0
- data/lib/enygma/adapters/berkeley.rb +23 -0
- data/lib/enygma/adapters/datamapper.rb +21 -0
- data/lib/enygma/adapters/memcache.rb +25 -0
- data/lib/enygma/adapters/sequel.rb +51 -0
- data/lib/enygma/adapters/tokyo_cabinet.rb +71 -0
- data/lib/enygma/configuration.rb +204 -0
- data/lib/enygma/extensions/float.rb +13 -0
- data/lib/enygma/geodistance_proxy.rb +66 -0
- data/lib/enygma/resource.rb +43 -0
- data/lib/enygma/search.rb +250 -0
- data/lib/enygma/version.rb +13 -0
- data/lib/enygma.rb +75 -0
- data/spec/adapters/abstract_adapter_spec.rb +5 -0
- data/spec/adapters/active_record_spec.rb +7 -0
- data/spec/adapters/datamapper_spec.rb +7 -0
- data/spec/adapters/sequel_spec.rb +7 -0
- data/spec/configuration_spec.rb +217 -0
- data/spec/enygma_spec.rb +74 -0
- data/spec/extensions/float_spec.rb +1 -0
- data/spec/geodistance_proxy_spec.rb +78 -0
- data/spec/search_spec.rb +5 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/version_spec.rb +20 -0
- metadata +33 -3
@@ -0,0 +1,50 @@
|
|
1
|
+
module Sphinx
|
2
|
+
# Pack ints, floats, strings, and arrays to internal representation
|
3
|
+
# needed by Sphinx search engine.
|
4
|
+
class Request
|
5
|
+
# Initialize new request.
|
6
|
+
def initialize
|
7
|
+
@request = ''
|
8
|
+
end
|
9
|
+
|
10
|
+
# Put int(s) to request.
|
11
|
+
def put_int(*ints)
|
12
|
+
ints.each { |i| @request << [i].pack('N') }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Put 64-bit int(s) to request.
|
16
|
+
def put_int64(*ints)
|
17
|
+
ints.each { |i| @request << [i].pack('q').reverse }#[i >> 32, i & ((1 << 32) - 1)].pack('NN') }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Put string(s) to request (first length, then the string itself).
|
21
|
+
def put_string(*strings)
|
22
|
+
strings.each { |s| @request << [s.length].pack('N') + s }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Put float(s) to request.
|
26
|
+
def put_float(*floats)
|
27
|
+
floats.each do |f|
|
28
|
+
t1 = [f].pack('f') # machine order
|
29
|
+
t2 = t1.unpack('L*').first # int in machine order
|
30
|
+
@request << [t2].pack('N')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Put array of ints to request (first length, then the array itself)
|
35
|
+
def put_int_array(arr)
|
36
|
+
put_int arr.length, *arr
|
37
|
+
end
|
38
|
+
|
39
|
+
# Put array of 64-bit ints to request (first length, then the array itself)
|
40
|
+
def put_int64_array(arr)
|
41
|
+
put_int arr.length
|
42
|
+
put_int64(*arr)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the entire message
|
46
|
+
def to_s
|
47
|
+
@request
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Sphinx
|
2
|
+
# Unpack internal Sphinx representation of ints, floats, strings, and arrays.
|
3
|
+
# needed by Sphinx search engine.
|
4
|
+
class Response
|
5
|
+
# Initialize new request.
|
6
|
+
def initialize(response)
|
7
|
+
@response = response
|
8
|
+
@position = 0
|
9
|
+
@size = response.length
|
10
|
+
end
|
11
|
+
|
12
|
+
# Gets current stream position.
|
13
|
+
def position
|
14
|
+
@position
|
15
|
+
end
|
16
|
+
|
17
|
+
# Gets response size.
|
18
|
+
def size
|
19
|
+
@size
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns <tt>true</tt> when response stream is out.
|
23
|
+
def eof?
|
24
|
+
@position >= @size
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get int from stream.
|
28
|
+
def get_int
|
29
|
+
raise EOFError if @position + 4 > @size
|
30
|
+
value = @response[@position, 4].unpack('N*').first
|
31
|
+
@position += 4
|
32
|
+
return value
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get 64-bit int from stream.
|
36
|
+
def get_int64
|
37
|
+
raise EOFError if @position + 8 > @size
|
38
|
+
hi, lo = @response[@position, 8].unpack('N*N*')
|
39
|
+
@position += 8
|
40
|
+
return (hi << 32) + lo
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get array of <tt>count</tt> ints from stream.
|
44
|
+
def get_ints(count)
|
45
|
+
length = 4 * count
|
46
|
+
raise EOFError if @position + length > @size
|
47
|
+
values = @response[@position, length].unpack('N*' * count)
|
48
|
+
@position += length
|
49
|
+
return values
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get string from stream.
|
53
|
+
def get_string
|
54
|
+
length = get_int
|
55
|
+
raise EOFError if @position + length > @size
|
56
|
+
value = length > 0 ? @response[@position, length] : ''
|
57
|
+
@position += length
|
58
|
+
return value
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get float from stream.
|
62
|
+
def get_float
|
63
|
+
raise EOFError if @position + 4 > @size
|
64
|
+
uval = @response[@position, 4].unpack('N*').first;
|
65
|
+
@position += 4
|
66
|
+
return ([uval].pack('L')).unpack('f*').first
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/api/sphinx.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Enygma
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
class AbstractAdapter
|
5
|
+
|
6
|
+
class InscrutableRecord < StandardError; end
|
7
|
+
class InvalidDatabase < StandardError; end
|
8
|
+
|
9
|
+
attr_reader :datastore
|
10
|
+
|
11
|
+
def connect!(datastore)
|
12
|
+
@datastore = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def query(args = {})
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_attribute(record, attribute)
|
20
|
+
if record.respond_to?(attribute.to_sym)
|
21
|
+
record.__send__(attribute.to_sym)
|
22
|
+
elsif record.respond_to?(:[])
|
23
|
+
record[attribute]
|
24
|
+
else
|
25
|
+
raise InscrutableRecord, "Attribute \"#{attribute}\" could not be extracted for record #{record.inspect}."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
module Enygma
|
5
|
+
module Adapters
|
6
|
+
|
7
|
+
class ActiveRecordAdapter < Enygma::Adapters::AbstractAdapter
|
8
|
+
|
9
|
+
def connect!(datastore)
|
10
|
+
unless datastore.is_a?(Class) && datastore.ancestors.include?(ActiveRecord::Base)
|
11
|
+
raise InvalidDatabase, "#{datastore.inspect} is not an ActiveRecord::Base subclass!"
|
12
|
+
end
|
13
|
+
@datastore = datastore
|
14
|
+
end
|
15
|
+
|
16
|
+
def query(args = {})
|
17
|
+
@datastore.scoped(:conditions => { :id => args[:ids] })
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_attribute(record, attribute)
|
21
|
+
record.read_attribute(attribute)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'mattbauer-dbd'
|
2
|
+
|
3
|
+
module Enygma
|
4
|
+
module Adapters
|
5
|
+
|
6
|
+
class BerkeleyAdapter < Enygma::Adapters::AbstractAdapter
|
7
|
+
|
8
|
+
def connect!(datastore)
|
9
|
+
raise "The BerkeleyDB adapter is current not implemented."
|
10
|
+
end
|
11
|
+
|
12
|
+
def query(args = {})
|
13
|
+
raise "The BerkeleyDB adapter is current not implemented."
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_attribute(record, attribute)
|
17
|
+
raise "The BerkeleyDB adapter is current not implemented."
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Enygma
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
class DatamapperAdapter < Enygma::Adapters::AbstractAdapter
|
5
|
+
|
6
|
+
def connect!(datastore)
|
7
|
+
raise "The Datamapper adapter is current not implemented."
|
8
|
+
end
|
9
|
+
|
10
|
+
def query(args = {})
|
11
|
+
raise "The Datamapper adapter is current not implemented."
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_attribute(record, attribute)
|
15
|
+
raise "The Datamapper adapter is current not implemented."
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'memcache'
|
2
|
+
|
3
|
+
module Enygma
|
4
|
+
module Adapters
|
5
|
+
|
6
|
+
class MemcacheAdapter < Enygma::Adapters::AbstractAdapter
|
7
|
+
|
8
|
+
def connect!(datastore)
|
9
|
+
@datastore = case datastore
|
10
|
+
when MemCache
|
11
|
+
datastore
|
12
|
+
else
|
13
|
+
MemCache.new(datastore)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def query(args = {})
|
18
|
+
ids = args.has_key?(:key_prefix) ? args[:ids].collect {|i| "#{args[:key_prefix]}#{i}"} : args[:ids]
|
19
|
+
@datastore.get_multi(*ids).values
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module Enygma
|
4
|
+
module Adapters
|
5
|
+
|
6
|
+
class SequelAdapter < Enygma::Adapters::AbstractAdapter
|
7
|
+
|
8
|
+
class InvalidTable < StandardError; end
|
9
|
+
|
10
|
+
def connect!(datastore)
|
11
|
+
@datastore = case datastore
|
12
|
+
when Sequel::Model
|
13
|
+
@table = datastore.table_name
|
14
|
+
datastore.db
|
15
|
+
when Sequel::Database
|
16
|
+
datastore
|
17
|
+
when :sqlite
|
18
|
+
Sequel.sqlite
|
19
|
+
else
|
20
|
+
Sequel.connect(datastore)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def query(args = {})
|
25
|
+
get_table(args[:table]).filter(:id => args[:ids])
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_attribute(record, attribute)
|
29
|
+
record[attribute]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def get_table(obj = nil)
|
35
|
+
case obj
|
36
|
+
when Symbol
|
37
|
+
@datastore ? @datastore[obj] : raise(InvalidTable)
|
38
|
+
when String
|
39
|
+
@datastore ? @datastore[obj.to_sym] : raise(InvalidTable)
|
40
|
+
when Sequel::Model
|
41
|
+
obj
|
42
|
+
when Sequel::Database
|
43
|
+
@table ? obj[@table] : raise(InvalidTable)
|
44
|
+
else
|
45
|
+
raise InvalidTable
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'tokyocabinet'
|
2
|
+
|
3
|
+
module Enygma
|
4
|
+
module Adapters
|
5
|
+
|
6
|
+
class TokyoCabinetAdapter < Enygma::Adapters::AbstractAdapter
|
7
|
+
|
8
|
+
def connect!(db)
|
9
|
+
@database = case db
|
10
|
+
when TokyoCabinet::HDB
|
11
|
+
db
|
12
|
+
when TokyoCabinet::BDB
|
13
|
+
db
|
14
|
+
when TokyoCabinet::FDB
|
15
|
+
db
|
16
|
+
when TokyoCabinet::TDB
|
17
|
+
db
|
18
|
+
when String
|
19
|
+
unless File.exist?(db)
|
20
|
+
raise InvalidDatabase, "The Tokyo Cabinet database couldn't be found."
|
21
|
+
end
|
22
|
+
case db
|
23
|
+
when /\.tch$/
|
24
|
+
tkcab = TokyoCabinet::HDB.new
|
25
|
+
tkcab.open(db, TokyoCabinet::HDB::OWRITER | TokyoCabinet::HDB::OCREAT)
|
26
|
+
tkcab
|
27
|
+
when /\.tcb$/
|
28
|
+
tkcab = TokyoCabinet::BDB.new
|
29
|
+
tkcab.open(db, TokyoCabinet::BDB::OWRITER | TokyoCabinet::BDB::OCREAT)
|
30
|
+
tkcab
|
31
|
+
when /\.tcf$/
|
32
|
+
tkcab = TokyoCabinet::FDB.new
|
33
|
+
tkcab.open(db, TokyoCabinet::FDB::OWRITER | TokyoCabinet::FDB::OCREAT)
|
34
|
+
tkcab
|
35
|
+
when /\.tct$/
|
36
|
+
tkcab = TokyoCabinet::TDB.new
|
37
|
+
tkcab.open(db, TokyoCabinet::TDB::OWRITER | TokyoCabinet::TDB::OCREAT)
|
38
|
+
tkcab
|
39
|
+
else
|
40
|
+
"The Tokyo Cabinet database type couldn't be inferred from the name given."
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise InvalidDatabase, "The Tokyo Cabinet database couldn't be found."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def query(args = {})
|
48
|
+
prefix = args[:key_prefix] || ''
|
49
|
+
args[:ids] ||= []
|
50
|
+
args[:ids].collect do |i|
|
51
|
+
value = @database.get("#{prefix}#{i}")
|
52
|
+
begin
|
53
|
+
Marshal.load(value)
|
54
|
+
rescue TypeError
|
55
|
+
value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_attribute(record, attribute)
|
61
|
+
if record.respond_to?(attribute.to_sym)
|
62
|
+
record.__send__(attribute.to_sym)
|
63
|
+
elsif record.respond_to?(:[])
|
64
|
+
record[attribute]
|
65
|
+
else
|
66
|
+
record
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Enygma
|
2
|
+
|
3
|
+
# An Enygma::Configuration object holds the data needed to perform a search query, but
|
4
|
+
# without the query-specific parameters. You can create a Configuration object and use it
|
5
|
+
# to configure numerous Search object with the same settings.
|
6
|
+
class Configuration
|
7
|
+
|
8
|
+
class InvalidAdapterName < StandardError
|
9
|
+
def message
|
10
|
+
"Invalid adapter type! Allowable adapters are #{Enygma::Configuration::ADAPTERS.join(', ')}."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AdapterNotSet < StandardError
|
15
|
+
def message
|
16
|
+
"You haven't chosen an adapter to use. Available adapters are #{Enygma::Configuration::ADAPTERS.join(', ')}."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class TooManyTables < StandardError
|
21
|
+
def message
|
22
|
+
"A class including Enygma::Resource can only search on one table."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class AmbiguousIndex < StandardError
|
27
|
+
def message
|
28
|
+
"You must specify which table goes with what index."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# The symbol names of the valid adapter types.
|
33
|
+
ADAPTERS = [ :sequel, :active_record, :datamapper, :memcache, :berkeley, :tokyo_cabinet ]
|
34
|
+
|
35
|
+
# If all your Sphinx index names end with the same suffix (default is '_idx'), you can refer
|
36
|
+
# to them just by their base name.
|
37
|
+
#
|
38
|
+
# For example, if you have two indexes names 'posts_index' and 'comments_index', declaring the
|
39
|
+
# Enygma::Configuration.index_suffix = '_index' will allow you to refer to those indexes as
|
40
|
+
# just :posts and :comments respectively. Such as in
|
41
|
+
# search(:posts).for("turkey").using_indexes(:posts, :comments)
|
42
|
+
def self.index_suffix(suffix = nil)
|
43
|
+
return @@index_suffix if suffix.nil?
|
44
|
+
@@index_suffix = suffix
|
45
|
+
end
|
46
|
+
@@index_suffix = '_idx'
|
47
|
+
|
48
|
+
# The target_attr is the Sphinx attribute that points to the identifier for the original record
|
49
|
+
# (usually the record's id). After querying Sphinx, Enygma will use the values returned for this
|
50
|
+
# attribute to fetch records from the database. Defaults to 'item_id'.
|
51
|
+
def self.target_attr(name = nil)
|
52
|
+
return @@target_attr if name.nil?
|
53
|
+
@@target_attr = name
|
54
|
+
end
|
55
|
+
@@target_attr = 'item_id'
|
56
|
+
|
57
|
+
# If you're using Enygma in a bunch of different places and always using the same adapter,
|
58
|
+
# you can declare it globally. New Configuration objects will default to using the named
|
59
|
+
# adapter.
|
60
|
+
def self.adapter(name = nil)
|
61
|
+
return @@adapter if name.nil?
|
62
|
+
if name == :none
|
63
|
+
@@database = nil
|
64
|
+
@@adapter = nil
|
65
|
+
return @@adapter
|
66
|
+
end
|
67
|
+
raise InvalidAdapterName unless ADAPTERS.include?(name)
|
68
|
+
case name
|
69
|
+
when :sequel
|
70
|
+
require 'enygma/adapters/sequel'
|
71
|
+
@@adapter = Enygma::Adapters::SequelAdapter.new
|
72
|
+
when :active_record
|
73
|
+
require 'enygma/adapters/active_record'
|
74
|
+
@@adapter = Enygma::Adapters::ActiveRecordAdapter.new
|
75
|
+
when :datamapper
|
76
|
+
require 'enygma/adapters/datamapper'
|
77
|
+
@@adapter = Enygma::Adapters::DatamapperAdapter.new
|
78
|
+
when :memcache
|
79
|
+
require 'enygma/adapters/memcache'
|
80
|
+
@@adapater = Enygma::Adapters::MemcacheAdapter.new
|
81
|
+
when :berkeley
|
82
|
+
require 'enygma/adapters/berkeley'
|
83
|
+
@@adapater = Enygma::Adapters::BerkeleyAdapter.new
|
84
|
+
when :tokyo_cabinet
|
85
|
+
require 'enygma/adapters/tokyo_cabinet'
|
86
|
+
@@adapater = Enygma::Adapters::TokyoCabinetAdapter.new
|
87
|
+
end
|
88
|
+
end
|
89
|
+
@@adapter = nil
|
90
|
+
|
91
|
+
# Sets the global port and host configuration for Sphinx.
|
92
|
+
# Defaults to { :port => 3312, :host => "localhost" }
|
93
|
+
def self.sphinx
|
94
|
+
@@sphinx
|
95
|
+
end
|
96
|
+
@@sphinx = { :port => 3312, :host => "localhost" }
|
97
|
+
class << @@sphinx
|
98
|
+
def port(portname = nil)
|
99
|
+
return self[:port] if portname.nil?
|
100
|
+
self[:port] = portname
|
101
|
+
end
|
102
|
+
|
103
|
+
def host(hostname = nil)
|
104
|
+
return self[:host] if hostname.nil?
|
105
|
+
self[:host] = hostname
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Evals the block against the Enygma::Configuration class to set global configuration.
|
110
|
+
def self.global(&config)
|
111
|
+
self.instance_eval(&config)
|
112
|
+
end
|
113
|
+
|
114
|
+
attr_reader :adapter, :table, :indexes, :target_attr, :match_mode, :weights, :latitude, :longitude, :resource
|
115
|
+
|
116
|
+
def initialize(attributes = {}, &block)
|
117
|
+
@adapter = @@adapter
|
118
|
+
@table = nil
|
119
|
+
@indexes = []
|
120
|
+
@target_attr = @@target_attr
|
121
|
+
@match_mode = :all
|
122
|
+
@weights = {}
|
123
|
+
@latitude = 'lat'
|
124
|
+
@longitude = 'lng'
|
125
|
+
@key_prefix = nil
|
126
|
+
attributes.each do |name, value|
|
127
|
+
self.__send__(name, value)
|
128
|
+
end
|
129
|
+
self.instance_eval(&block) if block
|
130
|
+
end
|
131
|
+
|
132
|
+
def adapter(name = nil)
|
133
|
+
return @adapter if name.nil?
|
134
|
+
if name == :none
|
135
|
+
@adapter = nil
|
136
|
+
return @adapter
|
137
|
+
end
|
138
|
+
raise InvalidAdapterName unless ADAPTERS.include?(name)
|
139
|
+
case name
|
140
|
+
when :sequel
|
141
|
+
require 'enygma/adapters/sequel'
|
142
|
+
@adapter = Enygma::Adapters::SequelAdapter.new
|
143
|
+
when :active_record
|
144
|
+
require 'enygma/adapters/active_record'
|
145
|
+
@adapter = Enygma::Adapters::ActiveRecordAdapter.new
|
146
|
+
when :datamapper
|
147
|
+
require 'enygma/adapters/datamapper'
|
148
|
+
@adapter = Enygma::Adapters::DatamapperAdapter.new
|
149
|
+
when :memcache
|
150
|
+
require 'enygma/adapters/memcache'
|
151
|
+
@adapter = Enygma::Adapters::MemcacheAdapter.new
|
152
|
+
when :berkeley
|
153
|
+
require 'enygma/adapters/berkeley'
|
154
|
+
@adapter = Enygma::Adapters::BerkeleyAdapter.new
|
155
|
+
when :tokyo_cabinet
|
156
|
+
require 'enygma/adapters/tokyo_cabinet'
|
157
|
+
@adapter = Enygma::Adapters::TokyoCabinetAdapter.new
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def datastore(store)
|
162
|
+
raise AdapterNotSet unless @adapter
|
163
|
+
@adapter.connect!(store)
|
164
|
+
end
|
165
|
+
|
166
|
+
def key_prefix(prefix = nil)
|
167
|
+
return @key_prefix if prefix.nil?
|
168
|
+
@key_prefix = prefix
|
169
|
+
end
|
170
|
+
|
171
|
+
def table(table_name = nil, options = {})
|
172
|
+
return @table if table_name.nil?
|
173
|
+
@table = table_name
|
174
|
+
if idxs = options[:index] || options[:indexes]
|
175
|
+
idx_names = [ *idxs ].collect { |idx| Enygma.indexify(idx) }
|
176
|
+
@indexes += idx_names
|
177
|
+
end
|
178
|
+
return @table
|
179
|
+
end
|
180
|
+
|
181
|
+
def weight(attribute, value)
|
182
|
+
@weights[attribute.to_s] = value
|
183
|
+
end
|
184
|
+
|
185
|
+
def index(index)
|
186
|
+
@indexes << Enygma.indexify(index)
|
187
|
+
return @indexes
|
188
|
+
end
|
189
|
+
|
190
|
+
def match_mode(mode = nil)
|
191
|
+
return @match_mode if mode.nil?
|
192
|
+
@match_mode = mode
|
193
|
+
end
|
194
|
+
|
195
|
+
def target_attr(name = nil)
|
196
|
+
return @target_attr if name.nil?
|
197
|
+
@target_attr = name
|
198
|
+
end
|
199
|
+
|
200
|
+
def sphinx
|
201
|
+
@@sphinx
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Enygma
|
2
|
+
class GeoDistanceProxy
|
3
|
+
|
4
|
+
class InvalidUnits < StandardError; end
|
5
|
+
|
6
|
+
failproc = Proc.new { |d| raise(InvalidUnits, "\"#{d}\" is not a supported distance unit.") }
|
7
|
+
|
8
|
+
UNIT_CONVERSION = Hash.new(failproc).merge({
|
9
|
+
:meters => Proc.new { |d| d },
|
10
|
+
:kilometers => Proc.new { |d| d / 1000.0 },
|
11
|
+
:feet => Proc.new { |d| d * 0.3048 },
|
12
|
+
:miles => Proc.new { |d| d / 1609.344 },
|
13
|
+
:yards => Proc.new { |d| d * 0.9144 }
|
14
|
+
})
|
15
|
+
|
16
|
+
def initialize(delegate, distance)
|
17
|
+
@delegate = delegate
|
18
|
+
@distance = distance
|
19
|
+
@units = :meters
|
20
|
+
end
|
21
|
+
|
22
|
+
def meters
|
23
|
+
@units = :meters
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def kilometers
|
28
|
+
@units = :kilometers
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def feet
|
33
|
+
@units = :feet
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def miles
|
38
|
+
@units = :miles
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def yards
|
43
|
+
@units = :yards
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def of(point_or_lat, lng = nil)
|
48
|
+
if lng.nil?
|
49
|
+
if point_or_lat.respond_to?(:lat) && point_or_lat.respond_to?(:lng)
|
50
|
+
lat, lng = point_or_lat.lat, point_or_lat.lng
|
51
|
+
elsif point_or_lat.respond_to?(:coordinates) && point_or_lat.coordinates.respond_to?(:lat) && point_or_lat.coordinates.respond_to?(:lng)
|
52
|
+
lat, lng = point_or_lat.coordinates.lat, point_or_lat.coordinates.lng
|
53
|
+
elsif point_or_lat.respond_to?(:point) && point_or_lat.point.respond_to?(:lat) && point_or_lat.point.respond_to?(:lng)
|
54
|
+
lat, lng = point_or_lat.point.lat, point_or_lat.point.lng
|
55
|
+
else
|
56
|
+
raise ArgumentError, "#{point_or_lat.inspect} doesn't seem to be a geometry-enabled object!"
|
57
|
+
end
|
58
|
+
else
|
59
|
+
lat, lng = point_or_lat, lng
|
60
|
+
end
|
61
|
+
@delegate.__send__(:geo_anchor, lat, lng)
|
62
|
+
@delegate.filter('@geodist', UNIT_CONVERSION[@units][@distance])
|
63
|
+
@delegate
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|