thinking-sphinx 1.5.0 → 2.0.0.rc1

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 (104) hide show
  1. data/README.textile +15 -48
  2. data/VERSION +1 -0
  3. data/features/attribute_transformation.feature +7 -7
  4. data/features/attribute_updates.feature +16 -18
  5. data/features/deleting_instances.feature +13 -16
  6. data/features/excerpts.feature +0 -8
  7. data/features/facets.feature +19 -25
  8. data/features/handling_edits.feature +20 -25
  9. data/features/searching_across_models.feature +1 -1
  10. data/features/searching_by_index.feature +5 -6
  11. data/features/searching_by_model.feature +29 -29
  12. data/features/sphinx_scopes.feature +0 -26
  13. data/features/step_definitions/common_steps.rb +6 -18
  14. data/features/step_definitions/scope_steps.rb +0 -4
  15. data/features/step_definitions/search_steps.rb +4 -9
  16. data/features/support/env.rb +10 -3
  17. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -8
  18. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  19. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  20. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  21. data/features/thinking_sphinx/db/fixtures/people.rb +1 -1
  22. data/features/thinking_sphinx/db/fixtures/posts.rb +1 -5
  23. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -1
  24. data/features/thinking_sphinx/models/alpha.rb +0 -1
  25. data/features/thinking_sphinx/models/beta.rb +0 -5
  26. data/features/thinking_sphinx/models/developer.rb +1 -6
  27. data/features/thinking_sphinx/models/music.rb +1 -3
  28. data/features/thinking_sphinx/models/person.rb +1 -2
  29. data/features/thinking_sphinx/models/post.rb +0 -1
  30. data/lib/cucumber/thinking_sphinx/external_world.rb +4 -8
  31. data/lib/cucumber/thinking_sphinx/internal_world.rb +27 -36
  32. data/lib/thinking_sphinx.rb +60 -132
  33. data/lib/thinking_sphinx/active_record.rb +98 -124
  34. data/lib/thinking_sphinx/active_record/attribute_updates.rb +13 -17
  35. data/lib/thinking_sphinx/active_record/delta.rb +15 -21
  36. data/lib/thinking_sphinx/active_record/has_many_association.rb +23 -16
  37. data/lib/thinking_sphinx/active_record/scopes.rb +0 -18
  38. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +15 -63
  39. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -4
  40. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +24 -65
  41. data/lib/thinking_sphinx/association.rb +11 -36
  42. data/lib/thinking_sphinx/attribute.rb +85 -92
  43. data/lib/thinking_sphinx/auto_version.rb +3 -21
  44. data/lib/thinking_sphinx/class_facet.rb +3 -8
  45. data/lib/thinking_sphinx/configuration.rb +58 -114
  46. data/lib/thinking_sphinx/context.rb +20 -22
  47. data/lib/thinking_sphinx/core/array.rb +13 -0
  48. data/lib/thinking_sphinx/deltas.rb +0 -2
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +22 -18
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +31 -30
  51. data/lib/thinking_sphinx/excerpter.rb +1 -2
  52. data/lib/thinking_sphinx/facet.rb +35 -45
  53. data/lib/thinking_sphinx/facet_search.rb +24 -58
  54. data/lib/thinking_sphinx/field.rb +0 -18
  55. data/lib/thinking_sphinx/index.rb +36 -38
  56. data/lib/thinking_sphinx/index/builder.rb +59 -74
  57. data/lib/thinking_sphinx/property.rb +45 -66
  58. data/lib/thinking_sphinx/railtie.rb +35 -0
  59. data/lib/thinking_sphinx/search.rb +250 -506
  60. data/lib/thinking_sphinx/source.rb +31 -50
  61. data/lib/thinking_sphinx/source/internal_properties.rb +3 -8
  62. data/lib/thinking_sphinx/source/sql.rb +31 -71
  63. data/lib/thinking_sphinx/tasks.rb +27 -48
  64. data/spec/thinking_sphinx/active_record/delta_spec.rb +41 -36
  65. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -96
  66. data/spec/thinking_sphinx/active_record/scopes_spec.rb +29 -29
  67. data/spec/thinking_sphinx/active_record_spec.rb +169 -140
  68. data/spec/thinking_sphinx/association_spec.rb +2 -20
  69. data/spec/thinking_sphinx/attribute_spec.rb +97 -101
  70. data/spec/thinking_sphinx/auto_version_spec.rb +11 -75
  71. data/spec/thinking_sphinx/configuration_spec.rb +62 -63
  72. data/spec/thinking_sphinx/context_spec.rb +66 -66
  73. data/spec/thinking_sphinx/facet_search_spec.rb +99 -99
  74. data/spec/thinking_sphinx/facet_spec.rb +4 -30
  75. data/spec/thinking_sphinx/field_spec.rb +3 -17
  76. data/spec/thinking_sphinx/index/builder_spec.rb +132 -169
  77. data/spec/thinking_sphinx/index_spec.rb +39 -45
  78. data/spec/thinking_sphinx/search_methods_spec.rb +33 -37
  79. data/spec/thinking_sphinx/search_spec.rb +269 -491
  80. data/spec/thinking_sphinx/source_spec.rb +48 -62
  81. data/spec/thinking_sphinx_spec.rb +49 -49
  82. data/tasks/distribution.rb +46 -0
  83. data/tasks/testing.rb +74 -0
  84. metadata +123 -199
  85. data/features/field_sorting.feature +0 -18
  86. data/features/thinking_sphinx/db/.gitignore +0 -1
  87. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  88. data/features/thinking_sphinx/models/andrew.rb +0 -17
  89. data/lib/thinking-sphinx.rb +0 -1
  90. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  91. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  92. data/lib/thinking_sphinx/connection.rb +0 -71
  93. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  94. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  95. data/lib/thinking_sphinx/rails_additions.rb +0 -181
  96. data/spec/fixtures/data.sql +0 -32
  97. data/spec/fixtures/database.yml.default +0 -3
  98. data/spec/fixtures/models.rb +0 -161
  99. data/spec/fixtures/structure.sql +0 -146
  100. data/spec/spec_helper.rb +0 -54
  101. data/spec/sphinx_helper.rb +0 -67
  102. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  103. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  104. data/spec/thinking_sphinx/rails_additions_spec.rb +0 -203
