ryanb-thinking_sphinx 0.9.8

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.
Files changed (38) hide show
  1. data/LICENCE +20 -0
  2. data/README +60 -0
  3. data/lib/riddle.rb +26 -0
  4. data/lib/riddle/client.rb +639 -0
  5. data/lib/riddle/client/filter.rb +44 -0
  6. data/lib/riddle/client/message.rb +65 -0
  7. data/lib/riddle/client/response.rb +84 -0
  8. data/lib/test.rb +46 -0
  9. data/lib/thinking_sphinx.rb +102 -0
  10. data/lib/thinking_sphinx/active_record.rb +141 -0
  11. data/lib/thinking_sphinx/active_record/delta.rb +97 -0
  12. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  13. data/lib/thinking_sphinx/active_record/search.rb +50 -0
  14. data/lib/thinking_sphinx/association.rb +144 -0
  15. data/lib/thinking_sphinx/attribute.rb +284 -0
  16. data/lib/thinking_sphinx/configuration.rb +283 -0
  17. data/lib/thinking_sphinx/field.rb +200 -0
  18. data/lib/thinking_sphinx/index.rb +340 -0
  19. data/lib/thinking_sphinx/index/builder.rb +195 -0
  20. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  21. data/lib/thinking_sphinx/rails_additions.rb +56 -0
  22. data/lib/thinking_sphinx/search.rb +482 -0
  23. data/lib/thinking_sphinx/tasks.rb +86 -0
  24. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +207 -0
  25. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
  26. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
  27. data/spec/unit/thinking_sphinx/active_record_spec.rb +236 -0
  28. data/spec/unit/thinking_sphinx/association_spec.rb +247 -0
  29. data/spec/unit/thinking_sphinx/attribute_spec.rb +360 -0
  30. data/spec/unit/thinking_sphinx/configuration_spec.rb +493 -0
  31. data/spec/unit/thinking_sphinx/field_spec.rb +219 -0
  32. data/spec/unit/thinking_sphinx/index/builder_spec.rb +33 -0
  33. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +68 -0
  34. data/spec/unit/thinking_sphinx/index_spec.rb +277 -0
  35. data/spec/unit/thinking_sphinx/search_spec.rb +190 -0
  36. data/spec/unit/thinking_sphinx_spec.rb +129 -0
  37. data/tasks/thinking_sphinx_tasks.rake +1 -0
  38. metadata +103 -0
