thinking-sphinx 2.0.4 → 2.0.5

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 (36) hide show
  1. data/README.textile +2 -0
  2. data/VERSION +1 -1
  3. data/features/searching_by_model.feature +13 -0
  4. data/features/support/env.rb +2 -1
  5. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  6. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  7. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  8. data/features/thinking_sphinx/db/fixtures/posts.rb +5 -1
  9. data/features/thinking_sphinx/db/migrations/create_posts.rb +1 -0
  10. data/features/thinking_sphinx/models/developer.rb +1 -0
  11. data/features/thinking_sphinx/models/music.rb +3 -1
  12. data/features/thinking_sphinx/models/post.rb +1 -0
  13. data/lib/thinking_sphinx.rb +2 -2
  14. data/lib/thinking_sphinx/active_record.rb +32 -2
  15. data/lib/thinking_sphinx/active_record/delta.rb +0 -27
  16. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +4 -0
  17. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +4 -0
  18. data/lib/thinking_sphinx/association.rb +55 -5
  19. data/lib/thinking_sphinx/attribute.rb +17 -10
  20. data/lib/thinking_sphinx/auto_version.rb +1 -1
  21. data/lib/thinking_sphinx/class_facet.rb +7 -3
  22. data/lib/thinking_sphinx/configuration.rb +3 -3
  23. data/lib/thinking_sphinx/facet.rb +1 -0
  24. data/lib/thinking_sphinx/facet_search.rb +5 -1
  25. data/lib/thinking_sphinx/field.rb +16 -0
  26. data/lib/thinking_sphinx/search.rb +21 -5
  27. data/lib/thinking_sphinx/sinatra.rb +7 -0
  28. data/lib/thinking_sphinx/source.rb +33 -2
  29. data/lib/thinking_sphinx/source/internal_properties.rb +8 -3
  30. data/lib/thinking_sphinx/source/sql.rb +10 -1
  31. data/spec/thinking_sphinx/attribute_spec.rb +86 -94
  32. data/spec/thinking_sphinx/auto_version_spec.rb +8 -0
  33. data/spec/thinking_sphinx/facet_search_spec.rb +12 -6
  34. data/spec/thinking_sphinx/index/builder_spec.rb +32 -29
  35. data/spec/thinking_sphinx/search_spec.rb +14 -11
  36. metadata +5 -4
data/README.textile CHANGED
@@ -208,3 +208,5 @@ Since I first released this library, there's been quite a few people who have su
208
208
  * Clemens Kofler
209
209
  * Ryan Mohr
210
210
  * Alex Chee
211
+ * Florent Piteau
212
+ * Bruno Santschi
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.4
1
+ 2.0.5
@@ -66,6 +66,12 @@ Feature: Searching on a single model
66
66
  When I filter by 2001-01-01 on comments_created_at
67
67
  Then I should get 1 result
68
68
 
69
+ Scenario: Filtering on a wordcount attribute
70
+ Given Sphinx is running
71
+ And I am searching on developers
72
+ When I filter between 0 and 1 on state_wordcount
73
+ Then I should get 5 results
74
+
69
75
  Scenario: Searching by NULL/0 values in MVAs
70
76
  Given Sphinx is running
71
77
  And I am searching on boxes
@@ -166,3 +172,10 @@ Feature: Searching on a single model
166
172
  And I am searching on posts
167
173
  When I search for "Shakespeare"
168
174
  Then I should get 1 result
175
+
176
+ Scenario: Searching on content from file field
177
+ Given Sphinx is running
178
+ And I am searching on posts
179
+ When I search for "foo bar baz"
180
+ Then I should get 1 result
181
+
@@ -9,13 +9,14 @@ Dir[File.join(File.dirname(__FILE__), '../../vendor/*/lib')].each do |path|
9
9
  $:.unshift path
10
10
  end
11
11
 
12
+ require 'active_support/core_ext/class/inheritable_attributes'
12
13
  require 'active_record'
