thinking-sphinx 2.0.4 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
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