thinking-sphinx 1.3.6 → 1.3.7

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.6
1
+ 1.3.7
@@ -74,3 +74,9 @@ Feature: Search and browse models by their defined facets
74
74
  Then the Tags facet should have an "Australia" key
75
75
  Then the Tags facet should have an "Melbourne" key
76
76
  Then the Tags facet should have an "Victoria" key
77
+
78
+ Scenario: Requesting MVA facets from source queries
79
+ Given Sphinx is running
80
+ And I am searching on posts
81
+ When I am requesting facet results
82
+ Then the Comment Ids facet should have 9 keys
@@ -5,7 +5,7 @@ Feature: Search and browse across models by their defined facets
5
5
  When I am requesting facet results
6
6
  And I want all possible attributes
7
7
  Then I should have valid facet results
8
- And I should have 10 facets
8
+ And I should have 11 facets
9
9
  And I should have the facet Class
10
10
  And the Class facet should have a "Person" key
11
11
  And I should have the facet Gender
@@ -19,7 +19,7 @@ Feature: Search and browse across models by their defined facets
19
19
  When I am requesting facet results
20
20
  And I want all possible attributes
21
21
  And I don't want classes included
22
- Then I should have 9 facets
22
+ Then I should have 10 facets
23
23
  And I should not have the facet Class
24
24
 
25
25
  Scenario: Requesting facets common to all indexed models
@@ -87,6 +87,10 @@ Then /^the ([\w_\s]+) facet should have an? (\d+\.?\d*) key$/ do |name, key|
87
87
  results[facet_name(name)].keys.should include(key)
88
88
  end
89
89
 
90
+ Then /^the ([\w\s]+) facet should have (\d+) keys$/ do |name, count|
91
+ results[facet_name(name)].keys.length.should == count.to_i
92
+ end
93
+
90
94
  def facet_name(string)
91
95
  string.gsub(/\s/, '').underscore.to_sym
92
96
  end
@@ -12,3 +12,8 @@ Feature: Searching via STI model relationships
12
12
  Given Sphinx is running
13
13
  And I am searching on cats
14
14
  Then I should get as many results as there are cats
15
+
16
+ Scenario: Searching across super and subclass indexes
17
+ Given Sphinx is running
18
+ When I search for "fantastic"
19
+ Then I should get 1 result
@@ -0,0 +1,3 @@
1
+ %w( fantastic ).each do |name|
2
+ Fox.create :name => name
3
+ end
@@ -1,10 +1,10 @@
1
1
  class Comment < ActiveRecord::Base
2
- belongs_to :post
3
- belongs_to :category
4
-
5
2
  define_index do
6
3
  indexes :content
7
4
 
8
5
  has category.name, :facet => true, :as => :category_name, :type => :string
9
6
  end
7
+
8
+ belongs_to :post
9
+ belongs_to :category
10
10
  end
@@ -0,0 +1,5 @@
1
+ class Fox < Animal
2
+ define_index do
3
+ indexes :name
4
+ end
5
+ end
@@ -12,7 +12,8 @@ class Post < ActiveRecord::Base
12
12
  indexes comments.content, :as => :comments
13
13
  indexes authors.name, :as => :authors
14
14
 
15
- has comments(:id), :as => :comment_ids, :source => :ranged_query
15
+ has comments(:id), :as => :comment_ids, :source => :ranged_query,
16
+ :facet => true
16
17
  has category.name, :facet => true, :as => :category_name, :type => :string
17
18
  has 'COUNT(DISTINCT comments.id)', :as => :comments_count, :type => :integer
18
19
  has comments.created_at, :as => :comments_created_at
@@ -10,6 +10,7 @@ require 'thinking_sphinx/active_record'
10
10
  require 'thinking_sphinx/association'
11
11
  require 'thinking_sphinx/attribute'
12
12
  require 'thinking_sphinx/configuration'
13
+ require 'thinking_sphinx/context'
13
14
  require 'thinking_sphinx/excerpter'
14
15
  require 'thinking_sphinx/facet'
15
16
  require 'thinking_sphinx/class_facet'
@@ -60,12 +61,17 @@ module ThinkingSphinx
60
61
  # The collection of indexed models. Keep in mind that Rails lazily loads
61
62
  # its classes, so this may not actually be populated with _all_ the models
62
63
  # that have Sphinx indexes.
63
- def self.indexed_models
64
- Thread.current[:thinking_sphinx_indexed_models] ||= []
64
+ def self.context
65
+ if Thread.current[:thinking_sphinx_context].nil?
66
+ Thread.current[:thinking_sphinx_context] = ThinkingSphinx::Context.new
67
+ Thread.current[:thinking_sphinx_context].prepare
68
+ end
69
+
70
+ Thread.current[:thinking_sphinx_context]
65
71
  end
66
72
 
67
73
  def self.unique_id_expression(offset = nil)
68
- "* #{indexed_models.size} + #{offset || 0}"
74
+ "* #{context.indexed_models.size} + #{offset || 0}"
69
75
  end
70
76
 
71
77
  # Check if index definition is disabled.
@@ -16,6 +16,8 @@ module ThinkingSphinx
16
16
  extend ThinkingSphinx::ActiveRecord::ClassMethods
17
17
 
18
18
  class << self
19
+ attr_accessor :sphinx_index_blocks
20
+
19
21
  def set_sphinx_primary_key(attribute)
