DrMark-thinking-sphinx 1.1.6 → 1.1.14
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/{README → README.textile} +84 -84
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
- data/lib/thinking_sphinx/active_record/delta.rb +10 -1
- data/lib/thinking_sphinx/active_record.rb +10 -3
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +1 -1
- data/lib/thinking_sphinx/attribute.rb +44 -134
- data/lib/thinking_sphinx/class_facet.rb +15 -0
- data/lib/thinking_sphinx/collection.rb +1 -0
- data/lib/thinking_sphinx/configuration.rb +7 -3
- data/lib/thinking_sphinx/deltas/datetime_delta.rb +1 -1
- data/lib/thinking_sphinx/deltas/default_delta.rb +3 -2
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +4 -2
- data/lib/thinking_sphinx/deltas.rb +9 -6
- data/lib/thinking_sphinx/deploy/capistrano.rb +82 -0
- data/lib/thinking_sphinx/facet.rb +68 -18
- data/lib/thinking_sphinx/facet_collection.rb +16 -17
- data/lib/thinking_sphinx/field.rb +7 -97
- data/lib/thinking_sphinx/index/builder.rb +255 -232
- data/lib/thinking_sphinx/index.rb +37 -349
- data/lib/thinking_sphinx/property.rb +160 -0
- data/lib/thinking_sphinx/search/facets.rb +98 -0
- data/lib/thinking_sphinx/search.rb +4 -73
- data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
- data/lib/thinking_sphinx/source/sql.rb +124 -0
- data/lib/thinking_sphinx/source.rb +150 -0
- data/lib/thinking_sphinx/tasks.rb +1 -1
- data/lib/thinking_sphinx.rb +3 -1
- data/spec/unit/thinking_sphinx/active_record_spec.rb +14 -12
- data/spec/unit/thinking_sphinx/attribute_spec.rb +16 -11
- data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
- data/spec/unit/thinking_sphinx/facet_spec.rb +278 -0
- data/spec/unit/thinking_sphinx/field_spec.rb +18 -9
- data/spec/unit/thinking_sphinx/index/builder_spec.rb +347 -1
- data/spec/unit/thinking_sphinx/index_spec.rb +22 -27
- data/spec/unit/thinking_sphinx/rails_additions_spec.rb +183 -0
- data/spec/unit/thinking_sphinx/search_spec.rb +71 -0
- data/spec/unit/thinking_sphinx/source_spec.rb +156 -0
- data/tasks/distribution.rb +1 -1
- data/tasks/testing.rb +7 -15
- metadata +19 -3
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'thinking_sphinx/search/facets'
|
2
|
+
|
1
3
|
module ThinkingSphinx
|
2
4
|
# Once you've got those indexes in and built, this is the stuff that
|
3
5
|
# matters - how to search! This class provides a generic search
|
@@ -13,6 +15,8 @@ module ThinkingSphinx
|
|
13
15
|
}
|
14
16
|
|
15
17
|
class << self
|
18
|
+
include ThinkingSphinx::Search::Facets
|
19
|
+
|
16
20
|
# Searches for results that match the parameters provided. Will only
|
17
21
|
# return the ids for the matching objects. See #search for syntax
|
18
22
|
# examples.
|
@@ -432,21 +436,6 @@ module ThinkingSphinx
|
|
432
436
|
end
|
433
437
|
end
|
434
438
|
|
435
|
-
# Model.facets *args
|
436
|
-
# ThinkingSphinx::Search.facets *args
|
437
|
-
# ThinkingSphinx::Search.facets *args, :all_attributes => true
|
438
|
-
# ThinkingSphinx::Search.facets *args, :class_facet => false
|
439
|
-
#
|
440
|
-
def facets(*args)
|
441
|
-
options = args.extract_options!
|
442
|
-
|
443
|
-
if options[:class]
|
444
|
-
facets_for_model options[:class], args, options
|
445
|
-
else
|
446
|
-
facets_for_all_models args, options
|
447
|
-
end
|
448
|
-
end
|
449
|
-
|
450
439
|
private
|
451
440
|
|
452
441
|
# This method handles the common search functionality, and returns both
|
@@ -717,64 +706,6 @@ module ThinkingSphinx
|
|
717
706
|
|
718
707
|
string
|
719
708
|
end
|
720
|
-
|
721
|
-
def facets_for_model(klass, args, options)
|
722
|
-
hash = ThinkingSphinx::FacetCollection.new args + [options]
|
723
|
-
options = options.clone.merge! :group_function => :attr
|
724
|
-
|
725
|
-
klass.sphinx_facets.inject(hash) do |hash, facet|
|
726
|
-
unless facet.name == :class && !options[:class_facet]
|
727
|
-
options[:group_by] = facet.attribute_name
|
728
|
-
hash.add_from_results facet, search(*(args + [options]))
|
729
|
-
end
|
730
|
-
|
731
|
-
hash
|
732
|
-
end
|
733
|
-
end
|
734
|
-
|
735
|
-
def facets_for_all_models(args, options)
|
736
|
-
options = GlobalFacetOptions.merge(options)
|
737
|
-
hash = ThinkingSphinx::FacetCollection.new args + [options]
|
738
|
-
options = options.merge! :group_function => :attr
|
739
|
-
|
740
|
-
facet_names(options).inject(hash) do |hash, name|
|
741
|
-
options[:group_by] = name
|
742
|
-
hash.add_from_results name, search(*(args + [options]))
|
743
|
-
hash
|
744
|
-
end
|
745
|
-
end
|
746
|
-
|
747
|
-
def facet_classes(options)
|
748
|
-
options[:classes] || ThinkingSphinx.indexed_models.collect { |model|
|
749
|
-
model.constantize
|
750
|
-
}
|
751
|
-
end
|
752
|
-
|
753
|
-
def facet_names(options)
|
754
|
-
classes = facet_classes(options)
|
755
|
-
names = options[:all_attributes] ?
|
756
|
-
facet_names_for_all_classes(classes) :
|
757
|
-
facet_names_common_to_all_classes(classes)
|
758
|
-
|
759
|
-
names.delete "class_crc" unless options[:class_facet]
|
760
|
-
names
|
761
|
-
end
|
762
|
-
|
763
|
-
def facet_names_for_all_classes(classes)
|
764
|
-
classes.collect { |klass|
|
765
|
-
klass.sphinx_facets.collect { |facet| facet.attribute_name }
|
766
|
-
}.flatten.uniq
|
767
|
-
end
|
768
|
-
|
769
|
-
def facet_names_common_to_all_classes(classes)
|
770
|
-
facet_names_for_all_classes(classes).select { |name|
|
771
|
-
classes.all? { |klass|
|
772
|
-
klass.sphinx_facets.detect { |facet|
|
773
|
-
facet.attribute_name == name
|
774
|
-
}
|
775
|
-
}
|
776
|
-
}
|
777
|
-
end
|
778
709
|
end
|
779
710
|
end
|
780
711
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
class Source
|
3
|
+
module InternalProperties
|
4
|
+
def add_internal_attributes_and_facets
|
5
|
+
add_internal_attribute :sphinx_internal_id, :integer, @model.primary_key.to_sym
|
6
|
+
add_internal_attribute :class_crc, :integer, crc_column, true
|
7
|
+
add_internal_attribute :subclass_crcs, :multi, subclasses_to_s
|
8
|
+
add_internal_attribute :sphinx_deleted, :integer, "0"
|
9
|
+
|
10
|
+
add_internal_facet :class_crc
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_internal_attribute(name, type, contents, facet = false)
|
14
|
+
return unless attribute_by_alias(name).nil?
|
15
|
+
|
16
|
+
Attribute.new(self,
|
17
|
+
ThinkingSphinx::Index::FauxColumn.new(contents),
|
18
|
+
:type => type,
|
19
|
+
:as => name,
|
20
|
+
:facet => facet,
|
21
|
+
:admin => true
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_internal_facet(name)
|
26
|
+
return unless facet_by_alias(name).nil?
|
27
|
+
|
28
|
+
@model.sphinx_facets << ClassFacet.new(attribute_by_alias(name))
|
29
|
+
end
|
30
|
+
|
31
|
+
def attribute_by_alias(attr_alias)
|
32
|
+
@attributes.detect { |attrib| attrib.alias == attr_alias }
|
33
|
+
end
|
34
|
+
|
35
|
+
def facet_by_alias(name)
|
36
|
+
@model.sphinx_facets.detect { |facet| facet.name == name }
|
37
|
+
end
|
38
|
+
|
39
|
+
def subclasses_to_s
|
40
|
+
"'" + (@model.send(:subclasses).collect { |klass|
|
41
|
+
klass.to_crc32.to_s
|
42
|
+
} << @model.to_crc32.to_s).join(",") + "'"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module ThinkingSphinx
|
2
|
+
class Source
|
3
|
+
module SQL
|
4
|
+
# Generates the big SQL statement to get the data back for all the fields
|
5
|
+
# and attributes, using all the relevant association joins. If you want
|
6
|
+
# the version filtered for delta values, send through :delta => true in the
|
7
|
+
# options. Won't do much though if the index isn't set up to support a
|
8
|
+
# delta sibling.
|
9
|
+
#
|
10
|
+
# Examples:
|
11
|
+
#
|
12
|
+
# source.to_sql
|
13
|
+
# source.to_sql(:delta => true)
|
14
|
+
#
|
15
|
+
def to_sql(options={})
|
16
|
+
sql = <<-SQL
|
17
|
+
SELECT #{ sql_select_clause options[:offset] }
|
18
|
+
FROM #{ @model.quoted_table_name }
|
19
|
+
#{ all_associations.collect { |assoc| assoc.to_sql }.join(' ') }
|
20
|
+
WHERE #{ sql_where_clause(options) }
|
21
|
+
GROUP BY #{ sql_group_clause }
|
22
|
+
SQL
|
23
|
+
|
24
|
+
sql += " ORDER BY NULL" if adapter.sphinx_identifier == "mysql"
|
25
|
+
sql
|
26
|
+
end
|
27
|
+
|
28
|
+
# Simple helper method for the query range SQL - which is a statement that
|
29
|
+
# returns minimum and maximum id values. These can be filtered by delta -
|
30
|
+
# so pass in :delta => true to get the delta version of the SQL.
|
31
|
+
#
|
32
|
+
def to_sql_query_range(options={})
|
33
|
+
min_statement = adapter.convert_nulls(
|
34
|
+
"MIN(#{quote_column(@model.primary_key)})", 1
|
35
|
+
)
|
36
|
+
max_statement = adapter.convert_nulls(
|
37
|
+
"MAX(#{quote_column(@model.primary_key)})", 1
|
38
|
+
)
|
39
|
+
|
40
|
+
sql = "SELECT #{min_statement}, #{max_statement} " +
|
41
|
+
"FROM #{@model.quoted_table_name} "
|
42
|
+
if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
|
43
|
+
sql << "WHERE #{@index.delta_object.clause(@model, options[:delta])}"
|
44
|
+
end
|
45
|
+
|
46
|
+
sql
|
47
|
+
end
|
48
|
+
|
49
|
+
# Simple helper method for the query info SQL - which is a statement that
|
50
|
+
# returns the single row for a corresponding id.
|
51
|
+
#
|
52
|
+
def to_sql_query_info(offset)
|
53
|
+
"SELECT * FROM #{@model.quoted_table_name} WHERE " +
|
54
|
+
"#{quote_column(@model.primary_key)} = (($id - #{offset}) / #{ThinkingSphinx.indexed_models.size})"
|
55
|
+
end
|
56
|
+
|
57
|
+
def sql_select_clause(offset)
|
58
|
+
unique_id_expr = ThinkingSphinx.unique_id_expression(offset)
|
59
|
+
|
60
|
+
(
|
61
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} #{unique_id_expr} AS #{quote_column(@model.primary_key)} "] +
|
62
|
+
@fields.collect { |field| field.to_select_sql } +
|
63
|
+
@attributes.collect { |attribute| attribute.to_select_sql }
|
64
|
+
).compact.join(", ")
|
65
|
+
end
|
66
|
+
|
67
|
+
def sql_where_clause(options)
|
68
|
+
logic = [
|
69
|
+
"#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} >= $start",
|
70
|
+
"#{@model.quoted_table_name}.#{quote_column(@model.primary_key)} <= $end"
|
71
|
+
]
|
72
|
+
|
73
|
+
if self.delta? && !@index.delta_object.clause(@model, options[:delta]).blank?
|
74
|
+
logic << "#{@index.delta_object.clause(@model, options[:delta])}"
|
75
|
+
end
|
76
|
+
|
77
|
+
logic += (@conditions || [])
|
78
|
+
|
79
|
+
logic.join(" AND ")
|
80
|
+
end
|
81
|
+
|
82
|
+
def sql_group_clause
|
83
|
+
internal_groupings = []
|
84
|
+
if @model.column_names.include?(@model.inheritance_column)
|
85
|
+
internal_groupings << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)}"
|
86
|
+
end
|
87
|
+
|
88
|
+
(
|
89
|
+
["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
|
90
|
+
@fields.collect { |field| field.to_group_sql }.compact +
|
91
|
+
@attributes.collect { |attribute| attribute.to_group_sql }.compact +
|
92
|
+
@groupings + internal_groupings
|
93
|
+
).join(", ")
|
94
|
+
end
|
95
|
+
|
96
|
+
def sql_query_pre_for_core
|
97
|
+
if self.delta? && !@index.delta_object.reset_query(@model).blank?
|
98
|
+
[@index.delta_object.reset_query(@model)]
|
99
|
+
else
|
100
|
+
[]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def sql_query_pre_for_delta
|
105
|
+
[""]
|
106
|
+
end
|
107
|
+
|
108
|
+
def quote_column(column)
|
109
|
+
@model.connection.quote_column_name(column)
|
110
|
+
end
|
111
|
+
|
112
|
+
def crc_column
|
113
|
+
if @model.column_names.include?(@model.inheritance_column)
|
114
|
+
adapter.cast_to_unsigned(adapter.convert_nulls(
|
115
|
+
adapter.crc(adapter.quote_with_table(@model.inheritance_column), true),
|
116
|
+
@model.to_crc32
|
117
|
+
))
|
118
|
+
else
|
119
|
+
@model.to_crc32.to_s
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'thinking_sphinx/source/internal_properties'
|
2
|
+
require 'thinking_sphinx/source/sql'
|
3
|
+
|
4
|
+
module ThinkingSphinx
|
5
|
+
class Source
|
6
|
+
include ThinkingSphinx::Source::InternalProperties
|
7
|
+
include ThinkingSphinx::Source::SQL
|
8
|
+
|
9
|
+
attr_accessor :model, :fields, :attributes, :conditions, :groupings,
|
10
|
+
:options
|
11
|
+
attr_reader :base
|
12
|
+
|
13
|
+
def initialize(index, options = {})
|
14
|
+
@index = index
|
15
|
+
@model = index.model
|
16
|
+
@fields = []
|
17
|
+
@attributes = []
|
18
|
+
@conditions = []
|
19
|
+
@groupings = []
|
20
|
+
@options = options
|
21
|
+
@associations = {}
|
22
|
+
|
23
|
+
@base = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(
|
24
|
+
@model, [], nil
|
25
|
+
)
|
26
|
+
|
27
|
+
unless @model.descends_from_active_record?
|
28
|
+
stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
|
29
|
+
@conditions << "#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'"
|
30
|
+
end
|
31
|
+
|
32
|
+
add_internal_attributes_and_facets
|
33
|
+
end
|
34
|
+
|
35
|
+
def name
|
36
|
+
@model.name.underscore.tr(':/\\', '_')
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_riddle_for_core(offset, index)
|
40
|
+
source = Riddle::Configuration::SQLSource.new(
|
41
|
+
"#{name}_core_#{index}", adapter.sphinx_identifier
|
42
|
+
)
|
43
|
+
|
44
|
+
set_source_database_settings source
|
45
|
+
set_source_attributes source, offset
|
46
|
+
set_source_sql source, offset
|
47
|
+
set_source_settings source
|
48
|
+
|
49
|
+
source
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_riddle_for_delta(offset, index)
|
53
|
+
source = Riddle::Configuration::SQLSource.new(
|
54
|
+
"#{name}_delta_#{index}", adapter.sphinx_identifier
|
55
|
+
)
|
56
|
+
source.parent = "#{name}_core_#{index}"
|
57
|
+
|
58
|
+
set_source_database_settings source
|
59
|
+
set_source_attributes source, offset
|
60
|
+
set_source_sql source, offset, true
|
61
|
+
|
62
|
+
source
|
63
|
+
end
|
64
|
+
|
65
|
+
def delta?
|
66
|
+
!@index.delta_object.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Gets the association stack for a specific key.
|
70
|
+
#
|
71
|
+
def association(key)
|
72
|
+
@associations[key] ||= Association.children(@model, key)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def adapter
|
78
|
+
@adapter ||= @model.sphinx_database_adapter
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_source_database_settings(source)
|
82
|
+
config = @model.connection.instance_variable_get(:@config)
|
83
|
+
|
84
|
+
source.sql_host = config[:host] || "localhost"
|
85
|
+
source.sql_user = config[:username] || config[:user] || ""
|
86
|
+
source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
|
87
|
+
source.sql_db = config[:database]
|
88
|
+
source.sql_port = config[:port]
|
89
|
+
source.sql_sock = config[:socket]
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_source_attributes(source, offset)
|
93
|
+
attributes.each do |attrib|
|
94
|
+
source.send(attrib.type_to_config) << attrib.config_value(offset)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def set_source_sql(source, offset, delta = false)
|
99
|
+
source.sql_query = to_sql(:offset => offset, :delta => delta).gsub(/\n/, ' ')
|
100
|
+
source.sql_query_range = to_sql_query_range(:delta => delta)
|
101
|
+
source.sql_query_info = to_sql_query_info(offset)
|
102
|
+
|
103
|
+
source.sql_query_pre += send(!delta ? :sql_query_pre_for_core : :sql_query_pre_for_delta)
|
104
|
+
|
105
|
+
if @options[:group_concat_max_len]
|
106
|
+
source.sql_query_pre << "SET SESSION group_concat_max_len = #{@options[:group_concat_max_len]}"
|
107
|
+
end
|
108
|
+
|
109
|
+
source.sql_query_pre += [adapter.utf8_query_pre].compact if utf8?
|
110
|
+
end
|
111
|
+
|
112
|
+
def set_source_settings(source)
|
113
|
+
config = ThinkingSphinx::Configuration.instance
|
114
|
+
config.source_options.each do |key, value|
|
115
|
+
source.send("#{key}=".to_sym, value)
|
116
|
+
end
|
117
|
+
|
118
|
+
source_options = ThinkingSphinx::Configuration::SourceOptions
|
119
|
+
@options.each do |key, value|
|
120
|
+
if source_options.include?(key.to_s) && !value.nil?
|
121
|
+
source.send("#{key}=".to_sym, value)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns all associations used amongst all the fields and attributes.
|
127
|
+
# This includes all associations between the model and what the actual
|
128
|
+
# columns are from.
|
129
|
+
#
|
130
|
+
def all_associations
|
131
|
+
@all_associations ||= (
|
132
|
+
# field associations
|
133
|
+
@fields.collect { |field|
|
134
|
+
field.associations.values
|
135
|
+
}.flatten +
|
136
|
+
# attribute associations
|
137
|
+
@attributes.collect { |attrib|
|
138
|
+
attrib.associations.values if attrib.include_as_association?
|
139
|
+
}.compact.flatten
|
140
|
+
).uniq.collect { |assoc|
|
141
|
+
# get ancestors as well as column-level associations
|
142
|
+
assoc.ancestors
|
143
|
+
}.flatten.uniq
|
144
|
+
end
|
145
|
+
|
146
|
+
def utf8?
|
147
|
+
@index.options[:charset_type] == "utf-8"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -30,7 +30,7 @@ namespace :thinking_sphinx do
|
|
30
30
|
if sphinx_running?
|
31
31
|
puts "Started successfully (pid #{sphinx_pid})."
|
32
32
|
else
|
33
|
-
puts "Failed to start searchd daemon. Check #{config.searchd_log_file}
|
33
|
+
puts "Failed to start searchd daemon. Check #{config.searchd_log_file}"
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
data/lib/thinking_sphinx.rb
CHANGED
@@ -7,6 +7,7 @@ require 'riddle'
|
|
7
7
|
require 'after_commit'
|
8
8
|
|
9
9
|
require 'thinking_sphinx/core/string'
|
10
|
+
require 'thinking_sphinx/property'
|
10
11
|
require 'thinking_sphinx/active_record'
|
11
12
|
require 'thinking_sphinx/association'
|
12
13
|
require 'thinking_sphinx/attribute'
|
@@ -17,6 +18,7 @@ require 'thinking_sphinx/class_facet'
|
|
17
18
|
require 'thinking_sphinx/facet_collection'
|
18
19
|
require 'thinking_sphinx/field'
|
19
20
|
require 'thinking_sphinx/index'
|
21
|
+
require 'thinking_sphinx/source'
|
20
22
|
require 'thinking_sphinx/rails_additions'
|
21
23
|
require 'thinking_sphinx/search'
|
22
24
|
require 'thinking_sphinx/deltas'
|
@@ -35,7 +37,7 @@ module ThinkingSphinx
|
|
35
37
|
module Version #:nodoc:
|
36
38
|
Major = 1
|
37
39
|
Minor = 1
|
38
|
-
Tiny =
|
40
|
+
Tiny = 14
|
39
41
|
|
40
42
|
String = [Major, Minor, Tiny].join('.')
|
41
43
|
end
|
@@ -14,7 +14,7 @@ describe "ThinkingSphinx::ActiveRecord" do
|
|
14
14
|
)
|
15
15
|
|
16
16
|
@index = ThinkingSphinx::Index.stub_instance(:delta? => false)
|
17
|
-
ThinkingSphinx::Index.stub_method(:
|
17
|
+
ThinkingSphinx::Index::Builder.stub_method(:generate => @index)
|
18
18
|
end
|
19
19
|
|
20
20
|
after :each do
|
@@ -24,17 +24,17 @@ describe "ThinkingSphinx::ActiveRecord" do
|
|
24
24
|
ThinkingSphinx.indexed_models.delete "TestModule::TestModel"
|
25
25
|
end
|
26
26
|
|
27
|
-
it "should
|
27
|
+
it "should do nothing if indexes are disabled" do
|
28
28
|
ThinkingSphinx.stub_method(:define_indexes? => false)
|
29
29
|
|
30
|
-
TestModule::TestModel.define_index {}
|
30
|
+
TestModule::TestModel.define_index {}
|
31
31
|
ThinkingSphinx::Index.should_not have_received(:new)
|
32
32
|
|
33
33
|
ThinkingSphinx.unstub_method(:define_indexes?)
|
34
34
|
end
|
35
35
|
|
36
36
|
it "should add a new index to the model" do
|
37
|
-
TestModule::TestModel.define_index
|
37
|
+
TestModule::TestModel.define_index {}
|
38
38
|
|
39
39
|
TestModule::TestModel.sphinx_indexes.length.should == 1
|
40
40
|
end
|
@@ -64,15 +64,15 @@ describe "ThinkingSphinx::ActiveRecord" do
|
|
64
64
|
|
65
65
|
TestModule::TestModel.define_index do; end
|
66
66
|
|
67
|
-
TestModule::TestModel.should have_received(:before_save)
|
68
|
-
TestModule::TestModel.should have_received(:after_commit)
|
67
|
+
TestModule::TestModel.should have_received(:before_save).with(:toggle_delta)
|
68
|
+
TestModule::TestModel.should have_received(:after_commit).with(:index_delta)
|
69
69
|
end
|
70
70
|
|
71
71
|
it "should not add before_save and after_commit hooks to the model if delta indexing is disabled" do
|
72
72
|
TestModule::TestModel.define_index do; end
|
73
73
|
|
74
|
-
TestModule::TestModel.should_not have_received(:before_save)
|
75
|
-
TestModule::TestModel.should_not have_received(:after_commit)
|
74
|
+
TestModule::TestModel.should_not have_received(:before_save).with(:toggle_delta)
|
75
|
+
TestModule::TestModel.should_not have_received(:after_commit).with(:index_delta)
|
76
76
|
end
|
77
77
|
|
78
78
|
it "should add an after_destroy hook with delta indexing enabled" do
|
@@ -144,7 +144,7 @@ describe "ThinkingSphinx::ActiveRecord" do
|
|
144
144
|
end
|
145
145
|
|
146
146
|
it "should return the parent if model inherits an index" do
|
147
|
-
|
147
|
+
Admin::Person.source_of_sphinx_index.should == Person
|
148
148
|
end
|
149
149
|
end
|
150
150
|
|
@@ -289,10 +289,12 @@ describe "ThinkingSphinx::ActiveRecord" do
|
|
289
289
|
end
|
290
290
|
|
291
291
|
it "should allow associations to other STI models" do
|
292
|
-
Child.sphinx_indexes.last.
|
293
|
-
sql =
|
292
|
+
source = Child.sphinx_indexes.last.sources.first
|
293
|
+
sql = source.to_riddle_for_core(0, 0).sql_query
|
294
294
|
sql.gsub!('$start', '0').gsub!('$end', '100')
|
295
|
-
lambda {
|
295
|
+
lambda {
|
296
|
+
Child.connection.execute(sql)
|
297
|
+
}.should_not raise_error(ActiveRecord::StatementInvalid)
|
296
298
|
end
|
297
299
|
end
|
298
300
|
|
@@ -1,23 +1,28 @@
|
|
1
1
|
require 'spec/spec_helper'
|
2
2
|
|
3
3
|
describe ThinkingSphinx::Attribute do
|
4
|
+
before :each do
|
5
|
+
@index = ThinkingSphinx::Index.new(Person)
|
6
|
+
@source = ThinkingSphinx::Source.new(@index)
|
7
|
+
end
|
8
|
+
|
4
9
|
describe '#initialize' do
|
5
10
|
it 'raises if no columns are provided so that configuration errors are easier to track down' do
|
6
11
|
lambda {
|
7
|
-
ThinkingSphinx::Attribute.new([])
|
12
|
+
ThinkingSphinx::Attribute.new(@source, [])
|
8
13
|
}.should raise_error(RuntimeError)
|
9
14
|
end
|
10
15
|
|
11
16
|
it 'raises if an element of the columns param is an integer - as happens when you use id instead of :id - so that configuration errors are easier to track down' do
|
12
17
|
lambda {
|
13
|
-
ThinkingSphinx::Attribute.new([1234])
|
18
|
+
ThinkingSphinx::Attribute.new(@source, [1234])
|
14
19
|
}.should raise_error(RuntimeError)
|
15
20
|
end
|
16
21
|
end
|
17
22
|
|
18
23
|
describe "unique_name method" do
|
19
24
|
before :each do
|
20
|
-
@attribute = ThinkingSphinx::Attribute.new [
|
25
|
+
@attribute = ThinkingSphinx::Attribute.new @source, [
|
21
26
|
Object.stub_instance(:__stack => [], :__name => "col_name")
|
22
27
|
]
|
23
28
|
end
|
@@ -42,7 +47,7 @@ describe ThinkingSphinx::Attribute do
|
|
42
47
|
|
43
48
|
describe "column_with_prefix method" do
|
44
49
|
before :each do
|
45
|
-
@attribute = ThinkingSphinx::Attribute.new [
|
50
|
+
@attribute = ThinkingSphinx::Attribute.new @source, [
|
46
51
|
ThinkingSphinx::Index::FauxColumn.new(:col_name)
|
47
52
|
]
|
48
53
|
@attribute.columns.each { |col| @attribute.associations[col] = [] }
|
@@ -88,7 +93,7 @@ describe ThinkingSphinx::Attribute do
|
|
88
93
|
@assoc_c = Object.stub_instance(:is_many? => true)
|
89
94
|
|
90
95
|
@attribute = ThinkingSphinx::Attribute.new(
|
91
|
-
[ThinkingSphinx::Index::FauxColumn.new(:col_name)]
|
96
|
+
@source, [ThinkingSphinx::Index::FauxColumn.new(:col_name)]
|
92
97
|
)
|
93
98
|
@attribute.associations = {
|
94
99
|
:a => @assoc_a, :b => @assoc_b, :c => @assoc_c
|
@@ -122,7 +127,7 @@ describe ThinkingSphinx::Attribute do
|
|
122
127
|
@col_c = ThinkingSphinx::Index::FauxColumn.new("c")
|
123
128
|
|
124
129
|
@attribute = ThinkingSphinx::Attribute.new(
|
125
|
-
[@col_a, @col_b, @col_c]
|
130
|
+
@source, [@col_a, @col_b, @col_c]
|
126
131
|
)
|
127
132
|
end
|
128
133
|
|
@@ -146,7 +151,7 @@ describe ThinkingSphinx::Attribute do
|
|
146
151
|
describe "type method" do
|
147
152
|
before :each do
|
148
153
|
@column = ThinkingSphinx::Index::FauxColumn.new(:col_name)
|
149
|
-
@attribute = ThinkingSphinx::Attribute.new([@column])
|
154
|
+
@attribute = ThinkingSphinx::Attribute.new(@source, [@column])
|
150
155
|
@attribute.model = Person
|
151
156
|
@attribute.stub_method(:is_many? => false)
|
152
157
|
end
|
@@ -177,7 +182,7 @@ describe ThinkingSphinx::Attribute do
|
|
177
182
|
|
178
183
|
describe "all_ints? method" do
|
179
184
|
it "should return true if all columns are integers" do
|
180
|
-
attribute = ThinkingSphinx::Attribute.new(
|
185
|
+
attribute = ThinkingSphinx::Attribute.new(@source,
|
181
186
|
[ ThinkingSphinx::Index::FauxColumn.new(:id),
|
182
187
|
ThinkingSphinx::Index::FauxColumn.new(:team_id) ]
|
183
188
|
)
|
@@ -188,7 +193,7 @@ describe ThinkingSphinx::Attribute do
|
|
188
193
|
end
|
189
194
|
|
190
195
|
it "should return false if only some columns are integers" do
|
191
|
-
attribute = ThinkingSphinx::Attribute.new(
|
196
|
+
attribute = ThinkingSphinx::Attribute.new(@source,
|
192
197
|
[ ThinkingSphinx::Index::FauxColumn.new(:id),
|
193
198
|
ThinkingSphinx::Index::FauxColumn.new(:first_name) ]
|
194
199
|
)
|
@@ -199,7 +204,7 @@ describe ThinkingSphinx::Attribute do
|
|
199
204
|
end
|
200
205
|
|
201
206
|
it "should return false if no columns are integers" do
|
202
|
-
attribute = ThinkingSphinx::Attribute.new(
|
207
|
+
attribute = ThinkingSphinx::Attribute.new(@source,
|
203
208
|
[ ThinkingSphinx::Index::FauxColumn.new(:first_name),
|
204
209
|
ThinkingSphinx::Index::FauxColumn.new(:last_name) ]
|
205
210
|
)
|
@@ -213,7 +218,7 @@ describe ThinkingSphinx::Attribute do
|
|
213
218
|
describe "with custom queries" do
|
214
219
|
before :each do
|
215
220
|
index = CricketTeam.sphinx_indexes.first
|
216
|
-
@statement = index.to_riddle_for_core(0, 0).sql_attr_multi.
|
221
|
+
@statement = index.sources.first.to_riddle_for_core(0, 0).sql_attr_multi.last
|
217
222
|
end
|
218
223
|
|
219
224
|
it "should track the query type accordingly" do
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe ThinkingSphinx::FacetCollection do
|
4
|
+
before do
|
5
|
+
@facet_collection = ThinkingSphinx::FacetCollection.new([])
|
6
|
+
end
|
7
|
+
|
8
|
+
# TODO fix nasty hack when we have internet!
|
9
|
+
def mock_results
|
10
|
+
return @results if defined? @results
|
11
|
+
@result = Person.find(:first)
|
12
|
+
@results = [@result]
|
13
|
+
@results.stub!(:each_with_groupby_and_count).and_yield(@result, @result.city.to_crc32, 1)
|
14
|
+
@results
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#add_from_results" do
|
18
|
+
describe "with empty result set" do
|
19
|
+
before do
|
20
|
+
@facet_collection.add_from_results('attribute_facet', [])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should add key as attribute" do
|
24
|
+
@facet_collection.should have_key(:attribute)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return an empty hash for the facet results" do
|
28
|
+
@facet_collection[:attribute].should be_empty
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "with non-empty result set" do
|
33
|
+
before do
|
34
|
+
@facet_collection.add_from_results('city_facet', mock_results)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return a hash" do
|
38
|
+
@facet_collection.should be_a_kind_of(Hash)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should add key as attribute" do
|
42
|
+
@facet_collection.keys.should include(:city)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return a hash" do
|
46
|
+
@facet_collection[:city].should == {@result.city => 1}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#for" do
|
52
|
+
before do
|
53
|
+
@facet_collection.add_from_results('city_facet', mock_results)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return the search results for the attribute and key pair" do
|
57
|
+
ThinkingSphinx::Search.should_receive(:search) do |options|
|
58
|
+
options[:with].should have_key('city_facet')
|
59
|
+
options[:with]['city_facet'].should == @result.city.to_crc32
|
60
|
+
end
|
61
|
+
@facet_collection.for(:city => @result.city)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|