initforthe-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 +141 -0
- data/lib/thinking_sphinx.rb +215 -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 +42 -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 +268 -0
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/collection.rb +148 -0
- data/lib/thinking_sphinx/configuration.rb +262 -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 +150 -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 +162 -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 +78 -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 +190 -0
|
@@ -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}indexer --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
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module ThinkingSphinx
|
|
2
|
+
# Fields - holding the string data which Sphinx indexes for your searches.
|
|
3
|
+
# This class isn't really useful to you unless you're hacking around with the
|
|
4
|
+
# internals of Thinking Sphinx - but hey, don't let that stop you.
|
|
5
|
+
#
|
|
6
|
+
# One key thing to remember - if you're using the field manually to
|
|
7
|
+
# generate SQL statements, you'll need to set the base model, and all the
|
|
8
|
+
# associations. Which can get messy. Use Index.link!, it really helps.
|
|
9
|
+
#
|
|
10
|
+
class Field < ThinkingSphinx::Property
|
|
11
|
+
attr_accessor :sortable, :infixes, :prefixes
|
|
12
|
+
|
|
13
|
+
# To create a new field, you'll need to pass in either a single Column
|
|
14
|
+
# or an array of them, and some (optional) options. The columns are
|
|
15
|
+
# references to the data that will make up the field.
|
|
16
|
+
#
|
|
17
|
+
# Valid options are:
|
|
18
|
+
# - :as => :alias_name
|
|
19
|
+
# - :sortable => true
|
|
20
|
+
# - :infixes => true
|
|
21
|
+
# - :prefixes => true
|
|
22
|
+
#
|
|
23
|
+
# Alias is only required in three circumstances: when there's
|
|
24
|
+
# another attribute or field with the same name, when the column name is
|
|
25
|
+
# 'id', or when there's more than one column.
|
|
26
|
+
#
|
|
27
|
+
# Sortable defaults to false - but is quite useful when set to true, as
|
|
28
|
+
# it creates an attribute with the same string value (which Sphinx converts
|
|
29
|
+
# to an integer value), which can be sorted by. Thinking Sphinx is smart
|
|
30
|
+
# enough to realise that when you specify fields in sort statements, you
|
|
31
|
+
# mean their respective attributes.
|
|
32
|
+
#
|
|
33
|
+
# If you have partial matching enabled (ie: enable_star), then you can
|
|
34
|
+
# specify certain fields to have their prefixes and infixes indexed. Keep
|
|
35
|
+
# in mind, though, that Sphinx's default is _all_ fields - so once you
|
|
36
|
+
# highlight a particular field, no other fields in the index will have
|
|
37
|
+
# these partial indexes.
|
|
38
|
+
#
|
|
39
|
+
# Here's some examples:
|
|
40
|
+
#
|
|
41
|
+
# Field.new(
|
|
42
|
+
# Column.new(:name)
|
|
43
|
+
# )
|
|
44
|
+
#
|
|
45
|
+
# Field.new(
|
|
46
|
+
# [Column.new(:first_name), Column.new(:last_name)],
|
|
47
|
+
# :as => :name, :sortable => true
|
|
48
|
+
# )
|
|
49
|
+
#
|
|
50
|
+
# Field.new(
|
|
51
|
+
# [Column.new(:posts, :subject), Column.new(:posts, :content)],
|
|
52
|
+
# :as => :posts, :prefixes => true
|
|
53
|
+
# )
|
|
54
|
+
#
|
|
55
|
+
def initialize(source, columns, options = {})
|
|
56
|
+
super
|
|
57
|
+
|
|
58
|
+
@sortable = options[:sortable] || false
|
|
59
|
+
@infixes = options[:infixes] || false
|
|
60
|
+
@prefixes = options[:prefixes] || false
|
|
61
|
+
|
|
62
|
+
source.fields << self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get the part of the SELECT clause related to this field. Don't forget
|
|
66
|
+
# to set your model and associations first though.
|
|
67
|
+
#
|
|
68
|
+
# This will concatenate strings if there's more than one data source or
|
|
69
|
+
# multiple data values (has_many or has_and_belongs_to_many associations).
|
|
70
|
+
#
|
|
71
|
+
def to_select_sql
|
|
72
|
+
clause = @columns.collect { |column|
|
|
73
|
+
column_with_prefix(column)
|
|
74
|
+
}.join(', ')
|
|
75
|
+
|
|
76
|
+
clause = adapter.concatenate(clause) if concat_ws?
|
|
77
|
+
clause = adapter.group_concatenate(clause) if is_many?
|
|
78
|
+
|
|
79
|
+
"#{adapter.cast_to_string clause } AS #{quote_column(unique_name)}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require 'thinking_sphinx/index/builder'
|
|
2
|
+
require 'thinking_sphinx/index/faux_column'
|
|
3
|
+
|
|
4
|
+
module ThinkingSphinx
|
|
5
|
+
# The Index class is a ruby representation of a Sphinx source (not a Sphinx
|
|
6
|
+
# index - yes, I know it's a little confusing. You'll manage). This is
|
|
7
|
+
# another 'internal' Thinking Sphinx class - if you're using it directly,
|
|
8
|
+
# you either know what you're doing, or messing with things beyond your ken.
|
|
9
|
+
# Enjoy.
|
|
10
|
+
#
|
|
11
|
+
class Index
|
|
12
|
+
attr_accessor :model, :sources, :delta_object
|
|
13
|
+
|
|
14
|
+
# Create a new index instance by passing in the model it is tied to, and
|
|
15
|
+
# a block to build it with (optional but recommended). For documentation
|
|
16
|
+
# on the syntax for inside the block, the Builder class is what you want.
|
|
17
|
+
#
|
|
18
|
+
# Quick Example:
|
|
19
|
+
#
|
|
20
|
+
# Index.new(User) do
|
|
21
|
+
# indexes login, email
|
|
22
|
+
#
|
|
23
|
+
# has created_at
|
|
24
|
+
#
|
|
25
|
+
# set_property :delta => true
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
def initialize(model, &block)
|
|
29
|
+
@model = model
|
|
30
|
+
@sources = []
|
|
31
|
+
@options = {}
|
|
32
|
+
@delta_object = nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fields
|
|
36
|
+
@sources.collect { |source| source.fields }.flatten
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def attributes
|
|
40
|
+
@sources.collect { |source| source.attributes }.flatten
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def name
|
|
44
|
+
self.class.name(@model)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.name(model)
|
|
48
|
+
model.name.underscore.tr(':/\\', '_')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def prefix_fields
|
|
52
|
+
fields.select { |field| field.prefixes }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def infix_fields
|
|
56
|
+
fields.select { |field| field.infixes }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def local_options
|
|
60
|
+
@options
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def options
|
|
64
|
+
all_index_options = ThinkingSphinx::Configuration.instance.index_options.clone
|
|
65
|
+
@options.keys.select { |key|
|
|
66
|
+
ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) ||
|
|
67
|
+
ThinkingSphinx::Configuration::CustomOptions.include?(key.to_s)
|
|
68
|
+
}.each { |key| all_index_options[key.to_sym] = @options[key] }
|
|
69
|
+
all_index_options
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def delta?
|
|
73
|
+
!@delta_object.nil?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def adapter
|
|
79
|
+
@adapter ||= @model.sphinx_database_adapter
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def utf8?
|
|
83
|
+
options[:charset_type] == "utf-8"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Does all the magic with the block provided to the base #initialize.
|
|
87
|
+
# Creates a new class subclassed from Builder, and evaluates the block
|
|
88
|
+
# on it, then pulls all relevant settings - fields, attributes, conditions,
|
|
89
|
+
# properties - into the new index.
|
|
90
|
+
#
|
|
91
|
+
def initialize_from_builder(&block)
|
|
92
|
+
#
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def sql_query_pre_for_delta
|
|
96
|
+
[""]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|