@@ -0,0 +1,219 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Field do
4
+ describe '#initialize' do
5
+ it 'raises if no columns are provided so that configuration errors are easier to track down' do
6
+ lambda {
7
+ ThinkingSphinx::Field.new([])
8
+ }.should raise_error(RuntimeError)
9
+ end
10
+
11
+ it 'raises if an element of the columns param is an integer - as happens when you use id instead of :id - so that configuration errors are easier to track down' do
12
+ lambda {
13
+ ThinkingSphinx::Field.new([1234])
14
+ }.should raise_error(RuntimeError)
15
+ end
16
+ end
17
+
18
+ describe "to_select_sql method with MySQL" do
19
+ before :each do
20
+ @index = Person.indexes.first
21
+ @index.link!
22
+ end
23
+
24
+ it "should concat with spaces if there are multiple columns" do
25
+ @index.fields.first.to_select_sql.should match(/CONCAT_WS\(' ', /)
26
+ end
27
+
28
+ it "should concat with spaces if a column has more than one association" do
29
+ @index.fields[1].to_select_sql.should match(/CONCAT_WS\(' ', /)
30
+ end
31
+
32
+ it "should group if any association for any column is a has_many or has_and_belongs_to_many" do
33
+ @index.fields[2].to_select_sql.should match(/GROUP_CONCAT/)
34
+ end
35
+ end
36
+
37
+ describe "to_select_sql method with PostgreSQL" do
38
+ before :each do
39
+ @index = Person.indexes.first
40
+ Person.connection.class.stub_method(
41
+ :name => "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
42
+ )
43
+ @index.link!
44
+ end
45
+
46
+ it "should concat with spaces if there are multiple columns" do
47
+ @index.fields.first.to_select_sql.should match(/|| ' ' ||/)
48
+ end
49
+
50
+ it "should concat with spaces if a column has more than one association" do
51
+ @index.fields[1].to_select_sql.should match(/|| ' ' ||/)
52
+ end
53
+
54
+ it "should group if any association for any column is a has_many or has_and_belongs_to_many" do
55
+ @index.fields[2].to_select_sql.should match(/array_to_string\(array_accum\(/)
56
+ end
57
+ end
58
+
59
+ describe "to_group_sql method" do
60
+ before :each do
61
+ @field = ThinkingSphinx::Field.new([Object.stub_instance(:__stack => [])])
62
+ @field.stub_methods(:is_many? => false)
63
+
64
+ ThinkingSphinx.stub_method(:use_group_by_shortcut? => false)
65
+ end
66
+
67
+ it "should return nil if is_many?" do
68
+ @field.stub_method(:is_many? => true)
69
+
70
+ @field.to_group_sql.should be_nil
71
+ end
72
+
73
+ it "should return nil if group_by shortcut is allowed" do
74
+ ThinkingSphinx.stub_method(:use_group_by_shortcut? => true)
75
+
76
+ @field.to_group_sql.should be_nil
77
+ end
78
+
79
+ it "should return an array if neither is_many? or shortcut allowed" do
80
+ @field.stub_method(:column_with_prefix => 'hello')
81
+ @field.to_group_sql.should be_a_kind_of(Array)
82
+ end
83
+ end
84
+
85
+ describe "unique_name method" do
86
+ before :each do
87
+ @field = ThinkingSphinx::Field.new [
88
+ Object.stub_instance(:__stack => [], :__name => "col_name")
89
+ ]
90
+ end
91
+
92
+ it "should use the alias if there is one" do
93
+ @field.alias = "alias"
94
+ @field.unique_name.should == "alias"
95
+ end
96
+
97
+ it "should use the alias if there's multiple columns" do
98
+ @field.columns << Object.stub_instance(:__stack => [], :__name => "col_name")
99
+ @field.unique_name.should be_nil
100
+
101
+ @field.alias = "alias"
102
+ @field.unique_name.should == "alias"
103
+ end
104
+
105
+ it "should use the column name if there's no alias and just one column" do
106
+ @field.unique_name.should == "col_name"
107
+ end
108
+ end
109
+
110
+ describe "prefixes method" do
111
+ it "should default to false" do
112
+ @field = ThinkingSphinx::Field.new([Object.stub_instance(:__stack => [])])
113
+ @field.prefixes.should be_false
114
+ end
115
+
116
+ it "should be true if the corresponding option is set" do
117
+ @field = ThinkingSphinx::Field.new(
118
+ [Object.stub_instance(:__stack => [])], :prefixes => true
119
+ )
120
+ @field.prefixes.should be_true
121
+ end
122
+ end
123
+
124
+ describe "infixes method" do
125
+ it "should default to false" do
126
+ @field = ThinkingSphinx::Field.new([Object.stub_instance(:__stack => [])])
127
+ @field.infixes.should be_false
128
+ end
129
+
130
+ it "should be true if the corresponding option is set" do
131
+ @field = ThinkingSphinx::Field.new(
132
+ [Object.stub_instance(:__stack => [])], :infixes => true
133
+ )
134
+ @field.infixes.should be_true
135
+ end
136
+ end
137
+
138
+ describe "quote_column_name method" do
139
+ it "should delegate the call to the model's connection" do
140
+ @field = ThinkingSphinx::Field.new [
141
+ ThinkingSphinx::Index::FauxColumn.new(:col_name)
142
+ ]
143
+ @field.model = Person
144
+ Person.connection.stub_method(:quote_column_name => "quoted!")
145
+
146
+ @field.send(:quote_column, "blah").should == "quoted!"
147
+ end
148
+ end
149
+
150
+ describe "column_with_prefix method" do
151
+ before :each do
152
+ @field = ThinkingSphinx::Field.new [
153
+ ThinkingSphinx::Index::FauxColumn.new(:col_name)
154
+ ]
155
+ @field.columns.each { |col| @field.associations[col] = [] }
156
+ @field.model = Person
157
+
158
+ @first_join = Object.stub_instance(:aliased_table_name => "tabular")
159
+ @second_join = Object.stub_instance(:aliased_table_name => "data")
160
+
161
+ @first_assoc = ThinkingSphinx::Association.stub_instance(
162
+ :join => @first_join, :has_column? => true
163
+ )
164
+ @second_assoc = ThinkingSphinx::Association.stub_instance(
165
+ :join => @second_join, :has_column? => true
166
+ )
167
+ end
168
+
169
+ it "should return the column with model's table prefix if there's no associations for the column" do
170
+ @field.send(:column_with_prefix, @field.columns.first).should == "`people`.`col_name`"
171
+ end
172
+
173
+ it "should return the column with its join table prefix if an association exists" do
174
+ column = @field.columns.first
175
+ @field.associations[column] = [@first_assoc]
176
+ @field.send(:column_with_prefix, column).should == "`tabular`.`col_name`"
177
+ end
178
+
179
+ it "should return multiple columns concatenated if more than one association exists" do
180
+ column = @field.columns.first
181
+ @field.associations[column] = [@first_assoc, @second_assoc]
182
+ @field.send(:column_with_prefix, column).should == "`tabular`.`col_name`, `data`.`col_name`"
183
+ end
184
+ end
185
+
186
+ describe "is_many? method" do
187
+ before :each do
188
+ @assoc_a = Object.stub_instance(:is_many? => true)
189
+ @assoc_b = Object.stub_instance(:is_many? => true)
190
+ @assoc_c = Object.stub_instance(:is_many? => true)
191
+
192
+ @field = ThinkingSphinx::Field.new(
193
+ [ThinkingSphinx::Index::FauxColumn.new(:col_name)]
194
+ )
195
+ @field.associations = {
196
+ :a => @assoc_a, :b => @assoc_b, :c => @assoc_c
197
+ }
198
+ end
199
+
200
+ it "should return true if all associations return true to is_many?" do
201
+ @field.send(:is_many?).should be_true
202
+ end
203
+
204
+ it "should return true if one association returns true to is_many?" do
205
+ @assoc_b.stub_method(:is_many? => false)
206
+ @assoc_c.stub_method(:is_many? => false)
207
+
208
+ @field.send(:is_many?).should be_true
209
+ end
210
+
211
+ it "should return false if all associations return false to is_many?" do
212
+ @assoc_a.stub_method(:is_many? => false)
213
+ @assoc_b.stub_method(:is_many? => false)
214
+ @assoc_c.stub_method(:is_many? => false)
215
+
216
+ @field.send(:is_many?).should be_false
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Index::Builder do
4
+ before :each do
5
+ @builder = Class.new(ThinkingSphinx::Index::Builder)
6
+ @builder.setup
7
+ end
8
+
9
+ describe "setup method" do
10
+ it "should set up the information arrays and properties hash" do
11
+ @builder.fields.should == []
12
+ @builder.attributes.should == []
13
+ @builder.conditions.should == []
14
+ @builder.properties.should == {}
15
+ end
16
+ end
17
+
18
+ describe "indexes method" do
19
+
20
+ end
21
+
22
+ describe "has method" do
23
+
24
+ end
25
+
26
+ describe "where method" do
27
+
28
+ end
29
+
30
+ describe "set_property method" do
31
+
32
+ end
33
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Index::FauxColumn do
4
+ it "should use the last argument as the name, with preceeding ones going into the stack" do
5
+ #
6
+ end
7
+
8
+ it "should access the name through __name" do
9
+ #
10
+ end
11
+
12
+ it "should access the stack through __stack" do
13
+ #
14
+ end
15
+
16
+ it "should return true from is_string? if the name is a string and the stack is empty" do
17
+ #
18
+ end
19
+
20
+ describe "coerce class method" do
21
+ before :each do
22
+ @column = ThinkingSphinx::Index::FauxColumn.stub_instance
23
+ ThinkingSphinx::Index::FauxColumn.stub_method(:new => @column)
24
+ end
25
+
26
+ it "should return a single faux column if passed a string" do
27
+ ThinkingSphinx::Index::FauxColumn.coerce("string").should == @column
28
+ end
29
+
30
+ it "should return a single faux column if passed a symbol" do
31
+ ThinkingSphinx::Index::FauxColumn.coerce(:string).should == @column
32
+ end
33
+
34
+ it "should return an array of faux columns if passed an array of strings" do
35
+ ThinkingSphinx::Index::FauxColumn.coerce(["one", "two"]).should == [
36
+ @column, @column
37
+ ]
38
+ end
39
+
40
+ it "should return an array of faux columns if passed an array of symbols" do
41
+ ThinkingSphinx::Index::FauxColumn.coerce([:one, :two]).should == [
42
+ @column, @column
43
+ ]
44
+ end
45
+ end
46
+
47
+ describe "method_missing calls with no arguments" do
48
+ it "should push any further method calls into name, and the old name goes into the stack" do
49
+ #
50
+ end
51
+
52
+ it "should return itself" do
53
+ #
54
+ end
55
+ end
56
+
57
+ describe "method_missing calls with one argument" do
58
+ it "should act as if calling method missing with method, then argument" do
59
+ #
60
+ end
61
+ end
62
+
63
+ describe "method_missing calls with more than one argument" do
64
+ it "should return a collection of Faux Columns sharing the same stack, but with each argument as the name" do
65
+ #
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,277 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Index do
4
+ describe "to_config method" do
5
+ before :each do
6
+ @index = ThinkingSphinx::Index.new(Person)
7
+
8
+ @index.stub_methods(
9
+ :attributes => [
10
+ ThinkingSphinx::Attribute.stub_instance(:to_sphinx_clause => "attr a"),
11
+ ThinkingSphinx::Attribute.stub_instance(:to_sphinx_clause => "attr b")
12
+ ],
13
+ :link! => true,
14
+ :adapter => :mysql,
15
+ :to_sql_query_pre => "sql_query_pre",
16
+ :to_sql => "SQL",
17
+ :to_sql_query_range => "sql_query_range",
18
+ :to_sql_query_info => "sql_query_info",
19
+ :delta? => false
20
+ )
21
+
22
+ @database = {
23
+ :host => "localhost",
24
+ :username => "username",
25
+ :password => "blank",
26
+ :database => "db"
27
+ }
28
+ end
29
+
30
+ it "should call link!" do
31
+ @index.to_config(0, @database, "utf-8")
32
+
33
+ @index.should have_received(:link!)
34
+ end
35
+
36
+ it "should raise an exception if the adapter isn't mysql or postgres" do
37
+ @index.stub_method(:adapter => :sqlite)
38
+
39
+ lambda { @index.to_config(0, @database, "utf-8") }.should raise_error
40
+ end
41
+
42
+ it "should set the core source name to {model}_{index}_core" do
43
+ @index.to_config(0, @database, "utf-8").should match(
44
+ /source person_0_core/
45
+ )
46
+ end
47
+
48
+ it "should include the database config supplied" do
49
+ conf = @index.to_config(0, @database, "utf-8")
50
+ conf.should match(/type\s+= mysql/)
51
+ conf.should match(/sql_host\s+= localhost/)
52
+ conf.should match(/sql_user\s+= username/)
53
+ conf.should match(/sql_pass\s+= blank/)
54
+ conf.should match(/sql_db\s+= db/)
55
+ end
56
+
57
+ it "should include the database socket if set" do
58
+ conf = @index.to_config(0, @database.merge(:socket => "dbsocket"), "utf-8")
59
+ conf.should match(/sql_sock\s+= dbsocket/)
60
+ end
61
+
62
+ it "should not include the database socket if not set" do
63
+ conf = @index.to_config(0, @database, "utf-8")
64
+ conf.should_not match(/sql_sock/)
65
+ end
66
+
67
+ it "should have a pre query 'SET NAMES utf8' if using mysql and utf8 charset" do
68
+ @index.to_config(0, @database, "utf-8").should match(
69
+ /sql_query_pre\s+= SET NAMES utf8/
70
+ )
71
+
72
+ @index.stub_method(:delta? => true)
73
+ @index.to_config(0, @database, "utf-8").should match(
74
+ /source person_0_delta.+sql_query_pre\s+= SET NAMES utf8/m
75
+ )
76
+
77
+ @index.stub_method(:delta? => false)
78
+ @index.to_config(0, @database, "non-utf-8").should_not match(
79
+ /SET NAMES utf8/
80
+ )
81
+
82
+ @index.stub_method(:adapter => :postgres)
83
+ @index.to_config(0, @database, "utf-8").should_not match(
84
+ /SET NAMES utf8/
85
+ )
86
+ end
87
+
88
+ it "should use the pre query from the index" do
89
+ @index.to_config(0, @database, "utf-8").should match(
90
+ /sql_query_pre\s+= sql_query_pre/
91
+ )
92
+ end
93
+
94
+ it "should not set group_concat_max_len if not specified" do
95
+ @index.to_config(0, @database, "utf-8").should_not match(
96
+ /group_concat_max_len/
97
+ )
98
+ end
99
+
100
+ it "should set group_concat_max_len if specified" do
101
+ @index.options.merge! :group_concat_max_len => 2056
102
+ @index.to_config(0, @database, "utf-8").should match(
103
+ /sql_query_pre\s+= SET SESSION group_concat_max_len = 2056/
104
+ )
105
+
106
+ @index.stub_method(:delta? => true)
107
+ @index.to_config(0, @database, "utf-8").should match(
108
+ /source person_0_delta.+sql_query_pre\s+= SET SESSION group_concat_max_len = 2056/m
109
+ )
110
+ end
111
+
112
+ it "should use the main query from the index" do
113
+ @index.to_config(0, @database, "utf-8").should match(
114
+ /sql_query\s+= SQL/
115
+ )
116
+ end
117
+
118
+ it "should use the range query from the index" do
119
+ @index.to_config(0, @database, "utf-8").should match(
120
+ /sql_query_range\s+= sql_query_range/
121
+ )
122
+ end
123
+
124
+ it "should use the info query from the index" do
125
+ @index.to_config(0, @database, "utf-8").should match(
126
+ /sql_query_info\s+= sql_query_info/
127
+ )
128
+ end
129
+
130
+ it "should include the attribute sources" do
131
+ @index.to_config(0, @database, "utf-8").should match(
132
+ /attr a\n\s+attr b/
133
+ )
134
+ end
135
+
136
+ it "should add a delta index with name {model}_{index}_delta if requested" do
137
+ @index.stub_method(:delta? => true)
138
+
139
+ @index.to_config(0, @database, "utf-8").should match(
140
+ /source person_0_delta/
141
+ )
142
+ end
143
+
144
+ it "should not add a delta index unless requested" do
145
+ @index.to_config(0, @database, "utf-8").should_not match(
146
+ /source person_0_delta/
147
+ )
148
+ end
149
+
150
+ it "should have the delta index inherit from the core index" do
151
+ @index.stub_method(:delta? => true)
152
+
153
+ @index.to_config(0, @database, "utf-8").should match(
154
+ /source person_0_delta : person_0_core/
155
+ )
156
+ end
157
+
158
+ it "should redefine the main query for the delta index" do
159
+ @index.stub_method(:delta? => true)
160
+
161
+ @index.to_config(0, @database, "utf-8").should match(
162
+ /source person_0_delta.+sql_query\s+= SQL/m
163
+ )
164
+ end
165
+
166
+ it "should redefine the range query for the delta index" do
167
+ @index.stub_method(:delta? => true)
168
+
169
+ @index.to_config(0, @database, "utf-8").should match(
170
+ /source person_0_delta.+sql_query_range\s+= sql_query_range/m
171
+ )
172
+ end
173
+
174
+ it "should redefine the pre query for the delta index" do
175
+ @index.stub_method(:delta? => true)
176
+
177
+ @index.to_config(0, @database, "utf-8").should match(
178
+ /source person_0_delta.+sql_query_pre\s+=\s*\n/m
179
+ )
180
+ end
181
+ end
182
+
183
+ describe "to_sql_query_range method" do
184
+ before :each do
185
+ @index = ThinkingSphinx::Index.new(Person)
186
+ end
187
+
188
+ it "should add COALESCE around MIN and MAX calls if using PostgreSQL" do
189
+ @index.stub_method(:adapter => :postgres)
190
+
191
+ @index.to_sql_query_range.should match(/COALESCE\(MIN.+COALESCE\(MAX/)
192
+ end
193
+
194
+ it "shouldn't add COALESCE if using MySQL" do
195
+ @index.to_sql_query_range.should_not match(/COALESCE/)
196
+ end
197
+ end
198
+
199
+ describe "prefix_fields method" do
200
+ before :each do
201
+ @index = ThinkingSphinx::Index.new(Person)
202
+
203
+ @field_a = ThinkingSphinx::Field.stub_instance(:prefixes => true)
204
+ @field_b = ThinkingSphinx::Field.stub_instance(:prefixes => false)
205
+ @field_c = ThinkingSphinx::Field.stub_instance(:prefixes => true)
206
+
207
+ @index.fields = [@field_a, @field_b, @field_c]
208
+ end
209
+
210
+ it "should return fields that are flagged as prefixed" do
211
+ @index.prefix_fields.should include(@field_a)
212
+ @index.prefix_fields.should include(@field_c)
213
+ end
214
+
215
+ it "should not return fields that aren't flagged as prefixed" do
216
+ @index.prefix_fields.should_not include(@field_b)
217
+ end
218
+ end
219
+
220
+ describe "infix_fields method" do
221
+ before :each do
222
+ @index = ThinkingSphinx::Index.new(Person)
223
+
224
+ @field_a = ThinkingSphinx::Field.stub_instance(:infixes => true)
225
+ @field_b = ThinkingSphinx::Field.stub_instance(:infixes => false)
226
+ @field_c = ThinkingSphinx::Field.stub_instance(:infixes => true)
227
+
228
+ @index.fields = [@field_a, @field_b, @field_c]
229
+ end
230
+
231
+ it "should return fields that are flagged as infixed" do
232
+ @index.infix_fields.should include(@field_a)
233
+ @index.infix_fields.should include(@field_c)
234
+ end
235
+
236
+ it "should not return fields that aren't flagged as infixed" do
237
+ @index.infix_fields.should_not include(@field_b)
238
+ end
239
+ end
240
+
241
+ describe "empty? method" do
242
+ before :each do
243
+ @index = ThinkingSphinx::Index.new(Person)
244
+ config = ThinkingSphinx::Configuration.new
245
+
246
+ `mkdir -p #{config.searchd_file_path}`
247
+ @file_path = "#{config.searchd_file_path}/#{@index.name}_core.spa"
248
+ end
249
+
250
+ after :each do
251
+ `rm #{@file_path}` if File.exists?(@file_path)
252
+ end
253
+
254
+ it "should return true if the core index files are empty" do
255
+ `touch #{@file_path}`
256
+ @index.should be_empty
257
+ end
258
+
259
+ it "should return true if the core index files don't exist" do
260
+ @index.should be_empty
261
+ end
262
+
263
+ it "should return false if the core index files aren't empty" do
264
+ `echo 'a' > #{@file_path}`
265
+ @index.should_not be_empty
266
+ end
267
+
268
+ it "should check the delta files if specified" do
269
+ @index.should be_empty(:delta)
270
+
271
+ `echo 'a' > #{@file_path.gsub(/_core.spa$/, '_delta.spa')}`
272
+ @index.should_not be_empty(:delta)
273
+
274
+ `rm #{@file_path}` if File.exists?(@file_path.gsub(/_core.spa$/, '_delta.spa'))
275
+ end
276
+ end
277
+ end