@@ -0,0 +1,13 @@
1
+ # FIXME
2
+ module ThinkingSphinx
3
+ class Search
4
+ end
5
+ end
6
+
7
+ module SearchAsArray
8
+ def ===(object)
9
+ object.is_a?(ThinkingSphinx::Search) || super
10
+ end
11
+ end
12
+
13
+ Array.extend SearchAsArray
@@ -1,6 +1,4 @@
1
1
  require 'thinking_sphinx/deltas/default_delta'
2
- require 'thinking_sphinx/deltas/delete_job'
3
- require 'thinking_sphinx/deltas/index_job'
4
2
 
5
3
  module ThinkingSphinx
6
4
  module Deltas
@@ -2,54 +2,58 @@ module ThinkingSphinx
2
2
  module Deltas
3
3
  class DefaultDelta
4
4
  attr_accessor :column
5
-
5
+
6
6
  def initialize(index, options)
7
7
  @index = index
8
8
  @column = options.delete(:delta_column) || :delta
9
9
  end
10
-
10
+
11
11
  def index(model, instance = nil)
12
12
  return true unless ThinkingSphinx.updates_enabled? &&
13
13
  ThinkingSphinx.deltas_enabled?
14
14
  return true if instance && !toggled(instance)
15
-
15
+
16
16
  update_delta_indexes model
17
17
  delete_from_core model, instance if instance
18
-
18
+
19
19
  true
20
20
  end
21
-
21
+
22
22
  def toggle(instance)
23
- instance.send "#{@column}=", true
23
+ instance.delta = true
24
24
  end
25
-
25
+
26
26
  def toggled(instance)
27
- instance.send "#{@column}"
27
+ instance.delta
28
28
  end
29
-
29
+
30
30
  def reset_query(model)
31
31
  "UPDATE #{model.quoted_table_name} SET " +
32
32
  "#{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(false)} " +
33
33
  "WHERE #{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(true)}"
34
34
  end
35
-
35
+
36
36
  def clause(model, toggled)
37
37
  "#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
38
38
  " = #{adapter.boolean(toggled)}"
39
39
  end
40
-
40
+
41
41
  private
42
-
42
+
43
43
  def update_delta_indexes(model)
44
- ThinkingSphinx::Deltas::IndexJob.new(model.delta_index_names).perform
44
+ config = ThinkingSphinx::Configuration.instance
45
+ rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
46
+
47
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config '#{config.config_file}' #{rotate} #{model.delta_index_names.join(' ')}`
48
+ puts(output) unless ThinkingSphinx.suppress_delta_output?
45
49
  end
46
-
50
+
47
51
  def delete_from_core(model, instance)
48
- ThinkingSphinx::Deltas::DeleteJob.new(
49
- model.core_index_names, instance.sphinx_document_id
50
- ).perform
52
+ model.core_index_names.each do |index_name|
53
+ model.delete_in_index index_name, instance.sphinx_document_id
54
+ end
51
55
  end
