thinking-sphinx 2.0.5 → 2.0.6

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 (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