zipme-thinking-sphinx 1.3.14

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 +168 -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 +51 -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 +188 -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/database.example.yml +3 -0
  34. data/features/support/db/fixtures/alphas.rb +10 -0
  35. data/features/support/db/fixtures/authors.rb +1 -0
  36. data/features/support/db/fixtures/betas.rb +10 -0
  37. data/features/support/db/fixtures/boxes.rb +9 -0
  38. data/features/support/db/fixtures/categories.rb +1 -0
  39. data/features/support/db/fixtures/cats.rb +3 -0
  40. data/features/support/db/fixtures/comments.rb +24 -0
  41. data/features/support/db/fixtures/developers.rb +29 -0
  42. data/features/support/db/fixtures/dogs.rb +3 -0
  43. data/features/support/db/fixtures/extensible_betas.rb +10 -0
  44. data/features/support/db/fixtures/foxes.rb +3 -0
  45. data/features/support/db/fixtures/gammas.rb +10 -0
  46. data/features/support/db/fixtures/music.rb +4 -0
  47. data/features/support/db/fixtures/people.rb +1001 -0
  48. data/features/support/db/fixtures/posts.rb +6 -0
  49. data/features/support/db/fixtures/robots.rb +14 -0
  50. data/features/support/db/fixtures/tags.rb +27 -0
  51. data/features/support/db/migrations/create_alphas.rb +8 -0
  52. data/features/support/db/migrations/create_animals.rb +5 -0
  53. data/features/support/db/migrations/create_authors.rb +3 -0
  54. data/features/support/db/migrations/create_authors_posts.rb +6 -0
  55. data/features/support/db/migrations/create_betas.rb +5 -0
  56. data/features/support/db/migrations/create_boxes.rb +5 -0
  57. data/features/support/db/migrations/create_categories.rb +3 -0
  58. data/features/support/db/migrations/create_comments.rb +10 -0
  59. data/features/support/db/migrations/create_developers.rb +9 -0
  60. data/features/support/db/migrations/create_extensible_betas.rb +5 -0
  61. data/features/support/db/migrations/create_gammas.rb +3 -0
  62. data/features/support/db/migrations/create_genres.rb +3 -0
  63. data/features/support/db/migrations/create_music.rb +6 -0
  64. data/features/support/db/migrations/create_people.rb +13 -0
  65. data/features/support/db/migrations/create_posts.rb +5 -0
  66. data/features/support/db/migrations/create_robots.rb +4 -0
  67. data/features/support/db/migrations/create_taggings.rb +5 -0
  68. data/features/support/db/migrations/create_tags.rb +4 -0
  69. data/features/support/env.rb +21 -0
  70. data/features/support/lib/generic_delta_handler.rb +8 -0
  71. data/features/support/models/alpha.rb +22 -0
  72. data/features/support/models/animal.rb +5 -0
  73. data/features/support/models/author.rb +3 -0
  74. data/features/support/models/beta.rb +8 -0
  75. data/features/support/models/box.rb +8 -0
  76. data/features/support/models/cat.rb +3 -0
  77. data/features/support/models/category.rb +4 -0
  78. data/features/support/models/comment.rb +10 -0
  79. data/features/support/models/developer.rb +16 -0
  80. data/features/support/models/dog.rb +3 -0
  81. data/features/support/models/extensible_beta.rb +9 -0
  82. data/features/support/models/fox.rb +5 -0
  83. data/features/support/models/gamma.rb +5 -0
  84. data/features/support/models/genre.rb +3 -0
  85. data/features/support/models/medium.rb +5 -0
  86. data/features/support/models/music.rb +8 -0
  87. data/features/support/models/person.rb +23 -0
  88. data/features/support/models/post.rb +21 -0
  89. data/features/support/models/robot.rb +12 -0
  90. data/features/support/models/tag.rb +3 -0
  91. data/features/support/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 +231 -0
  96. data/lib/thinking_sphinx/active_record.rb +373 -0
  97. data/lib/thinking_sphinx/active_record/attribute_updates.rb +46 -0
  98. data/lib/thinking_sphinx/active_record/delta.rb +61 -0
  99. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  100. data/lib/thinking_sphinx/active_record/scopes.rb +75 -0
  101. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
  102. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  103. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +143 -0
  104. data/lib/thinking_sphinx/association.rb +164 -0
  105. data/lib/thinking_sphinx/attribute.rb +362 -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 +283 -0
  109. data/lib/thinking_sphinx/context.rb +68 -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 +82 -0
  119. data/lib/thinking_sphinx/index.rb +157 -0
  120. data/lib/thinking_sphinx/index/builder.rb +296 -0
  121. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  122. data/lib/thinking_sphinx/property.rb +162 -0
  123. data/lib/thinking_sphinx/rails_additions.rb +150 -0
  124. data/lib/thinking_sphinx/search.rb +775 -0
  125. data/lib/thinking_sphinx/search_methods.rb +439 -0
  126. data/lib/thinking_sphinx/source.rb +153 -0
  127. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  128. data/lib/thinking_sphinx/source/sql.rb +130 -0
  129. data/lib/thinking_sphinx/tasks.rb +121 -0
  130. data/lib/thinking_sphinx/test.rb +52 -0
  131. data/rails/init.rb +16 -0
  132. data/spec/thinking_sphinx/active_record/delta_spec.rb +128 -0
  133. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +55 -0
  134. data/spec/thinking_sphinx/active_record/scopes_spec.rb +177 -0
  135. data/spec/thinking_sphinx/active_record_spec.rb +622 -0
  136. data/spec/thinking_sphinx/association_spec.rb +239 -0
  137. data/spec/thinking_sphinx/attribute_spec.rb +570 -0
  138. data/spec/thinking_sphinx/auto_version_spec.rb +39 -0
  139. data/spec/thinking_sphinx/configuration_spec.rb +234 -0
  140. data/spec/thinking_sphinx/context_spec.rb +119 -0
  141. data/spec/thinking_sphinx/core/array_spec.rb +9 -0
  142. data/spec/thinking_sphinx/core/string_spec.rb +9 -0
  143. data/spec/thinking_sphinx/excerpter_spec.rb +57 -0
  144. data/spec/thinking_sphinx/facet_search_spec.rb +176 -0
  145. data/spec/thinking_sphinx/facet_spec.rb +333 -0
  146. data/spec/thinking_sphinx/field_spec.rb +154 -0
  147. data/spec/thinking_sphinx/index/builder_spec.rb +479 -0
  148. data/spec/thinking_sphinx/index/faux_column_spec.rb +30 -0
  149. data/spec/thinking_sphinx/index_spec.rb +183 -0
  150. data/spec/thinking_sphinx/rails_additions_spec.rb +203 -0
  151. data/spec/thinking_sphinx/search_methods_spec.rb +152 -0
  152. data/spec/thinking_sphinx/search_spec.rb +1181 -0
  153. data/spec/thinking_sphinx/source_spec.rb +235 -0
  154. data/spec/thinking_sphinx_spec.rb +204 -0
  155. data/tasks/distribution.rb +39 -0
  156. data/tasks/rails.rake +1 -0
  157. data/tasks/testing.rb +72 -0
  158. metadata +242 -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,570 @@
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 '#column_with_prefix' 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?' 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?' 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' 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
+
197
+ it "should return :multi if the columns return multiple datetimes" do
198
+ @attribute.stub!(:is_many? => true)
199
+ @attribute.stub!(:all_datetimes? => true)
200
+
201
+ @attribute.type.should == :multi
202
+ end
203
+ end
204
+
205
+ describe '#all_ints?' do
206
+ it "should return true if all columns are integers" do
207
+ attribute = ThinkingSphinx::Attribute.new(@source,
208
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
209
+ ThinkingSphinx::Index::FauxColumn.new(:team_id) ]
210
+ )
211
+ attribute.model = Person
212
+ attribute.columns.each { |col| attribute.associations[col] = [] }
213
+
214
+ attribute.should be_all_ints
215
+ end
216
+
217
+ it "should return false if only some columns are integers" do
218
+ attribute = ThinkingSphinx::Attribute.new(@source,
219
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
220
+ ThinkingSphinx::Index::FauxColumn.new(:first_name) ]
221
+ )
222
+ attribute.model = Person
223
+ attribute.columns.each { |col| attribute.associations[col] = [] }
224
+
225
+ attribute.should_not be_all_ints
226
+ end
227
+
228
+ it "should return false if no columns are integers" do
229
+ attribute = ThinkingSphinx::Attribute.new(@source,
230
+ [ ThinkingSphinx::Index::FauxColumn.new(:first_name),
231
+ ThinkingSphinx::Index::FauxColumn.new(:last_name) ]
232
+ )
233
+ attribute.model = Person
234
+ attribute.columns.each { |col| attribute.associations[col] = [] }
235
+
236
+ attribute.should_not be_all_ints
237
+ end
238
+ end
239
+
240
+ describe '#all_datetimes?' do
241
+ it "should return true if all columns are datetimes" do
242
+ attribute = ThinkingSphinx::Attribute.new(@source,
243
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
244
+ ThinkingSphinx::Index::FauxColumn.new(:updated_at) ]
245
+ )
246
+ attribute.model = Friendship
247
+ attribute.columns.each { |col| attribute.associations[col] = [] }
248
+
249
+ attribute.should be_all_datetimes
250
+ end
251
+
252
+ it "should return false if only some columns are datetimes" do
253
+ attribute = ThinkingSphinx::Attribute.new(@source,
254
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
255
+ ThinkingSphinx::Index::FauxColumn.new(:created_at) ]
256
+ )
257
+ attribute.model = Friendship
258
+ attribute.columns.each { |col| attribute.associations[col] = [] }
259
+
260
+ attribute.should_not be_all_datetimes
261
+ end
262
+
263
+ it "should return true if all columns can be " do
264
+ attribute = ThinkingSphinx::Attribute.new(@source,
265
+ [ ThinkingSphinx::Index::FauxColumn.new(:created_at),
266
+ ThinkingSphinx::Index::FauxColumn.new(:created_on) ]
267
+ )
268
+ attribute.model = Friendship
269
+ attribute.columns.each { |col| attribute.associations[col] = [] }
270
+
271
+ attribute.should be_all_datetimes
272
+ end
273
+ end
274
+
275
+ describe '#all_strings?' do
276
+ it "should return true if all columns are strings or text" do
277
+ attribute = ThinkingSphinx::Attribute.new(@source,
278
+ [ ThinkingSphinx::Index::FauxColumn.new(:first_name),
279
+ ThinkingSphinx::Index::FauxColumn.new(:last_name) ]
280
+ )
281
+ attribute.model = Person
282
+ attribute.columns.each { |col| attribute.associations[col] = [] }
283
+
284
+ attribute.should be_all_strings
285
+ end
286
+
287
+ it "should return false if only some columns are strings" do
288
+ attribute = ThinkingSphinx::Attribute.new(@source,
289
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
290
+ ThinkingSphinx::Index::FauxColumn.new(:first_name) ]
291
+ )
292
+ attribute.model = Person
293
+ attribute.columns.each { |col| attribute.associations[col] = [] }
294
+
295
+ attribute.should_not be_all_strings
296
+ end
297
+
298
+ it "should return true if all columns are not strings" do
299
+ attribute = ThinkingSphinx::Attribute.new(@source,
300
+ [ ThinkingSphinx::Index::FauxColumn.new(:id),
301
+ ThinkingSphinx::Index::FauxColumn.new(:parent_id) ]
302
+ )
303
+ attribute.model = Person
304
+ attribute.columns.each { |col| attribute.associations[col] = [] }
305
+
306
+ attribute.should_not be_all_strings
307
+ end
308
+ end
309
+
310
+ describe "MVA with source query" do
311
+ before :each do
312
+ @attribute = ThinkingSphinx::Attribute.new(@source,
313
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
314
+ :as => :tag_ids, :source => :query
315
+ )
316
+ end
317
+
318
+ it "should use a query" do
319
+ @attribute.type_to_config.should == :sql_attr_multi
320
+
321
+ declaration, query = @attribute.config_value.split('; ')
322
+ declaration.should == "uint tag_ids from query"
323
+ query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags`"
324
+ end
325
+ end
326
+
327
+ describe "MVA with source query for a delta source" do
328
+ before :each do
329
+ @attribute = ThinkingSphinx::Attribute.new(@source,
330
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
331
+ :as => :tag_ids, :source => :query
332
+ )
333
+ end
334
+
335
+ it "should use a query" do
336
+ @attribute.type_to_config.should == :sql_attr_multi
337
+
338
+ declaration, query = @attribute.config_value(nil, true).split('; ')
339
+ declaration.should == "uint tag_ids from query"
340
+ 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)"
341
+ end
342
+ end
343
+
344
+ describe "MVA via a HABTM association with a source query" do
345
+ before :each do
346
+ @attribute = ThinkingSphinx::Attribute.new(@source,
347
+ [ThinkingSphinx::Index::FauxColumn.new(:links, :id)],
348
+ :as => :link_ids, :source => :query
349
+ )
350
+ end
351
+
352
+ it "should use a ranged query" do
353
+ @attribute.type_to_config.should == :sql_attr_multi
354
+
355
+ declaration, query = @attribute.config_value.split('; ')
356
+ declaration.should == "uint link_ids from query"
357
+ query.should == "SELECT `links_people`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `links_people`.`link_id` AS `link_ids` FROM `links_people`"
358
+ end
359
+ end
360
+
361
+ describe "MVA with ranged source query" do
362
+ before :each do
363
+ @attribute = ThinkingSphinx::Attribute.new(@source,
364
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
365
+ :as => :tag_ids, :source => :ranged_query
366
+ )
367
+ end
368
+
369
+ it "should use a ranged query" do
370
+ @attribute.type_to_config.should == :sql_attr_multi
371
+
372
+ declaration, query, range_query = @attribute.config_value.split('; ')
373
+ declaration.should == "uint tag_ids from ranged-query"
374
+ 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"
375
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
376
+ end
377
+ end
378
+
379
+ describe "MVA with ranged source query for a delta source" do
380
+ before :each do
381
+ @attribute = ThinkingSphinx::Attribute.new(@source,
382
+ [ThinkingSphinx::Index::FauxColumn.new(:tags, :id)],
383
+ :as => :tag_ids, :source => :ranged_query
384
+ )
385
+ end
386
+
387
+ it "should use a ranged query" do
388
+ @attribute.type_to_config.should == :sql_attr_multi
389
+
390
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
391
+ declaration.should == "uint tag_ids from ranged-query"
392
+ 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)"
393
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
394
+ end
395
+ end
396
+
397
+ describe "MVA via a has-many :through with a ranged source query" do
398
+ before :each do
399
+ @attribute = ThinkingSphinx::Attribute.new(@source,
400
+ [ThinkingSphinx::Index::FauxColumn.new(:football_teams, :id)],
401
+ :as => :football_team_ids, :source => :ranged_query
402
+ )
403
+ end
404
+
405
+ it "should use a ranged query" do
406
+ @attribute.type_to_config.should == :sql_attr_multi
407
+
408
+ declaration, query, range_query = @attribute.config_value.split('; ')
409
+ declaration.should == "uint football_team_ids from ranged-query"
410
+ 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"
411
+ range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
412
+ end
413
+ end
414
+
415
+ describe "MVA via a has-many :through using a foreign key with a ranged source query" do
416
+ before :each do
417
+ @attribute = ThinkingSphinx::Attribute.new(@source,
418
+ [ThinkingSphinx::Index::FauxColumn.new(:friends, :id)],
419
+ :as => :friend_ids, :source => :ranged_query
420
+ )
421
+ end
422
+
423
+ it "should use a ranged query" do
424
+ @attribute.type_to_config.should == :sql_attr_multi
425
+
426
+ declaration, query, range_query = @attribute.config_value.split('; ')
427
+ declaration.should == "uint friend_ids from ranged-query"
428
+ 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"
429
+ range_query.should == "SELECT MIN(`friendships`.`person_id`), MAX(`friendships`.`person_id`) FROM `friendships`"
430
+ end
431
+ end
432
+
433
+ describe "MVA via a HABTM with a ranged source query" do
434
+ before :each do
435
+ @attribute = ThinkingSphinx::Attribute.new(@source,
436
+ [ThinkingSphinx::Index::FauxColumn.new(:links, :id)],
437
+ :as => :link_ids, :source => :ranged_query
438
+ )
439
+ end
440
+
441
+ it "should use a ranged query" do
442
+ @attribute.type_to_config.should == :sql_attr_multi
443
+
444
+ declaration, query, range_query = @attribute.config_value.split('; ')
445
+ declaration.should == "uint link_ids from ranged-query"
446
+ 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"
447
+ range_query.should == "SELECT MIN(`links_people`.`person_id`), MAX(`links_people`.`person_id`) FROM `links_people`"
448
+ end
449
+ end
450
+
451
+ describe "MVA via two has-many associations with a ranged source query" do
452
+ before :each do
453
+ @index = ThinkingSphinx::Index.new(Alpha)
454
+ @source = ThinkingSphinx::Source.new(@index)
455
+ @attribute = ThinkingSphinx::Attribute.new(@source,
456
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
457
+ :as => :gamma_values, :source => :ranged_query
458
+ )
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.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"
467
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
468
+ end
469
+ end
470
+
471
+ describe "MVA via two has-many associations with a ranged source query for a delta source" do
472
+ before :each do
473
+ @index = ThinkingSphinx::Index.new(Alpha)
474
+ @source = ThinkingSphinx::Source.new(@index)
475
+ @attribute = ThinkingSphinx::Attribute.new(@source,
476
+ [ThinkingSphinx::Index::FauxColumn.new(:betas, :gammas, :value)],
477
+ :as => :gamma_values, :source => :ranged_query
478
+ )
479
+
480
+ @index.delta_object = ThinkingSphinx::Deltas::DefaultDelta.new @index, @index.local_options
481
+ end
482
+
483
+ it "should use a ranged query" do
484
+ @attribute.type_to_config.should == :sql_attr_multi
485
+
486
+ declaration, query, range_query = @attribute.config_value(nil, true).split('; ')
487
+ declaration.should == "uint gamma_values from ranged-query"
488
+ 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)"
489
+ range_query.should == "SELECT MIN(`betas`.`alpha_id`), MAX(`betas`.`alpha_id`) FROM `betas`"
490
+ end
491
+ end
492
+
493
+ describe "with custom queries" do
494
+ before :each do
495
+ index = CricketTeam.sphinx_indexes.first
496
+ @statement = index.sources.first.to_riddle_for_core(0, 0).sql_attr_multi.last
497
+ end
498
+
499
+ it "should track the query type accordingly" do
500
+ @statement.should match(/uint tags from query/)
501
+ end
502
+
503
+ it "should include the SQL statement" do
504
+ @statement.should match(/SELECT cricket_team_id, id FROM tags/)
505
+ end
506
+ end
507
+
508
+ describe '#live_value' do
509
+ before :each do
510
+ @attribute = ThinkingSphinx::Attribute.new @source, [
511
+ stub('column', :__stack => [], :__name => "col_name")
512
+ ]
513
+ @instance = stub('model')
514
+ end
515
+
516
+ it "should translate boolean values to integers" do
517
+ @instance.stub!(:col_name => true)
518
+ @attribute.live_value(@instance).should == 1
519
+
520
+ @instance.stub!(:col_name => false)
521
+ @attribute.live_value(@instance).should == 0
522
+ end
523
+
524
+ it "should translate timestamps to integers" do
525
+ now = Time.now
526
+ @instance.stub!(:col_name => now)
527
+ @attribute.live_value(@instance).should == now.to_i
528
+ end
529
+
530
+ it "should translate dates to timestamp integers" do
531
+ today = Date.today
532
+ @instance.stub!(:col_name => today)
533
+ @attribute.live_value(@instance).should == today.to_time.to_i
534
+ end
535
+
536
+ it "should translate nils to 0" do
537
+ @instance.stub!(:col_name => nil)
538
+ @attribute.live_value(@instance).should == 0
539
+ end
540
+
541
+ it "should return integers as integers" do
542
+ @instance.stub!(:col_name => 42)
543
+ @attribute.live_value(@instance).should == 42
544
+ end
545
+
546
+ it "should handle nils in the association chain" do
547
+ @attribute = ThinkingSphinx::Attribute.new @source, [
548
+ stub('column', :__stack => [:assoc_name], :__name => :id)
549
+ ]
550
+ @instance.stub!(:assoc_name => nil)
551
+ @attribute.live_value(@instance).should == 0
552
+ end
553
+
554
+ it "should handle association chains" do
555
+ @attribute = ThinkingSphinx::Attribute.new @source, [
556
+ stub('column', :__stack => [:assoc_name], :__name => :id)
557
+ ]
558
+ @instance.stub!(:assoc_name => stub('object', :id => 42))
559
+ @attribute.live_value(@instance).should == 42
560
+ end
561
+
562
+ it "should translate crc strings to their integer values" do
563
+ @attribute = ThinkingSphinx::Attribute.new @source, [
564
+ stub('column', :__stack => [], :__name => "col_name")
565
+ ], :crc => true, :type => :string
566
+ @instance.stub!(:col_name => 'foo')
567
+ @attribute.live_value(@instance).should == 'foo'.to_crc32
568
+ end
569
+ end
570
+ end