13
14
  require 'cucumber/thinking_sphinx/internal_world'
14
15
 
15
16
  world = Cucumber::ThinkingSphinx::InternalWorld.new
16
17
  world.configure_database
17
18
 
18
- require "thinking_sphinx"
19
+ require 'thinking_sphinx'
19
20
 
20
21
  ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
21
22
 
@@ -1,3 +1,3 @@
1
1
  %w( rogue nat molly jasper moggy ).each do |name|
2
- Cat.new(:name => name).save(false)
2
+ Cat.new(:name => name).save(:validate => false)
3
3
  end
@@ -1,3 +1,3 @@
1
1
  %w( rover lassie gaspode ).each do |name|
2
- Dog.new(:name => name).save(false)
2
+ Dog.new(:name => name).save(:validate => false)
3
3
  end
@@ -1,3 +1,3 @@
1
1
  %w( fantastic ).each do |name|
2
- Fox.new(:name => name).save(false)
2
+ Fox.new(:name => name).save(:validate => false)
3
3
  end
@@ -1,5 +1,9 @@
1
1
  post = Post.create(
2
- :subject => "Hello World", :content => "Um Text", :id => 1, :category_id => 1
2
+ :subject => "Hello World",
3
+ :content => "Um Text",
4
+ :id => 1,
5
+ :category_id => 1,
6
+ :keywords_file => (File.dirname(__FILE__) + '/post_keywords.txt')
3
7
  )
4
8
 
5
9
  post.authors << Author.find(:first)
@@ -2,4 +2,5 @@ ActiveRecord::Base.connection.create_table :posts, :force => true do |t|
2
2
  t.column :subject, :string, :null => false
3
3
  t.column :content, :text
4
4
  t.column :category_id, :integer, :null => false
5
+ t.column :keywords_file, :string
5
6
  end
@@ -12,6 +12,7 @@ class Developer < ActiveRecord::Base
12
12
 
13
13
  has age, :facet => true
14
14
  has tags(:id), :as => :tag_ids, :facet => true
15
+ has state, :as => :state_wordcount, :type => :wordcount
15
16
 
16
17
  facet "LOWER(city)", :as => :city, :type => :string, :value => :city
17
18
 
@@ -2,7 +2,9 @@ class Music < Medium
2
2
  set_table_name 'music'
3
3
 
4
4
  define_index do
5
- indexes artist, track, album
5
+ indexes artist, :with => :attribute
6
+ indexes track
7
+ indexes album, :with => :wordcount
6
8
  indexes genre(:name), :as => :genre
7
9
  end
8
10
  end
@@ -11,6 +11,7 @@ class Post < ActiveRecord::Base
11
11
  indexes tags.text, :as => :tags
12
12
  indexes comments.content, :as => :comments
13
13
  indexes authors.name, :as => :authors
14
+ indexes keywords_file, :as => :keywords, :file => true
14
15
 
15
16
  has comments(:id), :as => :comment_ids, :source => :ranged_query,
16
17
  :facet => true
@@ -102,8 +102,8 @@ module ThinkingSphinx
102
102
  end
103
103
  end
104
104
 
105
- def self.unique_id_expression(offset = nil)
106
- "* #{context.indexed_models.size} + #{offset || 0}"
105
+ def self.unique_id_expression(adapter, offset = nil)
106
+ "* #{adapter.cast_to_int context.indexed_models.size} + #{offset || 0}"
107
107
  end
108
108
 
109
109
  # Check if index definition is disabled.
@@ -13,7 +13,11 @@ module ThinkingSphinx
13
13
  module ActiveRecord
14
14
  def self.included(base)
15
15
  base.class_eval do
16
- class_inheritable_array :sphinx_indexes, :sphinx_facets
16
+ if defined?(class_attribute)
17
+ class_attribute :sphinx_indexes, :sphinx_facets
18
+ else
19
+ class_inheritable_array :sphinx_indexes, :sphinx_facets
20
+ end
17
21
 
