joshcutler-thinking-sphinx 1.3.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +167 -0
  3. data/VERSION +1 -0
  4. data/features/abstract_inheritance.feature +10 -0
  5. data/features/alternate_primary_key.feature +27 -0
  6. data/features/attribute_transformation.feature +22 -0
  7. data/features/attribute_updates.feature +77 -0
  8. data/features/deleting_instances.feature +67 -0
  9. data/features/direct_attributes.feature +11 -0
  10. data/features/excerpts.feature +13 -0
  11. data/features/extensible_delta_indexing.feature +9 -0
  12. data/features/facets.feature +82 -0
  13. data/features/facets_across_model.feature +29 -0
  14. data/features/handling_edits.feature +92 -0
  15. data/features/retry_stale_indexes.feature +24 -0
  16. data/features/searching_across_models.feature +20 -0
  17. data/features/searching_by_index.feature +40 -0
  18. data/features/searching_by_model.feature +175 -0
  19. data/features/searching_with_find_arguments.feature +56 -0
  20. data/features/sphinx_detection.feature +25 -0
  21. data/features/sphinx_scopes.feature +42 -0
  22. data/features/step_definitions/alpha_steps.rb +16 -0
  23. data/features/step_definitions/beta_steps.rb +7 -0
  24. data/features/step_definitions/common_steps.rb +193 -0
  25. data/features/step_definitions/extensible_delta_indexing_steps.rb +7 -0
  26. data/features/step_definitions/facet_steps.rb +96 -0
  27. data/features/step_definitions/find_arguments_steps.rb +36 -0
  28. data/features/step_definitions/gamma_steps.rb +15 -0
  29. data/features/step_definitions/scope_steps.rb +15 -0
  30. data/features/step_definitions/search_steps.rb +89 -0
  31. data/features/step_definitions/sphinx_steps.rb +35 -0
  32. data/features/sti_searching.feature +19 -0
  33. data/features/support/env.rb +21 -0
  34. data/features/support/lib/generic_delta_handler.rb +8 -0
  35. data/features/thinking_sphinx/database.example.yml +3 -0
  36. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -0
  37. data/features/thinking_sphinx/db/fixtures/authors.rb +1 -0
  38. data/features/thinking_sphinx/db/fixtures/betas.rb +11 -0
  39. data/features/thinking_sphinx/db/fixtures/boxes.rb +9 -0
  40. data/features/thinking_sphinx/db/fixtures/categories.rb +1 -0
  41. data/features/thinking_sphinx/db/fixtures/cats.rb +3 -0
  42. data/features/thinking_sphinx/db/fixtures/comments.rb +24 -0
  43. data/features/thinking_sphinx/db/fixtures/developers.rb +31 -0
  44. data/features/thinking_sphinx/db/fixtures/dogs.rb +3 -0
  45. data/features/thinking_sphinx/db/fixtures/extensible_betas.rb +10 -0
  46. data/features/thinking_sphinx/db/fixtures/foxes.rb +3 -0
  47. data/features/thinking_sphinx/db/fixtures/gammas.rb +10 -0
  48. data/features/thinking_sphinx/db/fixtures/music.rb +4 -0
  49. data/features/thinking_sphinx/db/fixtures/people.rb +1001 -0
  50. data/features/thinking_sphinx/db/fixtures/posts.rb +6 -0
  51. data/features/thinking_sphinx/db/fixtures/robots.rb +14 -0
  52. data/features/thinking_sphinx/db/fixtures/tags.rb +27 -0
  53. data/features/thinking_sphinx/db/migrations/create_alphas.rb +8 -0
  54. data/features/thinking_sphinx/db/migrations/create_animals.rb +5 -0
  55. data/features/thinking_sphinx/db/migrations/create_authors.rb +3 -0
  56. data/features/thinking_sphinx/db/migrations/create_authors_posts.rb +6 -0
  57. data/features/thinking_sphinx/db/migrations/create_betas.rb +5 -0
  58. data/features/thinking_sphinx/db/migrations/create_boxes.rb +5 -0
  59. data/features/thinking_sphinx/db/migrations/create_categories.rb +3 -0
  60. data/features/thinking_sphinx/db/migrations/create_comments.rb +10 -0
  61. data/features/thinking_sphinx/db/migrations/create_developers.rb +7 -0
  62. data/features/thinking_sphinx/db/migrations/create_extensible_betas.rb +5 -0
  63. data/features/thinking_sphinx/db/migrations/create_gammas.rb +3 -0
  64. data/features/thinking_sphinx/db/migrations/create_genres.rb +3 -0
  65. data/features/thinking_sphinx/db/migrations/create_music.rb +6 -0
  66. data/features/thinking_sphinx/db/migrations/create_people.rb +13 -0
  67. data/features/thinking_sphinx/db/migrations/create_posts.rb +5 -0
  68. data/features/thinking_sphinx/db/migrations/create_robots.rb +4 -0
  69. data/features/thinking_sphinx/db/migrations/create_taggings.rb +5 -0
  70. data/features/thinking_sphinx/db/migrations/create_tags.rb +4 -0
  71. data/features/thinking_sphinx/models/alpha.rb +22 -0
  72. data/features/thinking_sphinx/models/animal.rb +5 -0
  73. data/features/thinking_sphinx/models/author.rb +3 -0
  74. data/features/thinking_sphinx/models/beta.rb +8 -0
  75. data/features/thinking_sphinx/models/box.rb +8 -0
  76. data/features/thinking_sphinx/models/cat.rb +3 -0
  77. data/features/thinking_sphinx/models/category.rb +4 -0
  78. data/features/thinking_sphinx/models/comment.rb +10 -0
  79. data/features/thinking_sphinx/models/developer.rb +16 -0
  80. data/features/thinking_sphinx/models/dog.rb +3 -0
  81. data/features/thinking_sphinx/models/extensible_beta.rb +9 -0
  82. data/features/thinking_sphinx/models/fox.rb +5 -0
  83. data/features/thinking_sphinx/models/gamma.rb +5 -0
  84. data/features/thinking_sphinx/models/genre.rb +3 -0
  85. data/features/thinking_sphinx/models/medium.rb +5 -0
  86. data/features/thinking_sphinx/models/music.rb +8 -0
  87. data/features/thinking_sphinx/models/person.rb +23 -0
  88. data/features/thinking_sphinx/models/post.rb +21 -0
  89. data/features/thinking_sphinx/models/robot.rb +12 -0
  90. data/features/thinking_sphinx/models/tag.rb +3 -0
  91. data/features/thinking_sphinx/models/tagging.rb +4 -0
  92. data/lib/cucumber/thinking_sphinx/external_world.rb +8 -0
  93. data/lib/cucumber/thinking_sphinx/internal_world.rb +126 -0
  94. data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
  95. data/lib/thinking_sphinx.rb +242 -0
  96. data/lib/thinking_sphinx/active_record.rb +380 -0
  97. data/lib/thinking_sphinx/active_record/attribute_updates.rb +50 -0
  98. data/lib/thinking_sphinx/active_record/delta.rb +61 -0
  99. data/lib/thinking_sphinx/active_record/has_many_association.rb +51 -0
  100. data/lib/thinking_sphinx/active_record/scopes.rb +75 -0
  101. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +46 -0
  102. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +58 -0
  103. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +147 -0
  104. data/lib/thinking_sphinx/association.rb +164 -0
  105. data/lib/thinking_sphinx/attribute.rb +390 -0
  106. data/lib/thinking_sphinx/auto_version.rb +22 -0
  107. data/lib/thinking_sphinx/class_facet.rb +15 -0
  108. data/lib/thinking_sphinx/configuration.rb +292 -0
  109. data/lib/thinking_sphinx/context.rb +74 -0
  110. data/lib/thinking_sphinx/core/array.rb +7 -0
  111. data/lib/thinking_sphinx/core/string.rb +15 -0
  112. data/lib/thinking_sphinx/deltas.rb +28 -0
  113. data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
  114. data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
  115. data/lib/thinking_sphinx/excerpter.rb +22 -0
  116. data/lib/thinking_sphinx/facet.rb +125 -0
  117. data/lib/thinking_sphinx/facet_search.rb +136 -0
  118. data/lib/thinking_sphinx/field.rb +80 -0
  119. data/lib/thinking_sphinx/index.rb +157 -0
  120. data/lib/thinking_sphinx/index/builder.rb +302 -0
  121. data/lib/thinking_sphinx/index/faux_column.rb +118 -0
  122. data/lib/thinking_sphinx/join.rb +37 -0
  123. data/lib/thinking_sphinx/property.rb +168 -0
  124. data/lib/thinking_sphinx/rails_additions.rb +150 -0
  125. data/lib/thinking_sphinx/search.rb +785 -0
  126. data/lib/thinking_sphinx/search_methods.rb +439 -0
  127. data/lib/thinking_sphinx/source.rb +159 -0
  128. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  129. data/lib/thinking_sphinx/source/sql.rb +130 -0
  130. data/lib/thinking_sphinx/tasks.rb +121 -0
  131. data/lib/thinking_sphinx/test.rb +52 -0
  132. data/rails/init.rb +16 -0
  133. data/spec/thinking_sphinx/active_record/delta_spec.rb +128 -0
  134. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +71 -0
  135. data/spec/thinking_sphinx/active_record/scopes_spec.rb +177 -0
  136. data/spec/thinking_sphinx/active_record_spec.rb +618 -0
  137. data/spec/thinking_sphinx/association_spec.rb +239 -0
  138. data/spec/thinking_sphinx/attribute_spec.rb +548 -0
  139. data/spec/thinking_sphinx/auto_version_spec.rb +39 -0
  140. data/spec/thinking_sphinx/configuration_spec.rb +271 -0
  141. data/spec/thinking_sphinx/context_spec.rb +126 -0
  142. data/spec/thinking_sphinx/core/array_spec.rb +9 -0
  143. data/spec/thinking_sphinx/core/string_spec.rb +9 -0
  144. data/spec/thinking_sphinx/excerpter_spec.rb +49 -0
  145. data/spec/thinking_sphinx/facet_search_spec.rb +176 -0
  146. data/spec/thinking_sphinx/facet_spec.rb +333 -0
  147. data/spec/thinking_sphinx/field_spec.rb +113 -0
  148. data/spec/thinking_sphinx/index/builder_spec.rb +495 -0
  149. data/spec/thinking_sphinx/index/faux_column_spec.rb +36 -0
  150. data/spec/thinking_sphinx/index_spec.rb +183 -0
  151. data/spec/thinking_sphinx/rails_additions_spec.rb +203 -0
  152. data/spec/thinking_sphinx/search_methods_spec.rb +152 -0
  153. data/spec/thinking_sphinx/search_spec.rb +1206 -0
  154. data/spec/thinking_sphinx/source_spec.rb +243 -0
  155. data/spec/thinking_sphinx_spec.rb +204 -0
  156. data/tasks/distribution.rb +46 -0
  157. data/tasks/rails.rake +1 -0
  158. metadata +475 -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,548 @@
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' 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 '#to_select_sql' do
51
+ it "should convert a mixture of dates and datetimes to timestamps" do
52
+ attribute = ThinkingSphinx::Attribute.new(@source,
53
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
54
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ],
55
+ :as => :times
56
+ )
57
+ attribute.model = Friendship
58
+
59
+ attribute.to_select_sql.should == "CONCAT_WS(',', UNIX_TIMESTAMP(`friendships`.`created_at`), UNIX_TIMESTAMP(`friendships`.`created_on`)) AS `times`"
60
+ end
61
+
62
+ it "should handle columns which don't exist for polymorphic joins" do
63
+ attribute = ThinkingSphinx::Attribute.new(@source,
64
+ [ ThinkingSphinx::Index::FauxColumn.new(:team, :name),
65
+ ThinkingSphinx::Index::FauxColumn.new(:team, :league) ],
66
+ :as => :team
67
+ )
68
+
69
+ attribute.to_select_sql.should == "CONCAT_WS(' ', IFNULL(`cricket_teams`.`name`, ''), IFNULL(`football_teams`.`name`, ''), IFNULL(`football_teams`.`league`, '')) AS `team`"
70
+ end
71
+ end
72
+
73
+ describe '#is_many?' do
74
+ before :each do
75
+ @assoc_a = stub('assoc', :is_many? => true)
76
+ @assoc_b = stub('assoc', :is_many? => true)
77
+ @assoc_c = stub('assoc', :is_many? => true)
78
+
79
+ @attribute = ThinkingSphinx::Attribute.new(
80
+ @source, [ThinkingSphinx::Index::FauxColumn.new(:col_name)]
81
+ )
82
+ @attribute.associations = {
83
+ :a => @assoc_a, :b => @assoc_b, :c => @assoc_c
84
+ }
85
+ end
86
+
87
+ it "should return true if all associations return true to is_many?" do
88
+ @attribute.send(:is_many?).should be_true
89
+ end
90
+
91
+ it "should return true if one association returns true to is_many?" do
92
+ @assoc_b.stub!(:is_many? => false)
93
+ @assoc_c.stub!(:is_many? => false)
94
+
95
+ @attribute.send(:is_many?).should be_true
96
+ end
97
+
98
+ it "should return false if all associations return false to is_many?" do
99
+ @assoc_a.stub!(:is_many? => false)
100
+ @assoc_b.stub!(:is_many? => false)
101
+ @assoc_c.stub!(:is_many? => false)
102
+
103
+ @attribute.send(:is_many?).should be_false
104
+ end
105
+ end
106
+
107
+ describe '#is_string?' do
108
+ before :each do
109
+ @col_a = ThinkingSphinx::Index::FauxColumn.new("a")
110
+ @col_b = ThinkingSphinx::Index::FauxColumn.new("b")
111
+ @col_c = ThinkingSphinx::Index::FauxColumn.new("c")
112
+
113
+ @attribute = ThinkingSphinx::Attribute.new(
114
+ @source, [@col_a, @col_b, @col_c]
115
+ )
116
+ end
117
+
118
+ it "should return true if all columns return true to is_string?" do
119
+ @attribute.send(:is_string?).should be_true
120
+ end
121
+
122
+ it "should return false if one column returns true to is_string?" do
123
+ @col_a.send(:instance_variable_set, :@name, :a)
124
+ @attribute.send(:is_string?).should be_false
125
+ end
126
+
127
+ it "should return false if all columns return false to is_string?" do
128
+ @col_a.send(:instance_variable_set, :@name, :a)
129
+ @col_b.send(:instance_variable_set, :@name, :b)
130
+ @col_c.send(:instance_variable_set, :@name, :c)
131
+ @attribute.send(:is_string?).should be_false
132
+ end
133
+ end
134
+
135
+ describe '#type' do
136
+ before :each do
137
+ @column = ThinkingSphinx::Index::FauxColumn.new(:col_name)
138
+ @attribute = ThinkingSphinx::Attribute.new(@source, [@column])
139
+ @attribute.model = Person
140
+ @attribute.stub!(:is_many? => false)
141
+ end
142
+
143
+ it "should return :multi if is_many? is true" do
144
+ @attribute.stub!(:is_many? => true)
145
+ @attribute.send(:type).should == :multi
146
+ end
147
+
148
+ it "should return :string if there's more than one association" do
149
+ @attribute.associations = {:a => [:assoc], :b => [:assoc]}
150
+ @attribute.send(:type).should == :string
151
+ end
152
+
153
+ it "should return the column type from the database if not :multi or more than one association" do
154
+ @column.send(:instance_variable_set, :@name, "birthday")
155
+ @attribute.type.should == :datetime
156
+
157
+ @attribute.send(:instance_variable_set, :@type, nil)
158
+ @column.send(:instance_variable_set, :@name, "first_name")
159
+ @attribute.type.should == :string
160
+
161
+ @attribute.send(:instance_variable_set, :@type, nil)
162
+ @column.send(:instance_variable_set, :@name, "id")
163
+ @attribute.type.should == :integer
164
+ end
165
+
166
+ it "should return :multi if the columns return multiple datetimes" do
167
+ @attribute.stub!(:is_many? => true)
168
+ @attribute.stub!(:all_datetimes? => true)
169
+
170
+ @attribute.type.should == :multi
171
+ end
172
+
173
+ it "should return :bigint for 64bit integers" do
174
+ Person.columns.detect { |col|
175
+ col.name == 'id'
176
+ }.stub!(:sql_type => 'BIGINT(20)')
177
+ @column.send(:instance_variable_set, :@name, 'id')
178
+
179
+ @attribute.type.should == :bigint
180
+ end
181
+ end
182
+
183
+ describe '#all_ints?' do
184
+ it "should return true if all columns are integers" do
185
+ attribute = ThinkingSphinx::Attribute.new(@source,
186
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
187
+ ThinkingSphinx::Index::FauxColumn.new(:team_id) ]
188
+ )
189
+ attribute.model = Person
190
+ attribute.columns.each { |col| attribute.associations[col] = [] }
191
+
192
+ attribute.should be_all_ints
193
+ end
194
+
195
+ it "should return false if only some columns are integers" do
196
+ attribute = ThinkingSphinx::Attribute.new(@source,
197
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
198
+ ThinkingSphinx::Index::FauxColumn.new(:first_name) ]
199
+ )
200
+ attribute.model = Person
201
+ attribute.columns.each { |col| attribute.associations[col] = [] }
202
+
203
+ attribute.should_not be_all_ints
204
+ end
205
+
206
+ it "should return false if no columns are integers" do
207
+ attribute = ThinkingSphinx::Attribute.new(@source,
208
+ [ ThinkingSphinx::Index::FauxColumn.new(:first_name),
209
+ ThinkingSphinx::Index::FauxColumn.new(:last_name) ]
210
+ )
211
+ attribute.model = Person
212
+ attribute.columns.each { |col| attribute.associations[col] = [] }
213
+
214
+ attribute.should_not be_all_ints
215
+ end
216
+ end
217
+
218
+ describe '#all_datetimes?' do
219
+ it "should return true if all columns are datetimes" do
220
+ attribute = ThinkingSphinx::Attribute.new(@source,
221
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
222
+ ThinkingSphinx::Index::FauxColumn.new(:updated_at) ]
223
+ )
224
+ attribute.model = Friendship
225
+ attribute.columns.each { |col| attribute.associations[col] = [] }
226
+
227
+ attribute.should be_all_datetimes
228
+ end
229
+
230
+ it "should return false if only some columns are datetimes" do
231
+ attribute = ThinkingSphinx::Attribute.new(@source,
232
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
233
+ ThinkingSphinx::Index::FauxColumn.new(:created_at) ]
234
+ )
235
+ attribute.model = Friendship
236
+ attribute.columns.each { |col| attribute.associations[col] = [] }
237
+
238
+ attribute.should_not be_all_datetimes
239
+ end
240
+
241
+ it "should return true if all columns can be " do
242
+ attribute = ThinkingSphinx::Attribute.new(@source,
243
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
244
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ]
245
+ )
246
+ attribute.model = Friendship
247
+ attribute.columns.each { |col| attribute.associations[col] = [] }
248
+
249
+ attribute.should be_all_datetimes
250
+ end
251
+ end
252
+
253
+ describe '#all_strings?' do
254
+ it "should return true if all columns are strings or text" do
255
+ attribute = ThinkingSphinx::Attribute.new(@source,
256
+ [ ThinkingSphinx::Index::FauxColumn.new(:first_name),
257
+ ThinkingSphinx::Index::FauxColumn.new(:last_name) ]
258
+ )
259
+ attribute.model = Person
260
+ attribute.columns.each { |col| attribute.associations[col] = [] }
261
+
262
+ attribute.should be_all_strings
263
+ end
264
+
265
+ it "should return false if only some columns are strings" do
266
+ attribute = ThinkingSphinx::Attribute.new(@source,
267
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
268
+ ThinkingSphinx::Index::FauxColumn.new(:first_name) ]
269
+ )
270
+ attribute.model = Person
271
+ attribute.columns.each { |col| attribute.associations[col] = [] }
272
+
273
+ attribute.should_not be_all_strings
274
+ end
275
+
276
+ it "should return true if all columns are not strings" do
277
+ attribute = ThinkingSphinx::Attribute.new(@source,
278
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
279
+ ThinkingSphinx::Index::FauxColumn.new(:parent_id) ]
280
+ )
281
+ attribute.model = Person
282
+ attribute.columns.each { |col| attribute.associations[col] = [] }
283
+
284
+ attribute.should_not be_all_strings
285
+ end
286
+ end
287
+
288
+ describe "MVA with source query" do
289
+ before :each do
290
+ @attribute = ThinkingSphinx::Attribute.new(@source,
291
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
292
+ :as => :tag_ids, :source => :query
293
+ )
294
+ end
295
+
296
+ it "should use a query" do
297
+ @attribute.type_to_config.should == :sql_attr_multi
298
+
299
+ declaration, query = @attribute.config_value.split('; ')
300
+ declaration.should == "uint tag_ids from query"
301
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags`"
302
+ end
303
+ end
304
+
305
+ describe "MVA with source query for a delta source" do
306
+ before :each do
307
+ @attribute = ThinkingSphinx::Attribute.new(@source,
308
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
309
+ :as => :tag_ids, :source => :query
310
+ )
311
+ end
312
+
313
+ it "should use a query" do
314
+ @attribute.type_to_config.should == :sql_attr_multi
315
+
316
+ declaration, query = @attribute.config_value(nil, true).split('; ')
317
+ declaration.should == "uint tag_ids from query"
318
+ 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)"
319
+ end
320
+ end
321
+
322
+ describe "MVA via a HABTM association with a source query" do
323
+ before :each do
324
+ @attribute = ThinkingSphinx::Attribute.new(@source,
325
+ [ThinkingSphinx::Index::FauxColumn.new(:links, :id)],
326
+ :as => :link_ids, :source => :query
327
+ )
328
+ end
329
+
330
+ it "should use a ranged query" do
331
+ @attribute.type_to_config.should == :sql_attr_multi
332
+
333
+ declaration, query = @attribute.config_value.split('; ')
334
+ declaration.should == "uint link_ids from query"
335
+ query.should == "SELECT `links_people`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `links_people`.`link_id` AS `link_ids` FROM `links_people`"
336
+ end
337
+ end
338
+
339
+ describe "MVA with ranged source query" do
340
+ before :each do
341
+ @attribute = ThinkingSphinx::Attribute.new(@source,
342
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
343
+ :as => :tag_ids, :source => :ranged_query
344
+ )
345
+ end
346
+
347
+ it "should use a ranged query" do
348
+ @attribute.type_to_config.should == :sql_attr_multi
349
+
350
+ declaration, query, range_query = @attribute.config_value.split('; ')
351
+ declaration.should == "uint tag_ids from ranged-query"
352
+ 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"
353
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
354
+ end
355
+ end
356
+
357
+ describe "MVA with ranged source query for a delta source" do
358
+ before :each do
359
+ @attribute = ThinkingSphinx::Attribute.new(@source,
360
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
361
+ :as => :tag_ids, :source => :ranged_query
362
+ )
363
+ end
364
+
365
+ it "should use a ranged query" do
366
+ @attribute.type_to_config.should == :sql_attr_multi
367
+
368
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
369
+ declaration.should == "uint tag_ids from ranged-query"
370
+ 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)"
371
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
372
+ end
373
+ end
374
+
375
+ describe "MVA via a has-many :through with a ranged source query" do
376
+ before :each do
377
+ @attribute = ThinkingSphinx::Attribute.new(@source,
378
+ [ThinkingSphinx::Index::FauxColumn.new(:football_teams, :id)],
379
+ :as => :football_team_ids, :source => :ranged_query
380
+ )
381
+ end
382
+
383
+ it "should use a ranged query" do
384
+ @attribute.type_to_config.should == :sql_attr_multi
385
+
386
+ declaration, query, range_query = @attribute.config_value.split('; ')
387
+ declaration.should == "uint football_team_ids from ranged-query"
388
+ 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"
389
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
390
+ end
391
+ end
392
+
393
+ describe "MVA via a has-many :through using a foreign key with a ranged source query" do
394
+ before :each do
395
+ @attribute = ThinkingSphinx::Attribute.new(@source,
396
+ [ThinkingSphinx::Index::FauxColumn.new(:friends, :id)],
397
+ :as => :friend_ids, :source => :ranged_query
398
+ )
399
+ end
400
+
401
+ it "should use a ranged query" do
402
+ @attribute.type_to_config.should == :sql_attr_multi
403
+
404
+ declaration, query, range_query = @attribute.config_value.split('; ')
405
+ declaration.should == "uint friend_ids from ranged-query"
406
+ 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"
407
+ range_query.should == "SELECT MIN(`friendships`.`person_id`), MAX(`friendships`.`person_id`) FROM `friendships`"
408
+ end
409
+ end
410
+
411
+ describe "MVA via a HABTM with a ranged source query" do
412
+ before :each do
413
+ @attribute = ThinkingSphinx::Attribute.new(@source,
414
+ [ThinkingSphinx::Index::FauxColumn.new(:links, :id)],
415
+ :as => :link_ids, :source => :ranged_query
416
+ )
417
+ end
418
+
419
+ it "should use a ranged query" do
420
+ @attribute.type_to_config.should == :sql_attr_multi
421
+
422
+ declaration, query, range_query = @attribute.config_value.split('; ')
423
+ declaration.should == "uint link_ids from ranged-query"
424
+ 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"
425
+ range_query.should == "SELECT MIN(`links_people`.`person_id`), MAX(`links_people`.`person_id`) FROM `links_people`"
426
+ end
427
+ end
428
+
429
+ describe "MVA via two has-many associations with a ranged source query" do
430
+ before :each do
431
+ @index = ThinkingSphinx::Index.new(Alpha)
432
+ @source = ThinkingSphinx::Source.new(@index)
433
+ @attribute = ThinkingSphinx::Attribute.new(@source,
434
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
435
+ :as => :gamma_values, :source => :ranged_query
436
+ )
437
+ end
438
+
439
+ it "should use a ranged query" do
440
+ @attribute.type_to_config.should == :sql_attr_multi
441
+
442
+ declaration, query, range_query = @attribute.config_value.split('; ')
443
+ declaration.should == "uint gamma_values from ranged-query"
444
+ 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"
445
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
446
+ end
447
+ end
448
+
449
+ describe "MVA via two has-many associations with a ranged source query for a delta source" do
450
+ before :each do
451
+ @index = ThinkingSphinx::Index.new(Alpha)
452
+ @source = ThinkingSphinx::Source.new(@index)
453
+ @attribute = ThinkingSphinx::Attribute.new(@source,
454
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
455
+ :as => :gamma_values, :source => :ranged_query
456
+ )
457
+
458
+ @index.delta_object = ThinkingSphinx::Deltas::DefaultDelta.new @index, @index.local_options
459
+ end
460
+
461
+ it "should use a ranged query" do
462
+ @attribute.type_to_config.should == :sql_attr_multi
463
+
464
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
465
+ declaration.should == "uint gamma_values from ranged-query"
466
+ 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)"
467
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
468
+ end
469
+ end
470
+
471
+ describe "with custom queries" do
472
+ before :each do
473
+ index = CricketTeam.sphinx_indexes.first
474
+ @statement = index.sources.first.to_riddle_for_core(0, 0).sql_attr_multi.last
475
+ end
476
+
477
+ it "should track the query type accordingly" do
478
+ @statement.should match(/uint tags from query/)
479
+ end
480
+
481
+ it "should include the SQL statement" do
482
+ @statement.should match(/SELECT cricket_team_id, id FROM tags/)
483
+ end
484
+ end
485
+
486
+ describe '#live_value' do
487
+ before :each do
488
+ @attribute = ThinkingSphinx::Attribute.new @source, [
489
+ stub('column', :__stack => [], :__name => "col_name")
490
+ ]
491
+ @instance = stub('model')
492
+ end
493
+
494
+ it "should translate boolean values to integers" do
495
+ @instance.stub!(:col_name => true)
496
+ @attribute.live_value(@instance).should == 1
497
+
498
+ @instance.stub!(:col_name => false)
499
+ @attribute.live_value(@instance).should == 0
500
+ end
501
+
502
+ it "should translate timestamps to integers" do
503
+ now = Time.now
504
+ @instance.stub!(:col_name => now)
505
+ @attribute.live_value(@instance).should == now.to_i
506
+ end
507
+
508
+ it "should translate dates to timestamp integers" do
509
+ today = Date.today
510
+ @instance.stub!(:col_name => today)
511
+ @attribute.live_value(@instance).should == today.to_time.to_i
512
+ end
513
+
514
+ it "should translate nils to 0" do
515
+ @instance.stub!(:col_name => nil)
516
+ @attribute.live_value(@instance).should == 0
517
+ end
518
+
519
+ it "should return integers as integers" do
520
+ @instance.stub!(:col_name => 42)
521
+ @attribute.live_value(@instance).should == 42
522
+ end
523
+
524
+ it "should handle nils in the association chain" do
525
+ @attribute = ThinkingSphinx::Attribute.new @source, [
526
+ stub('column', :__stack => [:assoc_name], :__name => :id)
527
+ ]
528
+ @instance.stub!(:assoc_name => nil)
529
+ @attribute.live_value(@instance).should == 0
530
+ end
531
+
532
+ it "should handle association chains" do
533
+ @attribute = ThinkingSphinx::Attribute.new @source, [
534
+ stub('column', :__stack => [:assoc_name], :__name => :id)
535
+ ]
536
+ @instance.stub!(:assoc_name => stub('object', :id => 42))
537
+ @attribute.live_value(@instance).should == 42
538
+ end
539
+
540
+ it "should translate crc strings to their integer values" do
541
+ @attribute = ThinkingSphinx::Attribute.new @source, [
542
+ stub('column', :__stack => [], :__name => "col_name")
543
+ ], :crc => true, :type => :string
544
+ @instance.stub!(:col_name => 'foo')
545
+ @attribute.live_value(@instance).should == 'foo'.to_crc32
546
+ end
547
+ end
548
+ end