angelf-thinking-sphinx 1.3.18
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.
- data/LICENCE +20 -0
- data/README.textile +170 -0
- data/VERSION +1 -0
- data/features/abstract_inheritance.feature +10 -0
- data/features/alternate_primary_key.feature +27 -0
- data/features/attribute_transformation.feature +22 -0
- data/features/attribute_updates.feature +77 -0
- data/features/deleting_instances.feature +67 -0
- data/features/direct_attributes.feature +11 -0
- data/features/excerpts.feature +13 -0
- data/features/extensible_delta_indexing.feature +9 -0
- data/features/facets.feature +90 -0
- data/features/facets_across_model.feature +29 -0
- data/features/handling_edits.feature +92 -0
- data/features/retry_stale_indexes.feature +24 -0
- data/features/searching_across_models.feature +20 -0
- data/features/searching_by_index.feature +40 -0
- data/features/searching_by_model.feature +175 -0
- data/features/searching_with_find_arguments.feature +56 -0
- data/features/sphinx_detection.feature +25 -0
- data/features/sphinx_scopes.feature +42 -0
- data/features/step_definitions/alpha_steps.rb +16 -0
- data/features/step_definitions/beta_steps.rb +7 -0
- data/features/step_definitions/common_steps.rb +193 -0
- data/features/step_definitions/extensible_delta_indexing_steps.rb +7 -0
- data/features/step_definitions/facet_steps.rb +96 -0
- data/features/step_definitions/find_arguments_steps.rb +36 -0
- data/features/step_definitions/gamma_steps.rb +15 -0
- data/features/step_definitions/scope_steps.rb +15 -0
- data/features/step_definitions/search_steps.rb +89 -0
- data/features/step_definitions/sphinx_steps.rb +35 -0
- data/features/sti_searching.feature +19 -0
- data/features/support/env.rb +21 -0
- data/features/support/lib/generic_delta_handler.rb +8 -0
- data/features/thinking_sphinx/database.example.yml +3 -0
- data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -0
- data/features/thinking_sphinx/db/fixtures/authors.rb +1 -0
- data/features/thinking_sphinx/db/fixtures/betas.rb +11 -0
- data/features/thinking_sphinx/db/fixtures/boxes.rb +9 -0
- data/features/thinking_sphinx/db/fixtures/categories.rb +1 -0
- data/features/thinking_sphinx/db/fixtures/cats.rb +3 -0
- data/features/thinking_sphinx/db/fixtures/comments.rb +24 -0
- data/features/thinking_sphinx/db/fixtures/developers.rb +31 -0
- data/features/thinking_sphinx/db/fixtures/dogs.rb +3 -0
- data/features/thinking_sphinx/db/fixtures/extensible_betas.rb +10 -0
- data/features/thinking_sphinx/db/fixtures/foxes.rb +3 -0
- data/features/thinking_sphinx/db/fixtures/gammas.rb +10 -0
- data/features/thinking_sphinx/db/fixtures/music.rb +4 -0
- data/features/thinking_sphinx/db/fixtures/people.rb +1001 -0
- data/features/thinking_sphinx/db/fixtures/posts.rb +6 -0
- data/features/thinking_sphinx/db/fixtures/robots.rb +14 -0
- data/features/thinking_sphinx/db/fixtures/tags.rb +27 -0
- data/features/thinking_sphinx/db/migrations/create_alphas.rb +8 -0
- data/features/thinking_sphinx/db/migrations/create_animals.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_authors.rb +3 -0
- data/features/thinking_sphinx/db/migrations/create_authors_posts.rb +6 -0
- data/features/thinking_sphinx/db/migrations/create_betas.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_boxes.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_categories.rb +3 -0
- data/features/thinking_sphinx/db/migrations/create_comments.rb +10 -0
- data/features/thinking_sphinx/db/migrations/create_developers.rb +7 -0
- data/features/thinking_sphinx/db/migrations/create_extensible_betas.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_gammas.rb +3 -0
- data/features/thinking_sphinx/db/migrations/create_genres.rb +3 -0
- data/features/thinking_sphinx/db/migrations/create_music.rb +6 -0
- data/features/thinking_sphinx/db/migrations/create_people.rb +13 -0
- data/features/thinking_sphinx/db/migrations/create_posts.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_robots.rb +4 -0
- data/features/thinking_sphinx/db/migrations/create_taggings.rb +5 -0
- data/features/thinking_sphinx/db/migrations/create_tags.rb +4 -0
- data/features/thinking_sphinx/models/alpha.rb +22 -0
- data/features/thinking_sphinx/models/animal.rb +5 -0
- data/features/thinking_sphinx/models/author.rb +3 -0
- data/features/thinking_sphinx/models/beta.rb +8 -0
- data/features/thinking_sphinx/models/box.rb +8 -0
- data/features/thinking_sphinx/models/cat.rb +3 -0
- data/features/thinking_sphinx/models/category.rb +4 -0
- data/features/thinking_sphinx/models/comment.rb +10 -0
- data/features/thinking_sphinx/models/developer.rb +16 -0
- data/features/thinking_sphinx/models/dog.rb +3 -0
- data/features/thinking_sphinx/models/extensible_beta.rb +9 -0
- data/features/thinking_sphinx/models/fox.rb +5 -0
- data/features/thinking_sphinx/models/gamma.rb +5 -0
- data/features/thinking_sphinx/models/genre.rb +3 -0
- data/features/thinking_sphinx/models/medium.rb +5 -0
- data/features/thinking_sphinx/models/music.rb +8 -0
- data/features/thinking_sphinx/models/person.rb +23 -0
- data/features/thinking_sphinx/models/post.rb +21 -0
- data/features/thinking_sphinx/models/robot.rb +12 -0
- data/features/thinking_sphinx/models/tag.rb +3 -0
- data/features/thinking_sphinx/models/tagging.rb +4 -0
- data/lib/cucumber/thinking_sphinx/external_world.rb +8 -0
- data/lib/cucumber/thinking_sphinx/internal_world.rb +127 -0
- data/lib/cucumber/thinking_sphinx/sql_logger.rb +20 -0
- data/lib/thinking_sphinx.rb +242 -0
- data/lib/thinking_sphinx/active_record.rb +380 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +50 -0
- data/lib/thinking_sphinx/active_record/delta.rb +61 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +51 -0
- data/lib/thinking_sphinx/active_record/scopes.rb +75 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +47 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +58 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +147 -0
- data/lib/thinking_sphinx/association.rb +164 -0
- data/lib/thinking_sphinx/attribute.rb +380 -0
- data/lib/thinking_sphinx/auto_version.rb +22 -0
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/configuration.rb +292 -0
- data/lib/thinking_sphinx/context.rb +74 -0
- data/lib/thinking_sphinx/core/array.rb +7 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas.rb +28 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +62 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +100 -0
- data/lib/thinking_sphinx/excerpter.rb +22 -0
- data/lib/thinking_sphinx/facet.rb +125 -0
- data/lib/thinking_sphinx/facet_search.rb +146 -0
- data/lib/thinking_sphinx/field.rb +80 -0
- data/lib/thinking_sphinx/index.rb +157 -0
- data/lib/thinking_sphinx/index/builder.rb +302 -0
- data/lib/thinking_sphinx/index/faux_column.rb +118 -0
- data/lib/thinking_sphinx/join.rb +37 -0
- data/lib/thinking_sphinx/property.rb +168 -0
- data/lib/thinking_sphinx/rails_additions.rb +150 -0
- data/lib/thinking_sphinx/search.rb +785 -0
- data/lib/thinking_sphinx/search_methods.rb +439 -0
- data/lib/thinking_sphinx/source.rb +164 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
- data/lib/thinking_sphinx/source/sql.rb +130 -0
- data/lib/thinking_sphinx/tasks.rb +121 -0
- data/lib/thinking_sphinx/test.rb +55 -0
- data/rails/init.rb +16 -0
- data/spec/thinking_sphinx/active_record/delta_spec.rb +128 -0
- data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +71 -0
- data/spec/thinking_sphinx/active_record/scopes_spec.rb +177 -0
- data/spec/thinking_sphinx/active_record_spec.rb +618 -0
- data/spec/thinking_sphinx/association_spec.rb +239 -0
- data/spec/thinking_sphinx/attribute_spec.rb +548 -0
- data/spec/thinking_sphinx/auto_version_spec.rb +39 -0
- data/spec/thinking_sphinx/configuration_spec.rb +271 -0
- data/spec/thinking_sphinx/context_spec.rb +126 -0
- data/spec/thinking_sphinx/core/array_spec.rb +9 -0
- data/spec/thinking_sphinx/core/string_spec.rb +9 -0
- data/spec/thinking_sphinx/excerpter_spec.rb +49 -0
- data/spec/thinking_sphinx/facet_search_spec.rb +176 -0
- data/spec/thinking_sphinx/facet_spec.rb +333 -0
- data/spec/thinking_sphinx/field_spec.rb +113 -0
- data/spec/thinking_sphinx/index/builder_spec.rb +495 -0
- data/spec/thinking_sphinx/index/faux_column_spec.rb +36 -0
- data/spec/thinking_sphinx/index_spec.rb +183 -0
- data/spec/thinking_sphinx/rails_additions_spec.rb +203 -0
- data/spec/thinking_sphinx/search_methods_spec.rb +152 -0
- data/spec/thinking_sphinx/search_spec.rb +1206 -0
- data/spec/thinking_sphinx/source_spec.rb +243 -0
- data/spec/thinking_sphinx_spec.rb +204 -0
- data/tasks/distribution.rb +46 -0
- data/tasks/rails.rake +1 -0
- data/tasks/testing.rb +76 -0
- metadata +342 -0
|
@@ -0,0 +1,62 @@
|
|
|
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.delta = true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def toggled(instance)
|
|
27
|
+
instance.delta
|
|
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
|
|
@@ -0,0 +1,100 @@
|
|
|
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://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
|
+
./configure #{args.join(" ")}
|
|
39
|
+
make
|
|
40
|
+
#{try_sudo} make install
|
|
41
|
+
rm -rf sphinx-0.9.8.1 sphinx-0.9.8.1.tar.gz
|
|
42
|
+
CMD
|
|
43
|
+
run commands.split(/\n\s+/).join(" && ")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "Install Thinking Sphinx as a gem from GitHub"
|
|
47
|
+
task :ts do
|
|
48
|
+
run "#{try_sudo} gem install thinking-sphinx --source http://gemcutter.org"
|
|
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 for the production environment"
|
|
88
|
+
task :shared_sphinx_folder, :roles => :web do
|
|
89
|
+
run "mkdir -p #{shared_path}/db/sphinx/production"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def rake(*tasks)
|
|
93
|
+
rails_env = fetch(:rails_env, "production")
|
|
94
|
+
rake = fetch(:rake, "rake")
|
|
95
|
+
tasks.each do |t|
|
|
96
|
+
run "if [ -d #{release_path} ]; then cd #{release_path}; else cd #{current_path}; fi; #{rake} RAILS_ENV=#{rails_env} #{t}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class Excerpter
|
|
3
|
+
CoreMethods = %w( kind_of? object_id respond_to? should should_not stub! )
|
|
4
|
+
# Hide most methods, to allow them to be passed through to the instance.
|
|
5
|
+
instance_methods.select { |method|
|
|
6
|
+
method.to_s[/^__/].nil? && !CoreMethods.include?(method.to_s)
|
|
7
|
+
}.each { |method|
|
|
8
|
+
undef_method method
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def initialize(search, instance)
|
|
12
|
+
@search = search
|
|
13
|
+
@instance = instance
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def method_missing(method, *args, &block)
|
|
17
|
+
string = @instance.send(method, *args, &block).to_s
|
|
18
|
+
|
|
19
|
+
@search.excerpt_for(string, @instance.class)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class Facet
|
|
3
|
+
attr_reader :property
|
|
4
|
+
|
|
5
|
+
def initialize(property)
|
|
6
|
+
@property = property
|
|
7
|
+
|
|
8
|
+
if property.columns.length != 1
|
|
9
|
+
raise "Can't translate Facets on multiple-column field or attribute"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.name_for(facet)
|
|
14
|
+
case facet
|
|
15
|
+
when Facet
|
|
16
|
+
facet.name
|
|
17
|
+
when String, Symbol
|
|
18
|
+
facet.to_s.gsub(/(_facet|_crc)$/,'').to_sym
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.attribute_name_for(name)
|
|
23
|
+
name.to_s == 'class' ? 'class_crc' : "#{name}_facet"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.attribute_name_from_value(name, value)
|
|
27
|
+
case value
|
|
28
|
+
when String
|
|
29
|
+
attribute_name_for(name)
|
|
30
|
+
when Array
|
|
31
|
+
if value.all? { |val| val.is_a?(Integer) }
|
|
32
|
+
name
|
|
33
|
+
else
|
|
34
|
+
attribute_name_for(name)
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
name
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.translate?(property)
|
|
42
|
+
return true if property.is_a?(Field)
|
|
43
|
+
|
|
44
|
+
case property.type
|
|
45
|
+
when :string
|
|
46
|
+
true
|
|
47
|
+
when :integer, :boolean, :datetime, :float
|
|
48
|
+
false
|
|
49
|
+
when :multi
|
|
50
|
+
!property.all_ints?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def name
|
|
55
|
+
property.unique_name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def attribute_name
|
|
59
|
+
if translate?
|
|
60
|
+
Facet.attribute_name_for(@property.unique_name)
|
|
61
|
+
else
|
|
62
|
+
@property.unique_name.to_s
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def translate?
|
|
67
|
+
Facet.translate?(@property)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def type
|
|
71
|
+
@property.is_a?(Field) ? :string : @property.type
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def value(object, attribute_value)
|
|
75
|
+
return translate(object, attribute_value) if translate? || float?
|
|
76
|
+
|
|
77
|
+
case @property.type
|
|
78
|
+
when :datetime
|
|
79
|
+
Time.at(attribute_value)
|
|
80
|
+
when :boolean
|
|
81
|
+
attribute_value > 0
|
|
82
|
+
else
|
|
83
|
+
attribute_value
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def to_s
|
|
88
|
+
name
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def translate(object, attribute_value)
|
|
94
|
+
objects = source_objects(object)
|
|
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
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def source_objects(object)
|
|
107
|
+
column.__stack.each { |method|
|
|
108
|
+
object = Array(object).collect { |item|
|
|
109
|
+
item.send(method)
|
|
110
|
+
}.flatten.compact
|
|
111
|
+
|
|
112
|
+
return nil if object.empty?
|
|
113
|
+
}
|
|
114
|
+
Array(object)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def column
|
|
118
|
+
@property.columns.first
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def float?
|
|
122
|
+
@property.type == :float
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
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_crc" 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
|
+
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
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def facet_search_options
|
|
56
|
+
config = ThinkingSphinx::Configuration.instance
|
|
57
|
+
max = config.configuration.searchd.max_matches || 1000
|
|
58
|
+
|
|
59
|
+
options.merge(
|
|
60
|
+
:group_function => :attr,
|
|
61
|
+
:limit => max,
|
|
62
|
+
:max_matches => max,
|
|
63
|
+
:page => 1
|
|
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 add_from_results(facet, results)
|
|
105
|
+
name = ThinkingSphinx::Facet.name_for(facet)
|
|
106
|
+
|
|
107
|
+
self[name] ||= {}
|
|
108
|
+
|
|
109
|
+
return if results.empty?
|
|
110
|
+
|
|
111
|
+
facet = facet_from_object(results.first, facet) if facet.is_a?(String)
|
|
112
|
+
|
|
113
|
+
results.each_with_groupby_and_count { |result, group, count|
|
|
114
|
+
facet_value = facet.value(result, group)
|
|
115
|
+
|
|
116
|
+
self[name][facet_value] ||= 0
|
|
117
|
+
self[name][facet_value] += count
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def underlying_value(key, value)
|
|
122
|
+
case value
|
|
123
|
+
when Array
|
|
124
|
+
value.collect { |item| underlying_value(key, item) }
|
|
125
|
+
when String
|
|
126
|
+
value.to_crc32
|
|
127
|
+
else
|
|
128
|
+
value
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def facet_from_object(object, name)
|
|
133
|
+
facet = nil
|
|
134
|
+
klass = object.class
|
|
135
|
+
|
|
136
|
+
while klass != ::ActiveRecord::Base && facet.nil?
|
|
137
|
+
facet = klass.sphinx_facets.detect { |facet|
|
|
138
|
+
facet.attribute_name == name
|
|
139
|
+
}
|
|
140
|
+
klass = klass.superclass
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
facet
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|