20
22
  @sphinx_primary_key_attribute = attribute
21
23
  end
@@ -53,6 +55,14 @@ module ThinkingSphinx
53
55
 
54
56
  private
55
57
 
58
+ def defined_indexes?
59
+ @defined_indexes
60
+ end
61
+
62
+ def defined_indexes=(value)
63
+ @defined_indexes = value
64
+ end
65
+
56
66
  def sphinx_delta?
57
67
  self.sphinx_indexes.any? { |index| index.delta? }
58
68
  end
@@ -118,21 +128,37 @@ module ThinkingSphinx
118
128
  # at ThinkingSphinx::Index::Builder.
119
129
  #
120
130
  def define_index(name = nil, &block)
121
- return unless ThinkingSphinx.define_indexes?
122
-
123
- self.sphinx_indexes ||= []
124
- self.sphinx_facets ||= []
131
+ self.sphinx_index_blocks ||= []
132
+ self.sphinx_indexes ||= []
133
+ self.sphinx_facets ||= []
125
134
 
126
- index = ThinkingSphinx::Index::Builder.generate self, name, &block
135
+ ThinkingSphinx.context.add_indexed_model self
127
136
 
128
- unless ThinkingSphinx.indexed_models.include?(self.name)
129
- ThinkingSphinx.indexed_models << self.name
137
+ if sphinx_index_blocks.empty?
138
+ before_validation :define_indexes
139
+ before_destroy :define_indexes
130
140
  end
131
141
 
132
- add_sphinx_callbacks_and_extend(index.delta?)
133
- self.sphinx_indexes << index
142
+ self.sphinx_index_blocks << lambda {
143
+ index = ThinkingSphinx::Index::Builder.generate self, name, &block
144
+ add_sphinx_callbacks_and_extend(index.delta?)
145
+ add_sphinx_index index
146
+ }
147
+
148
+ include ThinkingSphinx::ActiveRecord::Scopes
149
+ include ThinkingSphinx::SearchMethods
150
+ end
151
+
152
+ def define_indexes
153
+ return if sphinx_index_blocks.nil? ||
154
+ defined_indexes? ||
155
+ !ThinkingSphinx.define_indexes?
156
+
157
+ sphinx_index_blocks.each do |block|
158
+ block.call
159
+ end
134
160
 
135
- index
161
+ self.defined_indexes = true
136
162
 
137
163
  # We want to make sure that if the database doesn't exist, then Thinking
138
164
  # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
@@ -146,6 +172,11 @@ module ThinkingSphinx
146
172
  end
147
173
  end
148
174
 
175
+ def add_sphinx_index(index)
176
+ self.sphinx_indexes << index
177
+ subclasses.each { |klass| klass.add_sphinx_index(index) }
178
+ end
179
+
149
180
  def indexed_by_sphinx?
150
181
  sphinx_indexes && sphinx_indexes.length > 0
151
182
  end
@@ -155,26 +186,31 @@ module ThinkingSphinx
155
186
  end
156
187
 
157
188
  def sphinx_index_names
189
+ define_indexes
158
190
  sphinx_indexes.collect(&:all_names).flatten
159
191
  end
160
192
 
161
193
  def core_index_names
194
+ define_indexes
162
195
  sphinx_indexes.collect(&:core_name)
163
196
  end
164
197
 
165
198
  def delta_index_names
199
+ define_indexes
166
200
  sphinx_indexes.select(&:delta?).collect(&:delta_name)
167
201
  end
168
202
 
169
- def to_riddle(offset)
203
+ def to_riddle
204
+ define_indexes
170
205
  sphinx_database_adapter.setup
171
206
 
172
207
  local_sphinx_indexes.collect { |index|
173
- index.to_riddle(offset)
208
+ index.to_riddle(sphinx_offset)
174
209
  }.flatten
175
210
  end
176
211
 
177
212
  def source_of_sphinx_index
213
+ define_indexes
178
214
  possible_models = self.sphinx_indexes.collect { |index| index.model }
179
215
  return self if possible_models.include?(self)
180
216
 
@@ -195,8 +231,13 @@ module ThinkingSphinx
195
231
  )
196
232
  end
197
233
 
198
- private
234
+ def sphinx_offset
235
+ ThinkingSphinx.context.superclass_indexed_models.
236
+ index eldest_indexed_ancestor
237
+ end
199
238
 
239
+ private
240
+
200
241
  def local_sphinx_indexes
