angelf-thinking-sphinx 1.3.18

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 (159) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +170 -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 +90 -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 +127 -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 +47 -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 +380 -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 +146 -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 +164 -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 +55 -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. data/tasks/testing.rb +76 -0
  159. metadata +342 -0
@@ -0,0 +1,239 @@
1
+ require '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_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