18
22
  extend ThinkingSphinx::ActiveRecord::ClassMethods
19
23
 
@@ -202,7 +206,6 @@ module ThinkingSphinx
202
206
 
203
207
  def insert_sphinx_index(index)
204
208
  self.sphinx_indexes << index
205
- descendants.each { |klass| klass.insert_sphinx_index(index) }
206
209
  end
207
210
 
208
211
  def has_sphinx_indexes?
@@ -272,6 +275,33 @@ module ThinkingSphinx
272
275
  index eldest_indexed_ancestor
273
276
  end
274
277
 
278
+ # Temporarily disable delta indexing inside a block, then perform a
279
+ # single rebuild of index at the end.
280
+ #
281
+ # Useful when performing updates to batches of models to prevent
282
+ # the delta index being rebuilt after each individual update.
283
+ #
284
+ # In the following example, the delta index will only be rebuilt
285
+ # once, not 10 times.
286
+ #
287
+ # SomeModel.suspended_delta do
288
+ # 10.times do
289
+ # SomeModel.create( ... )
290
+ # end
291
+ # end
292
+ #
293
+ def suspended_delta(reindex_after = true, &block)
294
+ define_indexes
295
+ original_setting = ThinkingSphinx.deltas_suspended?
296
+ ThinkingSphinx.deltas_suspended = true
297
+ begin
298
+ yield
299
+ ensure
300
+ ThinkingSphinx.deltas_suspended = original_setting
301
+ self.index_delta if reindex_after
302
+ end
303
+ end
304
+
275
305
  private
276
306
 
277
307
  def local_sphinx_indexes
@@ -22,33 +22,6 @@ module ThinkingSphinx
22
22
  def delta_objects
23
23
  self.sphinx_indexes.collect(&:delta_object).compact
24
24
  end
25
-
26
- # Temporarily disable delta indexing inside a block, then perform a
27
- # single rebuild of index at the end.
28
- #
29
- # Useful when performing updates to batches of models to prevent
30
- # the delta index being rebuilt after each individual update.
31
- #
32
- # In the following example, the delta index will only be rebuilt
33
- # once, not 10 times.
34
- #
35
- # SomeModel.suspended_delta do
36
- # 10.times do
37
- # SomeModel.create( ... )
38
- # end
39
- # end
40
- #
41
- def suspended_delta(reindex_after = true, &block)
42
- define_indexes
43
- original_setting = ThinkingSphinx.deltas_suspended?
44
- ThinkingSphinx.deltas_suspended = true
45
- begin
46
- yield
47
- ensure
48
- ThinkingSphinx.deltas_suspended = original_setting
49
- self.index_delta if reindex_after
50
- end
51
- end
52
25
  end
53
26
 
54
27
  def toggled_delta?
@@ -28,6 +28,10 @@ module ThinkingSphinx
28
28
  "CAST(#{clause} AS UNSIGNED)"
29
29
  end
30
30
 
31
+ def cast_to_int(clause)
32
+ "CAST(#{clause} AS SIGNED)"
33
+ end
34
+
31
35
  def convert_nulls(clause, default = '')
32
36
  default = "'#{default}'" if default.is_a?(String)
33
37
 
@@ -35,6 +35,10 @@ module ThinkingSphinx
35
35
  clause
36
36
  end
37
37
 
38
+ def cast_to_int(clause)
39
+ "#{clause}::INT8"
40
+ end
41
+
38
42
  def convert_nulls(clause, default = '')
39
43
  default = case default
40
44
  when String
@@ -58,16 +58,14 @@ module ThinkingSphinx
58
58
  def join_to(base_join)
59
59
  parent.join_to(base_join) if parent && parent.join.nil?
60
60
 