201
242
  sphinx_indexes.select { |index|
202
243
  index.model == self
@@ -206,10 +247,8 @@ module ThinkingSphinx
206
247
  def add_sphinx_callbacks_and_extend(delta = false)
207
248
  unless indexed_by_sphinx?
208
249
  after_destroy :toggle_deleted
209
-
210
- include ThinkingSphinx::SearchMethods
250
+
211
251
  include ThinkingSphinx::ActiveRecord::AttributeUpdates
212
- include ThinkingSphinx::ActiveRecord::Scopes
213
252
  end
214
253
 
215
254
  if delta && !delta_indexed_by_sphinx?
@@ -219,6 +258,12 @@ module ThinkingSphinx
219
258
  after_commit :index_delta
220
259
  end
221
260
  end
261
+
262
+ def eldest_indexed_ancestor
263
+ ancestors.reverse.detect { |ancestor|
264
+ ThinkingSphinx.context.indexed_models.include?(ancestor.name)
265
+ }.name
266
+ end
222
267
  end
223
268
 
224
269
  def in_index?(suffix)
@@ -262,8 +307,8 @@ module ThinkingSphinx
262
307
  end
263
308
 
264
309
  def sphinx_document_id
265
- primary_key_for_sphinx * ThinkingSphinx.indexed_models.size +
266
- ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
310
+ primary_key_for_sphinx * ThinkingSphinx.context.indexed_models.size +
311
+ self.class.sphinx_offset
267
312
  end
268
313
 
269
314
  private
@@ -271,5 +316,9 @@ module ThinkingSphinx
271
316
  def sphinx_index_name(suffix)
272
317
  "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_#{suffix}"
273
318
  end
319
+
320
+ def define_indexes
321
+ self.class.define_indexes
322
+ end
274
323
  end
275
324
  end
@@ -54,8 +54,8 @@ module ThinkingSphinx
54
54
  sql_query_killlist sql_ranged_throttle sql_query_post_index unpack_zlib
55
55
  unpack_mysqlcompress unpack_mysqlcompress_maxsize )
56
56
 
