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.
Files changed (40) hide show
  1. data/{README → README.textile} +84 -84
  2. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  3. data/lib/thinking_sphinx/active_record/delta.rb +10 -1
  4. data/lib/thinking_sphinx/active_record.rb +10 -3
  5. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +1 -1
  6. data/lib/thinking_sphinx/attribute.rb +44 -134
  7. data/lib/thinking_sphinx/class_facet.rb +15 -0
  8. data/lib/thinking_sphinx/collection.rb +1 -0
  9. data/lib/thinking_sphinx/configuration.rb +7 -3
  10. data/lib/thinking_sphinx/deltas/datetime_delta.rb +1 -1
  11. data/lib/thinking_sphinx/deltas/default_delta.rb +3 -2
  12. data/lib/thinking_sphinx/deltas/delayed_delta.rb +4 -2
  13. data/lib/thinking_sphinx/deltas.rb +9 -6
  14. data/lib/thinking_sphinx/deploy/capistrano.rb +82 -0
  15. data/lib/thinking_sphinx/facet.rb +68 -18
  16. data/lib/thinking_sphinx/facet_collection.rb +16 -17
  17. data/lib/thinking_sphinx/field.rb +7 -97
  18. data/lib/thinking_sphinx/index/builder.rb +255 -232
  19. data/lib/thinking_sphinx/index.rb +37 -349
  20. data/lib/thinking_sphinx/property.rb +160 -0
  21. data/lib/thinking_sphinx/search/facets.rb +98 -0
  22. data/lib/thinking_sphinx/search.rb +4 -73
  23. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  24. data/lib/thinking_sphinx/source/sql.rb +124 -0
  25. data/lib/thinking_sphinx/source.rb +150 -0
  26. data/lib/thinking_sphinx/tasks.rb +1 -1
  27. data/lib/thinking_sphinx.rb +3 -1
  28. data/spec/unit/thinking_sphinx/active_record_spec.rb +14 -12
  29. data/spec/unit/thinking_sphinx/attribute_spec.rb +16 -11
  30. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
  31. data/spec/unit/thinking_sphinx/facet_spec.rb +278 -0
  32. data/spec/unit/thinking_sphinx/field_spec.rb +18 -9
  33. data/spec/unit/thinking_sphinx/index/builder_spec.rb +347 -1
  34. data/spec/unit/thinking_sphinx/index_spec.rb +22 -27
  35. data/spec/unit/thinking_sphinx/rails_additions_spec.rb +183 -0
  36. data/spec/unit/thinking_sphinx/search_spec.rb +71 -0
  37. data/spec/unit/thinking_sphinx/source_spec.rb +156 -0
  38. data/tasks/distribution.rb +1 -1
  39. data/tasks/testing.rb +7 -15
  40. 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
 
@@ -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 = 6
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(:new => @index)
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 return nil and do nothing if indexes are disabled" do
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 {}.should be_nil
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 do; end
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
- Parent.source_of_sphinx_index.should == Person
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.link!
293
- sql = Child.sphinx_indexes.last.to_riddle_for_core(0, 0).sql_query
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 { Child.connection.execute(sql) }.should_not raise_error(ActiveRecord::StatementInvalid)
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.first
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