thinking-sphinx 1.2.12

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 (95) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +157 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/thinking_sphinx.rb +211 -0
  5. data/lib/thinking_sphinx/active_record.rb +307 -0
  6. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  7. data/lib/thinking_sphinx/active_record/delta.rb +87 -0
  8. data/lib/thinking_sphinx/active_record/has_many_association.rb +28 -0
  9. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  10. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
  11. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  12. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +136 -0
  13. data/lib/thinking_sphinx/association.rb +164 -0
  14. data/lib/thinking_sphinx/attribute.rb +342 -0
  15. data/lib/thinking_sphinx/class_facet.rb +15 -0
  16. data/lib/thinking_sphinx/configuration.rb +282 -0
  17. data/lib/thinking_sphinx/core/array.rb +7 -0
  18. data/lib/thinking_sphinx/core/string.rb +15 -0
  19. data/lib/thinking_sphinx/deltas.rb +30 -0
  20. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  21. data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
  22. data/lib/thinking_sphinx/deltas/delayed_delta.rb +30 -0
  23. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  24. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  25. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  26. data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
  27. data/lib/thinking_sphinx/excerpter.rb +22 -0
  28. data/lib/thinking_sphinx/facet.rb +125 -0
  29. data/lib/thinking_sphinx/facet_search.rb +134 -0
  30. data/lib/thinking_sphinx/field.rb +82 -0
  31. data/lib/thinking_sphinx/index.rb +99 -0
  32. data/lib/thinking_sphinx/index/builder.rb +286 -0
  33. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  34. data/lib/thinking_sphinx/property.rb +162 -0
  35. data/lib/thinking_sphinx/rails_additions.rb +150 -0
  36. data/lib/thinking_sphinx/search.rb +707 -0
  37. data/lib/thinking_sphinx/search_methods.rb +421 -0
  38. data/lib/thinking_sphinx/source.rb +150 -0
  39. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  40. data/lib/thinking_sphinx/source/sql.rb +128 -0
  41. data/lib/thinking_sphinx/tasks.rb +165 -0
  42. data/rails/init.rb +14 -0
  43. data/spec/lib/thinking_sphinx/active_record/delta_spec.rb +130 -0
  44. data/spec/lib/thinking_sphinx/active_record/has_many_association_spec.rb +49 -0
  45. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  46. data/spec/lib/thinking_sphinx/active_record_spec.rb +364 -0
  47. data/spec/lib/thinking_sphinx/association_spec.rb +239 -0
  48. data/spec/lib/thinking_sphinx/attribute_spec.rb +500 -0
  49. data/spec/lib/thinking_sphinx/configuration_spec.rb +268 -0
  50. data/spec/lib/thinking_sphinx/core/array_spec.rb +9 -0
  51. data/spec/lib/thinking_sphinx/core/string_spec.rb +9 -0
  52. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  53. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  54. data/spec/lib/thinking_sphinx/facet_spec.rb +333 -0
  55. data/spec/lib/thinking_sphinx/field_spec.rb +154 -0
  56. data/spec/lib/thinking_sphinx/index/builder_spec.rb +455 -0
  57. data/spec/lib/thinking_sphinx/index/faux_column_spec.rb +30 -0
  58. data/spec/lib/thinking_sphinx/index_spec.rb +45 -0
  59. data/spec/lib/thinking_sphinx/rails_additions_spec.rb +203 -0
  60. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  61. data/spec/lib/thinking_sphinx/search_spec.rb +1092 -0
  62. data/spec/lib/thinking_sphinx/source_spec.rb +227 -0
  63. data/spec/lib/thinking_sphinx_spec.rb +162 -0
  64. data/tasks/distribution.rb +50 -0
  65. data/tasks/rails.rake +1 -0
  66. data/tasks/testing.rb +83 -0
  67. data/vendor/after_commit/LICENSE +20 -0
  68. data/vendor/after_commit/README +16 -0
  69. data/vendor/after_commit/Rakefile +22 -0
  70. data/vendor/after_commit/init.rb +8 -0
  71. data/vendor/after_commit/lib/after_commit.rb +45 -0
  72. data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
  73. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  74. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  75. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  76. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  77. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  78. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  79. data/vendor/riddle/lib/riddle.rb +30 -0
  80. data/vendor/riddle/lib/riddle/client.rb +635 -0
  81. data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
  82. data/vendor/riddle/lib/riddle/client/message.rb +66 -0
  83. data/vendor/riddle/lib/riddle/client/response.rb +84 -0
  84. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  85. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  86. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  87. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  88. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  89. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  90. data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
  91. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  92. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  93. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  94. data/vendor/riddle/lib/riddle/controller.rb +53 -0
  95. metadata +172 -0