57
- IndexOptions = %w( charset_table charset_type docinfo enable_star
58
- exceptions html_index_attrs html_remove_elements html_strip
57
+ IndexOptions = %w( charset_table charset_type charset_dictpath docinfo
58
+ enable_star exceptions html_index_attrs html_remove_elements html_strip
59
59
  index_exact_words ignore_chars inplace_docinfo_gap inplace_enable
60
60
  inplace_hit_gap inplace_reloc_factor inplace_write_factor min_infix_len
61
61
  min_prefix_len min_stemming_len min_word_len mlock morphology ngram_chars
@@ -142,13 +142,14 @@ module ThinkingSphinx
142
142
  # indexer and searchd configuration, and sources and indexes details.
143
143
  #
144
144
  def build(file_path=nil)
145
- load_models
146
145
  file_path ||= "#{self.config_file}"
147
146
 
148
147
  @configuration.indexes.clear
149
148
 
150
- ThinkingSphinx.indexed_models.each_with_index do |model, model_index|
151
- @configuration.indexes.concat model.constantize.to_riddle(model_index)
149
+ ThinkingSphinx.context.indexed_models.each do |model|
150
+ model = model.constantize
151
+ model.define_indexes
152
+ @configuration.indexes.concat model.to_riddle
152
153
  end
153
154
 
154
155
  open(file_path, "w") do |file|
@@ -156,37 +157,6 @@ module ThinkingSphinx
156
157
  end
157
158
  end
158
159
 
159
- # Make sure all models are loaded - without reloading any that
160
- # ActiveRecord::Base is already aware of (otherwise we start to hit some
161
- # messy dependencies issues).
162
- #
163
- def load_models
164
- return if defined?(Rails) &&
165
- Rails::VERSION::STRING.to_f > 2.1 &&
166
- Rails.configuration.cache_classes
167
-
168
- self.model_directories.each do |base|
169
- Dir["#{base}**/*.rb"].each do |file|
170
- model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
171
-
172
- next if model_name.nil?
173
- next if ::ActiveRecord::Base.send(:subclasses).detect { |model|
174
- model.name == model_name
175
- }
176
-
177
- begin
178
- model_name.camelize.constantize
179
- rescue LoadError
180
- model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
181
- rescue NameError
182
- next
183
- rescue StandardError
184
- puts "Warning: Error loading #{file}"
185
- end
186
- end
187
- end
188
- end
189
-
190
160
  def address
191
161
  @configuration.searchd.address
192
162
  end
@@ -235,7 +205,7 @@ module ThinkingSphinx
235
205
 
236
206
  def models_by_crc
237
207
  @models_by_crc ||= begin
238
- ThinkingSphinx.indexed_models.inject({}) do |hash, model|
208
+ ThinkingSphinx.context.indexed_models.inject({}) do |hash, model|
239
209
  hash[model.constantize.to_crc32] = model
240
210
  Object.subclasses_of(model.constantize).each { |subclass|
241
211
  hash[subclass.to_crc32] = subclass.name
@@ -0,0 +1,65 @@
1
+ class ThinkingSphinx::Context
2
+ attr_reader :indexed_models
3
+
4
+ def initialize
5
+ @indexed_models = []
6
+ end
7
+
8
+ def prepare
9
+ load_models
10
+ end
11
+
12
+ def define_indexes
13
+ indexed_models.each { |model|
14
+ model.constantize.define_indexes
15
+ }
16
+ end
17
+
18
+ def add_indexed_model(model)
19
+ model = model.name if model.is_a?(Class)
20
+
21
+ indexed_models << model
22
+ indexed_models.uniq!
23
+ indexed_models.sort!
24
+ end
25
+
26
+ def superclass_indexed_models
27
+ klasses = indexed_models.collect { |name| name.constantize }
28
+ klasses.reject { |klass|
29
+ klass.superclass.ancestors.any? { |ancestor| klasses.include?(ancestor) }
30
+ }.collect { |klass| klass.name }
31
+ end
32
+
33
+ private
34
+
35
+ # Make sure all models are loaded - without reloading any that
36
+ # ActiveRecord::Base is already aware of (otherwise we start to hit some
37
+ # messy dependencies issues).
38
+ #
39
+ def load_models
40
+ return if defined?(Rails) &&
41
+ Rails::VERSION::STRING.to_f > 2.1 &&
42
+ Rails.configuration.cache_classes
43
+
44
+ ThinkingSphinx::Configuration.instance.model_directories.each do |base|
45
+ Dir["#{base}**/*.rb"].each do |file|
46
+ model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
47
+
48
+ next if model_name.nil?
49
+ next if ::ActiveRecord::Base.send(:subclasses).detect { |model|
50
+ model.name == model_name
51
+ }
52
+
53
+ begin
54
+ model_name.camelize.constantize
55
+ rescue LoadError
56
+ model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
57
+ rescue NameError
58
+ next
59
+ rescue StandardError
60
+ STDERR.puts "Warning: Error loading #{file}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -3,6 +3,8 @@ module ThinkingSphinx
3
3
  attr_accessor :args, :options
4
4
 
5
5
  def initialize(*args)
6
+ ThinkingSphinx.context.define_indexes
7
+
6
8
  @options = args.extract_options!
7
9
  @args = args
8
10
 
@@ -64,7 +66,7 @@ module ThinkingSphinx
64
66
 
65
67
  def facet_classes
66
68
  (
67
- options[:classes] || ThinkingSphinx.indexed_models.collect { |model|
69
+ options[:classes] || ThinkingSphinx.context.indexed_models.collect { |model|
68
70
  model.constantize
69
71
  }
70
72
  ).select { |klass| klass.sphinx_facets.any? }
@@ -58,6 +58,8 @@ module ThinkingSphinx
58
58
  end
59
59
 
60
60
  def initialize(*args)
61
+ ThinkingSphinx.context.define_indexes
62
+
61
63
  @array = []
62
64
  @options = args.extract_options!
63
65
  @args = args
@@ -145,7 +145,7 @@ module ThinkingSphinx
145
145
  end
146
146
 
147
147
  def utf8?
148
- @index.options[:charset_type] == "utf-8"
148
+ @index.options[:charset_type] =~ /utf-8|zh_cn.utf-8/
149
149
  end
150
150
  end
151
151
  end
@@ -55,7 +55,7 @@ GROUP BY #{ sql_group_clause }
55
55
  #
56
56
  def to_sql_query_info(offset)
57
57
  "SELECT * FROM #{@model.quoted_table_name} WHERE " +
58
- "#{quote_column(@model.primary_key_for_sphinx)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
58
+ "#{quote_column(@model.primary_key_for_sphinx)} = (($id - #{offset}) / #{ThinkingSphinx.context.indexed_models.size})"
59
59
  end
60
60
 
61
61
  def sql_select_clause(offset)
data/rails/init.rb CHANGED
@@ -13,6 +13,4 @@ ActionController::Dispatcher.to_prepare :thinking_sphinx do
13
13
  elsif Rails::VERSION::STRING.to_f > 2.1
14
14
  I18n.backend.load_translations(*I18n.load_path)
15
15
  end
16
-
17
- ThinkingSphinx::Configuration.instance.load_models
18
16
  end
@@ -5,6 +5,9 @@ describe ThinkingSphinx::ActiveRecord do
5
5
  @existing_alpha_indexes = Alpha.sphinx_indexes.clone
6
6
  @existing_beta_indexes = Beta.sphinx_indexes.clone
7
7
 
8
+ Alpha.send :defined_indexes=, false
9
+ Beta.send :defined_indexes=, false
10
+
8
11
  Alpha.sphinx_indexes.clear
9
12
  Beta.sphinx_indexes.clear
10
13
  end
@@ -12,6 +15,9 @@ describe ThinkingSphinx::ActiveRecord do
12
15
  after :each do
13
16
  Alpha.sphinx_indexes.replace @existing_alpha_indexes
14
17
  Beta.sphinx_indexes.replace @existing_beta_indexes
18
+
19
+ Alpha.sphinx_index_blocks.clear
20
+ Beta.sphinx_index_blocks.clear
15
21
  end
16
22
 
17
23
  describe '.define_index' do
@@ -20,62 +26,80 @@ describe ThinkingSphinx::ActiveRecord do
20
26
  ThinkingSphinx::Index.should_not_receive(:new)
21
27
 
22
28
  Alpha.define_index { }
29
+ Alpha.define_indexes
23
30
 
24
31
  ThinkingSphinx.define_indexes = true
25
32
  end
26
33
 
27
- it "should add a new index to the model" do
28
- index = Alpha.define_index { indexes :name }
29
-
30
- Alpha.sphinx_indexes.should include(index)
31
- end
32
-
33
- it "should add to ThinkingSphinx.indexed_models if the model doesn't already exist in the array" do
34
- Alpha.define_index { indexes :name }
35
-
36
- ThinkingSphinx.indexed_models.should include("Alpha")
34
+ it "should not evaluate the index block automatically" do
35
+ lambda {
36
+ Alpha.define_index { raise StandardError }
37
+ }.should_not raise_error
37
38
  end
38
39
 
39
- it "shouldn't add to ThinkingSphinx.indexed_models if the model already exists in the array" do
40
- Alpha.define_index { indexes :name }
40
+ it "should add the model to the context collection" do
41
41
  Alpha.define_index { indexes :name }
42
42
 
43
- ThinkingSphinx.indexed_models.select { |model|
44
- model == "Alpha"
45
- }.length.should == 1
46
- end
47
-
48
- it "should return the new index" do
49
- Alpha.define_index.should be_a(ThinkingSphinx::Index)
43
+ ThinkingSphinx.context.indexed_models.should include("Alpha")
50
44
  end
51
45
 
52
46
  it "should die quietly if there is a database error" do
53
47
  ThinkingSphinx::Index::Builder.stub(:generate) { raise Mysql::Error }
48
+ Alpha.define_index { indexes :name }
54
49
 
55
50
  lambda {
56
- Alpha.define_index { indexes :name }
51
+ Alpha.define_indexes
57
52
  }.should_not raise_error
58
53
  end
59
54
 
60
55
  it "should die noisily if there is a non-database error" do
61
56
  ThinkingSphinx::Index::Builder.stub(:generate) { raise StandardError }
57
+ Alpha.define_index { indexes :name }
62
58
 
63
59
  lambda {
64
- Alpha.define_index { indexes :name }
60
+ Alpha.define_indexes
65
61
  }.should raise_error
66
62
  end
67
63
 
68
64
  it "should set the index's name using the parameter if provided" do
69
- index = Alpha.define_index('custom') { indexes :name }
65
+ Alpha.define_index('custom') { indexes :name }
66
+ Alpha.define_indexes
70
67
 
71
- index.name.should == 'custom'
68
+ Alpha.sphinx_indexes.first.name.should == 'custom'
72
69
  end
73
70
 
74
71
  context 'callbacks' do
75
- it "should add a toggle_deleted callback" do
72
+ it "should add a before_validation callback to define_indexes" do
73
+ Alpha.should_receive(:before_validation).with(:define_indexes)
74
+
75
+ Alpha.define_index { }
76
+ end
77
+
78
+ it "should not add a before_validation callback twice" do
79
+ Alpha.should_receive(:before_validation).with(:define_indexes).once
80
+
81
+ Alpha.define_index { }
82
+ Alpha.define_index { }
83
+ end
84
+
85
+ it "should add a before_destroy callback to define_indexes" do
86
+ Alpha.should_receive(:before_destroy).with(:define_indexes)
87
+
88
+ Alpha.define_index { }
89
+ end
90
+
91
+ it "should not add a before_destroy callback twice" do
92
+ Alpha.should_receive(:before_destroy).with(:define_indexes).once
93
+
94
+ Alpha.define_index { }
95
+ Alpha.define_index { }
96
+ end
97
+
98
+ it "should add a toggle_deleted callback when defined" do
76
99
  Alpha.should_receive(:after_destroy).with(:toggle_deleted)
77
100
 
78
101
  Alpha.define_index { indexes :name }
102
+ Alpha.define_indexes
79
103
  end
80
104
 
81
105
  it "should not add toggle_deleted callback more than once" do
@@ -83,12 +107,14 @@ describe ThinkingSphinx::ActiveRecord do
83
107
 
84
108
  Alpha.define_index { indexes :name }
85
109
  Alpha.define_index { indexes :name }
110
+ Alpha.define_indexes
86
111
  end
87
112
 
88
- it "should add a update_attribute_values callback" do
113
+ it "should add a update_attribute_values callback when defined" do
89
114
  Alpha.should_receive(:after_commit).with(:update_attribute_values)
90
115
 
91
116
  Alpha.define_index { indexes :name }
117
+ Alpha.define_indexes
92
118
  end
93
119
 
94
120
  it "should not add update_attribute_values callback more than once" do
@@ -96,6 +122,7 @@ describe ThinkingSphinx::ActiveRecord do
96
122
 
97
123
  Alpha.define_index { indexes :name }
98
124
  Alpha.define_index { indexes :name }
125
+ Alpha.define_indexes
99
126
  end
100
127
 
101
128
  it "should add a toggle_delta callback if deltas are enabled" do
@@ -105,12 +132,14 @@ describe ThinkingSphinx::ActiveRecord do
105
132
  indexes :name
106
133
  set_property :delta => true
107
134
  }
135
+ Beta.define_indexes
108
136
  end
109
137
 
110
138
  it "should not add a toggle_delta callback if deltas are disabled" do
111
139
  Alpha.should_not_receive(:before_save).with(:toggle_delta)
112
140
 
113
141
  Alpha.define_index { indexes :name }
142
+ Alpha.define_indexes
114
143
  end
115
144
 
116
145
  it "should add the toggle_delta callback if deltas are disabled in other indexes" do
@@ -121,6 +150,7 @@ describe ThinkingSphinx::ActiveRecord do
121
150
  indexes :name
122
151
  set_property :delta => true
123
152
  }
153
+ Beta.define_indexes
124
154
  end
125
155
 
126
156
  it "should only add the toggle_delta callback once" do
@@ -134,6 +164,7 @@ describe ThinkingSphinx::ActiveRecord do
134
164
  indexes :name
135
165
  set_property :delta => true
136
166
  }
167
+ Beta.define_indexes
137
168
  end
138
169
 
139
170
  it "should add an index_delta callback if deltas are enabled" do
@@ -144,12 +175,14 @@ describe ThinkingSphinx::ActiveRecord do
144
175
  indexes :name
145
176
  set_property :delta => true
146
177
  }
