freelancing-god-thinking-sphinx 1.1.12 → 1.1.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,15 +2,23 @@ module ThinkingSphinx
2
2
  class Property
3
3
  attr_accessor :alias, :columns, :associations, :model, :faceted, :admin
4
4
 
5
- def initialize(columns, options = {})
5
+ def initialize(source, columns, options = {})
6
+ @source = source
7
+ @model = source.model
6
8
  @columns = Array(columns)
7
9
  @associations = {}
8
10
 
9
- raise "Cannot define a field with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
11
+ raise "Cannot define a field or attribute in #{source.model.name} with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) }
10
12
 
11
13
  @alias = options[:as]
12
14
  @faceted = options[:facet]
13
15
  @admin = options[:admin]
16
+
17
+ @columns.each { |col|
18
+ @associations[col] = association_stack(col.__stack.clone).each { |assoc|
19
+ assoc.join_to(source.base)
20
+ }
21
+ }
14
22
  end
15
23
 
16
24
  # Returns the unique name of the attribute - which is either the alias of
@@ -129,5 +137,24 @@ module ThinkingSphinx
129
137
  }.compact.join(', ')
130
138
  end
131
139
  end
140
+
141
+ # Gets a stack of associations for a specific path.
142
+ #
143
+ def association_stack(path, parent = nil)
144
+ assocs = []
145
+
146
+ if parent.nil?
147
+ assocs = @source.association(path.shift)
148
+ else
149
+ assocs = parent.children(path.shift)
150
+ end
151
+
152
+ until path.empty?
153
+ point = path.shift
154
+ assocs = assocs.collect { |assoc| assoc.children(point) }.flatten
155
+ end
156
+
157
+ assocs
158
+ end
132
159
  end
133
160
  end
@@ -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,75 +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! facet_query_options
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! facet_query_options
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_query_options
748
- config = ThinkingSphinx::Configuration.instance
749
- max = config.configuration.searchd.max_matches || 1000
750
-
751
- {
752
- :group_function => :attr,
753
- :limit => max,
754
- :max_matches => max
755
- }
756
- end
757
-
758
- def facet_classes(options)
759
- options[:classes] || ThinkingSphinx.indexed_models.collect { |model|
760
- model.constantize
761
- }
762
- end
763
-
764
- def facet_names(options)
765
- classes = facet_classes(options)
766
- names = options[:all_attributes] ?
767
- facet_names_for_all_classes(classes) :
768
- facet_names_common_to_all_classes(classes)
769
-
770
- names.delete "class_crc" unless options[:class_facet]
771
- names
772
- end
773
-
774
- def facet_names_for_all_classes(classes)
775
- classes.collect { |klass|
776
- klass.sphinx_facets.collect { |facet| facet.attribute_name }
777
- }.flatten.uniq
778
- end
779
-
780
- def facet_names_common_to_all_classes(classes)
781
- facet_names_for_all_classes(classes).select { |name|
782
- classes.all? { |klass|
783
- klass.sphinx_facets.detect { |facet|
784
- facet.attribute_name == name
785
- }
786
- }
787
- }
788
- end
789
709
  end
790
710
  end
791
711
  end
@@ -0,0 +1,98 @@
1
+ module ThinkingSphinx
2
+ class Search
3
+ module Facets
4
+ # Model.facets *args
5
+ # ThinkingSphinx::Search.facets *args
6
+ # ThinkingSphinx::Search.facets *args, :all_attributes => true
7
+ # ThinkingSphinx::Search.facets *args, :class_facet => false
8
+ #
9
+ def facets(*args)
10
+ options = args.extract_options!
11
+
12
+ if options[:class]
13
+ facets_for_model options[:class], args, options
14
+ else
15
+ facets_for_all_models args, options
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def facets_for_model(klass, args, options)
22
+ hash = ThinkingSphinx::FacetCollection.new args + [options]
23
+ options = options.clone.merge! facet_query_options
24
+
25
+ klass.sphinx_facets.inject(hash) do |hash, facet|
26
+ unless facet.name == :class && !options[:class_facet]
27
+ options[:group_by] = facet.attribute_name
28
+ hash.add_from_results facet, search(*(args + [options]))
29
+ end
30
+
31
+ hash
32
+ end
33
+ end
34
+
35
+ def facets_for_all_models(args, options)
36
+ options = GlobalFacetOptions.merge(options)
37
+ hash = ThinkingSphinx::FacetCollection.new args + [options]
38
+ options = options.merge! facet_query_options
39
+
40
+ facet_names(options).inject(hash) do |hash, name|
41
+ options[:group_by] = name
42
+ hash.add_from_results name, search(*(args + [options]))
43
+ hash
44
+ end
45
+ end
46
+
47
+ def facet_query_options
48
+ config = ThinkingSphinx::Configuration.instance
49
+ max = config.configuration.searchd.max_matches || 1000
50
+
51
+ {
52
+ :group_function => :attr,
53
+ :limit => max,
54
+ :max_matches => max
55
+ }
56
+ end
57
+
58
+ def facet_classes(options)
59
+ options[:classes] || ThinkingSphinx.indexed_models.collect { |model|
60
+ model.constantize
61
+ }
62
+ end
63
+
64
+ def facet_names(options)
65
+ classes = facet_classes(options)
66
+ names = options[:all_attributes] ?
67
+ facet_names_for_all_classes(classes) :
68
+ facet_names_common_to_all_classes(classes)
69
+
70
+ names.delete "class_crc" unless options[:class_facet]
71
+ names
72
+ end
73
+
74
+ def facet_names_for_all_classes(classes)
75
+ all_facets = classes.collect { |klass| klass.sphinx_facets }.flatten
76
+
77
+ all_facets.group_by { |facet|
78
+ facet.name
79
+ }.collect { |name, facets|
80
+ if facets.collect { |facet| facet.type }.uniq.length > 1
81
+ raise "Facet #{name} exists in more than one model with different types"
82
+ end
83
+ facets.first.attribute_name
84
+ }
85
+ end
86
+
87
+ def facet_names_common_to_all_classes(classes)
88
+ facet_names_for_all_classes(classes).select { |name|
89
+ classes.all? { |klass|
90
+ klass.sphinx_facets.detect { |facet|
91
+ facet.attribute_name == name
92
+ }
93
+ }
94
+ }
95
+ end
96
+ end
97
+ end
98
+ 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
@@ -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