thinking-sphinx 2.0.5 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/README.textile +7 -1
  2. data/features/searching_by_model.feature +24 -30
  3. data/features/step_definitions/common_steps.rb +5 -5
  4. data/features/thinking_sphinx/db/.gitignore +1 -0
  5. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +1 -0
  6. data/spec/fixtures/data.sql +32 -0
  7. data/spec/fixtures/database.yml.default +3 -0
  8. data/spec/fixtures/models.rb +161 -0
  9. data/spec/fixtures/structure.sql +146 -0
  10. data/spec/spec_helper.rb +62 -0
  11. data/spec/sphinx_helper.rb +61 -0
  12. data/spec/support/rails.rb +18 -0
  13. data/spec/thinking_sphinx/active_record/delta_spec.rb +24 -24
  14. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +27 -0
  15. data/spec/thinking_sphinx/active_record/scopes_spec.rb +25 -25
  16. data/spec/thinking_sphinx/active_record_spec.rb +108 -107
  17. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +38 -38
  18. data/spec/thinking_sphinx/association_spec.rb +69 -35
  19. data/spec/thinking_sphinx/context_spec.rb +61 -64
  20. data/spec/thinking_sphinx/search_spec.rb +7 -0
  21. data/spec/thinking_sphinx_spec.rb +47 -46
  22. metadata +49 -141
  23. data/VERSION +0 -1
  24. data/lib/cucumber/thinking_sphinx/external_world.rb +0 -12
  25. data/lib/cucumber/thinking_sphinx/internal_world.rb +0 -127
  26. data/lib/cucumber/thinking_sphinx/sql_logger.rb +0 -20
  27. data/lib/thinking-sphinx.rb +0 -1
  28. data/lib/thinking_sphinx.rb +0 -301
  29. data/lib/thinking_sphinx/action_controller.rb +0 -31
  30. data/lib/thinking_sphinx/active_record.rb +0 -384
  31. data/lib/thinking_sphinx/active_record/attribute_updates.rb +0 -52
  32. data/lib/thinking_sphinx/active_record/delta.rb +0 -65
  33. data/lib/thinking_sphinx/active_record/has_many_association.rb +0 -36
  34. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  35. data/lib/thinking_sphinx/active_record/log_subscriber.rb +0 -61
  36. data/lib/thinking_sphinx/active_record/scopes.rb +0 -93
  37. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +0 -87
  38. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -62
  39. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +0 -157
  40. data/lib/thinking_sphinx/association.rb +0 -219
  41. data/lib/thinking_sphinx/attribute.rb +0 -396
  42. data/lib/thinking_sphinx/auto_version.rb +0 -38
  43. data/lib/thinking_sphinx/bundled_search.rb +0 -44
  44. data/lib/thinking_sphinx/class_facet.rb +0 -20
  45. data/lib/thinking_sphinx/configuration.rb +0 -339
  46. data/lib/thinking_sphinx/context.rb +0 -76
  47. data/lib/thinking_sphinx/core/string.rb +0 -15
  48. data/lib/thinking_sphinx/deltas.rb +0 -28
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +0 -62
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +0 -101
  51. data/lib/thinking_sphinx/excerpter.rb +0 -23
  52. data/lib/thinking_sphinx/facet.rb +0 -128
  53. data/lib/thinking_sphinx/facet_search.rb +0 -170
  54. data/lib/thinking_sphinx/field.rb +0 -98
  55. data/lib/thinking_sphinx/index.rb +0 -157
  56. data/lib/thinking_sphinx/index/builder.rb +0 -312
  57. data/lib/thinking_sphinx/index/faux_column.rb +0 -118
  58. data/lib/thinking_sphinx/join.rb +0 -37
  59. data/lib/thinking_sphinx/property.rb +0 -185
  60. data/lib/thinking_sphinx/railtie.rb +0 -46
  61. data/lib/thinking_sphinx/search.rb +0 -972
  62. data/lib/thinking_sphinx/search_methods.rb +0 -439
  63. data/lib/thinking_sphinx/sinatra.rb +0 -7
  64. data/lib/thinking_sphinx/source.rb +0 -194
  65. data/lib/thinking_sphinx/source/internal_properties.rb +0 -51
  66. data/lib/thinking_sphinx/source/sql.rb +0 -157
  67. data/lib/thinking_sphinx/tasks.rb +0 -130
  68. data/lib/thinking_sphinx/test.rb +0 -55
  69. data/tasks/distribution.rb +0 -33
  70. data/tasks/testing.rb +0 -80