178
+ Beta.define_indexes
147
179
  end
148
180
 
149
181
  it "should not add an index_delta callback if deltas are disabled" do
150
182
  Alpha.should_not_receive(:after_commit).with(:index_delta)
151
183
 
152
184
  Alpha.define_index { indexes :name }
185
+ Alpha.define_indexes
153
186
  end
154
187
 
155
188
  it "should add the index_delta callback if deltas are disabled in other indexes" do
@@ -161,6 +194,7 @@ describe ThinkingSphinx::ActiveRecord do
161
194
  indexes :name
162
195
  set_property :delta => true
163
196
  }
197
+ Beta.define_indexes
164
198
  end
165
199
 
166
200
  it "should only add the index_delta callback once" do
@@ -175,10 +209,29 @@ describe ThinkingSphinx::ActiveRecord do
175
209
  indexes :name
176
210
  set_property :delta => true
177
211
  }
212
+ Beta.define_indexes
178
213
  end
179
214
  end
180
215
  end
181
-
216
+
217
+ describe '.define_indexes' do
218
+ it "should process define_index blocks" do
219
+ Beta.define_index { indexes :name }
220
+ Beta.sphinx_indexes.length.should == 0
221
+
222
+ Beta.define_indexes
223
+ Beta.sphinx_indexes.length.should == 1
224
+ end
225
+
226
+ it "should not re-add indexes" do
227
+ Beta.define_index { indexes :name }
228
+ Beta.define_indexes
229
+ Beta.define_indexes
230
+
231
+ Beta.sphinx_indexes.length.should == 1
232
+ end
233
+ end
234
+
182
235
  describe "index methods" do