61
- @join ||= ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.new(
62
- @reflection, base_join, parent ? parent.join : base_join.joins.first
61
+ @join ||= join_association_class.new(
62
+ @reflection, base_join, parent ? parent.join : join_parent(base_join)
63
63
  )
64
64
  end
65
65
 
66
66
  def arel_join
67
67
  @join.join_type = Arel::OuterJoin
68
- @join.options[:conditions].gsub!(/::ts_join_alias::/,
69
- "#{@reflection.klass.connection.quote_table_name(@join.parent.aliased_table_name)}"
70
- ) if @join.options[:conditions].is_a?(String)
68
+ rewrite_conditions
71
69
 
72
70
  @join
73
71
  end
@@ -165,5 +163,57 @@ module ThinkingSphinx
165
163
 
166
164
  options
167
165
  end
166
+
167
+ def join_association_class
168
+ if rails_3_1?
169
+ ::ActiveRecord::Associations::JoinDependency::JoinAssociation
170
+ else
171
+ ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
172
+ end
173
+ end
174
+
175
+ def join_parent(join)
176
+ if rails_3_1?
177
+ join.join_parts.first
178
+ else
179
+ join.joins.first
180
+ end
181
+ end
182
+
183
+ def rails_3_1?
184
+ ::ActiveRecord::Associations.constants.include?(:JoinDependency) ||
185
+ ::ActiveRecord::Associations.constants.include?('JoinDependency')
186
+ end
187
+
188
+ def rewrite_conditions
189
+ @join.options[:conditions] = case @join.options[:conditions]
190
+ when String
191
+ rewrite_condition @join.options[:conditions]
192
+ when Array
193
+ @join.options[:conditions].collect { |condition|
194
+ rewrite_condition condition
195
+ }
196
+ else
197
+ @join.options[:conditions]
198
+ end
199
+ end
200
+
201
+ def rewrite_condition(condition)
202
+ return condition unless condition.is_a?(String)
203
+
204
+ if defined?(ActsAsTaggableOn) &&
205
+ @reflection.klass == ActsAsTaggableOn::Tagging &&
206
+ @reflection.name.to_s[/_taggings$/]
207
+ condition = condition.gsub /taggings\./, "#{quoted_alias @join}."
208
+ end
209
+
210
+ condition.gsub /::ts_join_alias::/, quoted_alias(@join.parent)
211
+ end
212
+
213
+ def quoted_alias(join)
214
+ @reflection.klass.connection.quote_table_name(
215
+ join.aliased_table_name
216
+ )
217
+ end
168
218
  end
169
219
  end
@@ -11,6 +11,21 @@ module ThinkingSphinx
11
11
  class Attribute < ThinkingSphinx::Property
12
12
  attr_accessor :query_source
13
13
 
14
+ SphinxTypeMappings = {
15
+ :multi => :sql_attr_multi,
16
+ :datetime => :sql_attr_timestamp,
17
+ :string => :sql_attr_str2ordinal,
18
+ :float => :sql_attr_float,
19
+ :boolean => :sql_attr_bool,
20
+ :integer => :sql_attr_uint,
21
+ :bigint => :sql_attr_bigint,
22
+ :wordcount => :sql_attr_str2wordcount
23
+ }
24
+
25
+ if Riddle.loaded_version.to_i > 1
26
+ SphinxTypeMappings[:string] = :sql_attr_string
27
+ end
28
+
14
29
  # To create a new attribute, you'll need to pass in either a single Column
15
30
  # or an array of them, and some (optional) options.
16
31
  #
@@ -117,15 +132,7 @@ module ThinkingSphinx
117
132
  end
118
133
 
119
134
  def type_to_config
120
- {
121
- :multi => :sql_attr_multi,
122
- :datetime => :sql_attr_timestamp,
123
- :string => :sql_attr_str2ordinal,
124
- :float => :sql_attr_float,
125
- :boolean => :sql_attr_bool,
126
- :integer => :sql_attr_uint,
127
- :bigint => :sql_attr_bigint
128
- }[type]
135
+ SphinxTypeMappings[type]
129
136
  end
130
137
 
131
138
  def include_as_association?
@@ -231,7 +238,7 @@ module ThinkingSphinx
231
238
  on(*join.association_join)
232
239
  end
233
240
 
234
- relation = relation.project "#{foreign_key_for_mva base_assoc} #{ThinkingSphinx.unique_id_expression(offset)} AS #{quote_column('id')}, #{primary_key_for_mva(end_assoc)} AS #{quote_column(unique_name)}"
241
+ relation = relation.project "#{foreign_key_for_mva base_assoc} #{ThinkingSphinx.unique_id_expression(adapter, offset)} AS #{quote_column('id')}, #{primary_key_for_mva(end_assoc)} AS #{quote_column(unique_name)}"
235
242
 
236
243
  relation.to_sql
237
244
  end
@@ -7,7 +7,7 @@ module ThinkingSphinx
7
7
  require "riddle/#{version}"
8
8
  when /1.10/
9
9
  require 'riddle/1.10'
10
- when /2.0.1/
10
+ when /2.0.\d/
11
11
  require 'riddle/2.0.1'
12
12
  else
13
13
  documentation_link = %Q{
@@ -5,12 +5,16 @@ module ThinkingSphinx
5
5
  end
6
6
 
7
7
  def attribute_name
8
- "class_crc"
8
+ Riddle.loaded_version.to_i < 2 ? 'class_crc' : 'sphinx_internal_class'
9
9
  end
10
10
 
11
11
  def value(object, attribute_hash)
12
- crc = attribute_hash['class_crc']
13
- ThinkingSphinx::Configuration.instance.models_by_crc[crc]
12
+ if Riddle.loaded_version.to_i < 2
13
+ crc = attribute_hash['class_crc']
14
+ ThinkingSphinx::Configuration.instance.models_by_crc[crc]
15
+ else
16
+ attribute_hash['sphinx_internal_class']
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -91,9 +91,9 @@ module ThinkingSphinx
91
91
  if custom_app_root
92
92
  self.app_root = custom_app_root
93
93
  else
94
- self.app_root = Rails.root if defined?(Rails)
95
94
  self.app_root = Merb.root if defined?(Merb)
96
95
  self.app_root = Sinatra::Application.root if defined?(Sinatra)
96
+ self.app_root = Rails.root if defined?(Rails)
97
97
  self.app_root ||= app_root
98
98
  end
99
99
 
@@ -132,10 +132,10 @@ module ThinkingSphinx
132
132
  ThinkingSphinx.mutex.synchronize do
133
133
  @@environment ||= if defined?(Merb)
134
134
  Merb.environment
135
- elsif defined?(Sinatra)
136
- Sinatra::Application.environment.to_s
137
135
  elsif defined?(Rails)
138
136
  Rails.env
137
+ elsif defined?(Sinatra)
138
+ Sinatra::Application.environment.to_s
139
139
  else
140
140
  ENV['RAILS_ENV'] || 'development'
141
141
  end
@@ -16,6 +16,7 @@ module ThinkingSphinx
16
16
  when Facet
17
17
  facet.name
18
18
  when String, Symbol
19
+ return :class if facet.to_s == 'sphinx_internal_class'
19
20
  facet.to_s.gsub(/(_facet|_crc)$/,'').to_sym
20
21
  end
21
22
  end
@@ -29,7 +29,7 @@ module ThinkingSphinx
29
29
  names = options[:all_facets] ?
30
30
  facet_names_for_all_classes : facet_names_common_to_all_classes
31
31
 
32
- names.delete "class_crc" unless options[:class_facet]
32
+ names.delete class_facet unless options[:class_facet]
33
33
  names
34
34
  end
35
35
  end
@@ -162,5 +162,9 @@ module ThinkingSphinx
162
162
  facet.name == name
163
163
  }
164
164
  end
165
+
166
+ def class_facet
167
+ Riddle.loaded_version.to_i < 2 ? 'class_crc' : 'sphinx_internal_class'
168
+ end
165
169
  end
166
170
  end