@@ -1,15 +0,0 @@
1
- require 'zlib'
2
-
3
- module ThinkingSphinx
4
- module Core
5
- module String
6
- def to_crc32
7
- Zlib.crc32 self
8
- end
9
- end
10
- end
11
- end
12
-
13
- class String
14
- include ThinkingSphinx::Core::String
15
- end
@@ -1,28 +0,0 @@
1
- require 'thinking_sphinx/deltas/default_delta'
2
-
3
- module ThinkingSphinx
4
- module Deltas
5
- def self.parse(index)
6
- delta_option = index.local_options.delete(:delta)
7
- case delta_option
8
- when TrueClass, :default
9
- DefaultDelta.new index, index.local_options
10
- when :delayed
11
- DelayedDelta.new index, index.local_options
12
- when :datetime
13
- DatetimeDelta.new index, index.local_options
14
- when FalseClass, nil
15
- nil
16
- else
17
- if delta_option.is_a?(String)
18
- delta_option = Kernel.const_get(delta_option)
19
- end
20
- if delta_option.ancestors.include?(ThinkingSphinx::Deltas::DefaultDelta)
21
- delta_option.new index, index.local_options
22
- else
23
- raise "Unknown delta type"
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,62 +0,0 @@
1
- module ThinkingSphinx
2
- module Deltas
3
- class DefaultDelta
4
- attr_accessor :column
5
-
6
- def initialize(index, options)
7
- @index = index
8
- @column = options.delete(:delta_column) || :delta
9
- end
10
-
11
- def index(model, instance = nil)
12
- return true unless ThinkingSphinx.updates_enabled? &&
13
- ThinkingSphinx.deltas_enabled?
14
- return true if instance && !toggled(instance)
15
-
16
- update_delta_indexes model
17
- delete_from_core model, instance if instance
18
-
19
- true
20
- end
21
-
22
- def toggle(instance)
23
- instance.send "#{@column}=", true
24
- end
25
-
26
- def toggled(instance)
27
- instance.send "#{@column}"
28
- end
29
-
30
- def reset_query(model)
31
- "UPDATE #{model.quoted_table_name} SET " +
32
- "#{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(false)} " +
33
- "WHERE #{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(true)}"
34
- end
35
-
36
- def clause(model, toggled)
37
- "#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
38
- " = #{adapter.boolean(toggled)}"
39
- end
40
-
41
- private
42
-
43
- def update_delta_indexes(model)
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?
49
- end
50
-
51
- def delete_from_core(model, instance)
52
- model.core_index_names.each do |index_name|
53
- model.delete_in_index index_name, instance.sphinx_document_id
54
- end
55
- end
56
-
57
- def adapter
58
- @adapter = @index.model.sphinx_database_adapter
59
- end
60
- end
61
- end
62
- end
@@ -1,101 +0,0 @@
1
- Capistrano::Configuration.instance(:must_exist).load do
2
- namespace :thinking_sphinx do
3
- namespace :install do
4
- desc <<-DESC
5
- Install Sphinx by source
6
-
7
- If Postgres is available, Sphinx will use it.
8
-
9
- If the variable :thinking_sphinx_configure_args is set, it will
10
- be passed to the Sphinx configure script. You can use this to
11
- install Sphinx in a non-standard location:
12
-
13
- set :thinking_sphinx_configure_args, "--prefix=$HOME/software"
14
- DESC
15
-
16
- task :sphinx do
17
- with_postgres = false
18
- begin
19
- run "which pg_config" do |channel, stream, data|
20
- with_postgres = !(data.nil? || data == "")
21
- end
22
- rescue Capistrano::CommandError => e
23
- puts "Continuing despite error: #{e.message}"
24
- end
25
-
26
- args = []
27
- if with_postgres
28
- run "pg_config --pkgincludedir" do |channel, stream, data|
29
- args << "--with-pgsql=#{data}"
30
- end
31
- end
32
- args << fetch(:thinking_sphinx_configure_args, '')
33
-
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
38
- ./configure #{args.join(" ")}
39
- make
40
- #{try_sudo} make install
41
- rm -rf sphinx-0.9.9 sphinx-0.9.9.tar.gz
42
- CMD
43
- run commands.split(/\n\s+/).join(" && ")
44
- end
45
-
46
- desc "Install Thinking Sphinx as a gem"
47
- task :ts do
48
- run "#{try_sudo} gem install thinking-sphinx"
49
- end
50
- end
51
-
52
- desc "Generate the Sphinx configuration file"
53
- task :configure do
54
- rake "thinking_sphinx:configure"
55
- end
56
-
57
- desc "Index data"
58
- task :index do
59
- rake "thinking_sphinx:index"
60
- end
61
-
62
- desc "Start the Sphinx daemon"
63
- task :start do
64
- configure
65
- rake "thinking_sphinx:start"
66
- end
67
-
68
- desc "Stop the Sphinx daemon"
69
- task :stop do
70
- configure
71
- rake "thinking_sphinx:stop"
72
- end
73
-
74
- desc "Stop and then start the Sphinx daemon"
75
- task :restart do
76
- stop
77
- start
78
- end
79
-
80
- desc "Stop, re-index and then start the Sphinx daemon"
81
- task :rebuild do
82
- stop
83
- index
84
- start
85
- end
86
-
87
- desc "Add the shared folder for sphinx files"
88
- task :shared_sphinx_folder, :roles => :web do
89
- rails_env = fetch(:rails_env, "production")
90
- run "mkdir -p #{shared_path}/sphinx/#{rails_env}"
91
- end
92
-
93
- def rake(*tasks)
94
- rails_env = fetch(:rails_env, "production")
95
- rake = fetch(:rake, "rake")
96
- tasks.each do |t|
97
- run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; #{rake} RAILS_ENV=#{rails_env} #{t}"
98
- end
99
- end
100
- end
101
- end
@@ -1,23 +0,0 @@
1
- module ThinkingSphinx
2
- class Excerpter
3
- CoreMethods = %w( kind_of? object_id respond_to? respond_to_missing? should
4
- should_not stub! )
5
- # Hide most methods, to allow them to be passed through to the instance.
6
- instance_methods.select { |method|
7
- method.to_s[/^__/].nil? && !CoreMethods.include?(method.to_s)
8
- }.each { |method|
9
- undef_method method
10
- }
11
-
12
- def initialize(search, instance)
13
- @search = search
14
- @instance = instance
15
- end
16
-
17
- def method_missing(method, *args, &block)
18
- string = @instance.send(method, *args, &block).to_s
19
-
20
- @search.excerpt_for(string, @instance.class)
21
- end
22
- end
23
- end
@@ -1,128 +0,0 @@
1
- module ThinkingSphinx
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
-
9
- if property.columns.length != 1
10
- raise "Can't translate Facets on multiple-column field or attribute"
11
- end
12
- end
13
-
14
- def self.name_for(facet)
15
- case facet
16
- when Facet
17
- facet.name
18
- when String, Symbol
19
- return :class if facet.to_s == 'sphinx_internal_class'
20
- facet.to_s.gsub(/(_facet|_crc)$/,'').to_sym
21
- end
22
- end
23
-
24
- def self.attribute_name_for(name)
25
- name.to_s == 'class' ? 'class_crc' : "#{name}_facet"
26
- end
27
-
28
- def self.attribute_name_from_value(name, value)
29
- case value
30
- when String
31
- attribute_name_for(name)
32
- when Array
33
- if value.all? { |val| val.is_a?(Integer) }
34
- name
35
- else
36
- attribute_name_for(name)
37
- end
38
- else
39
- name
40
- end
41
- end
42
-
43
- def self.translate?(property)
44
- return true if property.is_a?(Field)
45
-
46
- case property.type
47
- when :string
48
- true
49
- when :integer, :boolean, :datetime, :float
50
- false
51
- when :multi
52
- !property.all_ints?
53
- end
54
- end
55
-
56
- def name
57
- property.unique_name
58
- end
59
-
60
- def attribute_name
61
- if translate?
62
- Facet.attribute_name_for(@property.unique_name)
63
- else
64
- @property.unique_name.to_s
65
- end
66
- end
67
-
68
- def translate?
69
- Facet.translate?(@property)
70
- end
71
-
72
- def type
73
- @property.is_a?(Field) ? :string : @property.type
74
- end
75
-
76
- def float?
77
- @property.type == :float
78
- end
79
-
80
- def value(object, attribute_hash)
81
- attribute_value = attribute_hash['@groupby']
82
- return translate(object, attribute_value) if translate? || float?
83
-
84
- case @property.type
85
- when :datetime
86
- Time.at(attribute_value)
87
- when :boolean
88
- attribute_value > 0
89
- else
90
- attribute_value
91
- end
92
- end
93
-
94
- def to_s
95
- name
96
- end
97
-
98
- private
99
-
100
- def translate(object, attribute_value)
101
- 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
- result && result.to_crc32 == attribute_value
108
- }
109
-
110
- object.try(method)
111
- end
112
-
113
- def source_objects(object)
114
- column.__stack.each { |method|
115
- object = Array(object).collect { |item|
116
- item.send(method)
117
- }.flatten.compact
118
-
119
- return nil if object.empty?
120
- }
121
- Array(object)
122
- end
123
-
124
- def column
125
- @property.columns.first
126
- end
127
- end
128
- end
@@ -1,170 +0,0 @@
1
- module ThinkingSphinx
2
- class FacetSearch < Hash
3
- attr_accessor :args, :options
4
-
5
- def initialize(*args)
6
- ThinkingSphinx.context.define_indexes
7
-
8
- @options = args.extract_options!
9
- @args = args
10
-
11
- set_default_options
12
-
13
- populate
14
- end
15
-
16
- def for(hash = {})
17
- for_options = {:with => {}}.merge(options)
18
-
19
- hash.each do |key, value|
20
- attrib = ThinkingSphinx::Facet.attribute_name_from_value(key, value)
21
- for_options[:with][attrib] = underlying_value key, value
22
- end
23
-
24
- ThinkingSphinx.search *(args + [for_options])
25
- end
26
-
27
- def facet_names
28
- @facet_names ||= begin
29
- names = options[:all_facets] ?
30
- facet_names_for_all_classes : facet_names_common_to_all_classes
31
-
32
- names.delete class_facet unless options[:class_facet]
33
- names
34
- end
35
- end
36
-
37
- private
38
-
39
- def set_default_options
40
- options[:all_facets] ||= false
41
- if options[:class_facet].nil?
42
- options[:class_facet] = ((options[:classes] || []).length != 1)
43
- end
44
- end
45
-
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
- }
54
- end
55
-
56
- def facet_search_options(facet_name)
57
- options.merge(
58
- :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)
64
- )
65
- end
66
-
67
- def facet_classes
68
- (
69
- options[:classes] || ThinkingSphinx.context.indexed_models.collect { |model|
70
- model.constantize
71
- }
72
- ).select { |klass| klass.sphinx_facets.any? }
73
- end
74
-
75
- def all_facets
76
- facet_classes.collect { |klass|
77
- klass.sphinx_facets
78
- }.flatten.select { |facet|
79
- options[:facets].blank? || Array(options[:facets]).include?(facet.name)
80
- }
81
- end
82
-
83
- def facet_names_for_all_classes
84
- all_facets.group_by { |facet|
85
- facet.name
86
- }.collect { |name, facets|
87
- if facets.collect { |facet| facet.type }.uniq.length > 1
88
- raise "Facet #{name} exists in more than one model with different types"
89
- end
90
- facets.first.attribute_name
91
- }
92
- end
93
-
94
- def facet_names_common_to_all_classes
95
- facet_names_for_all_classes.select { |name|
96
- facet_classes.all? { |klass|
97
- klass.sphinx_facets.detect { |facet|
98
- facet.attribute_name == name
99
- }
100
- }
101
- }
102
- end
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)
121
-
122
- self[name] ||= {}
123
-
124
- return if search.empty?
125
-
126
- search.each_with_match do |result, match|
127
- facet_value = facet.value(result, match[:attributes])
128
-
129
- self[name][facet_value] ||= 0
130
- self[name][facet_value] += match[:attributes]["@count"]
131
- end
132
- end
133
-
134
- def underlying_value(key, value)
135
- case value
136
- when Array
137
- value.collect { |item| underlying_value(key, item) }
138
- when String
139
- value.to_crc32
140
- else
141
- value
142
- end
143
- end
144
-
145
- 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'
168
- end
169
- end
170
- end