183
236
  before(:all) do
184
237
  @person = Person.find(:first)
@@ -364,20 +417,10 @@ describe ThinkingSphinx::ActiveRecord do
364
417
 
365
418
  it "should return values with the expected offset" do
366
419
  person = Person.find(:first)
367
- model_count = ThinkingSphinx.indexed_models.length
368
- offset = ThinkingSphinx.indexed_models.index("Person")
420
+ model_count = ThinkingSphinx.context.indexed_models.length
421
+ Person.stub!(:sphinx_offset => 3)
369
422
 
370
- (person.id * model_count + offset).should == person.sphinx_document_id
371
-
372
- alpha = Alpha.find(:first)
373
- offset = ThinkingSphinx.indexed_models.index("Alpha")
374
-
375
- (alpha.id * model_count + offset).should == alpha.sphinx_document_id
376
-
377
- beta = Beta.find(:first)
378
- offset = ThinkingSphinx.indexed_models.index("Beta")
379
-
380
- (beta.id * model_count + offset).should == beta.sphinx_document_id
423
+ (person.id * model_count + 3).should == person.sphinx_document_id
381
424
  end
382
425
  end
383
426
 
@@ -409,6 +452,7 @@ describe ThinkingSphinx::ActiveRecord do
409
452
  describe '.sphinx_index_names' do
410
453
  it "should return the core index" do
411
454
  Alpha.define_index { indexes :name }
455
+ Alpha.define_indexes
412
456
  Alpha.sphinx_index_names.should == ['alpha_core']
413
457
  end
414
458
 
@@ -417,6 +461,7 @@ describe ThinkingSphinx::ActiveRecord do
417
461
  indexes :name
418
462
  set_property :delta => true
419
463
  }
464
+ Beta.define_indexes
420
465
 
421
466
  Beta.sphinx_index_names.should == ['beta_core', 'beta_delta']
422
467
  end
@@ -429,6 +474,7 @@ describe ThinkingSphinx::ActiveRecord do
429
474
  describe '.indexed_by_sphinx?' do
430
475
  it "should return true if there is at least one index on the model" do
431
476
  Alpha.define_index { indexes :name }
477
+ Alpha.define_indexes
432
478
 
433
479
  Alpha.should be_indexed_by_sphinx
434
480
  end
@@ -444,12 +490,14 @@ describe ThinkingSphinx::ActiveRecord do
444
490
  indexes :name
445
491
  set_property :delta => true
446
492
  }
493
+ Beta.define_indexes
447
494
 
448
495
  Beta.should be_delta_indexed_by_sphinx
449
496
  end
450
497
 
451
498
  it "should return false if there are no delta indexes on the model" do
452
499
  Alpha.define_index { indexes :name }
500
+ Alpha.define_indexes
453
501
 
454
502
  Alpha.should_not be_delta_indexed_by_sphinx
455
503
  end
@@ -490,10 +538,11 @@ describe ThinkingSphinx::ActiveRecord do
490
538
 
491
539
  describe '.core_index_names' do
492
540
  it "should return each index's core name" do
493
- foo = Alpha.define_index { indexes :name }
494
- foo.name = 'foo'
495
- bar = Alpha.define_index { indexes :name }
496
- bar.name = 'bar'
541
+ Alpha.define_index { indexes :name }
542
+ Alpha.define_index { indexes :name }
543
+ Alpha.define_indexes
544
+ Alpha.sphinx_indexes.first.name = 'foo'
545
+ Alpha.sphinx_indexes.last.name = 'bar'
497
546
 
498
547
  Alpha.core_index_names.should == ['foo_core', 'bar_core']
499
548
  end
@@ -501,13 +550,39 @@ describe ThinkingSphinx::ActiveRecord do
501
550
 
502
551
  describe '.delta_index_names' do
503
552
  it "should return index delta names, for indexes with deltas enabled" do
