ultrasphinx 1.5.3 → 1.6
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +7 -1
- data/Manifest +6 -6
- data/README +3 -3
- data/TODO +2 -1
- data/examples/default.base +1 -0
- data/lib/ultrasphinx.rb +2 -1
- data/lib/ultrasphinx/configure.rb +10 -11
- data/lib/ultrasphinx/core_extensions.rb +2 -1
- data/lib/ultrasphinx/fields.rb +29 -18
- data/lib/ultrasphinx/search.rb +77 -92
- data/lib/ultrasphinx/search/internals.rb +133 -102
- data/lib/ultrasphinx/ultrasphinx.rb +0 -8
- data/test/integration/app/app/models/geo/state.rb +2 -1
- data/test/integration/app/app/models/person/user.rb +2 -1
- data/test/integration/app/config/environment.rb +0 -2
- data/test/integration/app/config/ultrasphinx/development.conf +6 -6
- data/test/integration/app/config/ultrasphinx/development.conf.canonical +6 -6
- data/test/integration/app/test/fixtures/sellers.yml +2 -2
- data/test/integration/app/test/fixtures/users.yml +2 -2
- data/test/integration/search_test.rb +67 -40
- data/test/setup.rb +3 -0
- data/ultrasphinx.gemspec +18 -17
- data/vendor/riddle/MIT-LICENSE +20 -0
- data/vendor/riddle/riddle.rb +15 -0
- data/vendor/riddle/riddle/client.rb +409 -0
- data/vendor/riddle/riddle/client/filter.rb +42 -0
- data/vendor/riddle/riddle/client/message.rb +54 -0
- data/vendor/riddle/riddle/client/response.rb +67 -0
- metadata +22 -16
- metadata.gz.sig +0 -0
- data/test/ts.multi +0 -2
- data/vendor/sphinx/LICENSE +0 -58
- data/vendor/sphinx/README +0 -40
- data/vendor/sphinx/Rakefile +0 -21
- data/vendor/sphinx/init.rb +0 -1
- data/vendor/sphinx/lib/client.rb +0 -647
@@ -5,7 +5,7 @@
|
|
5
5
|
crypted_password: "2fdefe5c83d80a03a828dd65e90cfff65f0fb42d043a254ca2cad6af968d0e15" #password
|
6
6
|
email: <%= "user#{num}@test.com" %>
|
7
7
|
salt: "1000"
|
8
|
-
|
9
|
-
|
8
|
+
updated_at: <%= (time = Time.parse("Tue Oct 23 04:28:11")).to_s(:db) %>
|
9
|
+
created_at: <%= (time - 180).to_s(:db) %>
|
10
10
|
deleted: <%= num == 41 ? true : false %>
|
11
11
|
<% end %>
|
@@ -14,22 +14,42 @@ class SearchTest < Test::Unit::TestCase
|
|
14
14
|
assert_equal 20, @s.results.size
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
|
19
|
-
assert_raises(E) { @s.total_entries }
|
20
|
-
assert_raises(E) { @s.response }
|
21
|
-
assert_raises(E) { @s.facets }
|
22
|
-
assert_raises(E) { @s.results }
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_subtotals
|
17
|
+
def test_with_subtotals_option
|
18
|
+
S.client_options['with_subtotals'] = true
|
26
19
|
@s = S.new.run
|
27
20
|
assert_equal @s.total_entries, @s.subtotals.values._sum
|
21
|
+
S.client_options['with_subtotals'] = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_ignore_missing_records_option
|
25
|
+
@s = S.new(:per_page => 1).run
|
26
|
+
@record = @s.first
|
27
|
+
assert_equal 1, @s.size
|
28
|
+
|
29
|
+
@record.destroy
|
30
|
+
|
31
|
+
assert_raises(ActiveRecord::RecordNotFound) do
|
32
|
+
@s = S.new(:per_page => 1).run
|
33
|
+
end
|
34
|
+
|
35
|
+
S.client_options['ignore_missing_records'] = true
|
36
|
+
assert_nothing_raised do
|
37
|
+
@s = S.new(:per_page => 1).run
|
38
|
+
end
|
39
|
+
assert_equal 0, @s.size
|
40
|
+
assert_equal 1, @s.per_page
|
41
|
+
|
42
|
+
S.client_options['ignore_missing_records'] = false
|
43
|
+
|
44
|
+
# Re-insert the record... ugh
|
45
|
+
@new_record = @record.class.new(@record.attributes)
|
46
|
+
@new_record.id = @record.id
|
47
|
+
@new_record.save!
|
28
48
|
end
|
29
49
|
|
30
50
|
def test_query_retries_and_fails
|
31
51
|
system("cd #{RAILS_ROOT}; rake ultrasphinx:daemon:stop &> /dev/null")
|
32
|
-
assert_raises(
|
52
|
+
assert_raises(Ultrasphinx::DaemonError) do
|
33
53
|
S.new.run
|
34
54
|
end
|
35
55
|
system("cd #{RAILS_ROOT}; rake ultrasphinx:daemon:start &> /dev/null")
|
@@ -77,11 +97,10 @@ class SearchTest < Test::Unit::TestCase
|
|
77
97
|
end
|
78
98
|
|
79
99
|
def test_sort_by_string
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
# )
|
100
|
+
assert_equal(
|
101
|
+
Seller.find(:all, :limit => 5, :order => 'mission_statement ASC').map(&:mission_statement),
|
102
|
+
S.new(:class_names => 'Seller', :sort_by => 'mission_statement', :sort_mode => 'ascending', :per_page => 5).run.map(&:mission_statement)
|
103
|
+
)
|
85
104
|
end
|
86
105
|
|
87
106
|
def test_filter
|
@@ -104,20 +123,19 @@ class SearchTest < Test::Unit::TestCase
|
|
104
123
|
end
|
105
124
|
|
106
125
|
def test_date_range_filter
|
107
|
-
# XXX some strange boundary error in this one
|
108
126
|
@first, @last = Seller.find(5).created_at, Seller.find(10).created_at
|
109
|
-
@items = Seller.find(:all, :conditions => ['created_at
|
127
|
+
@items = Seller.find(:all, :conditions => ['created_at >= ? AND created_at <= ?', @last, @first]).sort_by(&:id)
|
110
128
|
@count = @items.size
|
111
129
|
|
112
130
|
@search = S.new(:class_names => 'Seller', :filters => {'created_at' => @first..@last}).run.sort_by(&:id)
|
113
131
|
assert_equal(@count, @search.size)
|
114
|
-
assert_equal(@items.first, @search.first)
|
115
|
-
assert_equal(@items.last, @search.last)
|
132
|
+
assert_equal(@items.first.created_at, @search.first.created_at)
|
133
|
+
assert_equal(@items.last.created_at, @search.last.created_at)
|
116
134
|
|
117
135
|
assert_equal(@count,
|
118
136
|
S.new(:class_names => 'Seller', :filters => {'created_at' => @last..@first}).run.size)
|
119
137
|
assert_equal(@count,
|
120
|
-
S.new(:class_names => 'Seller', :filters => {'created_at' => @last.strftime(STRFTIME)
|
138
|
+
S.new(:class_names => 'Seller', :filters => {'created_at' => @last.strftime(STRFTIME)..@first.strftime(STRFTIME)}).run.size)
|
121
139
|
|
122
140
|
assert_raises(Ultrasphinx::UsageError) do
|
123
141
|
S.new(:class_names => 'Seller', :filters => {'created_at' => "bogus".."sugob"}).run.size
|
@@ -132,7 +150,7 @@ class SearchTest < Test::Unit::TestCase
|
|
132
150
|
end
|
133
151
|
|
134
152
|
def test_invalid_filter
|
135
|
-
assert_raises(
|
153
|
+
assert_raises(Ultrasphinx::UsageError) do
|
136
154
|
S.new(:class_names => 'Seller', :filters => {'bogus' => 17}).run
|
137
155
|
end
|
138
156
|
end
|
@@ -140,14 +158,16 @@ class SearchTest < Test::Unit::TestCase
|
|
140
158
|
def test_conditions
|
141
159
|
@deleted_count = User.count(:conditions => {:deleted => true })
|
142
160
|
assert_equal 1, @deleted_count
|
143
|
-
assert_equal User.count - @deleted_count, S.new(:
|
161
|
+
assert_equal User.count - @deleted_count, S.new(:class_names => 'User').run.total_entries
|
144
162
|
end
|
145
163
|
|
146
|
-
# def test_mismatched_facet_configuration
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
164
|
+
# def test_mismatched_facet_configuration
|
165
|
+
# # XXX Should be caught at configuration time. For now it's your own fault
|
166
|
+
# # if you do it and get confused.
|
167
|
+
# assert_raises(Ultrasphinx::ConfigurationError) do
|
168
|
+
# Ultrasphinx::Search.new(:facets => 'company_name').run
|
169
|
+
# end
|
170
|
+
# end
|
151
171
|
|
152
172
|
def test_bogus_facet_name
|
153
173
|
assert_raises(Ultrasphinx::UsageError) do
|
@@ -156,14 +176,21 @@ class SearchTest < Test::Unit::TestCase
|
|
156
176
|
end
|
157
177
|
|
158
178
|
def test_unconfigured_sortable_name
|
159
|
-
assert_raises(
|
160
|
-
S.new(:class_names => 'Seller', :sort_by => 'company_name'
|
179
|
+
assert_raises(Ultrasphinx::UsageError) do
|
180
|
+
S.new(:class_names => 'Seller', :sort_by => 'company_name', :sort_mode => 'ascending', :per_page => 5).run
|
161
181
|
end
|
162
182
|
end
|
163
183
|
|
184
|
+
def test_sorting_by_field_with_relevance_order
|
185
|
+
assert_raises(Ultrasphinx::UsageError) do
|
186
|
+
# Defaults to :sort_mode => 'relevance'
|
187
|
+
S.new(:class_names => 'Seller', :sort_by => 'created_at', :per_page => 5).run
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
164
191
|
def test_nonexistent_sortable_name
|
165
|
-
assert_raises(
|
166
|
-
S.new(:class_names => 'Seller', :sort_by => 'bogus'
|
192
|
+
assert_raises(Ultrasphinx::UsageError) do
|
193
|
+
S.new(:class_names => 'Seller', :sort_by => 'bogus', :per_page => 5).run
|
167
194
|
end
|
168
195
|
end
|
169
196
|
|
@@ -175,7 +202,7 @@ class SearchTest < Test::Unit::TestCase
|
|
175
202
|
def test_numeric_facet
|
176
203
|
@user_id_count = Seller.find(:all).map(&:user_id).uniq.size
|
177
204
|
|
178
|
-
@s = Ultrasphinx::Search.new(:
|
205
|
+
@s = Ultrasphinx::Search.new(:class_names => 'Seller', :facets => 'user_id').run
|
179
206
|
assert_equal @user_id_count, @s.facets['user_id'].size
|
180
207
|
|
181
208
|
@s = Ultrasphinx::Search.new(:facets => 'user_id').run
|
@@ -193,25 +220,25 @@ class SearchTest < Test::Unit::TestCase
|
|
193
220
|
end
|
194
221
|
|
195
222
|
def test_float_facet
|
196
|
-
@s = Ultrasphinx::Search.new(:
|
223
|
+
@s = Ultrasphinx::Search.new(:class_names => 'Seller', :facets => 'capitalization').run
|
197
224
|
@s.facets['capitalization'].keys.each do |key|
|
198
|
-
# XXX
|
225
|
+
# XXX http://www.sphinxsearch.com/forum/view.html?id=963
|
199
226
|
# assert key.is_a?(Float)
|
200
227
|
end
|
201
228
|
end
|
202
229
|
|
203
|
-
def
|
204
|
-
|
230
|
+
def test_table_aliasing_and_association_sql
|
231
|
+
assert_equal 2, Ultrasphinx::Search.new(:class_names => 'User', :query => 'company_two:replacement').run.size
|
205
232
|
end
|
206
233
|
|
207
234
|
def test_weights
|
208
|
-
@unweighted = Ultrasphinx::Search.new(:query => '
|
209
|
-
@weighted = Ultrasphinx::Search.new(:query => '
|
210
|
-
assert_not_equal @unweighted
|
235
|
+
@unweighted = Ultrasphinx::Search.new(:query => 'seller16', :per_page => 1).run.first
|
236
|
+
@weighted = Ultrasphinx::Search.new(:query => 'seller16', :weights => {'company' => 2}, :per_page => 1).run.first
|
237
|
+
assert_not_equal @unweighted, @weighted
|
211
238
|
end
|
212
239
|
|
213
240
|
def test_excerpts
|
214
|
-
@s = Ultrasphinx::Search.new(:query => 'seller10')
|
241
|
+
@s = Ultrasphinx::Search.new(:class_names => 'Seller', :query => 'seller10')
|
215
242
|
@excerpted_item = @s.excerpt.first
|
216
243
|
@item = @s.run.first
|
217
244
|
assert_not_equal @item.name, @excerpted_item.name
|
data/test/setup.rb
CHANGED
@@ -5,6 +5,9 @@ puts ["Please make sure that test/integration/app/vendor/rails is symlinked",
|
|
5
5
|
"to a Rails trunk checkout in order for the rake tasks to run properly."]
|
6
6
|
|
7
7
|
Dir.chdir "#{File.dirname(__FILE__)}/integration/app/" do
|
8
|
+
Dir.chdir "vendor/plugins" do
|
9
|
+
system("rm ultrasphinx; ln -s ../../../../../ ultrasphinx")
|
10
|
+
end
|
8
11
|
system("rake db:create")
|
9
12
|
system("rake db:migrate db:fixtures:load")
|
10
13
|
system("rake us:boot")
|
data/ultrasphinx.gemspec
CHANGED
@@ -1,21 +1,28 @@
|
|
1
1
|
|
2
|
-
# Gem::Specification for Ultrasphinx-1.
|
2
|
+
# Gem::Specification for Ultrasphinx-1.6
|
3
3
|
# Originally generated by Echoe
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = %q{ultrasphinx}
|
7
|
-
s.version = "1.
|
8
|
-
|
9
|
-
s.
|
7
|
+
s.version = "1.6"
|
8
|
+
|
9
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.authors = [""]
|
13
|
+
s.date = %q{2007-11-14}
|
14
|
+
s.description = %q{Ruby on Rails configurator and client to the Sphinx fulltext search engine.}
|
10
15
|
s.email = %q{}
|
16
|
+
s.files = ["CHANGELOG", "examples/ap.multi", "examples/default.base", "init.rb", "lib/ultrasphinx/autoload.rb", "lib/ultrasphinx/configure.rb", "lib/ultrasphinx/core_extensions.rb", "lib/ultrasphinx/fields.rb", "lib/ultrasphinx/hex_to_int.sql", "lib/ultrasphinx/is_indexed.rb", "lib/ultrasphinx/search/internals.rb", "lib/ultrasphinx/search/parser.rb", "lib/ultrasphinx/search.rb", "lib/ultrasphinx/spell.rb", "lib/ultrasphinx/ultrasphinx.rb", "lib/ultrasphinx.rb", "LICENSE", "Manifest", "README", "tasks/ultrasphinx.rake", "test/config/ultrasphinx/test.base", "test/integration/app/app/controllers/addresses_controller.rb", "test/integration/app/app/controllers/application.rb", "test/integration/app/app/controllers/sellers_controller.rb", "test/integration/app/app/controllers/states_controller.rb", "test/integration/app/app/controllers/users_controller.rb", "test/integration/app/app/helpers/addresses_helper.rb", "test/integration/app/app/helpers/application_helper.rb", "test/integration/app/app/helpers/sellers_helper.rb", "test/integration/app/app/helpers/states_helper.rb", "test/integration/app/app/helpers/users_helper.rb", "test/integration/app/app/models/geo/address.rb", "test/integration/app/app/models/geo/state.rb", "test/integration/app/app/models/person/user.rb", "test/integration/app/app/models/seller.rb", "test/integration/app/app/views/addresses/edit.html.erb", "test/integration/app/app/views/addresses/index.html.erb", "test/integration/app/app/views/addresses/new.html.erb", "test/integration/app/app/views/addresses/show.html.erb", "test/integration/app/app/views/layouts/addresses.html.erb", "test/integration/app/app/views/layouts/sellers.html.erb", "test/integration/app/app/views/layouts/states.html.erb", "test/integration/app/app/views/layouts/users.html.erb", "test/integration/app/app/views/sellers/edit.html.erb", "test/integration/app/app/views/sellers/index.html.erb", "test/integration/app/app/views/sellers/new.html.erb", "test/integration/app/app/views/sellers/show.html.erb", "test/integration/app/app/views/states/edit.html.erb", "test/integration/app/app/views/states/index.html.erb", "test/integration/app/app/views/states/new.html.erb", "test/integration/app/app/views/states/show.html.erb", "test/integration/app/app/views/users/edit.html.erb", "test/integration/app/app/views/users/index.html.erb", "test/integration/app/app/views/users/new.html.erb", "test/integration/app/app/views/users/show.html.erb", "test/integration/app/config/boot.rb", "test/integration/app/config/database.yml", "test/integration/app/config/environment.rb", "test/integration/app/config/environments/development.rb", "test/integration/app/config/environments/production.rb", "test/integration/app/config/environments/test.rb", "test/integration/app/config/locomotive.yml", "test/integration/app/config/routes.rb", "test/integration/app/config/ultrasphinx/default.base", "test/integration/app/config/ultrasphinx/development.conf", "test/integration/app/config/ultrasphinx/development.conf.canonical", "test/integration/app/db/migrate/001_create_users.rb", "test/integration/app/db/migrate/002_create_sellers.rb", "test/integration/app/db/migrate/003_create_addresses.rb", "test/integration/app/db/migrate/004_create_states.rb", "test/integration/app/db/migrate/005_add_capitalization_to_seller.rb", "test/integration/app/db/migrate/006_add_deleted_to_user.rb", "test/integration/app/db/migrate/007_add_lat_and_long_to_address.rb", "test/integration/app/db/migrate/008_add_mission_statement_to_seller.rb", "test/integration/app/db/schema.rb", "test/integration/app/doc/README_FOR_APP", "test/integration/app/public/404.html", "test/integration/app/public/500.html", "test/integration/app/public/dispatch.cgi", "test/integration/app/public/dispatch.fcgi", "test/integration/app/public/dispatch.rb", "test/integration/app/public/favicon.ico", "test/integration/app/public/images/rails.png", "test/integration/app/public/index.html", "test/integration/app/public/javascripts/application.js", "test/integration/app/public/javascripts/controls.js", "test/integration/app/public/javascripts/dragdrop.js", "test/integration/app/public/javascripts/effects.js", "test/integration/app/public/javascripts/prototype.js", "test/integration/app/public/robots.txt", "test/integration/app/public/stylesheets/scaffold.css", "test/integration/app/Rakefile", "test/integration/app/README", "test/integration/app/script/about", "test/integration/app/script/breakpointer", "test/integration/app/script/console", "test/integration/app/script/destroy", "test/integration/app/script/generate", "test/integration/app/script/performance/benchmarker", "test/integration/app/script/performance/profiler", "test/integration/app/script/plugin", "test/integration/app/script/process/inspector", "test/integration/app/script/process/reaper", "test/integration/app/script/process/spawner", "test/integration/app/script/runner", "test/integration/app/script/server", "test/integration/app/test/fixtures/addresses.yml", "test/integration/app/test/fixtures/sellers.yml", "test/integration/app/test/fixtures/states.yml", "test/integration/app/test/fixtures/users.yml", "test/integration/app/test/functional/addresses_controller_test.rb", "test/integration/app/test/functional/sellers_controller_test.rb", "test/integration/app/test/functional/states_controller_test.rb", "test/integration/app/test/functional/users_controller_test.rb", "test/integration/app/test/test_helper.rb", "test/integration/app/test/unit/address_test.rb", "test/integration/app/test/unit/seller_test.rb", "test/integration/app/test/unit/state_test.rb", "test/integration/app/test/unit/user_test.rb", "test/integration/configure_test.rb", "test/integration/search_test.rb", "test/integration/server_test.rb", "test/integration/spell_test.rb", "test/setup.rb", "test/test_all.rb", "test/test_helper.rb", "test/unit/parser_test.rb", "TODO", "vendor/riddle/MIT-LICENSE", "vendor/riddle/riddle/client/filter.rb", "vendor/riddle/riddle/client/message.rb", "vendor/riddle/riddle/client/response.rb", "vendor/riddle/riddle/client.rb", "vendor/riddle/riddle.rb", "vendor/will_paginate/LICENSE", "ultrasphinx.gemspec"]
|
17
|
+
s.has_rdoc = true
|
11
18
|
s.homepage = %q{http://blog.evanweaver.com/files/doc/fauna/ultrasphinx/}
|
19
|
+
s.require_paths = ["lib"]
|
12
20
|
s.rubyforge_project = %q{fauna}
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
s.authors = [""]
|
16
|
-
s.files = ["CHANGELOG", "examples/ap.multi", "examples/default.base", "init.rb", "lib/ultrasphinx/autoload.rb", "lib/ultrasphinx/configure.rb", "lib/ultrasphinx/core_extensions.rb", "lib/ultrasphinx/fields.rb", "lib/ultrasphinx/hex_to_int.sql", "lib/ultrasphinx/is_indexed.rb", "lib/ultrasphinx/search/internals.rb", "lib/ultrasphinx/search/parser.rb", "lib/ultrasphinx/search.rb", "lib/ultrasphinx/spell.rb", "lib/ultrasphinx/ultrasphinx.rb", "lib/ultrasphinx.rb", "LICENSE", "Manifest", "README", "tasks/ultrasphinx.rake", "test/config/ultrasphinx/test.base", "test/integration/app/app/controllers/addresses_controller.rb", "test/integration/app/app/controllers/application.rb", "test/integration/app/app/controllers/sellers_controller.rb", "test/integration/app/app/controllers/states_controller.rb", "test/integration/app/app/controllers/users_controller.rb", "test/integration/app/app/helpers/addresses_helper.rb", "test/integration/app/app/helpers/application_helper.rb", "test/integration/app/app/helpers/sellers_helper.rb", "test/integration/app/app/helpers/states_helper.rb", "test/integration/app/app/helpers/users_helper.rb", "test/integration/app/app/models/geo/address.rb", "test/integration/app/app/models/geo/state.rb", "test/integration/app/app/models/person/user.rb", "test/integration/app/app/models/seller.rb", "test/integration/app/app/views/addresses/edit.html.erb", "test/integration/app/app/views/addresses/index.html.erb", "test/integration/app/app/views/addresses/new.html.erb", "test/integration/app/app/views/addresses/show.html.erb", "test/integration/app/app/views/layouts/addresses.html.erb", "test/integration/app/app/views/layouts/sellers.html.erb", "test/integration/app/app/views/layouts/states.html.erb", "test/integration/app/app/views/layouts/users.html.erb", "test/integration/app/app/views/sellers/edit.html.erb", "test/integration/app/app/views/sellers/index.html.erb", "test/integration/app/app/views/sellers/new.html.erb", "test/integration/app/app/views/sellers/show.html.erb", "test/integration/app/app/views/states/edit.html.erb", "test/integration/app/app/views/states/index.html.erb", "test/integration/app/app/views/states/new.html.erb", "test/integration/app/app/views/states/show.html.erb", "test/integration/app/app/views/users/edit.html.erb", "test/integration/app/app/views/users/index.html.erb", "test/integration/app/app/views/users/new.html.erb", "test/integration/app/app/views/users/show.html.erb", "test/integration/app/config/boot.rb", "test/integration/app/config/database.yml", "test/integration/app/config/environment.rb", "test/integration/app/config/environments/development.rb", "test/integration/app/config/environments/production.rb", "test/integration/app/config/environments/test.rb", "test/integration/app/config/locomotive.yml", "test/integration/app/config/routes.rb", "test/integration/app/config/ultrasphinx/default.base", "test/integration/app/config/ultrasphinx/development.conf", "test/integration/app/config/ultrasphinx/development.conf.canonical", "test/integration/app/db/migrate/001_create_users.rb", "test/integration/app/db/migrate/002_create_sellers.rb", "test/integration/app/db/migrate/003_create_addresses.rb", "test/integration/app/db/migrate/004_create_states.rb", "test/integration/app/db/migrate/005_add_capitalization_to_seller.rb", "test/integration/app/db/migrate/006_add_deleted_to_user.rb", "test/integration/app/db/migrate/007_add_lat_and_long_to_address.rb", "test/integration/app/db/migrate/008_add_mission_statement_to_seller.rb", "test/integration/app/db/schema.rb", "test/integration/app/doc/README_FOR_APP", "test/integration/app/public/404.html", "test/integration/app/public/500.html", "test/integration/app/public/dispatch.cgi", "test/integration/app/public/dispatch.fcgi", "test/integration/app/public/dispatch.rb", "test/integration/app/public/favicon.ico", "test/integration/app/public/images/rails.png", "test/integration/app/public/index.html", "test/integration/app/public/javascripts/application.js", "test/integration/app/public/javascripts/controls.js", "test/integration/app/public/javascripts/dragdrop.js", "test/integration/app/public/javascripts/effects.js", "test/integration/app/public/javascripts/prototype.js", "test/integration/app/public/robots.txt", "test/integration/app/public/stylesheets/scaffold.css", "test/integration/app/Rakefile", "test/integration/app/README", "test/integration/app/script/about", "test/integration/app/script/breakpointer", "test/integration/app/script/console", "test/integration/app/script/destroy", "test/integration/app/script/generate", "test/integration/app/script/performance/benchmarker", "test/integration/app/script/performance/profiler", "test/integration/app/script/plugin", "test/integration/app/script/process/inspector", "test/integration/app/script/process/reaper", "test/integration/app/script/process/spawner", "test/integration/app/script/runner", "test/integration/app/script/server", "test/integration/app/test/fixtures/addresses.yml", "test/integration/app/test/fixtures/sellers.yml", "test/integration/app/test/fixtures/states.yml", "test/integration/app/test/fixtures/users.yml", "test/integration/app/test/functional/addresses_controller_test.rb", "test/integration/app/test/functional/sellers_controller_test.rb", "test/integration/app/test/functional/states_controller_test.rb", "test/integration/app/test/functional/users_controller_test.rb", "test/integration/app/test/test_helper.rb", "test/integration/app/test/unit/address_test.rb", "test/integration/app/test/unit/seller_test.rb", "test/integration/app/test/unit/state_test.rb", "test/integration/app/test/unit/user_test.rb", "test/integration/configure_test.rb", "test/integration/search_test.rb", "test/integration/server_test.rb", "test/integration/spell_test.rb", "test/setup.rb", "test/test_all.rb", "test/test_helper.rb", "test/ts.multi", "test/unit/parser_test.rb", "TODO", "vendor/sphinx/init.rb", "vendor/sphinx/lib/client.rb", "vendor/sphinx/LICENSE", "vendor/sphinx/Rakefile", "vendor/sphinx/README", "vendor/will_paginate/LICENSE", "ultrasphinx.gemspec"]
|
21
|
+
s.rubygems_version = %q{0.9.4.6}
|
22
|
+
s.summary = %q{Ruby on Rails configurator and client to the Sphinx fulltext search engine.}
|
17
23
|
s.test_files = ["test/test_all.rb"]
|
18
|
-
|
24
|
+
|
25
|
+
s.add_dependency(%q<chronic>, [">= 0"])
|
19
26
|
end
|
20
27
|
|
21
28
|
|
@@ -31,12 +38,6 @@ end
|
|
31
38
|
# p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
|
32
39
|
# p.rdoc_pattern = /is_indexed.rb|search.rb|spell.rb|ultrasphinx.rb|^README|TODO|CHANGELOG|^LICENSE/
|
33
40
|
# p.dependencies = "chronic"
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# require 'rcov/rcovtask'
|
37
|
-
#
|
38
|
-
# Rcov::RcovTask.new(:coverage) do |t|
|
39
|
-
# t.test_files = FileList['test/integration/*.rb', 'test/unit/*.rb']
|
40
|
-
# t.rcov_opts << '--include-file test\/integration\/app\/vendor\/plugins\/ultrasphinx\/lib\/.*\.rb'
|
41
|
-
# t.verbose = true
|
41
|
+
# p.test_pattern = ["test/integration/*.rb", "test/unit/*.rb"]
|
42
|
+
# p.rcov_options << '--include-file test\/integration\/app\/vendor\/plugins\/ultrasphinx\/lib\/.*\.rb'
|
42
43
|
# end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Pat Allan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'riddle/client'
|
2
|
+
require 'riddle/client/filter'
|
3
|
+
require 'riddle/client/message'
|
4
|
+
require 'riddle/client/response'
|
5
|
+
|
6
|
+
module Riddle #:nodoc:
|
7
|
+
module Version #:nodoc:
|
8
|
+
Major = 0
|
9
|
+
Minor = 9
|
10
|
+
Tiny = 8
|
11
|
+
Rev = 871
|
12
|
+
|
13
|
+
String = [Major, Minor, Tiny].join('.') + "r#{Rev}"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,409 @@
|
|
1
|
+
module Riddle
|
2
|
+
class VersionError < StandardError; end
|
3
|
+
class ResponseError < StandardError; end
|
4
|
+
|
5
|
+
# This class was heavily based on the existing Client API by Dmytro Shteflyuk
|
6
|
+
# and Alexy Kovyrin. Their code worked fine, I just wanted something a bit
|
7
|
+
# more Ruby-ish (ie. lowercase and underscored method names). I also have
|
8
|
+
# used a few helper classes, just to neaten things up.
|
9
|
+
#
|
10
|
+
# I do plan to release this part of the plugin as a standalone library at
|
11
|
+
# some point - if you're interested in using it, and are feeling impatient,
|
12
|
+
# feel free to hassle me.
|
13
|
+
#
|
14
|
+
class Client
|
15
|
+
Commands = {
|
16
|
+
:search => 0, # SEARCHD_COMMAND_SEARCH
|
17
|
+
:excerpt => 1, # SEARCHD_COMMAND_EXCERPT
|
18
|
+
:update => 2 # SEARCHD_COMMAND_UPDATE
|
19
|
+
}
|
20
|
+
|
21
|
+
Versions = {
|
22
|
+
:search => 0x10F, # VER_COMMAND_SEARCH
|
23
|
+
:excerpt => 0x100, # VER_COMMAND_EXCERPT
|
24
|
+
:update => 0x100 # VER_COMMAND_UPDATE
|
25
|
+
}
|
26
|
+
|
27
|
+
Statuses = {
|
28
|
+
:ok => 0, # SEARCHD_OK
|
29
|
+
:error => 1, # SEARCHD_ERROR
|
30
|
+
:retry => 2, # SEARCHD_RETRY
|
31
|
+
:warning => 3 # SEARCHD_WARNING
|
32
|
+
}
|
33
|
+
|
34
|
+
MatchModes = {
|
35
|
+
:all => 0, # SPH_MATCH_ALL
|
36
|
+
:any => 1, # SPH_MATCH_ANY
|
37
|
+
:phrase => 2, # SPH_MATCH_PHRASE
|
38
|
+
:boolean => 3, # SPH_MATCH_BOOLEAN
|
39
|
+
:extended => 4 # SPH_MATCH_EXTENDED
|
40
|
+
}
|
41
|
+
|
42
|
+
SortModes = {
|
43
|
+
:relevance => 0, # SPH_SORT_RELEVANCE
|
44
|
+
:attr_desc => 1, # SPH_SORT_ATTR_DESC
|
45
|
+
:attr_asc => 2, # SPH_SORT_ATTR_ASC
|
46
|
+
:time_segments => 3, # SPH_SORT_TIME_SEGMENTS
|
47
|
+
:extended => 4 # SPH_SORT_EXTENDED
|
48
|
+
}
|
49
|
+
|
50
|
+
AttributeTypes = {
|
51
|
+
:integer => 1, # SPH_ATTR_INTEGER
|
52
|
+
:timestamp => 2, # SPH_ATTR_TIMESTAMP
|
53
|
+
:ordinal => 3, # SPH_ATTR_ORDINAL
|
54
|
+
:bool => 4, # SPH_ATTR_BOOL
|
55
|
+
:float => 5, # SPH_ATTR_FLOAT
|
56
|
+
:multi => 0x40000000 # SPH_ATTR_MULTI
|
57
|
+
}
|
58
|
+
|
59
|
+
GroupFunctions = {
|
60
|
+
:day => 0, # SPH_GROUPBY_DAY
|
61
|
+
:week => 1, # SPH_GROUPBY_WEEK
|
62
|
+
:month => 2, # SPH_GROUPBY_MONTH
|
63
|
+
:year => 3, # SPH_GROUPBY_YEAR
|
64
|
+
:attr => 4, # SPH_GROUPBY_ATTR
|
65
|
+
:attrpair => 5 # SPH_GROUPBY_ATTRPAIR
|
66
|
+
}
|
67
|
+
|
68
|
+
FilterTypes = {
|
69
|
+
:values => 0, # SPH_FILTER_VALUES
|
70
|
+
:range => 1, # SPH_FILTER_RANGE
|
71
|
+
:float_range => 2 # SPH_FILTER_FLOATRANGE
|
72
|
+
}
|
73
|
+
|
74
|
+
attr_accessor :server, :port, :offset, :limit, :max_matches,
|
75
|
+
:match_mode, :sort_mode, :sort_by, :weights, :id_range, :filters,
|
76
|
+
:group_by, :group_function, :group_clause, :group_distinct, :cut_off,
|
77
|
+
:retry_count, :retry_delay, :anchor
|
78
|
+
attr_reader :queue
|
79
|
+
|
80
|
+
# Can instantiate with a specific server and port - otherwise it assumes
|
81
|
+
# defaults of localhost and 3312 respectively. All other settings can be
|
82
|
+
# accessed and changed via the attribute accessors.
|
83
|
+
def initialize(server=nil, port=nil)
|
84
|
+
@server = server || "localhost"
|
85
|
+
@port = port || 3312
|
86
|
+
|
87
|
+
# defaults
|
88
|
+
@offset = 0
|
89
|
+
@limit = 20
|
90
|
+
@max_matches = 1000
|
91
|
+
@match_mode = :all
|
92
|
+
@sort_mode = :relevance
|
93
|
+
@sort_by = ''
|
94
|
+
@weights = []
|
95
|
+
@id_range = 0..0
|
96
|
+
@filters = []
|
97
|
+
@group_by = ''
|
98
|
+
@group_function = :day
|
99
|
+
@group_clause = '@group desc'
|
100
|
+
@group_distinct = ''
|
101
|
+
@cut_off = 0
|
102
|
+
@retry_count = 0
|
103
|
+
@retry_delay = 0
|
104
|
+
@anchor = {}
|
105
|
+
# string keys are index names, integer values are weightings
|
106
|
+
@index_weights = {}
|
107
|
+
|
108
|
+
@queue = []
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_anchor(lat_attr, lat, long_attr, long)
|
112
|
+
@anchor = {
|
113
|
+
:latitude_attribute => lat_attr,
|
114
|
+
:latitude => lat,
|
115
|
+
:longtitude_attribute => long_attr,
|
116
|
+
:longtitude => long
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def append_query(search, index = '*')
|
121
|
+
@queue << query_message(search, index)
|
122
|
+
end
|
123
|
+
|
124
|
+
def run
|
125
|
+
response = Response.new request(:search, @queue)
|
126
|
+
|
127
|
+
results = @queue.collect do
|
128
|
+
result = {
|
129
|
+
:matches => [],
|
130
|
+
:fields => [],
|
131
|
+
:attributes => {},
|
132
|
+
:attribute_names => [],
|
133
|
+
:words => {}
|
134
|
+
}
|
135
|
+
|
136
|
+
result[:status] = response.next_int
|
137
|
+
case result[:status]
|
138
|
+
when Statuses[:warning]
|
139
|
+
result[:warning] = response.next
|
140
|
+
when Statuses[:error]
|
141
|
+
result[:error] = response.next
|
142
|
+
next result
|
143
|
+
end
|
144
|
+
|
145
|
+
result[:fields] = response.next_array
|
146
|
+
|
147
|
+
attributes = response.next_int
|
148
|
+
for i in 0...attributes
|
149
|
+
attribute_name = response.next
|
150
|
+
type = response.next_int
|
151
|
+
|
152
|
+
result[:attributes][attribute_name] = type
|
153
|
+
result[:attribute_names] << attribute_name
|
154
|
+
end
|
155
|
+
|
156
|
+
matches = response.next_int
|
157
|
+
is_64_bit = response.next_int
|
158
|
+
for i in 0...matches
|
159
|
+
doc = is_64_bit > 0 ? (response.next_int() << 32) + response.next_int : response.next_int
|
160
|
+
weight = response.next_int
|
161
|
+
|
162
|
+
result[:matches] << {:doc => doc, :weight => weight, :index => i, :attributes => {}}
|
163
|
+
result[:attribute_names].each do |attr|
|
164
|
+
case result[:attributes][attr]
|
165
|
+
when AttributeTypes[:float]
|
166
|
+
result[:matches].last[:attributes][attr] = response.next_float
|
167
|
+
when AttributeTypes[:multi]
|
168
|
+
result[:matches].last[:attributes][attr] = response.next_int_array
|
169
|
+
else
|
170
|
+
result[:matches].last[:attributes][attr] = response.next_int
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
result[:total] = response.next_int.to_i || 0
|
176
|
+
result[:total_found] = response.next_int.to_i || 0
|
177
|
+
result[:time] = ('%.3f' % (response.next_int / 1000.0)).to_f || 0.0
|
178
|
+
|
179
|
+
words = response.next_int
|
180
|
+
for i in 0...words
|
181
|
+
word = response.next
|
182
|
+
docs = response.next_int
|
183
|
+
hits = response.next_int
|
184
|
+
result[:words][word] = {:docs => docs, :hits => hits}
|
185
|
+
end
|
186
|
+
|
187
|
+
result
|
188
|
+
end
|
189
|
+
|
190
|
+
@queue.clear
|
191
|
+
results
|
192
|
+
end
|
193
|
+
|
194
|
+
# Query the Sphinx daemon - defaulting to all indexes, but you can specify
|
195
|
+
# a specific one if you wish. The search parameter should be a string
|
196
|
+
# following Sphinx's expectations.
|
197
|
+
def query(search, index = '*')
|
198
|
+
@queue << query_message(search, index)
|
199
|
+
self.run.first
|
200
|
+
end
|
201
|
+
|
202
|
+
# Grab excerpts from the indexes. As part of the options, you will need to
|
203
|
+
# define:
|
204
|
+
# * :docs
|
205
|
+
# * :words
|
206
|
+
# * :index
|
207
|
+
#
|
208
|
+
# Optional settings include:
|
209
|
+
# * :before_match (defaults to <span class="match">)
|
210
|
+
# * :after_match (defaults to </span>)
|
211
|
+
# * :chunk_separator (defaults to ' … ' - which is an HTML ellipsis)
|
212
|
+
# * :limit (defaults to 256)
|
213
|
+
# * :around (defaults to 5)
|
214
|
+
#
|
215
|
+
# The defaults differ from the official PHP client, as I've opted for
|
216
|
+
# semantic HTML markup.
|
217
|
+
def excerpts(options = {})
|
218
|
+
options[:index] ||= '*'
|
219
|
+
options[:before_match] ||= '<span class="match">'
|
220
|
+
options[:after_match] ||= '</span>'
|
221
|
+
options[:chunk_separator] ||= ' … ' # ellipsis
|
222
|
+
options[:limit] ||= 256
|
223
|
+
options[:around] ||= 5
|
224
|
+
|
225
|
+
response = Response.new request(:excerpt, excerpts_message(options))
|
226
|
+
|
227
|
+
options[:docs].collect { response.next }
|
228
|
+
end
|
229
|
+
|
230
|
+
# Update attributes
|
231
|
+
def update(index, attributes, values_by_doc)
|
232
|
+
response = Response.new request(
|
233
|
+
:update,
|
234
|
+
update_message(index, attributes, values_by_doc)
|
235
|
+
)
|
236
|
+
|
237
|
+
response.next_int
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
# Connects to the Sphinx daemon, and yields a socket to use. The socket is
|
243
|
+
# closed at the end of the block.
|
244
|
+
def connect(&block)
|
245
|
+
socket = TCPSocket.new @server, @port
|
246
|
+
|
247
|
+
# Checking version
|
248
|
+
version = socket.recv(4).unpack('N*').first
|
249
|
+
if version < 1
|
250
|
+
socket.close
|
251
|
+
raise VersionError, "Can only connect to searchd version 1.0 or better, not version #{version}"
|
252
|
+
end
|
253
|
+
|
254
|
+
# Send version
|
255
|
+
socket.send [1].pack('N'), 0
|
256
|
+
|
257
|
+
begin
|
258
|
+
yield socket
|
259
|
+
ensure
|
260
|
+
socket.close
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Send a collection of messages, for a command type (eg, search, excerpts,
|
265
|
+
# update), to the Sphinx daemon.
|
266
|
+
def request(command, messages)
|
267
|
+
response = ""
|
268
|
+
status = -1
|
269
|
+
version = 0
|
270
|
+
length = 0
|
271
|
+
message = Array(messages).join("")
|
272
|
+
|
273
|
+
connect do |socket|
|
274
|
+
case command
|
275
|
+
when :search
|
276
|
+
# Message length is +4 to account for the following count value for
|
277
|
+
# the number of messages (well, that's what I'm assuming).
|
278
|
+
socket.send [
|
279
|
+
Commands[command], Versions[command],
|
280
|
+
4+message.length, messages.length
|
281
|
+
].pack("nnNN") + message, 0
|
282
|
+
else
|
283
|
+
socket.send [
|
284
|
+
Commands[command], Versions[command], message.length
|
285
|
+
].pack("nnN") + message, 0
|
286
|
+
end
|
287
|
+
|
288
|
+
header = socket.recv(8)
|
289
|
+
status, version, length = header.unpack('n2N')
|
290
|
+
|
291
|
+
while response.length < length
|
292
|
+
part = socket.recv(length - response.length)
|
293
|
+
response << part if part
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
if response.empty? || response.length != length
|
298
|
+
raise ResponseError, "No response from searchd (status: #{status}, version: #{version})"
|
299
|
+
end
|
300
|
+
|
301
|
+
case status
|
302
|
+
when Statuses[:ok]
|
303
|
+
if version < Versions[command]
|
304
|
+
puts format("searchd command v.%d.%d older than client (v.%d.%d)",
|
305
|
+
version >> 8, version & 0xff,
|
306
|
+
Versions[command] >> 8, Versions[command] & 0xff)
|
307
|
+
end
|
308
|
+
response
|
309
|
+
when Statuses[:warning]
|
310
|
+
length = response[0, 4].unpack('N*').first
|
311
|
+
puts response[4, length]
|
312
|
+
response[4 + length, response.length - 4 - length]
|
313
|
+
when Statuses[:error], Statuses[:retry]
|
314
|
+
raise ResponseError, "searchd error (status: #{status}): #{response[4, response.length - 4]}"
|
315
|
+
else
|
316
|
+
raise ResponseError, "Unknown searchd error (status: #{status})"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Generation of the message to send to Sphinx for a search.
|
321
|
+
def query_message(search, index)
|
322
|
+
message = Message.new
|
323
|
+
|
324
|
+
# Mode, Limits, Sort Mode
|
325
|
+
message.append_ints @offset, @limit, MatchModes[@match_mode], SortModes[@sort_mode]
|
326
|
+
message.append_string @sort_by
|
327
|
+
|
328
|
+
# Query
|
329
|
+
message.append_string search
|
330
|
+
|
331
|
+
# Weights
|
332
|
+
message.append_int @weights.length
|
333
|
+
message.append_ints *@weights
|
334
|
+
|
335
|
+
# Index
|
336
|
+
message.append_string index
|
337
|
+
|
338
|
+
# ID Range
|
339
|
+
message.append_ints 0, @id_range.first, @id_range.last
|
340
|
+
|
341
|
+
# Filters
|
342
|
+
message.append_int @filters.length
|
343
|
+
@filters.each { |filter| message.append filter.query_message }
|
344
|
+
|
345
|
+
# Grouping
|
346
|
+
message.append_int GroupFunctions[@group_function]
|
347
|
+
message.append_string @group_by
|
348
|
+
message.append_int @max_matches
|
349
|
+
message.append_string @group_clause
|
350
|
+
message.append_ints @cut_off, @retry_count, @retry_delay
|
351
|
+
message.append_string @group_distinct
|
352
|
+
|
353
|
+
# Anchor Point
|
354
|
+
if @anchor.empty?
|
355
|
+
message.append_int 0
|
356
|
+
else
|
357
|
+
message.append_int 1
|
358
|
+
message.append_string @anchor[:latitude_attribute]
|
359
|
+
message.append_string @anchor[:longtitude_attribute]
|
360
|
+
message.append_floats @anchor[:latitude], @anchor[:longtitude]
|
361
|
+
end
|
362
|
+
|
363
|
+
# Per Index Weights
|
364
|
+
message.append_int @index_weights.length
|
365
|
+
@index_weights.each do |key,val|
|
366
|
+
message.append_string key
|
367
|
+
message.append_int val
|
368
|
+
end
|
369
|
+
|
370
|
+
message.to_s
|
371
|
+
end
|
372
|
+
|
373
|
+
# Generation of the message to send to Sphinx for an excerpts request.
|
374
|
+
def excerpts_message(options)
|
375
|
+
message = Message.new
|
376
|
+
|
377
|
+
message.append [0, 1].pack('N2') # mode = 0, flags = 1
|
378
|
+
message.append_string options[:index]
|
379
|
+
message.append_string options[:words]
|
380
|
+
|
381
|
+
# options
|
382
|
+
message.append_string options[:before_match]
|
383
|
+
message.append_string options[:after_match]
|
384
|
+
message.append_string options[:chunk_separator]
|
385
|
+
message.append_ints options[:limit], options[:around]
|
386
|
+
|
387
|
+
message.append_array options[:docs]
|
388
|
+
|
389
|
+
message.to_s
|
390
|
+
end
|
391
|
+
|
392
|
+
# Generation of the message to send to Sphinx to update attributes of a
|
393
|
+
# document.
|
394
|
+
def update_message(index, attributes, values_by_doc)
|
395
|
+
message = Message.new
|
396
|
+
|
397
|
+
message.append_string index
|
398
|
+
message.append_array attributes
|
399
|
+
|
400
|
+
message.append_int values_by_doc.length
|
401
|
+
values_by_doc.each do |key,values|
|
402
|
+
message.append_int key # document ID
|
403
|
+
message.append_ints *values # array of new values (integers)
|
404
|
+
end
|
405
|
+
|
406
|
+
message.to_s
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|