52
-
56
+
53
57
  def adapter
54
58
  @adapter = @index.model.sphinx_database_adapter
55
59
  end
@@ -3,13 +3,13 @@ Capistrano::Configuration.instance(:must_exist).load do
3
3
  namespace :install do
4
4
  desc <<-DESC
5
5
  Install Sphinx by source
6
-
6
+
7
7
  If Postgres is available, Sphinx will use it.
8
-
8
+
9
9
  If the variable :thinking_sphinx_configure_args is set, it will
10
10
  be passed to the Sphinx configure script. You can use this to
11
11
  install Sphinx in a non-standard location:
12
-
12
+
13
13
  set :thinking_sphinx_configure_args, "--prefix=$HOME/software"
14
14
  DESC
15
15
 
@@ -22,7 +22,7 @@ DESC
22
22
  rescue Capistrano::CommandError => e
23
23
  puts "Continuing despite error: #{e.message}"
24
24
  end
25
-
25
+
26
26
  args = []
27
27
  if with_postgres
28
28
  run "pg_config --pkgincludedir" do |channel, stream, data|
@@ -30,69 +30,70 @@ DESC
30
30
  end
31
31
  end
32
32
  args << fetch(:thinking_sphinx_configure_args, '')
33
-
33
+
34
34
  commands = <<-CMD
35
- wget -q http://sphinxsearch.com/downloads/sphinx-0.9.9.tar.gz >> sphinx.log
36
- tar xzvf sphinx-0.9.9.tar.gz
37
- cd sphinx-0.9.9
35
+ wget -q http://www.sphinxsearch.com/downloads/sphinx-0.9.8.1.tar.gz >> sphinx.log
36
+ tar xzvf sphinx-0.9.8.1.tar.gz
37
+ cd sphinx-0.9.8.1
38
38
  ./configure #{args.join(" ")}
39
39
  make
40
40
  #{try_sudo} make install
41
- rm -rf sphinx-0.9.9 sphinx-0.9.9.tar.gz
41
+ rm -rf sphinx-0.9.8.1 sphinx-0.9.8.1.tar.gz
42
42
  CMD
43
43
  run commands.split(/\n\s+/).join(" && ")
44
44
  end
45
-
46
- desc "Install Thinking Sphinx as a gem"
45
+
46
+ desc "Install Thinking Sphinx as a gem from GitHub"
47
47
  task :ts do
48
- run "#{try_sudo} gem install thinking-sphinx"
48
+ run "#{try_sudo} gem install thinking-sphinx --source http://gemcutter.org"
49
49
  end
50
50
  end
51
-
51
+
52
52
  desc "Generate the Sphinx configuration file"
53
53
  task :configure do
54
54
  rake "thinking_sphinx:configure"
55
55
  end
56
-
56
+
57
57
  desc "Index data"
58
58
  task :index do
59
59
  rake "thinking_sphinx:index"
60
60
  end
61
-
61
+
62
62
  desc "Start the Sphinx daemon"
63
63
  task :start do
64
- rake "thinking_sphinx:configure thinking_sphinx:start"
64
+ configure
65
+ rake "thinking_sphinx:start"
65
66
  end
66
-
67
+
67
68
  desc "Stop the Sphinx daemon"
68
69
  task :stop do
69
- rake "thinking_sphinx:configure thinking_sphinx:stop"
70
+ configure
71
+ rake "thinking_sphinx:stop"
70
72
  end
71
-
73
+
72
74
  desc "Stop and then start the Sphinx daemon"
73
75
  task :restart do
74
- rake "thinking_sphinx:configure thinking_sphinx:stop \
75
- thinking_sphinx:start"
76
+ stop
77
+ start
76
78
  end
77
-
79
+
78
80
  desc "Stop, re-index and then start the Sphinx daemon"
79
81
  task :rebuild do
80
- rake "thinking_sphinx:configure thinking_sphinx:stop \
81
- thinking_sphinx:reindex \
82
- thinking_sphinx:start"
82
+ stop
83
+ index
84
+ start
83
85
  end
84
-
85
- desc "Add the shared folder for sphinx files"
86
+
87
+ desc "Add the shared folder for sphinx files for the production environment"
86
88
  task :shared_sphinx_folder, :roles => :web do
87
- rails_env = fetch(:rails_env, "production")
88
- run "mkdir -p #{shared_path}/sphinx/#{rails_env}"
89
+ run "mkdir -p #{shared_path}/db/sphinx/production"
89
90
  end