504
- foo = Alpha.define_index { indexes :name }
505
- foo.name = 'foo'
506
- foo.delta_object = stub('delta')
507
- bar = Alpha.define_index { indexes :name }
508
- bar.name = 'bar'
553
+ Alpha.define_index { indexes :name }
554
+ Alpha.define_index { indexes :name }
555
+ Alpha.define_indexes
556
+ Alpha.sphinx_indexes.first.name = 'foo'
557
+ Alpha.sphinx_indexes.first.delta_object = stub('delta')
558
+ Alpha.sphinx_indexes.last.name = 'bar'
509
559
 
510
560
  Alpha.delta_index_names.should == ['foo_delta']
511
561
  end
512
562
  end
563
+
564
+ describe '.sphinx_offset' do
565
+ before :each do
566
+ @context = ThinkingSphinx.context
567
+ end
568
+
569
+ it "should return the index of the model's name in all known indexed models" do
570
+ @context.stub!(:indexed_models => ['Alpha', 'Beta'])
571
+
572
+ Alpha.sphinx_offset.should == 0
573
+ Beta.sphinx_offset.should == 1
574
+ end
575
+
576
+ it "should ignore classes that have indexed superclasses" do
577
+ @context.stub!(:indexed_models => ['Alpha', 'Parent', 'Person'])
578
+
579
+ Person.sphinx_offset.should == 1
580
+ end
581
+
582
+ it "should respect first known indexed parents" do
583
+ @context.stub!(:indexed_models => ['Alpha', 'Parent', 'Person'])
584
+
585
+ Parent.sphinx_offset.should == 1
586
+ end
587
+ end
513
588
  end
@@ -130,62 +130,6 @@ describe ThinkingSphinx::Configuration do
130
130
  end
131
131
  end
132
132
 
133
- describe "#load_models" do
134
- before :each do
135
- @config = ThinkingSphinx::Configuration.instance
136
- @config.model_directories = ['']
137
-
138
- @file_name = 'a.rb'
139
- @model_name_lower = 'a'
140
- @class_name = 'A'
141
-
142
- @file_name.stub!(:gsub).and_return(@model_name_lower)
143
- @model_name_lower.stub!(:camelize).and_return(@class_name)
144
- Dir.stub(:[]).and_return([@file_name])
145
- end
146
-
147
- it "should load the files by guessing the file name" do
148
- @class_name.should_receive(:constantize).and_return(true)
149
-
150
- @config.load_models
151
- end
152
-
153
- it "should not raise errors if the model name is nil" do
154
- @file_name.stub!(:gsub).and_return(nil)
155
-
156
- lambda {
157
- @config.load_models
158
- }.should_not raise_error
159
- end
160
-
161
- it "should not raise errors if the file name does not represent a class name" do
162
- @class_name.should_receive(:constantize).and_raise(NameError)
163
-
164
- lambda {
165
- @config.load_models
166
- }.should_not raise_error
167
- end
168
-
169
- it "should retry if the first pass fails and contains a directory" do
170
- @model_name_lower.stub!(:gsub!).and_return(true, nil)
171
- @class_name.stub(:constantize).and_raise(LoadError)
172
- @model_name_lower.should_receive(:camelize).twice
173
-
174
- lambda {
175
- @config.load_models
176
- }.should_not raise_error
177
- end
178
-
179
- it "should catch database errors with a warning" do
180
- @class_name.should_receive(:constantize).and_raise(Mysql::Error)
181
- @config.should_receive(:puts).with('Warning: Error loading a.rb')
182
-
183
- lambda {
184
- @config.load_models
185
- }.should_not raise_error
186
- end
187
- end
188
-
189
133
  it "should insert set index options into the configuration file" do
190
134
  config = ThinkingSphinx::Configuration.instance
191
135
 