@@ -0,0 +1,239 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Association do
4
+ describe '.children' do
5
+ before :each do
6
+ @normal_reflection = stub('reflection', :options => {
7
+ :polymorphic => false
8
+ })
9
+ @normal_association = ThinkingSphinx::Association.new(nil, nil)
10
+ @poly_reflection = stub('reflection',
11
+ :options => {:polymorphic => true},
12
+ :macro => :has_many,
13
+ :name => 'polly',
14
+ :active_record => 'AR'
15
+ )
16
+ @non_poly_reflection = stub('reflection')
17
+
18
+ Person.stub!(:reflect_on_association => @normal_reflection)
19
+ ThinkingSphinx::Association.stub!(
20
+ :new => @normal_association,
21
+ :polymorphic_classes => [Person, Person],
22
+ :casted_options => {:casted => :options}
23
+ )
24
+ ::ActiveRecord::Reflection::AssociationReflection.stub!(
25
+ :new => @non_poly_reflection
26
+ )
27
+ end
28
+
29
+ it "should return an empty array if no association exists" do
30
+ Person.stub!(:reflect_on_association => nil)
31
+
32
+ ThinkingSphinx::Association.children(Person, :assoc).should == []
33
+ end
34
+
35
+ it "should return a single association instance in an array if assocation isn't polymorphic" do
36
+ ThinkingSphinx::Association.children(Person, :assoc).should == [@normal_association]
37
+ end
38
+
39
+ it "should return multiple association instances for polymorphic associations" do
40
+ Person.stub!(:reflect_on_association => @poly_reflection)
41
+
42
+ ThinkingSphinx::Association.children(Person, :assoc).should ==
43
+ [@normal_association, @normal_association]
44
+ end
45
+
46
+ it "should generate non-polymorphic 'casted' associations for each polymorphic possibility" do
47
+ Person.stub!(:reflect_on_association => @poly_reflection)
48
+ ThinkingSphinx::Association.should_receive(:casted_options).with(
49
+ Person, @poly_reflection
50
+ ).twice
51
+ ::ActiveRecord::Reflection::AssociationReflection.should_receive(:new).
52
+ with(:has_many, :polly_Person, {:casted => :options}, "AR").twice
53
+ ThinkingSphinx::Association.should_receive(:new).with(
54
+ nil, @non_poly_reflection
55
+ ).twice
56
+
57
+ ThinkingSphinx::Association.children(Person, :assoc)
58
+ end
59
+ end
60
+
61
+ describe '#children' do
62
+ before :each do
63
+ @reflection = stub('reflection', :klass => :klass)
64
+ @association = ThinkingSphinx::Association.new(nil, @reflection)
65
+ ThinkingSphinx::Association.stub!(:children => :result)
66
+ end
67
+
68
+ it "should return the children associations for the given association" do
69
+ @association.children(:assoc).should == :result
70
+ end
71
+
72
+ it "should request children for the reflection klass" do
73
+ ThinkingSphinx::Association.should_receive(:children).
74
+ with(:klass, :assoc, @association)
75
+
76
+ @association.children(:assoc)
77
+ end
78
+ end
79
+
80
+ describe '#join_to' do
81
+ before :each do
82
+ @parent_join = stub('join assoc').as_null_object
83
+ @join = stub('join assoc').as_null_object
84
+ @parent = ThinkingSphinx::Association.new(nil, nil)
85
+ @parent.stub!(:join_to => true, :join => nil)
86
+ @base_join = stub('base join', :joins => [:a, :b, :c])
87
+ ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.stub!(:new => @join)
88
+ end
89
+
90
+ it "should call the parent's join_to if parent has no join" do
91
+ @assoc = ThinkingSphinx::Association.new(@parent, :ref)
92
+ @parent.should_receive(:join_to).with(@base_join)
93
+
94
+ @assoc.join_to(@base_join)
95
+ end
96
+
97
+ it "should not call the parent's join_to if it already has a join" do
98
+ @assoc = ThinkingSphinx::Association.new(@parent, :ref)
99
+ @parent.stub!(:join => @parent_join)
100
+ @parent.should_not_receive(:join_to)
101
+
102
+ @assoc.join_to(@base_join)
103
+ end
104
+
105
+ it "should define the join association with a JoinAssociation instance" do
106
+ @assoc = ThinkingSphinx::Association.new(@parent, :ref)
107
+
108
+ @assoc.join_to(@base_join).should == @join
109
+ @assoc.join.should == @join
110
+ end
111
+ end
112
+
113
+ describe '#to_sql' do
114
+ before :each do
115
+ @reflection = stub('reflection', :klass => Person)
116
+ @association = ThinkingSphinx::Association.new(nil, @reflection)
117
+ @parent = stub('parent', :aliased_table_name => "ALIAS TABLE NAME")
118
+ @join = stub('join assoc',
119
+ :association_join => "full association join SQL",
120
+ :parent => @parent
121
+ )
122
+ @association.join = @join
123
+ end
124
+
125
+ it "should return the join's association join value" do
126
+ @association.to_sql.should == "full association join SQL"
127
+ end
128
+
129
+ it "should replace ::ts_join_alias:: with the aliased table name" do
130
+ @join.stub!(:association_join => "text with ::ts_join_alias:: gone")
131
+
132
+ @association.to_sql.should == "text with `ALIAS TABLE NAME` gone"
133
+ end
134
+ end
135
+
136
+ describe '#is_many?' do
137
+ before :each do
138
+ @parent = stub('assoc', :is_many? => :parent_is_many)
139
+ @reflection = stub('reflection', :macro => :has_many)
140
+ end
141
+
142
+ it "should return true if association is either a has_many or a habtm" do
143
+ association = ThinkingSphinx::Association.new(@parent, @reflection)
144
+ association.is_many?.should be_true
145
+
146
+ @reflection.stub!(:macro => :has_and_belongs_to_many)
147
+ association.is_many?.should be_true
148
+ end
149
+
150
+ it "should return the parent value if not a has many or habtm and there is a parent" do
151
+ association = ThinkingSphinx::Association.new(@parent, @reflection)
152
+ @reflection.stub!(:macro => :belongs_to)
153
+ association.is_many?.should == :parent_is_many
154
+ end
155
+
156
+ it "should return false if no parent and not a has many or habtm" do
157
+ association = ThinkingSphinx::Association.new(nil, @reflection)
158
+ @reflection.stub!(:macro => :belongs_to)
159
+ association.is_many?.should be_false
160
+ end
161
+ end
162
+
163
+ describe '#ancestors' do
164
+ it "should return an array of associations - including all parents" do
165
+ parent = stub('assoc', :ancestors => [:all, :ancestors])
166
+ association = ThinkingSphinx::Association.new(parent, @reflection)
167
+ association.ancestors.should == [:all, :ancestors, association]
168
+ end
169
+ end
170
+
171
+ describe '.polymorphic_classes' do
172
+ it "should return all the polymorphic result types as classes" do
173
+ Person.connection.stub!(:select_all => [
174
+ {"person_type" => "Person"},
175
+ {"person_type" => "Friendship"}
176
+ ])
177
+ ref = stub('ref',
178
+ :active_record => Person,
179
+ :options => {:foreign_type => "person_type"}
180
+ )
181
+
182
+ ThinkingSphinx::Association.send(:polymorphic_classes, ref).should == [Person, Friendship]
183
+ end
184
+ end
185
+
186
+ describe '.casted_options' do
187
+ before :each do
188
+ @options = {
189
+ :foreign_key => "thing_id",
190
+ :foreign_type => "thing_type",
191
+ :polymorphic => true
192
+ }
193
+ @reflection = stub('assoc reflection', :options => @options)
194
+ end
195
+
196
+ it "should return a new options set for a specific class" do
197
+ ThinkingSphinx::Association.send(:casted_options, Person, @reflection).should == {
198
+ :polymorphic => nil,
199
+ :class_name => "Person",
200
+ :foreign_key => "thing_id",
201
+ :foreign_type => "thing_type",
202
+ :conditions => "::ts_join_alias::.`thing_type` = 'Person'"
203
+ }
204
+ end
205
+
206
+ it "should append to existing Array of conditions" do
207
+ @options[:conditions] = ["first condition"]
208
+ ThinkingSphinx::Association.send(:casted_options, Person, @reflection).should == {
209
+ :polymorphic => nil,
210
+ :class_name => "Person",
211
+ :foreign_key => "thing_id",
212
+ :foreign_type => "thing_type",
213
+ :conditions => ["first condition", "::ts_join_alias::.`thing_type` = 'Person'"]
214
+ }
215
+ end
216
+
217
+ it "should merge to an existing Hash of conditions" do
218
+ @options[:conditions] = {"field" => "value"}
219
+ ThinkingSphinx::Association.send(:casted_options, Person, @reflection).should == {
220
+ :polymorphic => nil,
221
+ :class_name => "Person",
222
+ :foreign_key => "thing_id",
223
+ :foreign_type => "thing_type",
224
+ :conditions => {"field" => "value", "thing_type" => "Person"}
225
+ }
226
+ end
227
+
228
+ it "should append to an existing String of conditions" do
229
+ @options[:conditions] = "first condition"
230
+ ThinkingSphinx::Association.send(:casted_options, Person, @reflection).should == {
231
+ :polymorphic => nil,
232
+ :class_name => "Person",
233
+ :foreign_key => "thing_id",
234
+ :foreign_type => "thing_type",
235
+ :conditions => "first condition AND ::ts_join_alias::.`thing_type` = 'Person'"
236
+ }
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,500 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Attribute do
4
+ before :each do
5
+ @index = ThinkingSphinx::Index.new(Person)
6
+ @source = ThinkingSphinx::Source.new(@index)
7
+
8
+ @index.delta_object = ThinkingSphinx::Deltas::DefaultDelta.new @index, @index.local_options
9
+ end
10
+
11
+ describe '#initialize' do
12
+ it 'raises if no columns are provided so that configuration errors are easier to track down' do
13
+ lambda {
14
+ ThinkingSphinx::Attribute.new(@source, [])
15
+ }.should raise_error(RuntimeError)
16
+ end
17
+
18
+ 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
19
+ lambda {
20
+ ThinkingSphinx::Attribute.new(@source, [1234])
21
+ }.should raise_error(RuntimeError)
22
+ end
23
+ end
24
+
25
+ describe "unique_name method" do
26
+ before :each do
27
+ @attribute = ThinkingSphinx::Attribute.new @source, [
28
+ stub('column', :__stack => [], :__name => "col_name")
29
+ ]
30
+ end
31
+
32
+ it "should use the alias if there is one" do
33
+ @attribute.alias = "alias"
34
+ @attribute.unique_name.should == "alias"
35
+ end
36
+
37
+ it "should use the alias if there's multiple columns" do
38
+ @attribute.columns << stub('column', :__stack => [], :__name => "col_name")
39
+ @attribute.unique_name.should be_nil
40
+
41
+ @attribute.alias = "alias"
42
+ @attribute.unique_name.should == "alias"
43
+ end
44
+
45
+ it "should use the column name if there's no alias and just one column" do
46
+ @attribute.unique_name.should == "col_name"
47
+ end
48
+ end
49
+
50
+ describe "column_with_prefix method" do
51
+ before :each do
52
+ @attribute = ThinkingSphinx::Attribute.new @source, [
53
+ ThinkingSphinx::Index::FauxColumn.new(:col_name)
54
+ ]
55
+ @attribute.columns.each { |col| @attribute.associations[col] = [] }
56
+ @attribute.model = Person
57
+
58
+ @first_join = Object.new
59
+ @first_join.stub!(:aliased_table_name => "tabular")
60
+ @second_join = Object.new
61
+ @second_join.stub!(:aliased_table_name => "data")
62
+
63
+ @first_assoc = ThinkingSphinx::Association.new nil, nil
64
+ @first_assoc.stub!(:join => @first_join, :has_column? => true)
65
+ @second_assoc = ThinkingSphinx::Association.new nil, nil
66
+ @second_assoc.stub!(:join => @second_join, :has_column? => true)
67
+ end
68
+
69
+ it "should return the column name if the column is a string" do
70
+ @attribute.columns = [ThinkingSphinx::Index::FauxColumn.new("string")]
71
+ @attribute.send(:column_with_prefix, @attribute.columns.first).should == "string"
72
+ end
73
+
74
+ it "should return the column with model's table prefix if there's no associations for the column" do
75
+ @attribute.send(:column_with_prefix, @attribute.columns.first).should == "`people`.`col_name`"
76
+ end
77
+
78
+ it "should return the column with its join table prefix if an association exists" do
79
+ column = @attribute.columns.first
80
+ @attribute.associations[column] = [@first_assoc]
81
+ @attribute.send(:column_with_prefix, column).should == "`tabular`.`col_name`"
82
+ end
83
+
84
+ it "should return multiple columns concatenated if more than one association exists" do
85
+ column = @attribute.columns.first
86
+ @attribute.associations[column] = [@first_assoc, @second_assoc]
87
+ @attribute.send(:column_with_prefix, column).should == "`tabular`.`col_name`, `data`.`col_name`"
88
+ end
89
+ end
90
+
91
+ describe '#to_select_sql' do
92
+ it "should convert a mixture of dates and datetimes to timestamps" do
93
+ attribute = ThinkingSphinx::Attribute.new(@source,
94
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
95
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ],
96
+ :as => :times
97
+ )
98
+ attribute.model = Friendship
99
+
100
+ attribute.to_select_sql.should == "CONCAT_WS(',', UNIX_TIMESTAMP(`friendships`.`created_at`), UNIX_TIMESTAMP(`friendships`.`created_on`)) AS `times`"
101
+ end
102
+ end
103
+
104
+ describe "is_many? method" do
105
+ before :each do
106
+ @assoc_a = stub('assoc', :is_many? => true)
107
+ @assoc_b = stub('assoc', :is_many? => true)
108
+ @assoc_c = stub('assoc', :is_many? => true)
109
+
110
+ @attribute = ThinkingSphinx::Attribute.new(
111
+ @source, [ThinkingSphinx::Index::FauxColumn.new(:col_name)]
112
+ )
113
+ @attribute.associations = {
114
+ :a => @assoc_a, :b => @assoc_b, :c => @assoc_c
115
+ }
116
+ end
117
+
118
+ it "should return true if all associations return true to is_many?" do
119
+ @attribute.send(:is_many?).should be_true
120
+ end
121
+
122
+ it "should return true if one association returns true to is_many?" do
123
+ @assoc_b.stub!(:is_many? => false)
124
+ @assoc_c.stub!(:is_many? => false)
125
+
126
+ @attribute.send(:is_many?).should be_true
127
+ end
128
+
129
+ it "should return false if all associations return false to is_many?" do
130
+ @assoc_a.stub!(:is_many? => false)
131
+ @assoc_b.stub!(:is_many? => false)
132
+ @assoc_c.stub!(:is_many? => false)
133
+
134
+ @attribute.send(:is_many?).should be_false
135
+ end
136
+ end
137
+
138
+ describe "is_string? method" do
139
+ before :each do
140
+ @col_a = ThinkingSphinx::Index::FauxColumn.new("a")
141
+ @col_b = ThinkingSphinx::Index::FauxColumn.new("b")
142
+ @col_c = ThinkingSphinx::Index::FauxColumn.new("c")
143
+
144
+ @attribute = ThinkingSphinx::Attribute.new(
145
+ @source, [@col_a, @col_b, @col_c]
146
+ )
147
+ end
148
+
149
+ it "should return true if all columns return true to is_string?" do
150
+ @attribute.send(:is_string?).should be_true
151
+ end
152
+
153
+ it "should return false if one column returns true to is_string?" do
154
+ @col_a.send(:instance_variable_set, :@name, :a)
155
+ @attribute.send(:is_string?).should be_false
156
+ end
157
+
158
+ it "should return false if all columns return false to is_string?" do
159
+ @col_a.send(:instance_variable_set, :@name, :a)
160
+ @col_b.send(:instance_variable_set, :@name, :b)
161
+ @col_c.send(:instance_variable_set, :@name, :c)
162
+ @attribute.send(:is_string?).should be_false
163
+ end
164
+ end
165
+
166
+ describe "type method" do
167
+ before :each do
168
+ @column = ThinkingSphinx::Index::FauxColumn.new(:col_name)
169
+ @attribute = ThinkingSphinx::Attribute.new(@source, [@column])
170
+ @attribute.model = Person
171
+ @attribute.stub!(:is_many? => false)
172
+ end
173
+
174
+ it "should return :multi if is_many? is true" do
175
+ @attribute.stub!(:is_many? => true)
176
+ @attribute.send(:type).should == :multi
177
+ end
178
+
179
+ it "should return :string if there's more than one association" do
180
+ @attribute.associations = {:a => [:assoc], :b => [:assoc]}
181
+ @attribute.send(:type).should == :string
182
+ end
183
+
184
+ it "should return the column type from the database if not :multi or more than one association" do
185
+ @column.send(:instance_variable_set, :@name, "birthday")
186
+ @attribute.send(:type).should == :datetime
187
+
188
+ @attribute.send(:instance_variable_set, :@type, nil)
189
+ @column.send(:instance_variable_set, :@name, "first_name")
190
+ @attribute.send(:type).should == :string
191
+
192
+ @attribute.send(:instance_variable_set, :@type, nil)
193
+ @column.send(:instance_variable_set, :@name, "id")
194
+ @attribute.send(:type).should == :integer
195
+ end
196
+ end
197
+
198
+ describe "all_ints? method" do
199
+ it "should return true if all columns are integers" do
200
+ attribute = ThinkingSphinx::Attribute.new(@source,
201
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
202
+ ThinkingSphinx::Index::FauxColumn.new(:team_id) ]
203
+ )
204
+ attribute.model = Person
205
+ attribute.columns.each { |col| attribute.associations[col] = [] }
206
+
207
+ attribute.should be_all_ints
208
+ end
209
+
210
+ it "should return false if only some columns are integers" do
211
+ attribute = ThinkingSphinx::Attribute.new(@source,
212
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
213
+ ThinkingSphinx::Index::FauxColumn.new(:first_name) ]
214
+ )
215
+ attribute.model = Person
216
+ attribute.columns.each { |col| attribute.associations[col] = [] }
217
+
218
+ attribute.should_not be_all_ints
219
+ end
220
+
221
+ it "should return false if no columns are integers" do
222
+ attribute = ThinkingSphinx::Attribute.new(@source,
223
+ [ ThinkingSphinx::Index::FauxColumn.new(:first_name),
224
+ ThinkingSphinx::Index::FauxColumn.new(:last_name) ]
225
+ )
226
+ attribute.model = Person
227
+ attribute.columns.each { |col| attribute.associations[col] = [] }
228
+
229
+ attribute.should_not be_all_ints
230
+ end
231
+ end
232
+
233
+ describe "all_datetimes? method" do
234
+ it "should return true if all columns are datetimes" do
235
+ attribute = ThinkingSphinx::Attribute.new(@source,
236
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
237
+ ThinkingSphinx::Index::FauxColumn.new(:updated_at) ]
238
+ )
239
+ attribute.model = Friendship
240
+ attribute.columns.each { |col| attribute.associations[col] = [] }
241
+
242
+ attribute.should be_all_datetimes
243
+ end
244
+
245
+ it "should return false if only some columns are datetimes" do
246
+ attribute = ThinkingSphinx::Attribute.new(@source,
247
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
248
+ ThinkingSphinx::Index::FauxColumn.new(:created_at) ]
249
+ )
250
+ attribute.model = Friendship
251
+ attribute.columns.each { |col| attribute.associations[col] = [] }
252
+
253
+ attribute.should_not be_all_datetimes
254
+ end
255
+
256
+ it "should return true if all columns can be " do
257
+ attribute = ThinkingSphinx::Attribute.new(@source,
258
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
259
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ]
260
+ )
261
+ attribute.model = Friendship
262
+ attribute.columns.each { |col| attribute.associations[col] = [] }
263
+
264
+ attribute.should be_all_datetimes
265
+ end
266
+ end
267
+
268
+ describe '#all_strings?' do
269
+ it "should return true if all columns are strings or text" do
270
+ attribute = ThinkingSphinx::Attribute.new(@source,
271
+ [ ThinkingSphinx::Index::FauxColumn.new(:first_name),
272
+ ThinkingSphinx::Index::FauxColumn.new(:last_name) ]
273
+ )
274
+ attribute.model = Person
275
+ attribute.columns.each { |col| attribute.associations[col] = [] }
276
+
277
+ attribute.should be_all_strings
278
+ end
279
+
280
+ it "should return false if only some columns are strings" do
281
+ attribute = ThinkingSphinx::Attribute.new(@source,
282
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
283
+ ThinkingSphinx::Index::FauxColumn.new(:first_name) ]
284
+ )
285
+ attribute.model = Person
286
+ attribute.columns.each { |col| attribute.associations[col] = [] }
287
+
288
+ attribute.should_not be_all_strings
289
+ end
290
+
291
+ it "should return true if all columns are not strings" do
292
+ attribute = ThinkingSphinx::Attribute.new(@source,
293
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
294
+ ThinkingSphinx::Index::FauxColumn.new(:parent_id) ]
295
+ )
296
+ attribute.model = Person
297
+ attribute.columns.each { |col| attribute.associations[col] = [] }
298
+
299
+ attribute.should_not be_all_strings
300
+ end
301
+ end
302
+
303
+ describe "MVA with source query" do
304
+ before :each do
305
+ @attribute = ThinkingSphinx::Attribute.new(@source,
306
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
307
+ :as => :tag_ids, :source => :query
308
+ )
309
+ end
310
+
311
+ it "should use a query" do
312
+ @attribute.type_to_config.should == :sql_attr_multi
313
+
314
+ declaration, query = @attribute.config_value.split('; ')
315
+ declaration.should == "uint tag_ids from query"
316
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags`"
317
+ end
318
+ end
319
+
320
+ describe "MVA with source query for a delta source" do
321
+ before :each do
322
+ @attribute = ThinkingSphinx::Attribute.new(@source,
323
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
324
+ :as => :tag_ids, :source => :query
325
+ )
326
+ end
327
+
328
+ it "should use a query" do
329
+ @attribute.type_to_config.should == :sql_attr_multi
330
+
331
+ declaration, query = @attribute.config_value(nil, true).split('; ')
332
+ declaration.should == "uint tag_ids from query"
333
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` IN (SELECT `id` FROM `people` WHERE `people`.`delta` = 1)"
334
+ end
335
+ end
336
+
337
+ describe "MVA via a HABTM association with a source query" do
338
+ before :each do
339
+ @attribute = ThinkingSphinx::Attribute.new(@source,
340
+ [ThinkingSphinx::Index::FauxColumn.new(:links, :id)],
341
+ :as => :link_ids, :source => :query
342
+ )
343
+ end
344
+
345
+ it "should use a ranged query" do
346
+ @attribute.type_to_config.should == :sql_attr_multi
347
+
348
+ declaration, query = @attribute.config_value.split('; ')
349
+ declaration.should == "uint link_ids from query"
350
+ query.should == "SELECT `links_people`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `links_people`.`link_id` AS `link_ids` FROM `links_people`"
351
+ end
352
+ end
353
+
354
+ describe "MVA with ranged source query" do
355
+ before :each do
356
+ @attribute = ThinkingSphinx::Attribute.new(@source,
357
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
358
+ :as => :tag_ids, :source => :ranged_query
359
+ )
360
+ end
361
+
362
+ it "should use a ranged query" do
363
+ @attribute.type_to_config.should == :sql_attr_multi
364
+
365
+ declaration, query, range_query = @attribute.config_value.split('; ')
366
+ declaration.should == "uint tag_ids from ranged-query"
367
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end"
368
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
369
+ end
370
+ end
371
+
372
+ describe "MVA with ranged source query for a delta source" do
373
+ before :each do
374
+ @attribute = ThinkingSphinx::Attribute.new(@source,
375
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
376
+ :as => :tag_ids, :source => :ranged_query
377
+ )
378
+ end
379
+
380
+ it "should use a ranged query" do
381
+ @attribute.type_to_config.should == :sql_attr_multi
382
+
383
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
384
+ declaration.should == "uint tag_ids from ranged-query"
385
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end AND `tags`.`person_id` IN (SELECT `id` FROM `people` WHERE `people`.`delta` = 1)"
386
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
387
+ end
388
+ end
389
+
390
+ describe "MVA via a has-many :through with a ranged source query" do
391
+ before :each do
392
+ @attribute = ThinkingSphinx::Attribute.new(@source,
393
+ [ThinkingSphinx::Index::FauxColumn.new(:football_teams, :id)],
394
+ :as => :football_team_ids, :source => :ranged_query
395
+ )
396
+ end
397
+
398
+ it "should use a ranged query" do
399
+ @attribute.type_to_config.should == :sql_attr_multi
400
+
401
+ declaration, query, range_query = @attribute.config_value.split('; ')
402
+ declaration.should == "uint football_team_ids from ranged-query"
403
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`football_team_id` AS `football_team_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end"
404
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
405
+ end
406
+ end
407
+
408
+ describe "MVA via a has-many :through using a foreign key with a ranged source query" do
409
+ before :each do
410
+ @attribute = ThinkingSphinx::Attribute.new(@source,
411
+ [ThinkingSphinx::Index::FauxColumn.new(:friends, :id)],
412
+ :as => :friend_ids, :source => :ranged_query
413
+ )
414
+ end
415
+
416
+ it "should use a ranged query" do
417
+ @attribute.type_to_config.should == :sql_attr_multi
418
+
419
+ declaration, query, range_query = @attribute.config_value.split('; ')
420
+ declaration.should == "uint friend_ids from ranged-query"
421
+ query.should == "SELECT `friendships`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `friendships`.`friend_id` AS `friend_ids` FROM `friendships` WHERE `friendships`.`person_id` >= $start AND `friendships`.`person_id` <= $end"
422
+ range_query.should == "SELECT MIN(`friendships`.`person_id`), MAX(`friendships`.`person_id`) FROM `friendships`"
423
+ end
424
+ end
425
+
426
+ describe "MVA via a HABTM with a ranged source query" do
427
+ before :each do
428
+ @attribute = ThinkingSphinx::Attribute.new(@source,
429
+ [ThinkingSphinx::Index::FauxColumn.new(:links, :id)],
430
+ :as => :link_ids, :source => :ranged_query
431
+ )
432
+ end
433
+
434
+ it "should use a ranged query" do
435
+ @attribute.type_to_config.should == :sql_attr_multi
436
+
437
+ declaration, query, range_query = @attribute.config_value.split('; ')
438
+ declaration.should == "uint link_ids from ranged-query"
439
+ query.should == "SELECT `links_people`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `links_people`.`link_id` AS `link_ids` FROM `links_people` WHERE `links_people`.`person_id` >= $start AND `links_people`.`person_id` <= $end"
440
+ range_query.should == "SELECT MIN(`links_people`.`person_id`), MAX(`links_people`.`person_id`) FROM `links_people`"
441
+ end
442
+ end
443
+
444
+ describe "MVA via two has-many associations with a ranged source query" do
445
+ before :each do
446
+ @index = ThinkingSphinx::Index.new(Alpha)
447
+ @source = ThinkingSphinx::Source.new(@index)
448
+ @attribute = ThinkingSphinx::Attribute.new(@source,
449
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
450
+ :as => :gamma_values, :source => :ranged_query
451
+ )
452
+ end
453
+
454
+ it "should use a ranged query" do
455
+ @attribute.type_to_config.should == :sql_attr_multi
456
+
457
+ declaration, query, range_query = @attribute.config_value.split('; ')
458
+ declaration.should == "uint gamma_values from ranged-query"
459
+ query.should == "SELECT `betas`.`alpha_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `gammas`.`value` AS `gamma_values` FROM `betas` LEFT OUTER JOIN `gammas` ON gammas.beta_id = betas.id WHERE `betas`.`alpha_id` >= $start AND `betas`.`alpha_id` <= $end"
460
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
461
+ end
462
+ end
463
+
464
+ describe "MVA via two has-many associations with a ranged source query for a delta source" do
465
+ before :each do
466
+ @index = ThinkingSphinx::Index.new(Alpha)
467
+ @source = ThinkingSphinx::Source.new(@index)
468
+ @attribute = ThinkingSphinx::Attribute.new(@source,
469
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
470
+ :as => :gamma_values, :source => :ranged_query
471
+ )
472
+
473
+ @index.delta_object = ThinkingSphinx::Deltas::DefaultDelta.new @index, @index.local_options
474
+ end
475
+
476
+ it "should use a ranged query" do
477
+ @attribute.type_to_config.should == :sql_attr_multi
478
+
479
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
480
+ declaration.should == "uint gamma_values from ranged-query"
481
+ query.should == "SELECT `betas`.`alpha_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `gammas`.`value` AS `gamma_values` FROM `betas` LEFT OUTER JOIN `gammas` ON gammas.beta_id = betas.id WHERE `betas`.`alpha_id` >= $start AND `betas`.`alpha_id` <= $end AND `betas`.`alpha_id` IN (SELECT `id` FROM `alphas` WHERE `alphas`.`delta` = 1)"
482
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
483
+ end
484
+ end
485
+
486
+ describe "with custom queries" do
487
+ before :each do
488
+ index = CricketTeam.sphinx_indexes.first
489
+ @statement = index.sources.first.to_riddle_for_core(0, 0).sql_attr_multi.last
490
+ end
491
+
492
+ it "should track the query type accordingly" do
493
+ @statement.should match(/uint tags from query/)
494
+ end
495
+
496
+ it "should include the SQL statement" do
497
+ @statement.should match(/SELECT cricket_team_id, id FROM tags/)
498
+ end
499
+ end
500
+ end