mongosphinx 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,164 @@
1
+ = MongoSphinx
2
+
3
+ The MongoSphinx library implements an interface between MongoDBand Sphinx
4
+ supporting MongoMapper to automatically index objects in Sphinx. It tries to
5
+ act as transparent as possible: Just an additional method in MongoMapper
6
+ and some Sphinx configuration are needed to get going.
7
+
8
+ == Prerequisites
9
+
10
+ MongoSphinx needs gems MongoMapper and Riddle as well as a running Sphinx
11
+ and a MongoDB installation.
12
+
13
+ sudo gem sources -a http://gems.github.com # Only needed once!
14
+ sudo gem install riddle
15
+ sudo gem install mongomapper
16
+ sudo gem install burke-mongosphinx
17
+
18
+ No additional configuraton is needed for interfacing with MongoDB: Setup is
19
+ done when MongoMapper is able to talk to the MongoDB server.
20
+
21
+ A proper "sphinx.conf" file and a script for retrieving index data have to
22
+ be provided for interfacing with Sphinx: Sorry, no UltraSphinx like
23
+ magic... :-) Depending on the amount of data, more than one index may be used
24
+ and indexes may be consolidated from time to time.
25
+
26
+ This is a sample configuration for a single "main" index:
27
+
28
+ searchd {
29
+ address = 0.0.0.0
30
+ port = 3312
31
+
32
+ log = ./sphinx/searchd.log
33
+ query_log = ./sphinx/query.log
34
+ pid_file = ./sphinx/searchd.pid
35
+ }
36
+
37
+ source mongoblog {
38
+ type = xmlpipe2
39
+
40
+ xmlpipe_command = "rake sphinx:genxml"
41
+ }
42
+
43
+ index mongoblog {
44
+ source = mongoblog
45
+
46
+ charset_type = utf-8
47
+ path = ./sphinx/sphinx_index_main
48
+ }
49
+
50
+ Notice the line "xmlpipe_command =". This is what the indexer runs to generate
51
+ its input. You can change this to whatever works best for you, but I set it up as
52
+ a rake task, with the following in `lib/tasks/sphinx.rake` .
53
+
54
+ Here, :fields is a list of fields to export. Performance tends to suffer if you export
55
+ everything, so you'll probably want to just list the fields you're indexing.
56
+
57
+ namespace :sphinx do
58
+ task :genxml => :environment do
59
+ puts MongoSphinx::Indexer::XMLDocset.new(Food.all(:fields => 'name')).to_s
60
+ end
61
+ end
62
+
63
+ == Models
64
+
65
+ Use method <tt>fulltext_index</tt> to enable indexing of a model. The
66
+ default is to index all attributes but it is recommended to provide a list of
67
+ attribute keys.
68
+
69
+ A side effect of calling this method is, that MongoSphinx overrides the
70
+ default of letting MongoDB create new IDs: Sphinx only allows numeric IDs and
71
+ MongoSphinx forces new objects with the name of the class, a hyphen and an
72
+ integer as ID (e.g. <tt>Post-38497238</tt>). Again: Only these objects are
73
+ indexed due to internal restrictions of Sphinx.
74
+
75
+ Sample:
76
+
77
+ class Post
78
+ include MongoMapper::Document
79
+
80
+ key :title, String
81
+ key :body, String
82
+
83
+ fulltext_index :title, :body
84
+ end
85
+
86
+ Add options <tt>:server</tt> and <tt>:port</tt> to <tt>fulltext_index</tt> if
87
+ the Sphinx server to query is running on a different server (defaults to
88
+ "localhost" with port 3312).
89
+
90
+ Here is a full-featured sample setting additional options:
91
+
92
+ fulltext_index :title, :body, :server => 'my.other.server', :port => 3313
93
+
94
+ == Indexing
95
+
96
+ Automatically starting the reindexing of objects the moment new objects are
97
+ created can be implemented by adding a filter to the model class:
98
+
99
+ after_save :reindex
100
+ def reindex
101
+ `sudo indexer --all --rotate` # Configure sudo to allow this call...
102
+ end
103
+
104
+ This or a similar callback should be added to all models needing instant
105
+ indexing. If indexing is not that crucial or load is high, some additional
106
+ checks for the time of the last call should be added.
107
+
108
+ Keep in mind that reindexing is not incremental, and xml is generated to pass
109
+ data from mongo to sphinx. It's not a speedy operation on large datasets.
110
+
111
+
112
+ == Queries
113
+
114
+ An additional instance method <tt>by_fulltext_index</tt> is added for each
115
+ fulltext indexed model. This method takes a Sphinx query like
116
+ "foo @title bar", runs it within the context of the current class and returns
117
+ an Array of matching MongoDB documents.
118
+
119
+ Samples:
120
+
121
+ Post.by_fulltext_index('first')
122
+ => [...]
123
+
124
+ post = Post.by_fulltext_index('this is @title post').first
125
+ post.title
126
+ => "First Post"
127
+ post.class
128
+ => Post
129
+
130
+ Additional options <tt>:match_mode</tt>, <tt>:limit</tt> and
131
+ <tt>:max_matches</tt> can be provided to customize the behaviour of Riddle.
132
+ Option <tt>:raw</tt> can be set to <tt>true</tt> to do no lookup of the
133
+ document IDs but return the raw IDs instead.
134
+
135
+ Sample:
136
+
137
+ Post.by_fulltext_index('my post', :limit => 100)
138
+
139
+ == Copyright & License
140
+
141
+ Copyright (c) 2009 Burke Libbey, Ryan Neufeld
142
+
143
+ CouchSphinx Copyright (c) 2009 Holtzbrinck Digital GmbH, Jan Ulbrich
144
+
145
+ Permission is hereby granted, free of charge, to any person
146
+ obtaining a copy of this software and associated documentation
147
+ files (the "Software"), to deal in the Software without
148
+ restriction, including without limitation the rights to use,
149
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
150
+ copies of the Software, and to permit persons to whom the
151
+ Software is furnished to do so, subject to the following
152
+ conditions:
153
+
154
+ The above copyright notice and this permission notice shall be
155
+ included in all copies or substantial portions of the Software.
156
+
157
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
158
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
159
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
160
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
161
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
162
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
163
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
164
+ OTHER DEALINGS IN THE SOFTWARE.
data/lib/indexer.rb ADDED
@@ -0,0 +1,217 @@
1
+ # MongoSphinx, a full text indexing extension for MongoDB using
2
+ # Sphinx.
3
+ #
4
+ # This file contains the MongoSphinx::Indexer::XMLDocset and
5
+ # MongoSphinx::Indexer::XMLDoc classes.
6
+
7
+ # Namespace module for the MongoSphinx gem.
8
+
9
+ module MongoSphinx #:nodoc:
10
+
11
+ # Module Indexer contains classes for creating XML input documents for the
12
+ # indexer. Each Sphinx index consists of a single "sphinx:docset" with any
13
+ # number of "sphinx:document" tags.
14
+ #
15
+ # The XML source can be generated from an array of CouchRest objects or from
16
+ # an array of Hashes containing at least fields "classname" and "_id"
17
+ # as returned by MongoDB view "MongoSphinxIndex/couchrests_by_timestamp".
18
+ #
19
+ # Sample:
20
+ #
21
+ # rows = [{ 'name' => 'John', 'phone' => '199 43828',
22
+ # 'classname' => 'Address', '_id' => 'Address-234164'
23
+ # },
24
+ # { 'name' => 'Sue', 'mobile' => '828 19439',
25
+ # 'classname' => 'Address', '_id' => 'Address-422433'
26
+ # }
27
+ # ]
28
+ # puts MongoSphinx::Indexer::XMLDocset.new(rows).to_s
29
+ #
30
+ # <?xml version="1.0" encoding="utf-8"?>
31
+ # <sphinx:docset>
32
+ # <sphinx:schema>
33
+ # <sphinx:attr name="csphinx-class" type="multi"/>
34
+ # <sphinx:field name="classname"/>
35
+ # <sphinx:field name="name"/>
36
+ # <sphinx:field name="phone"/>
37
+ # <sphinx:field name="mobile"/>
38
+ # <sphinx:field name="created_at"/>
39
+ # </sphinx:schema>
40
+ # <sphinx:document id="234164">
41
+ # <csphinx-class>336,623,883,1140</csphinx-class>
42
+ # <classname>Address</classname>
43
+ # <name><![CDATA[[John]]></name>
44
+ # <phone><![CDATA[[199 422433]]></phone>
45
+ # <mobile><![CDATA[[]]></mobile>
46
+ # <created_at><![CDATA[[]]></created_at>
47
+ # </sphinx:document>
48
+ # <sphinx:document id="423423">
49
+ # <csphinx-class>336,623,883,1140</csphinx-class>
50
+ # <classname>Address</classname>
51
+ # <name><![CDATA[[Sue]]></name>
52
+ # <phone><![CDATA[[]]></phone>
53
+ # <mobile><![CDATA[[828 19439]]></mobile>
54
+ # <created_at><![CDATA[[]]></created_at>
55
+ # </sphinx:document>
56
+ # </sphinx:docset>"
57
+
58
+ module Indexer
59
+
60
+ # Class XMLDocset wraps the XML representation of a document to index. It
61
+ # contains a complete "sphinx:docset" including its schema definition.
62
+
63
+ class XMLDocset
64
+
65
+ # Objects contained in document set.
66
+
67
+ attr_reader :xml_docs
68
+
69
+ # XML generated for opening the document.
70
+
71
+ attr_reader :xml_header
72
+
73
+ # XML generated for closing the document.
74
+
75
+ attr_reader :xml_footer
76
+
77
+ # Creates a XMLDocset object from the provided data. It defines a
78
+ # superset of all fields of the classes to index objects for. The class
79
+ # names are collected from the provided objects as well.
80
+ #
81
+ # Parameters:
82
+ #
83
+ # [data] Array with objects of type CouchRest::Document or Hash to create XML for
84
+
85
+ def initialize(rows = [])
86
+ raise ArgumentError, 'Missing class names' if rows.nil?
87
+
88
+ xml = '<?xml version="1.0" encoding="utf-8"?>'
89
+
90
+ xml << '<sphinx:docset><sphinx:schema>'
91
+
92
+ @xml_docs = []
93
+ classes = []
94
+
95
+ rows.each do |row|
96
+ object = nil
97
+
98
+ if row.kind_of? MongoMapper::Document
99
+ object = row
100
+ elsif row.kind_of? Hash
101
+ row = row['value'] if row['classname'].nil?
102
+
103
+ if row and (class_name = row['classname'])
104
+ object = eval(class_name.to_s).new(row) rescue nil
105
+ end
106
+ end
107
+
108
+ if object and object.sphinx_id
109
+ classes << object.class if not classes.include? object.class
110
+ @xml_docs << XMLDoc.from_object(object)
111
+ end
112
+ end
113
+
114
+ field_names = classes.collect { |clas| clas.fulltext_keys rescue []
115
+ }.flatten.uniq
116
+
117
+ field_names.each do |key, value|
118
+ xml << "<sphinx:field name=\"#{key}\"/>"
119
+ end
120
+
121
+ xml << '<sphinx:field name="classname"/>'
122
+ xml << '<sphinx:attr name="csphinx-class" type="multi"/>'
123
+
124
+ xml << '</sphinx:schema>'
125
+
126
+ @xml_header = xml
127
+ @xml_footer = '</sphinx:docset>'
128
+ end
129
+
130
+ # Returns the encoded data as XML.
131
+
132
+ def to_xml
133
+ return to_s
134
+ end
135
+
136
+ # Returns the encoded data as XML.
137
+
138
+ def to_s
139
+ return self.xml_header + self.xml_docs.join + self.xml_footer
140
+ end
141
+ end
142
+
143
+ # Class XMLDoc wraps the XML representation of a single document to index
144
+ # and contains a complete "sphinx:document" tag.
145
+
146
+ class XMLDoc
147
+
148
+ # Returns the ID of the encoded data.
149
+
150
+ attr_reader :id
151
+
152
+ # Returns the class name of the encoded data.
153
+
154
+ attr_reader :class_name
155
+
156
+ # Returns the encoded data.
157
+
158
+ attr_reader :xml
159
+
160
+ # Creates a XMLDoc object from the provided CouchRest object.
161
+ #
162
+ # Parameters:
163
+ #
164
+ # [object] Object to index
165
+
166
+ def self.from_object(object)
167
+ raise ArgumentError, 'Missing object' if object.nil?
168
+ raise ArgumentError, 'No compatible ID' if (id = object.sphinx_id).nil?
169
+
170
+ return new(id, object.class.to_s, object.fulltext_attributes)
171
+ end
172
+
173
+ # Creates a XMLDoc object from the provided ID, class name and data.
174
+ #
175
+ # Parameters:
176
+ #
177
+ # [id] ID of the object to index
178
+ # [class_name] Name of the class
179
+ # [data] Hash with the properties to index
180
+
181
+ def initialize(id, class_name, properties)
182
+ raise ArgumentError, 'Missing id' if id.nil?
183
+ raise ArgumentError, 'Missing class_name' if class_name.nil?
184
+
185
+ xml = "<sphinx:document id=\"#{id}\">"
186
+
187
+ xml << '<csphinx-class>'
188
+ xml << MongoSphinx::MultiAttribute.encode(class_name)
189
+ xml << '</csphinx-class>'
190
+ xml << "<classname>#{class_name}</classname>"
191
+
192
+ properties.each do |key, value|
193
+ xml << "<#{key}><![CDATA[[#{value}]]></#{key}>"
194
+ end
195
+
196
+ xml << '</sphinx:document>'
197
+
198
+ @xml = xml
199
+
200
+ @id = id
201
+ @class_name = class_name
202
+ end
203
+
204
+ # Returns the encoded data as XML.
205
+
206
+ def to_xml
207
+ return to_s
208
+ end
209
+
210
+ # Returns the encoded data as XML.
211
+
212
+ def to_s
213
+ return self.xml
214
+ end
215
+ end
216
+ end
217
+ end
Binary file
@@ -0,0 +1,181 @@
1
+ # MongoSphinx, a full text indexing extension for MongoDB/MongoMapper using
2
+ # Sphinx.
3
+ #
4
+ # This file contains the MongoMapper::Mixins::Indexer module which in turn
5
+ # includes MongoMapper::Mixins::Indexer::ClassMethods.
6
+
7
+ module MongoMapper # :nodoc:
8
+ module Mixins # :nodoc:
9
+
10
+ # Mixin for MongoMapper adding indexing stuff. See class ClassMethods for
11
+ # details.
12
+
13
+ module Indexer #:nodoc:
14
+
15
+ # Bootstrap method to include patches with.
16
+ #
17
+ # Parameters:
18
+ #
19
+ # [base] Class to include class methods of module into
20
+
21
+ def self.included(base)
22
+ base.extend(ClassMethods)
23
+ end
24
+
25
+ # Patches to the MongoMapper Document module: Adds the
26
+ # "fulltext_index" method for enabling indexing and defining the fields
27
+ # to include as a domain specific extention. This method also assures
28
+ # the existence of a special design document used to generate indexes
29
+ # from.
30
+ #
31
+ # An additional save callback sets an ID like "Post-123123" (class name
32
+ # plus pure numeric ID compatible with Sphinx) for new objects).
33
+ #
34
+ # Last but not least method "by_fulltext_index" is defined allowing a
35
+
36
+ # full text search like "foo @title bar" within the context of the
37
+ # current class.
38
+ #
39
+ # Samples:
40
+ #
41
+ # class Post < MongoMapper::Document
42
+ # use_database SERVER.default_database
43
+ #
44
+ # property :title
45
+ # property :body
46
+ #
47
+ # fulltext_index :title, :body
48
+ # end
49
+ #
50
+ # Post.by_fulltext_index('first')
51
+ # => [...]
52
+ # post = Post.by_fulltext_index('this is @title post').first
53
+ # post.title
54
+ # => "First Post"
55
+ # post.class
56
+ # => Post
57
+
58
+ def save_callback()
59
+ object = self
60
+ if object.id.nil?
61
+ idsize = fulltext_opts[:idsize] || 32
62
+ limit = (1 << idsize) - 1
63
+
64
+ while true
65
+ id = rand(limit)
66
+ candidate = "#{self.class.to_s}-#{id}"
67
+
68
+ begin
69
+ object.class.find(candidate) # Resource not found exception if available
70
+ rescue MongoMapper::DocumentNotFound
71
+ object.id = candidate
72
+ break
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+
80
+ module ClassMethods
81
+
82
+ # Method for enabling fulltext indexing and for defining the fields to
83
+ # include.
84
+ #
85
+ # Parameters:
86
+ #
87
+ # [keys] Array of field keys to include plus options Hash
88
+ #
89
+ # Options:
90
+ #
91
+ # [:server] Server name (defaults to localhost)
92
+ # [:port] Server port (defaults to 3312)
93
+ # [:idsize] Number of bits for the ID to generate (defaults to 32)
94
+
95
+ def fulltext_index(*keys)
96
+ opts = keys.pop if keys.last.is_a?(Hash)
97
+ opts ||= {} # Handle some options: Future use... :-)
98
+
99
+ # Save the keys to index and the options for later use in callback.
100
+ # Helper method cattr_accessor is already bootstrapped by couchrest
101
+ # gem.
102
+
103
+ cattr_accessor :fulltext_keys
104
+ cattr_accessor :fulltext_opts
105
+
106
+ self.fulltext_keys = keys
107
+ self.fulltext_opts = opts
108
+
109
+ # Overwrite setting of new ID to do something compatible with
110
+ # Sphinx. If an ID already exists, we try to match it with our
111
+ # Schema and cowardly ignore if not.
112
+
113
+ before_save :save_callback
114
+
115
+ end
116
+
117
+ # Searches for an object of this model class (e.g. Post, Comment) and
118
+ # the requested query string. The query string may contain any query
119
+ # provided by Sphinx.
120
+ #
121
+ # Call MongoMapper::Document.by_fulltext_index() to query
122
+ # without reducing to a single class type.
123
+ #
124
+ # Parameters:
125
+ #
126
+ # [query] Query string like "foo @title bar"
127
+ # [options] Additional options to set
128
+ #
129
+ # Options:
130
+ #
131
+ # [:match_mode] Optional Riddle match mode (defaults to :extended)
132
+ # [:limit] Optional Riddle limit (Riddle default)
133
+ # [:max_matches] Optional Riddle max_matches (Riddle default)
134
+ # [:sort_by] Optional Riddle sort order (also sets sort_mode to :extended)
135
+ # [:raw] Flag to return only IDs and do not lookup objects (defaults to false)
136
+
137
+ def by_fulltext_index(query, options = {})
138
+ if self == Document
139
+ client = Riddle::Client.new
140
+ else
141
+ client = Riddle::Client.new(fulltext_opts[:server],
142
+ fulltext_opts[:port])
143
+
144
+ query = query + " @classname #{self}"
145
+ end
146
+
147
+ client.match_mode = options[:match_mode] || :extended
148
+
149
+ if (limit = options[:limit])
150
+ client.limit = limit
151
+ end
152
+
153
+ if (max_matches = options[:max_matches])
154
+ client.max_matches = matches
155
+ end
156
+
157
+ if (sort_by = options[:sort_by])
158
+ client.sort_mode = :extended
159
+ client.sort_by = sort_by
160
+ end
161
+
162
+ result = client.query(query)
163
+
164
+ #TODO
165
+ if result and result[:status] == 0 and (matches = result[:matches])
166
+ classname = nil
167
+ ids = matches.collect do |row|
168
+ classname = MongoSphinx::MultiAttribute.decode(row[:attributes]['csphinx-class'])
169
+ (classname + '-' + row[:doc].to_s) rescue nil
170
+ end.compact
171
+
172
+ return ids if options[:raw]
173
+ return Object.const_get(classname).find(ids)
174
+ else
175
+ return []
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,64 @@
1
+ # MongoSphinx, a full text indexing extension for MongoDB using
2
+ # Sphinx.
3
+ #
4
+ # This file contains the MongoMapper::Mixins::Properties module.
5
+
6
+ # Patches to the CouchRest library.
7
+
8
+ module MongoMapper # :nodoc:
9
+ module Mixins # :nodoc:
10
+
11
+ # Patches to the CouchRest Properties module: Adds the "attributes" method
12
+ # plus some fulltext relevant stuff.
13
+ #
14
+ # Samples:
15
+ #
16
+ # data = SERVER.default_database.view('CouchSphinxIndex/couchrests_by_timestamp')
17
+ # rows = data['rows']
18
+ # post = Post.new(rows.first)
19
+ #
20
+ # post.attributes
21
+ # => {:tags=>"one, two, three", :updated_at=>Tue Jun 09 14:45:00 +0200 2009,
22
+ # :author=>nil, :title=>"First Post",
23
+ # :created_at=>Tue Jun 09 14:45:00 +0200 2009,
24
+ # :body=>"This is the first post. This is the [...] first post. "}
25
+ #
26
+ # post.fulltext_attributes
27
+ # => {:title=>"First Post", :author=>nil,
28
+ # :created_at=>Tue Jun 09 14:45:00 +0200 2009
29
+ # :body=>"This is the first post. This is the [...] first post. "}
30
+ #
31
+ # post.sphinx_id
32
+ # => "921744775"
33
+ # post.id
34
+ # => "Post-921744775"
35
+
36
+ module Properties
37
+
38
+ # Returns a Hash of all attributes allowed to be indexed. As a side
39
+ # effect it sets the fulltext_keys variable if still blank or empty.
40
+
41
+ def fulltext_attributes
42
+ clas = self.class
43
+
44
+ if not clas.fulltext_keys or clas.fulltext_keys.empty?
45
+ clas.fulltext_keys = self.attributes.collect { |k,v| k.intern }
46
+ end
47
+
48
+ return self.attributes.reject do |k, v|
49
+ not (clas.fulltext_keys.include? k.intern)
50
+ end
51
+ end
52
+
53
+ # Returns the numeric part of the document ID (compatible to Sphinx).
54
+
55
+ def sphinx_id
56
+ if (match = self.id.match(/#{self.class}-([0-9]+)/))
57
+ return match[1]
58
+ else
59
+ return nil
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,57 @@
1
+ # MongoSphinx, a full text indexing extension for MongoDB using
2
+ # Sphinx.
3
+ #
4
+ # This file contains the MongoSphinx::MultiAttribute class.
5
+
6
+ # Namespace module for the MongoSphinx gem.
7
+
8
+ module MongoSphinx #:nodoc:
9
+
10
+ # Module MultiAttribute implements helpers to translate back and
11
+ # forth between Ruby Strings and an array of integers suitable for Sphinx
12
+ # attributes of type "multi".
13
+ #
14
+ # Background: Getting an ID as result for a query is OK, but for example to
15
+ # allow cast safety, we need an aditional attribute. Sphinx supports
16
+ # attributes which are returned together with the ID, but they behave a
17
+ # little different than expected: Instead we can use arrays of integers with
18
+ # ASCII character codes. These values are returned in ascending (!) order of
19
+ # value (yes, sounds funny but is reasonable from an internal view to
20
+ # Sphinx). So we mask each byte with 0x0100++ to keep the order...
21
+ #
22
+ # Sample:
23
+ #
24
+ # MongoSphinx::MultiAttribute.encode('Hello')
25
+ # => "328,613,876,1132,1391"
26
+ # MongoSphinx::MultiAttribute.decode('328,613,876,1132,1391')
27
+ # => "Hello"
28
+
29
+ module MultiAttribute
30
+
31
+ # Returns an numeric representation of a Ruby String suitable for "multi"
32
+ # attributes of Sphinx.
33
+ #
34
+ # Parameters:
35
+ #
36
+ # [str] String to translate
37
+
38
+ def self.encode(str)
39
+ offset = 0
40
+ return str.bytes.collect { |c| (offset+= 0x0100) + c }.join(',')
41
+ end
42
+
43
+ # Returns the original MongoDB ID created from a Sphinx ID. Only works if
44
+ # the ID was created from a MongoDB ID before!
45
+ #
46
+ # Parameters:
47
+ #
48
+ # [multi] Sphinx "multi" attribute to translate back
49
+
50
+ def self.decode(multi)
51
+ offset = 0
52
+ multi = multi.split(',') if not multi.kind_of? Array
53
+
54
+ return multi.collect {|x| (x.to_i-(offset+=0x0100)).chr}.to_s
55
+ end
56
+ end
57
+ end
data/mongosphinx.rb ADDED
@@ -0,0 +1,41 @@
1
+ # MongoSphinx, a full text indexing extension for using
2
+ # Sphinx.
3
+ #
4
+ # This file contains the includes implementing this library. Have a look at
5
+ # the README.rdoc as a starting point.
6
+
7
+ begin
8
+ require 'rubygems'
9
+ rescue LoadError; end
10
+ require 'mongomapper'
11
+ require 'riddle'
12
+
13
+
14
+ module MongoSphinx
15
+ if (match = __FILE__.match(/mongosphinx-([0-9.-]*)/))
16
+ VERSION = match[1]
17
+ else
18
+ VERSION = 'unknown'
19
+ end
20
+ end
21
+
22
+ require 'lib/multi_attribute'
23
+ require 'lib/indexer'
24
+ require 'lib/mixins/indexer'
25
+ require 'lib/mixins/properties'
26
+
27
+
28
+ # Include the Indexer mixin from the original Document class of
29
+ # MongoMapper which adds a few methods and allows calling method indexed_with.
30
+
31
+ module MongoMapper # :nodoc:
32
+ module Document # :nodoc:
33
+ include MongoMapper::Mixins::Indexer
34
+ module InstanceMethods
35
+ include MongoMapper::Mixins::Properties
36
+ end
37
+ module ClassMethods
38
+ include MongoMapper::Mixins::Indexer::ClassMethods
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongosphinx
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Burke Libbey
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-14 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: burke@burkelibbey.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - README.rdoc
26
+ - mongosphinx.rb
27
+ - lib/multi_attribute.rb
28
+ - lib/mixins/properties.rb
29
+ - lib/mixins/indexer.rb
30
+ - lib/mixins/.indexer.rb.swp
31
+ - lib/indexer.rb
32
+ has_rdoc: true
33
+ homepage: http://github.com/burke/mongosphinx
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --exclude
39
+ - pkg
40
+ - --exclude
41
+ - tmp
42
+ - --all
43
+ - --title
44
+ - MongoSphinx
45
+ - --main
46
+ - README.rdoc
47
+ require_paths:
48
+ - .
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A full text indexing extension for MongoDB using Sphinx.
68
+ test_files: []
69
+