90
91
 
91
92
  def rake(*tasks)
92
93
  rails_env = fetch(:rails_env, "production")
93
94
  rake = fetch(:rake, "rake")
94
95
  tasks.each do |t|
95
- run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; if [ -f Rakefile ]; then #{rake} RAILS_ENV=#{rails_env} #{t}; fi;"
96
+ run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; #{rake} RAILS_ENV=#{rails_env} #{t}"
96
97
  end
97
98
  end
98
99
  end
@@ -1,7 +1,6 @@
1
1
  module ThinkingSphinx
2
2
  class Excerpter
3
- CoreMethods = %w( kind_of? object_id respond_to? respond_to_missing? should
4
- should_not stub! )
3
+ CoreMethods = %w( kind_of? object_id respond_to? should should_not stub! )
5
4
  # Hide most methods, to allow them to be passed through to the instance.
6
5
  instance_methods.select { |method|
7
6
  method.to_s[/^__/].nil? && !CoreMethods.include?(method.to_s)
@@ -1,11 +1,10 @@
1
1
  module ThinkingSphinx
2
2
  class Facet
3
- attr_reader :property, :value_source
4
-
5
- def initialize(property, value_source = nil)
6
- @property = property
7
- @value_source = value_source
8
-
3
+ attr_reader :property
4
+
5
+ def initialize(property)
6
+ @property = property
7
+
9
8
  if property.columns.length != 1
10
9
  raise "Can't translate Facets on multiple-column field or attribute"
11
10
  end
@@ -16,15 +15,14 @@ module ThinkingSphinx
16
15
  when Facet
17
16
  facet.name
18
17
  when String, Symbol
19
- return :class if facet.to_s == 'sphinx_internal_class'
20
18
  facet.to_s.gsub(/(_facet|_crc)$/,'').to_sym
21
19
  end
22
20
  end
23
-
21
+
24
22
  def self.attribute_name_for(name)
25
23
  name.to_s == 'class' ? 'class_crc' : "#{name}_facet"
26
24
  end
27
-
25
+
28
26
  def self.attribute_name_from_value(name, value)
29
27
  case value
30
28
  when String
@@ -39,10 +37,10 @@ module ThinkingSphinx
39
37
  name
40
38
  end
41
39
  end
42
-
40
+
43
41
  def self.translate?(property)
44
42
  return true if property.is_a?(Field)
45
-
43
+
46
44
  case property.type
47
45
  when :string
48
46
  true
@@ -52,11 +50,11 @@ module ThinkingSphinx
52
50
  !property.all_ints?
53
51
  end
54
52
  end
55
-
53
+
56
54
  def name
57
55
  property.unique_name
58
56
  end
59
-
57
+
60
58
  def attribute_name
61
59
  if translate?
62
60
  Facet.attribute_name_for(@property.unique_name)
@@ -64,23 +62,18 @@ module ThinkingSphinx
64
62
  @property.unique_name.to_s
65
63
  end
66
64
  end
67
-
65
+
68
66
  def translate?
69
67
  Facet.translate?(@property)
70
68
  end
71
-
69
+
72
70
  def type
73
71
  @property.is_a?(Field) ? :string : @property.type
74
72
  end
75
-
76
- def float?
77
- @property.type == :float
78
- end
79
-
80
- def value(object, attribute_hash)
81
- attribute_value = attribute_hash['@groupby']
73
+
74
+ def value(object, attribute_value)
82
75
  return translate(object, attribute_value) if translate? || float?
83
-
76
+
84
77
  case @property.type
85
78
  when :datetime
86
79
  Time.at(attribute_value)
@@ -90,46 +83,43 @@ module ThinkingSphinx
90
83
  attribute_value
91
84
  end
92
85
  end
93
-
86
+
94
87
  def to_s
95
88
  name
96
89
  end
97
-
90
+
98
91
  private
99
-
92
+
100
93
  def translate(object, attribute_value)
101
94
  objects = source_objects(object)
102
- return if objects.blank?
103
-
104
- method = value_source || column.__name
105
- object = objects.one? ? objects.first : objects.detect { |item|
106
- result = item.send(method)
107
- case result
108
- when String
109
- result.to_crc32 == attribute_value
110
- when NilClass
111
- false
112
- else
113
- result == attribute_value
114
- end
115
- }
116
-
117
- object ? object.send(method) : nil
95
+ return nil if objects.nil? || objects.empty?
96
+
97
+ if objects.length > 1
98
+ objects.collect { |item| item.send(column.__name) }.detect { |item|
99
+ item.to_crc32 == attribute_value
100
+ }
101
+ else
102
+ objects.first.send(column.__name)
103
+ end
118
104
  end
119
-
105
+
120
106
  def source_objects(object)
121
107
  column.__stack.each { |method|
122
108
  object = Array(object).collect { |item|
123
109
  item.send(method)
124
110
  }.flatten.compact
125
-
111
+
126
112
  return nil if object.empty?
127
113
  }
128
114
  Array(object)
129
115
  end
130
-
116
+
131
117
  def column
132
118
  @property.columns.first
133
119
  end
120
+
121
+ def float?
122
+ @property.type == :float
123
+ end
134
124
  end
135
125
  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_facet unless options[:class_facet]
32
+ names.delete "class_crc" unless options[:class_facet]
33
33
  names
34
34
  end
35
35
  end
@@ -44,23 +44,23 @@ module ThinkingSphinx
44
44
  end
45
45
 
46
46
  def populate
47
- return if facet_names.empty?
48
-
49
- ThinkingSphinx::Search.bundle_searches(facet_names) { |sphinx, name|
50
- sphinx.search *(args + [facet_search_options(name)])
51
- }.each_with_index { |search, index|
52
- add_from_results facet_names[index], search
53
- }
47
+ facet_names.each do |name|
48
+ search_options = facet_search_options.merge(:group_by => name)
49
+ add_from_results name, ThinkingSphinx.search(
50
+ *(args + [search_options])
51
+ )
52
+ end
54
53
  end
55
54
 
56
- def facet_search_options(facet_name)
55
+ def facet_search_options
56
+ config = ThinkingSphinx::Configuration.instance
57
+ max = config.configuration.searchd.max_matches || 1000
58
+
57
59
  options.merge(
58
60
  :group_function => :attr,
59
- :limit => max_matches,
60
- :max_matches => max_matches,
61
- :page => 1,
62
- :group_by => facet_name,
63
- :ids_only => !translate?(facet_name)
61
+ :limit => max,
62
+ :max_matches => max,
63
+ :page => 1
64
64
  )
65
65
  end
66
66
 
@@ -101,34 +101,21 @@ module ThinkingSphinx
101
101
  }
