ebeigarts-thinking-sphinx 1.1.21
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 +143 -0
- data/lib/thinking_sphinx.rb +217 -0
- data/lib/thinking_sphinx/active_record.rb +278 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
- data/lib/thinking_sphinx/active_record/delta.rb +87 -0
- data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
- data/lib/thinking_sphinx/active_record/search.rb +57 -0
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +53 -0
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +135 -0
- data/lib/thinking_sphinx/association.rb +164 -0
- data/lib/thinking_sphinx/attribute.rb +269 -0
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/collection.rb +148 -0
- data/lib/thinking_sphinx/configuration.rb +275 -0
- data/lib/thinking_sphinx/core/string.rb +15 -0
- data/lib/thinking_sphinx/deltas.rb +30 -0
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
- data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +27 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
- data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
- data/lib/thinking_sphinx/deploy/capistrano.rb +82 -0
- data/lib/thinking_sphinx/facet.rb +108 -0
- data/lib/thinking_sphinx/facet_collection.rb +59 -0
- data/lib/thinking_sphinx/field.rb +82 -0
- data/lib/thinking_sphinx/index.rb +99 -0
- data/lib/thinking_sphinx/index/builder.rb +287 -0
- data/lib/thinking_sphinx/index/faux_column.rb +110 -0
- data/lib/thinking_sphinx/property.rb +160 -0
- data/lib/thinking_sphinx/rails_additions.rb +136 -0
- data/lib/thinking_sphinx/search.rb +727 -0
- data/lib/thinking_sphinx/search/facets.rb +104 -0
- data/lib/thinking_sphinx/source.rb +175 -0
- data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
- data/lib/thinking_sphinx/source/sql.rb +126 -0
- data/lib/thinking_sphinx/tasks.rb +245 -0
- data/rails/init.rb +14 -0
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
- data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
- data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
- data/spec/unit/thinking_sphinx/active_record_spec.rb +329 -0
- data/spec/unit/thinking_sphinx/association_spec.rb +246 -0
- data/spec/unit/thinking_sphinx/attribute_spec.rb +338 -0
- data/spec/unit/thinking_sphinx/collection_spec.rb +15 -0
- data/spec/unit/thinking_sphinx/configuration_spec.rb +222 -0
- data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
- data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
- data/spec/unit/thinking_sphinx/facet_spec.rb +302 -0
- data/spec/unit/thinking_sphinx/field_spec.rb +154 -0
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +355 -0
- data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +45 -0
- data/spec/unit/thinking_sphinx/rails_additions_spec.rb +191 -0
- data/spec/unit/thinking_sphinx/search_spec.rb +228 -0
- data/spec/unit/thinking_sphinx/source_spec.rb +217 -0
- data/spec/unit/thinking_sphinx_spec.rb +151 -0
- data/tasks/distribution.rb +67 -0
- data/tasks/rails.rake +1 -0
- data/tasks/testing.rb +100 -0
- data/vendor/after_commit/LICENSE +20 -0
- data/vendor/after_commit/README +16 -0
- data/vendor/after_commit/Rakefile +22 -0
- data/vendor/after_commit/init.rb +8 -0
- data/vendor/after_commit/lib/after_commit.rb +45 -0
- data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
- data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
- data/vendor/after_commit/test/after_commit_test.rb +53 -0
- data/vendor/delayed_job/lib/delayed/job.rb +251 -0
- data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
- data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
- data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
- data/vendor/riddle/lib/riddle.rb +30 -0
- data/vendor/riddle/lib/riddle/client.rb +619 -0
- data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
- data/vendor/riddle/lib/riddle/client/message.rb +65 -0
- data/vendor/riddle/lib/riddle/client/response.rb +84 -0
- data/vendor/riddle/lib/riddle/configuration.rb +33 -0
- data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
- data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
- data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
- data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
- data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
- data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
- data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
- data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
- data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
- data/vendor/riddle/lib/riddle/controller.rb +44 -0
- metadata +191 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module Deltas
|
|
3
|
+
class DatetimeDelta < ThinkingSphinx::Deltas::DefaultDelta
|
|
4
|
+
attr_accessor :column, :threshold
|
|
5
|
+
|
|
6
|
+
def initialize(index, options)
|
|
7
|
+
@index = index
|
|
8
|
+
@column = options.delete(:delta_column) || :updated_at
|
|
9
|
+
@threshold = options.delete(:threshold) || 1.day
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def index(model, instance = nil)
|
|
13
|
+
# do nothing
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def delayed_index(model)
|
|
18
|
+
config = ThinkingSphinx::Configuration.instance
|
|
19
|
+
rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
|
|
20
|
+
|
|
21
|
+
output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
|
|
22
|
+
output += `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} --merge #{core_index_name model} #{delta_index_name model} --merge-dst-range sphinx_deleted 0 0`
|
|
23
|
+
puts output unless ThinkingSphinx.suppress_delta_output?
|
|
24
|
+
|
|
25
|
+
true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def toggle(instance)
|
|
29
|
+
# do nothing
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def toggled(instance)
|
|
33
|
+
instance.send(@column) > @threshold.ago
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reset_query(model)
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def clause(model, toggled)
|
|
41
|
+
if toggled
|
|
42
|
+
"#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
|
|
43
|
+
" > #{adapter.time_difference(@threshold)}"
|
|
44
|
+
else
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
config = ThinkingSphinx::Configuration.instance
|
|
17
|
+
client = Riddle::Client.new config.address, config.port
|
|
18
|
+
rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
|
|
19
|
+
|
|
20
|
+
output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} #{rotate} #{delta_index_name model}`
|
|
21
|
+
puts(output) unless ThinkingSphinx.suppress_delta_output?
|
|
22
|
+
|
|
23
|
+
client.update(
|
|
24
|
+
core_index_name(model),
|
|
25
|
+
['sphinx_deleted'],
|
|
26
|
+
{instance.sphinx_document_id => [1]}
|
|
27
|
+
) if instance && ThinkingSphinx.sphinx_running? && instance.in_both_indexes?
|
|
28
|
+
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def toggle(instance)
|
|
33
|
+
instance.delta = true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def toggled(instance)
|
|
37
|
+
instance.delta
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reset_query(model)
|
|
41
|
+
"UPDATE #{model.quoted_table_name} SET " +
|
|
42
|
+
"#{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(false)} " +
|
|
43
|
+
"WHERE #{model.connection.quote_column_name(@column.to_s)} = #{adapter.boolean(true)}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def clause(model, toggled)
|
|
47
|
+
"#{model.quoted_table_name}.#{model.connection.quote_column_name(@column.to_s)}" +
|
|
48
|
+
" = #{adapter.boolean(toggled)}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
protected
|
|
52
|
+
|
|
53
|
+
def core_index_name(model)
|
|
54
|
+
"#{model.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_core"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def delta_index_name(model)
|
|
58
|
+
"#{model.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_delta"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def adapter
|
|
64
|
+
@adapter = @index.model.sphinx_database_adapter
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'delayed/job'
|
|
2
|
+
|
|
3
|
+
require 'thinking_sphinx/deltas/delayed_delta/delta_job'
|
|
4
|
+
require 'thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job'
|
|
5
|
+
require 'thinking_sphinx/deltas/delayed_delta/job'
|
|
6
|
+
|
|
7
|
+
module ThinkingSphinx
|
|
8
|
+
module Deltas
|
|
9
|
+
class DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta
|
|
10
|
+
def index(model, instance = nil)
|
|
11
|
+
ThinkingSphinx::Deltas::Job.enqueue(
|
|
12
|
+
ThinkingSphinx::Deltas::DeltaJob.new(delta_index_name(model)),
|
|
13
|
+
ThinkingSphinx::Configuration.instance.delayed_job_priority
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
Delayed::Job.enqueue(
|
|
17
|
+
ThinkingSphinx::Deltas::FlagAsDeletedJob.new(
|
|
18
|
+
core_index_name(model), instance.sphinx_document_id
|
|
19
|
+
),
|
|
20
|
+
ThinkingSphinx::Configuration.instance.delayed_job_priority
|
|
21
|
+
) if instance
|
|
22
|
+
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module Deltas
|
|
3
|
+
class DeltaJob
|
|
4
|
+
attr_accessor :index
|
|
5
|
+
|
|
6
|
+
def initialize(index)
|
|
7
|
+
@index = index
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def perform
|
|
11
|
+
return true unless ThinkingSphinx.updates_enabled? &&
|
|
12
|
+
ThinkingSphinx.deltas_enabled?
|
|
13
|
+
|
|
14
|
+
config = ThinkingSphinx::Configuration.instance
|
|
15
|
+
client = Riddle::Client.new config.address, config.port
|
|
16
|
+
|
|
17
|
+
output = `#{config.bin_path}#{config.indexer_binary_name} --config #{config.config_file} --rotate #{index}`
|
|
18
|
+
puts output unless ThinkingSphinx.suppress_delta_output?
|
|
19
|
+
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module Deltas
|
|
3
|
+
class FlagAsDeletedJob
|
|
4
|
+
attr_accessor :index, :document_id
|
|
5
|
+
|
|
6
|
+
def initialize(index, document_id)
|
|
7
|
+
@index, @document_id = index, document_id
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def perform
|
|
11
|
+
return true unless ThinkingSphinx.updates_enabled?
|
|
12
|
+
|
|
13
|
+
config = ThinkingSphinx::Configuration.instance
|
|
14
|
+
client = Riddle::Client.new config.address, config.port
|
|
15
|
+
|
|
16
|
+
client.update(
|
|
17
|
+
@index,
|
|
18
|
+
['sphinx_deleted'],
|
|
19
|
+
{@document_id => [1]}
|
|
20
|
+
) if ThinkingSphinx.sphinx_running? &&
|
|
21
|
+
ThinkingSphinx::Search.search_for_id(@document_id, @index)
|
|
22
|
+
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
module Deltas
|
|
3
|
+
class Job < Delayed::Job
|
|
4
|
+
def self.enqueue(object, priority = 0)
|
|
5
|
+
super unless duplicates_exist(object)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.cancel_thinking_sphinx_jobs
|
|
9
|
+
if connection.tables.include?("delayed_jobs")
|
|
10
|
+
delete_all("handler LIKE '--- !ruby/object:ThinkingSphinx::Deltas::%'")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def self.duplicates_exist(object)
|
|
17
|
+
count(
|
|
18
|
+
:conditions => {
|
|
19
|
+
:handler => object.to_yaml,
|
|
20
|
+
:locked_at => nil
|
|
21
|
+
}
|
|
22
|
+
) > 0
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
|
2
|
+
namespace :thinking_sphinx do
|
|
3
|
+
namespace :install do
|
|
4
|
+
desc "Install Sphinx by source"
|
|
5
|
+
task :sphinx do
|
|
6
|
+
with_postgres = false
|
|
7
|
+
run "which pg_config" do |channel, stream, data|
|
|
8
|
+
with_postgres = !(data.nil? || data == "")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
args = []
|
|
12
|
+
if with_postgres
|
|
13
|
+
run "pg_config --pkgincludedir" do |channel, stream, data|
|
|
14
|
+
args << "--with-pgsql=#{data}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
commands = <<-CMD
|
|
19
|
+
wget -q http://www.sphinxsearch.com/downloads/sphinx-0.9.8.1.tar.gz >> sphinx.log
|
|
20
|
+
tar xzvf sphinx-0.9.8.1.tar.gz
|
|
21
|
+
cd sphinx-0.9.8.1
|
|
22
|
+
./configure #{args.join(" ")}
|
|
23
|
+
make
|
|
24
|
+
sudo make install
|
|
25
|
+
rm -rf sphinx-0.9.8.1 sphinx-0.9.8.1.tar.gz
|
|
26
|
+
CMD
|
|
27
|
+
run commands.split(/\n\s+/).join(" && ")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc "Install Thinking Sphinx as a gem from GitHub"
|
|
31
|
+
task :ts do
|
|
32
|
+
sudo "gem install freelancing-god-thinking-sphinx --source http://gems.github.com"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Generate the Sphinx configuration file"
|
|
37
|
+
task :configure do
|
|
38
|
+
rake "thinking_sphinx:configure"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
desc "Index data"
|
|
42
|
+
task :index do
|
|
43
|
+
rake "thinking_sphinx:index"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "Start the Sphinx daemon"
|
|
47
|
+
task :start do
|
|
48
|
+
configure
|
|
49
|
+
rake "thinking_sphinx:start"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "Stop the Sphinx daemon"
|
|
53
|
+
task :stop do
|
|
54
|
+
configure
|
|
55
|
+
rake "thinking_sphinx:stop"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
desc "Stop and then start the Sphinx daemon"
|
|
59
|
+
task :restart do
|
|
60
|
+
stop
|
|
61
|
+
start
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
desc "Stop, re-index and then start the Sphinx daemon"
|
|
65
|
+
task :rebuild do
|
|
66
|
+
stop
|
|
67
|
+
index
|
|
68
|
+
start
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
desc "Add the shared folder for sphinx files for the production environment"
|
|
72
|
+
task :shared_sphinx_folder, :roles => :web do
|
|
73
|
+
sudo "mkdir -p #{shared_path}/db/sphinx/production"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def rake(*tasks)
|
|
77
|
+
tasks.each do |t|
|
|
78
|
+
run "cd #{current_path} && rake #{t} RAILS_ENV=production"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
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?
|
|
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
|
+
column.__stack.each { |method|
|
|
95
|
+
return nil unless object = object.send(method)
|
|
96
|
+
}
|
|
97
|
+
if object.is_a?(Array)
|
|
98
|
+
object.collect { |item| item.send(column.__name) }
|
|
99
|
+
else
|
|
100
|
+
object.send(column.__name)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def column
|
|
105
|
+
@property.columns.first
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
class FacetCollection < Hash
|
|
3
|
+
attr_accessor :arguments
|
|
4
|
+
|
|
5
|
+
def initialize(arguments)
|
|
6
|
+
@arguments = arguments.clone
|
|
7
|
+
@facet_names = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_from_results(facet, results)
|
|
11
|
+
name = ThinkingSphinx::Facet.name_for(facet)
|
|
12
|
+
|
|
13
|
+
self[name] ||= {}
|
|
14
|
+
@facet_names << name
|
|
15
|
+
|
|
16
|
+
return if results.empty?
|
|
17
|
+
|
|
18
|
+
facet = facet_from_object(results.first, facet) if facet.is_a?(String)
|
|
19
|
+
|
|
20
|
+
results.each_with_groupby_and_count { |result, group, count|
|
|
21
|
+
facet_value = facet.value(result, group)
|
|
22
|
+
|
|
23
|
+
self[name][facet_value] ||= 0
|
|
24
|
+
self[name][facet_value] += count
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def for(hash = {})
|
|
29
|
+
arguments = @arguments.clone
|
|
30
|
+
options = arguments.extract_options!
|
|
31
|
+
options[:with] ||= {}
|
|
32
|
+
|
|
33
|
+
hash.each do |key, value|
|
|
34
|
+
attrib = ThinkingSphinx::Facet.attribute_name_from_value(key, value)
|
|
35
|
+
options[:with][attrib] = underlying_value key, value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
arguments << options
|
|
39
|
+
ThinkingSphinx::Search.search *arguments
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def underlying_value(key, value)
|
|
45
|
+
case value
|
|
46
|
+
when Array
|
|
47
|
+
value.collect { |item| underlying_value(key, item) }
|
|
48
|
+
when String
|
|
49
|
+
value.to_crc32
|
|
50
|
+
else
|
|
51
|
+
value
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def facet_from_object(object, name)
|
|
56
|
+
object.sphinx_facets.detect { |facet| facet.attribute_name == name }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|