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,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ThinkingSphinx::ActiveRecord::HasManyAssociation' do
4
+ describe "search method" do
5
+ before :each do
6
+ Friendship.stub!(:search => true)
7
+
8
+ @person = Person.find(:first)
9
+ @index = Friendship.sphinx_indexes.first
10
+ end
11
+
12
+ it "should raise an error if the required attribute doesn't exist" do
13
+ @index.stub!(:attributes => [])
14
+
15
+ lambda { @person.friendships.search "test" }.should raise_error(RuntimeError)
16
+ end
17
+
18
+ it "should add a filter for the attribute into a normal search call" do
19
+ Friendship.should_receive(:search) do |query, options|
20
+ options[:with][:person_id].should == @person.id
21
+ end
22
+
23
+ @person.friendships.search "test"
24
+ end
25
+
26
+ it "should define indexes for the reflection class" do
27
+ Friendship.should_receive(:define_indexes)
28
+
29
+ @person.friendships.search 'test'
30
+ end
31
+ end
32
+
33
+ describe "search method for has_many :through" do
34
+ before :each do
35
+ Person.stub!(:search => true)
36
+
37
+ @person = Person.find(:first)
38
+ @index = Person.sphinx_indexes.first
39
+ end
40
+
41
+ it "should raise an error if the required attribute doesn't exist" do
42
+ @index.stub!(:attributes => [])
43
+
44
+ lambda { @person.friends.search "test" }.should raise_error(RuntimeError)
45
+ end
46
+
47
+ it "should add a filter for the attribute into a normal search call" do
48
+ Person.should_receive(:search).with do |query, options|
49
+ options[:with][:friendly_ids].should == @person.id
50
+ end
51
+
52
+ @person.friends.search "test"
53
+ end
54
+ end
55
+
56
+ describe 'filtering sphinx scopes' do
57
+ before :each do
58
+ Friendship.stub!(:search => Friendship)
59
+
60
+ @person = Person.find(:first)
61
+ end
62
+
63
+ it "should add a filter for the attribute in a sphinx scope call" do
64
+ Friendship.should_receive(:search).with do |options|
65
+ options[:with][:person_id].should == @person.id
66
+ end
67
+
68
+ @person.friendships.reverse
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,177 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::ActiveRecord::Scopes do
4
+ after :each do
5
+ Alpha.remove_sphinx_scopes
6
+ end
7
+
8
+ it "should be included into models with indexes" do
9
+ Alpha.included_modules.should include(ThinkingSphinx::ActiveRecord::Scopes)
10
+ end
11
+
12
+ it "should not be included into models without indexes" do
13
+ Gamma.included_modules.should_not include(
14
+ ThinkingSphinx::ActiveRecord::Scopes
15
+ )
16
+ end
17
+
18
+ describe '.sphinx_scope' do
19
+ before :each do
20
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
21
+ end
22
+
23
+ it "should define a method on the model" do
24
+ Alpha.should respond_to(:by_name)
25
+ end
26
+ end
27
+
28
+ describe '.sphinx_scopes' do
29
+ before :each do
30
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
31
+ end
32
+
33
+ it "should return an array of defined scope names as symbols" do
34
+ Alpha.sphinx_scopes.should == [:by_name]
35
+ end
36
+ end
37
+
38
+ describe '.default_sphinx_scope' do
39
+ before :each do
40
+ Alpha.sphinx_scope(:scope_used_as_default_scope) { {:conditions => {:name => 'name'}} }
41
+ Alpha.default_sphinx_scope :scope_used_as_default_scope
42
+ end
43
+
44
+ it "should return an array of defined scope names as symbols" do
45
+ Alpha.sphinx_scopes.should == [:scope_used_as_default_scope]
46
+ end
47
+
48
+ it "should have a default_sphinx_scope" do
49
+ Alpha.has_default_sphinx_scope?.should be_true
50
+ end
51
+ end
52
+
53
+ describe '.remove_sphinx_scopes' do
54
+ before :each do
55
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
56
+ Alpha.remove_sphinx_scopes
57
+ end
58
+
59
+ it "should remove sphinx scope methods" do
60
+ Alpha.should_not respond_to(:by_name)
61
+ end
62
+
63
+ it "should empty the list of sphinx scopes" do
64
+ Alpha.sphinx_scopes.should be_empty
65
+ end
66
+ end
67
+
68
+ describe '.example_default_scope' do
69
+ before :each do
70
+ Alpha.sphinx_scope(:foo_scope){ {:conditions => {:name => 'foo'}} }
71
+ Alpha.default_sphinx_scope :foo_scope
72
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
73
+ Alpha.sphinx_scope(:by_foo) { |foo| {:conditions => {:foo => foo}} }
74
+ end
75
+
76
+ it "should return a ThinkingSphinx::Search object" do
77
+ Alpha.search.should be_a(ThinkingSphinx::Search)
78
+ end
79
+
80
+ it "should apply the default scope options to the underlying search object" do
81
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
82
+ search.search.options[:conditions].should == {:name => 'foo'}
83
+ end
84
+
85
+ it "should apply the default scope options and scope options to the underlying search object" do
86
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
87
+ search.by_foo('foo').search.options[:conditions].should == {:foo => 'foo', :name => 'foo'}
88
+ end
89
+
90
+ # FIXME: Probably the other way around is more logical? How to do this?
91
+ it "should apply the default scope options after other scope options to the underlying search object" do
92
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
93
+ search.by_name('bar').search.options[:conditions].should == {:name => 'foo'}
94
+ end
95
+ end
96
+
97
+ describe '.example_scope' do
98
+ before :each do
99
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
100
+ Alpha.sphinx_scope(:by_foo) { |foo| {:conditions => {:foo => foo}} }
101
+ Alpha.sphinx_scope(:with_betas) { {:classes => [Beta]} }
102
+ end
103
+
104
+ it "should return a ThinkingSphinx::Search object" do
105
+ Alpha.by_name('foo').should be_a(ThinkingSphinx::Search)
106
+ end
107
+
108
+ it "should set the classes option" do
109
+ Alpha.by_name('foo').options[:classes].should == [Alpha]
110
+ end
111
+
112
+ it "should be able to be called on a ThinkingSphinx::Search object" do
113
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
114
+ lambda {
115
+ search.by_name('foo')
116
+ }.should_not raise_error
117
+ end
118
+
119
+ it "should return the search object it gets called upon" do
120
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
121
+ search.by_name('foo').should == search
122
+ end
123
+
124
+ it "should apply the scope options to the underlying search object" do
125
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
126
+ search.by_name('foo').options[:conditions].should == {:name => 'foo'}
127
+ end
128
+
129
+ it "should combine hash option scopes such as :conditions" do
130
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
131
+ search.by_name('foo').by_foo('bar').options[:conditions].
132
+ should == {:name => 'foo', :foo => 'bar'}
133
+ end
134
+
135
+ it "should combine array option scopes such as :classes" do
136
+ search = ThinkingSphinx::Search.new(:classes => [Alpha])
137
+ search.with_betas.options[:classes].should == [Alpha, Beta]
138
+ end
139
+ end
140
+
141
+ describe '.search_count_with_scope' do
142
+ before :each do
143
+ @config = ThinkingSphinx::Configuration.instance
144
+ @client = Riddle::Client.new
145
+
146
+ @config.stub!(:client => @client)
147
+ @client.stub!(:query => {:matches => [], :total_found => 43})
148
+ Alpha.sphinx_scope(:by_name) { |name| {:conditions => {:name => name}} }
149
+ Alpha.sphinx_scope(:ids_only) { {:ids_only => true} }
150
+ end
151
+
152
+ it "should return the total number of results" do
153
+ Alpha.by_name('foo').search_count.should == 43
154
+ end
155
+
156
+ it "should not make any calls to the database" do
157
+ Alpha.should_not_receive(:find)
158
+
159
+ Alpha.by_name('foo').search_count
160
+ end
161
+
162
+ it "should not leave the :ids_only option set and the results populated if it was not set before" do
163
+ stored_scope = Alpha.by_name('foo')
164
+ stored_scope.search_count
165
+ stored_scope.options[:ids_only].should be_false
166
+ stored_scope.populated?.should be_false
167
+ end
168
+
169
+ it "should leave the :ids_only option set and the results populated if it was set before" do
170
+ stored_scope = Alpha.by_name('foo').ids_only
171
+ stored_scope.search_count
172
+ stored_scope.options[:ids_only].should be_true
173
+ stored_scope.populated?.should be_true
174
+ end
175
+ end
176
+
177
+ end
@@ -0,0 +1,618 @@
1
+ require 'spec_helper'
2
+
3
+ describe ThinkingSphinx::ActiveRecord do
4
+ before :each do
5
+ @existing_alpha_indexes = Alpha.sphinx_indexes.clone
6
+ @existing_beta_indexes = Beta.sphinx_indexes.clone
7
+
8
+ Alpha.send :defined_indexes=, false
9
+ Beta.send :defined_indexes=, false
10
+
11
+ Alpha.sphinx_indexes.clear
12
+ Beta.sphinx_indexes.clear
13
+ end
14
+
15
+ after :each do
16
+ Alpha.sphinx_indexes.replace @existing_alpha_indexes
17
+ Beta.sphinx_indexes.replace @existing_beta_indexes
18
+
19
+ Alpha.send :defined_indexes=, true
20
+ Beta.send :defined_indexes=, true
21
+
22
+ Alpha.sphinx_index_blocks.clear
23
+ Beta.sphinx_index_blocks.clear
24
+ end
25
+
26
+ describe '.define_index' do
27
+ it "should do nothing if indexes are disabled" do
28
+ ThinkingSphinx.define_indexes = false
29
+ ThinkingSphinx::Index.should_not_receive(:new)
30
+
31
+ Alpha.define_index { }
32
+ Alpha.define_indexes
33
+
34
+ ThinkingSphinx.define_indexes = true
35
+ end
36
+
37
+ it "should not evaluate the index block automatically" do
38
+ lambda {
39
+ Alpha.define_index { raise StandardError }
40
+ }.should_not raise_error
41
+ end
42
+
43
+ it "should add the model to the context collection" do
44
+ Alpha.define_index { indexes :name }
45
+
46
+ ThinkingSphinx.context.indexed_models.should include("Alpha")
47
+ end
48
+
49
+ it "should die quietly if there is a database error" do
50
+ ThinkingSphinx::Index::Builder.stub(:generate) { raise Mysql::Error }
51
+ Alpha.define_index { indexes :name }
52
+
53
+ lambda {
54
+ Alpha.define_indexes
55
+ }.should_not raise_error
56
+ end
57
+
58
+ it "should die noisily if there is a non-database error" do
59
+ ThinkingSphinx::Index::Builder.stub(:generate) { raise StandardError }
60
+ Alpha.define_index { indexes :name }
61
+
62
+ lambda {
63
+ Alpha.define_indexes
64
+ }.should raise_error
65
+ end
66
+
67
+ it "should set the index's name using the parameter if provided" do
68
+ Alpha.define_index('custom') { indexes :name }
69
+ Alpha.define_indexes
70
+
71
+ Alpha.sphinx_indexes.first.name.should == 'custom'
72
+ end
73
+
74
+ context 'callbacks' do
75
+ it "should add a before_validation callback to define_indexes" do
76
+ Alpha.should_receive(:before_validation).with(:define_indexes)
77
+
78
+ Alpha.define_index { }
79
+ end
80
+
81
+ it "should not add a before_validation callback twice" do
82
+ Alpha.should_receive(:before_validation).with(:define_indexes).once
83
+
84
+ Alpha.define_index { }
85
+ Alpha.define_index { }
86
+ end
87
+
88
+ it "should add a before_destroy callback to define_indexes" do
89
+ Alpha.should_receive(:before_destroy).with(:define_indexes)
90
+
91
+ Alpha.define_index { }
92
+ end
93
+
94
+ it "should not add a before_destroy callback twice" do
95
+ Alpha.should_receive(:before_destroy).with(:define_indexes).once
96
+
97
+ Alpha.define_index { }
98
+ Alpha.define_index { }
99
+ end
100
+
101
+ it "should add a toggle_deleted callback when defined" do
102
+ Alpha.should_receive(:after_destroy).with(:toggle_deleted)
103
+
104
+ Alpha.define_index { indexes :name }
105
+ Alpha.define_indexes
106
+ end
107
+
108
+ it "should not add toggle_deleted callback more than once" do
109
+ Alpha.should_receive(:after_destroy).with(:toggle_deleted).once
110
+
111
+ Alpha.define_index { indexes :name }
112
+ Alpha.define_index { indexes :name }
113
+ Alpha.define_indexes
114
+ end
115
+
116
+ it "should add a update_attribute_values callback when defined" do
117
+ Alpha.should_receive(:after_commit).with(:update_attribute_values)
118
+
119
+ Alpha.define_index { indexes :name }
120
+ Alpha.define_indexes
121
+ end
122
+
123
+ it "should not add update_attribute_values callback more than once" do
124
+ Alpha.should_receive(:after_commit).with(:update_attribute_values).once
125
+
126
+ Alpha.define_index { indexes :name }
127
+ Alpha.define_index { indexes :name }
128
+ Alpha.define_indexes
129
+ end
130
+
131
+ it "should add a toggle_delta callback if deltas are enabled" do
132
+ Beta.should_receive(:before_save).with(:toggle_delta)
133
+
134
+ Beta.define_index {
135
+ indexes :name
136
+ set_property :delta => true
137
+ }
138
+ Beta.define_indexes
139
+ end
140
+
141
+ it "should not add a toggle_delta callback if deltas are disabled" do
142
+ Alpha.should_not_receive(:before_save).with(:toggle_delta)
143
+
144
+ Alpha.define_index { indexes :name }
145
+ Alpha.define_indexes
146
+ end
147
+
148
+ it "should add the toggle_delta callback if deltas are disabled in other indexes" do
149
+ Beta.should_receive(:before_save).with(:toggle_delta).once
150
+
151
+ Beta.define_index { indexes :name }
152
+ Beta.define_index('foo') {
153
+ indexes :name
154
+ set_property :delta => true
155
+ }
156
+ Beta.define_indexes
157
+ end
158
+
159
+ it "should only add the toggle_delta callback once" do
160
+ Beta.should_receive(:before_save).with(:toggle_delta).once
161
+
162
+ Beta.define_index {
163
+ indexes :name
164
+ set_property :delta => true
165
+ }
166
+ Beta.define_index {
167
+ indexes :name
168
+ set_property :delta => true
169
+ }
170
+ Beta.define_indexes
171
+ end
172
+
173
+ it "should add an index_delta callback if deltas are enabled" do
174
+ Beta.stub!(:after_commit => true)
175
+ Beta.should_receive(:after_commit).with(:index_delta)
176
+
177
+ Beta.define_index {
178
+ indexes :name
179
+ set_property :delta => true
180
+ }
181
+ Beta.define_indexes
182
+ end
183
+
184
+ it "should not add an index_delta callback if deltas are disabled" do
185
+ Alpha.should_not_receive(:after_commit).with(:index_delta)
186
+
187
+ Alpha.define_index { indexes :name }
188
+ Alpha.define_indexes
189
+ end
190
+
191
+ it "should add the index_delta callback if deltas are disabled in other indexes" do
192
+ Beta.stub!(:after_commit => true)
193
+ Beta.should_receive(:after_commit).with(:index_delta).once
194
+
195
+ Beta.define_index { indexes :name }
196
+ Beta.define_index('foo') {
197
+ indexes :name
198
+ set_property :delta => true
199
+ }
200
+ Beta.define_indexes
201
+ end
202
+
203
+ it "should only add the index_delta callback once" do
204
+ Beta.stub!(:after_commit => true)
205
+ Beta.should_receive(:after_commit).with(:index_delta).once
206
+
207
+ Beta.define_index {
208
+ indexes :name
209
+ set_property :delta => true
210
+ }
211
+ Beta.define_index {
212
+ indexes :name
213
+ set_property :delta => true
214
+ }
215
+ Beta.define_indexes
216
+ end
217
+ end
218
+ end
219
+
220
+ describe '.define_indexes' do
221
+ it "should process define_index blocks" do
222
+ Beta.define_index { indexes :name }
223
+ Beta.sphinx_indexes.length.should == 0
224
+
225
+ Beta.define_indexes
226
+ Beta.sphinx_indexes.length.should == 1
227
+ end
228
+
229
+ it "should not re-add indexes" do
230
+ Beta.define_index { indexes :name }
231
+ Beta.define_indexes
232
+ Beta.define_indexes
233
+
234
+ Beta.sphinx_indexes.length.should == 1
235
+ end
236
+ end
237
+
238
+ describe "index methods" do
239
+ before(:all) do
240
+ @person = Person.find(:first)
241
+ end
242
+
243
+ describe "in_both_indexes?" do
244
+ it "should return true if in core and delta indexes" do
245
+ @person.should_receive(:in_core_index?).and_return(true)
246
+ @person.should_receive(:in_delta_index?).and_return(true)
247
+ @person.in_both_indexes?.should be_true
248
+ end
249
+
250
+ it "should return false if in one index and not the other" do
251
+ @person.should_receive(:in_core_index?).and_return(true)
252
+ @person.should_receive(:in_delta_index?).and_return(false)
253
+ @person.in_both_indexes?.should be_false
254
+ end
255
+ end
256
+
257
+ describe "in_core_index?" do
258
+ it "should call in_index? with core" do
259
+ @person.should_receive(:in_index?).with('core')
260
+ @person.in_core_index?
261
+ end
262
+ end
263
+
264
+ describe "in_delta_index?" do
265
+ it "should call in_index? with delta" do
266
+ @person.should_receive(:in_index?).with('delta')
267
+ @person.in_delta_index?
268
+ end
269
+ end
270
+
271
+ describe "in_index?" do
272
+ it "should return true if in the specified index" do
273
+ @person.should_receive(:sphinx_document_id).and_return(1)
274
+ @person.should_receive(:sphinx_index_name).and_return('person_core')
275
+ Person.should_receive(:search_for_id).with(1, 'person_core').and_return(true)
276
+
277
+ @person.in_index?('core').should be_true
278
+ end
279
+ end
280
+ end
281
+
282
+ describe '.source_of_sphinx_index' do
283
+ it "should return self if model defines an index" do
284
+ Person.source_of_sphinx_index.should == Person
285
+ end
286
+
287
+ it "should return the parent if model inherits an index" do
288
+ Admin::Person.source_of_sphinx_index.should == Person
289
+ end
290
+ end
291
+
292
+ describe '.to_crc32' do
293
+ it "should return an integer" do
294
+ Person.to_crc32.should be_a_kind_of(Integer)
295
+ end
296
+ end
297
+
298
+ describe '.to_crc32s' do
299
+ it "should return an array" do
300
+ Person.to_crc32s.should be_a_kind_of(Array)
301
+ end
302
+ end
303
+
304
+ describe "toggle_deleted method" do
305
+ before :each do
306
+ ThinkingSphinx.stub!(:sphinx_running? => true)
307
+
308
+ @configuration = ThinkingSphinx::Configuration.instance
309
+ @configuration.stub!(
310
+ :address => "an address",
311
+ :port => 123
312
+ )
313
+ @client = Riddle::Client.new
314
+ @client.stub!(:update => true)
315
+ @person = Person.find(:first)
316
+
317
+ @configuration.stub!(:client => @client)
318
+ Person.sphinx_indexes.each { |index| index.stub!(:delta? => false) }
319
+ Person.stub!(:search_for_id => true)
320
+ end
321
+
322
+ it "should update the core index's deleted flag if in core index" do
323
+ @client.should_receive(:update).with(
324
+ "person_core", ["sphinx_deleted"], {@person.sphinx_document_id => [1]}
325
+ )
326
+
327
+ @person.toggle_deleted
328
+ end
329
+
330
+ it "shouldn't update the core index's deleted flag if the record isn't in it" do
331
+ Person.stub!(:search_for_id => false)
332
+ @client.should_not_receive(:update).with(
333
+ "person_core", ["sphinx_deleted"], {@person.sphinx_document_id => [1]}
334
+ )
335
+
336
+ @person.toggle_deleted
337
+ end
338
+
339
+ it "shouldn't attempt to update the deleted flag if sphinx isn't running" do
340
+ ThinkingSphinx.stub!(:sphinx_running? => false)
341
+ @client.should_not_receive(:update)
342
+ Person.should_not_receive(:search_for_id)
343
+
344
+ @person.toggle_deleted
345
+ end
346
+
347
+ it "should update the delta index's deleted flag if delta indexes are enabled and the instance's delta is true" do
348
+ ThinkingSphinx.deltas_enabled = true
349
+ Person.sphinx_indexes.each { |index| index.stub!(:delta? => true) }
350
+ @person.delta = true
351
+ @client.should_receive(:update).with(
352
+ "person_delta", ["sphinx_deleted"], {@person.sphinx_document_id => [1]}
353
+ )
354
+
355
+ @person.toggle_deleted
356
+ end
357
+
358
+ it "should not update the delta index's deleted flag if delta indexes are enabled and the instance's delta is false" do
359
+ ThinkingSphinx.deltas_enabled = true
360
+ Person.sphinx_indexes.each { |index| index.stub!(:delta? => true) }
361
+ @person.delta = false
362
+ @client.should_not_receive(:update).with(
363
+ "person_delta", ["sphinx_deleted"], {@person.sphinx_document_id => [1]}
364
+ )
365
+
366
+ @person.toggle_deleted
367
+ end
368
+
369
+ it "should not update the delta index's deleted flag if delta indexes are enabled and the instance's delta is equivalent to false" do
370
+ ThinkingSphinx.deltas_enabled = true
371
+ Person.sphinx_indexes.each { |index| index.stub!(:delta? => true) }
372
+ @person.delta = 0
373
+ @client.should_not_receive(:update).with(
374
+ "person_delta", ["sphinx_deleted"], {@person.sphinx_document_id => [1]}
375
+ )
376
+
377
+ @person.toggle_deleted
378
+ end
379
+
380
+ it "shouldn't update the delta index if delta indexes are disabled" do
381
+ ThinkingSphinx.deltas_enabled = true
382
+ @client.should_not_receive(:update).with(
383
+ "person_delta", ["sphinx_deleted"], {@person.sphinx_document_id => [1]}
384
+ )
385
+
386
+ @person.toggle_deleted
387
+ end
388
+
389
+ it "should not update either index if updates are disabled" do
390
+ ThinkingSphinx.updates_enabled = false
391
+ ThinkingSphinx.deltas_enabled = true
392
+ Person.sphinx_indexes.each { |index| index.stub!(:delta? => true) }
393
+ @person.delta = true
394
+ @client.should_not_receive(:update)
395
+
396
+ @person.toggle_deleted
397
+ end
398
+ end
399
+
400
+ describe "sphinx_indexes in the inheritance chain (STI)" do
401
+ it "should hand defined indexes on a class down to its child classes" do
402
+ Child.sphinx_indexes.should include(*Person.sphinx_indexes)
403
+ end
404
+
405
+ it "should allow associations to other STI models" do
406
+ source = Child.sphinx_indexes.last.sources.first
407
+ sql = source.to_riddle_for_core(0, 0).sql_query
408
+ sql.gsub!('$start', '0').gsub!('$end', '100')
409
+ lambda {
410
+ Child.connection.execute(sql)
411
+ }.should_not raise_error(ActiveRecord::StatementInvalid)
412
+ end
413
+ end
414
+
415
+ describe '#sphinx_document_id' do
416
+ before :each do
417
+ Alpha.define_index { indexes :name }
418
+ Beta.define_index { indexes :name }
419
+ end
420
+
421
+ it "should return values with the expected offset" do
422
+ person = Person.find(:first)
423
+ model_count = ThinkingSphinx.context.indexed_models.length
424
+ Person.stub!(:sphinx_offset => 3)
425
+
426
+ (person.id * model_count + 3).should == person.sphinx_document_id
427
+ end
428
+ end
429
+
430
+ describe '#primary_key_for_sphinx' do
431
+ before :each do
432
+ @person = Person.find(:first)
433
+ end
434
+
435
+ after :each do
436
+ Person.set_sphinx_primary_key nil
437
+ end
438
+
439
+ it "should return the id by default" do
440
+ @person.primary_key_for_sphinx.should == @person.id
441
+ end
442
+
443
+ it "should use the sphinx primary key to determine the value" do
444
+ Person.set_sphinx_primary_key :first_name
445
+ @person.primary_key_for_sphinx.should == @person.first_name
446
+ end
447
+
448
+ it "should not use accessor methods but the attributes hash" do
449
+ id = @person.id
450
+ @person.stub!(:id => 'unique_hash')
451
+ @person.primary_key_for_sphinx.should == id
452
+ end
453
+ end
454
+
455
+ describe '.sphinx_index_names' do
456
+ it "should return the core index" do
457
+ Alpha.define_index { indexes :name }
458
+ Alpha.define_indexes
459
+ Alpha.sphinx_index_names.should == ['alpha_core']
460
+ end
461
+
462
+ it "should return the delta index if enabled" do
463
+ Beta.define_index {
464
+ indexes :name
465
+ set_property :delta => true
466
+ }
467
+ Beta.define_indexes
468
+
469
+ Beta.sphinx_index_names.should == ['beta_core', 'beta_delta']
470
+ end
471
+
472
+ it "should return the superclass with an index definition" do
473
+ Parent.sphinx_index_names.should == ['person_core', 'person_delta']
474
+ end
475
+ end
476
+
477
+ describe '.indexed_by_sphinx?' do
478
+ it "should return true if there is at least one index on the model" do
479
+ Alpha.define_index { indexes :name }
480
+ Alpha.define_indexes
481
+
482
+ Alpha.should be_indexed_by_sphinx
483
+ end
484
+
485
+ it "should return false if there are no indexes on the model" do
486
+ Gamma.should_not be_indexed_by_sphinx
487
+ end
488
+ end
489
+
490
+ describe '.delta_indexed_by_sphinx?' do
491
+ it "should return true if there is at least one delta index on the model" do
492
+ Beta.define_index {
493
+ indexes :name
494
+ set_property :delta => true
495
+ }
496
+ Beta.define_indexes
497
+
498
+ Beta.should be_delta_indexed_by_sphinx
499
+ end
500
+
501
+ it "should return false if there are no delta indexes on the model" do
502
+ Alpha.define_index { indexes :name }
503
+ Alpha.define_indexes
504
+
505
+ Alpha.should_not be_delta_indexed_by_sphinx
506
+ end
507
+ end
508
+
509
+ describe '.delete_in_index' do
510
+ before :each do
511
+ @client = stub('client')
512
+ ThinkingSphinx.stub!(:sphinx_running? => true)
513
+ ThinkingSphinx::Configuration.instance.stub!(:client => @client)
514
+ Alpha.stub!(:search_for_id => true)
515
+ end
516
+
517
+ it "should not update if the document isn't in the given index" do
518
+ Alpha.stub!(:search_for_id => false)
519
+ @client.should_not_receive(:update)
520
+
521
+ Alpha.delete_in_index('alpha_core', 42)
522
+ end
523
+
524
+ it "should direct the update to the supplied index" do
525
+ @client.should_receive(:update) do |index, attributes, values|
526
+ index.should == 'custom_index_core'
527
+ end
528
+
529
+ Alpha.delete_in_index('custom_index_core', 42)
530
+ end
531
+
532
+ it "should set the sphinx_deleted flag to true" do
533
+ @client.should_receive(:update) do |index, attributes, values|
534
+ attributes.should == ['sphinx_deleted']
535
+ values.should == {42 => [1]}
536
+ end
537
+
538
+ Alpha.delete_in_index('alpha_core', 42)
539
+ end
540
+ end
541
+
542
+ describe '.core_index_names' do
543
+ it "should return each index's core name" do
544
+ Alpha.define_index('foo') { indexes :name }
545
+ Alpha.define_index('bar') { indexes :name }
546
+ Alpha.define_indexes
547
+
548
+ Alpha.core_index_names.should == ['foo_core', 'bar_core']
549
+ end
550
+ end
551
+
552
+ describe '.delta_index_names' do
553
+ it "should return index delta names, for indexes with deltas enabled" do
554
+ Alpha.define_index('foo') { indexes :name }
555
+ Alpha.define_index('bar') { indexes :name }
556
+ Alpha.define_indexes
557
+ Alpha.sphinx_indexes.first.delta_object = stub('delta')
558
+
559
+ Alpha.delta_index_names.should == ['foo_delta']
560
+ end
561
+ end
562
+
563
+ describe '.sphinx_offset' do
564
+ before :each do
565
+ @context = ThinkingSphinx.context
566
+ end
567
+
568
+ it "should return the index of the model's name in all known indexed models" do
569
+ @context.stub!(:indexed_models => ['Alpha', 'Beta'])
570
+
571
+ Alpha.sphinx_offset.should == 0
572
+ Beta.sphinx_offset.should == 1
573
+ end
574
+
575
+ it "should ignore classes that have indexed superclasses" do
576
+ @context.stub!(:indexed_models => ['Alpha', 'Parent', 'Person'])
577
+
578
+ Person.sphinx_offset.should == 1
579
+ end
580
+
581
+ it "should respect first known indexed parents" do
582
+ @context.stub!(:indexed_models => ['Alpha', 'Parent', 'Person'])
583
+
584
+ Parent.sphinx_offset.should == 1
585
+ end
586
+ end
587
+
588
+ describe '.has_sphinx_indexes?' do
589
+ it "should return true if there are sphinx indexes defined" do
590
+ Alpha.sphinx_indexes.replace [stub('index')]
591
+ Alpha.sphinx_index_blocks.replace []
592
+
593
+ Alpha.should have_sphinx_indexes
594
+ end
595
+
596
+ it "should return true if there are sphinx index blocks defined" do
597
+ Alpha.sphinx_indexes.replace []
598
+ Alpha.sphinx_index_blocks.replace [stub('lambda')]
599
+
600
+ Alpha.should have_sphinx_indexes
601
+ end
602
+
603
+ it "should return false if there are no sphinx indexes or blocks" do
604
+ Alpha.sphinx_indexes.clear
605
+ Alpha.sphinx_index_blocks.clear
606
+
607
+ Alpha.should_not have_sphinx_indexes
608
+ end
609
+ end
610
+
611
+ describe '.reset_subclasses' do
612
+ it "should reset the stored context" do
613
+ ThinkingSphinx.should_receive(:reset_context!)
614
+
615
+ ActiveRecord::Base.reset_subclasses
616
+ end
617
+ end
618
+ end