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.
@@ -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,6 @@
1
+ require File.dirname(__FILE__) + '/sphinx/request'
2
+ require File.dirname(__FILE__) + '/sphinx/response'
3
+ require File.dirname(__FILE__) + '/sphinx/client'
4
+
5
+ module Sphinx
6
+ end
@@ -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,13 @@
1
+ module Enygma
2
+ module Extensions
3
+ module FloatExtensions
4
+
5
+ def to_rad
6
+ (self * 2.0 * Math::PI) / 360.0
7
+ end
8
+
9
+ end
10
+ end
11
+ end
12
+
13
+ Float.__send__(:include, Enygma::Extensions::FloatExtensions)
@@ -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