102
102
  end
103
103
 
104
- def translate?(name)
105
- facet = facet_from_name(name)
106
- facet.translate? || facet.float?
107
- end
108
-
109
- def config
110
- ThinkingSphinx::Configuration.instance
111
- end
112
-
113
- def max_matches
114
- @max_matches ||= config.configuration.searchd.max_matches || 1000
115
- end
116
-
117
- # example: facet = country_facet; name = :country
118
- def add_from_results(facet, search)
119
- name = ThinkingSphinx::Facet.name_for(facet)
120
- facet = facet_from_name(facet)
104
+ def add_from_results(facet, results)
105
+ name = ThinkingSphinx::Facet.name_for(facet)
121
106
 
122
107
  self[name] ||= {}
123
108
 
124
- return if search.empty?
109
+ return if results.empty?
110
+
111
+ facet = facet_from_object(results.first, facet) if facet.is_a?(String)
125
112
 
126
- search.each_with_match do |result, match|
127
- facet_value = facet.value(result, match[:attributes])
113
+ results.each_with_groupby_and_count { |result, group, count|
114
+ facet_value = facet.value(result, group)
128
115
 
129
116
  self[name][facet_value] ||= 0
130
- self[name][facet_value] += match[:attributes]["@count"]
131
- end
117
+ self[name][facet_value] += count
118
+ }
132
119
  end
133
120
 
134
121
  def underlying_value(key, value)
@@ -143,28 +130,7 @@ module ThinkingSphinx
143
130
  end
144
131
 
145
132
  def facet_from_object(object, name)
146
- facet = nil
147
- klass = object.class
148
-
149
- while klass != ::ActiveRecord::Base && facet.nil?
150
- facet = klass.sphinx_facets.detect { |facet|
151
- facet.attribute_name == name
152
- }
153
- klass = klass.superclass
154
- end
155
-
156
- facet
157
- end
158
-
159
- def facet_from_name(name)
160
- name = ThinkingSphinx::Facet.name_for(name)
161
- all_facets.detect { |facet|
162
- facet.name == name
163
- }
164
- end
165
-
166
- def class_facet
167
- Riddle.loaded_version.to_i < 2 ? 'class_crc' : 'sphinx_internal_class'
133
+ object.sphinx_facets.detect { |facet| facet.attribute_name == name }
168
134
  end
169
135
  end
170
136
  end