@@ -0,0 +1,119 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Context do
4
+ before :each do
5
+ @context = ThinkingSphinx::Context.new
6
+ end
7
+
8
+ describe '#prepare' do
9
+ before :each do
10
+ @config = ThinkingSphinx::Configuration.instance
11
+ @config.model_directories = ['']
12
+
13
+ @file_name = 'a.rb'
14
+ @model_name_lower = 'a'
15
+ @class_name = 'A'
16
+
17
+ @file_name.stub!(:gsub).and_return(@model_name_lower)
18
+ @model_name_lower.stub!(:camelize).and_return(@class_name)
19
+ Dir.stub(:[]).and_return([@file_name])
20
+ end
21
+
22
+ it "should load the files by guessing the file name" do
23
+ @class_name.should_receive(:constantize).and_return(true)
24
+
25
+ @context.prepare
26
+ end
27
+
28
+ it "should not raise errors if the model name is nil" do
29
+ @file_name.stub!(:gsub).and_return(nil)
30
+
31
+ lambda {
32
+ @context.prepare
33
+ }.should_not raise_error
34
+ end
35
+
36
+ it "should not raise errors if the file name does not represent a class name" do
37
+ @class_name.should_receive(:constantize).and_raise(NameError)
38
+
39
+ lambda {
40
+ @context.prepare
41
+ }.should_not raise_error
42
+ end
43
+
44
+ it "should retry if the first pass fails and contains a directory" do
45
+ @model_name_lower.stub!(:gsub!).and_return(true, nil)
46
+ @class_name.stub(:constantize).and_raise(LoadError)
47
+ @model_name_lower.should_receive(:camelize).twice
48
+
49
+ lambda {
50
+ @context.prepare
51
+ }.should_not raise_error
52
+ end
53
+
54
+ it "should catch database errors with a warning" do
55
+ @class_name.should_receive(:constantize).and_raise(Mysql::Error)
56
+ STDERR.should_receive(:puts).with('Warning: Error loading a.rb')
57
+
58
+ lambda {
59
+ @context.prepare
60
+ }.should_not raise_error
61
+ end
62
+ end
63
+
64
+ describe '#define_indexes' do
65
+ it "should call define_indexes on all known indexed models" do
66
+ @context.stub!(:indexed_models => ['Alpha', 'Beta'])
67
+ Alpha.should_receive(:define_indexes)
68
+ Beta.should_receive(:define_indexes)
69
+
70
+ @context.define_indexes
71
+ end
72
+ end
73
+
74
+ describe '#add_indexed_model' do
75
+ before :each do
76
+ @context.indexed_models.clear
77
+ end
78
+
79
+ it "should add the model to the collection" do
80
+ @context.add_indexed_model 'Alpha'
81
+
82
+ @context.indexed_models.should == ['Alpha']
83
+ end
84
+
85
+ it "should not duplicate models in the collection" do
86
+ @context.add_indexed_model 'Alpha'
87
+ @context.add_indexed_model 'Alpha'
88
+
89
+ @context.indexed_models.should == ['Alpha']
90
+ end
91
+
92
+ it "should keep the collection in alphabetical order" do
93
+ @context.add_indexed_model 'Beta'
94
+ @context.add_indexed_model 'Alpha'
95
+
96
+ @context.indexed_models.should == ['Alpha', 'Beta']
97
+ end
98
+
99
+ it "should translate classes to their names" do
100
+ @context.add_indexed_model Alpha
101
+
102
+ @context.indexed_models.should == ['Alpha']
103
+ end
104
+ end
105
+
106
+ describe '#superclass_indexed_models' do
107
+ it "should return indexed model names" do
108
+ @context.stub!(:indexed_models => ['Alpha', 'Beta'])
109
+
110
+ @context.superclass_indexed_models.should == ['Alpha', 'Beta']
111
+ end
112
+
113
+ it "should not include classes which have indexed superclasses" do
114
+ @context.stub!(:indexed_models => ['Parent', 'Person'])
115
+
116
+ @context.superclass_indexed_models.should == ['Person']
117
+ end
118
+ end
119
+ end
@@ -161,7 +161,7 @@ describe ThinkingSphinx::Source do
161
161
  end
162
162
 
163
163
  it "should filter the primary key with the offset" do
164
- model_count = ThinkingSphinx.indexed_models.size
164
+ model_count = ThinkingSphinx.context.indexed_models.size
165
165
  @query.should match(/WHERE `id` = \(\(\$id - 1\) \/ #{model_count}\)/)
166
166
  end
167
167
  end
@@ -1,6 +1,17 @@
1
1
  require 'spec/spec_helper'
2
2
 
3
3
  describe ThinkingSphinx do
4
+ describe '.context' do
5
+ it "should return a Context instance" do
6
+ ThinkingSphinx.context.should be_a(ThinkingSphinx::Context)
7
+ end
8
+
9
+ it "should remember changes to the Context instance" do
10
+ ThinkingSphinx.context.indexed_models.replace([:model])
11
+ ThinkingSphinx.context.indexed_models.should == [:model]
12
+ end
13
+ end
14
+
4
15
  describe '.define_indexes?' do
5
16
  it "should define indexes by default" do
6
17
  ThinkingSphinx.define_indexes?.should be_true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thinking-sphinx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.6
4
+ version: 1.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-26 00:00:00 +11:00
12
+ date: 2009-11-29 00:00:00 +11:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -72,6 +72,7 @@ files:
72
72
  - lib/thinking_sphinx/attribute.rb
73
73
  - lib/thinking_sphinx/class_facet.rb
74
74
  - lib/thinking_sphinx/configuration.rb
75
+ - lib/thinking_sphinx/context.rb
75
76
  - lib/thinking_sphinx/core/array.rb
76
77
  - lib/thinking_sphinx/core/string.rb
77
78
  - lib/thinking_sphinx/deltas.rb
@@ -169,6 +170,7 @@ test_files:
169
170
  - features/support/db/fixtures/developers.rb
170
171
  - features/support/db/fixtures/dogs.rb
171
172
  - features/support/db/fixtures/extensible_betas.rb
173
+ - features/support/db/fixtures/foxes.rb
172
174
  - features/support/db/fixtures/gammas.rb
173
175
  - features/support/db/fixtures/people.rb
174
176
  - features/support/db/fixtures/posts.rb
@@ -203,6 +205,7 @@ test_files:
203
205
  - features/support/models/developer.rb
204
206
  - features/support/models/dog.rb
205
207
  - features/support/models/extensible_beta.rb
208
+ - features/support/models/fox.rb
206
209
  - features/support/models/gamma.rb
207
210
  - features/support/models/person.rb
208
211
  - features/support/models/post.rb
@@ -216,6 +219,7 @@ test_files:
216
219
  - spec/thinking_sphinx/association_spec.rb
217
220
  - spec/thinking_sphinx/attribute_spec.rb
218
221
  - spec/thinking_sphinx/configuration_spec.rb
222
+ - spec/thinking_sphinx/context_spec.rb
219
223
  - spec/thinking_sphinx/core/array_spec.rb
220
224
  - spec/thinking_sphinx/core/string_spec.rb
221
225
  - spec/thinking_sphinx/excerpter_spec.rb