thinking-sphinx 1.5.0 → 2.0.0.rc1

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