ebeigarts-thinking-sphinx 1.1.22 → 1.2.10

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 (61) hide show
  1. data/README.textile +14 -0
  2. data/VERSION.yml +4 -0
  3. data/lib/thinking_sphinx.rb +60 -64
  4. data/lib/thinking_sphinx/active_record.rb +35 -7
  5. data/lib/thinking_sphinx/active_record/scopes.rb +39 -0
  6. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +3 -2
  7. data/lib/thinking_sphinx/attribute.rb +62 -22
  8. data/lib/thinking_sphinx/configuration.rb +21 -1
  9. data/lib/thinking_sphinx/core/array.rb +7 -0
  10. data/lib/thinking_sphinx/deltas/delayed_delta.rb +3 -0
  11. data/lib/thinking_sphinx/deploy/capistrano.rb +26 -8
  12. data/lib/thinking_sphinx/excerpter.rb +22 -0
  13. data/lib/thinking_sphinx/facet.rb +8 -2
  14. data/lib/thinking_sphinx/facet_search.rb +134 -0
  15. data/lib/thinking_sphinx/index.rb +2 -2
  16. data/lib/thinking_sphinx/index/builder.rb +0 -1
  17. data/lib/thinking_sphinx/property.rb +2 -0
  18. data/lib/thinking_sphinx/rails_additions.rb +14 -0
  19. data/lib/thinking_sphinx/search.rb +633 -671
  20. data/lib/thinking_sphinx/search_methods.rb +421 -0
  21. data/lib/thinking_sphinx/source.rb +5 -5
  22. data/lib/thinking_sphinx/source/internal_properties.rb +1 -1
  23. data/lib/thinking_sphinx/source/sql.rb +10 -8
  24. data/lib/thinking_sphinx/tasks.rb +14 -9
  25. data/spec/{unit → lib}/thinking_sphinx/active_record/delta_spec.rb +1 -1
  26. data/spec/{unit → lib}/thinking_sphinx/active_record/has_many_association_spec.rb +0 -0
  27. data/spec/lib/thinking_sphinx/active_record/scopes_spec.rb +96 -0
  28. data/spec/{unit → lib}/thinking_sphinx/active_record_spec.rb +44 -5
  29. data/spec/{unit → lib}/thinking_sphinx/association_spec.rb +0 -0
  30. data/spec/{unit → lib}/thinking_sphinx/attribute_spec.rb +110 -3
  31. data/spec/{unit → lib}/thinking_sphinx/configuration_spec.rb +87 -41
  32. data/spec/lib/thinking_sphinx/core/array_spec.rb +9 -0
  33. data/spec/{unit → lib}/thinking_sphinx/core/string_spec.rb +0 -0
  34. data/spec/lib/thinking_sphinx/excerpter_spec.rb +49 -0
  35. data/spec/lib/thinking_sphinx/facet_search_spec.rb +176 -0
  36. data/spec/{unit → lib}/thinking_sphinx/facet_spec.rb +34 -15
  37. data/spec/{unit → lib}/thinking_sphinx/field_spec.rb +0 -0
  38. data/spec/{unit → lib}/thinking_sphinx/index/builder_spec.rb +100 -0
  39. data/spec/{unit → lib}/thinking_sphinx/index/faux_column_spec.rb +0 -0
  40. data/spec/{unit → lib}/thinking_sphinx/index_spec.rb +0 -0
  41. data/spec/{unit → lib}/thinking_sphinx/rails_additions_spec.rb +12 -0
  42. data/spec/lib/thinking_sphinx/search_methods_spec.rb +152 -0
  43. data/spec/lib/thinking_sphinx/search_spec.rb +1066 -0
  44. data/spec/{unit → lib}/thinking_sphinx/source_spec.rb +10 -0
  45. data/spec/{unit → lib}/thinking_sphinx_spec.rb +10 -0
  46. data/tasks/distribution.rb +20 -38
  47. data/tasks/testing.rb +3 -1
  48. data/vendor/riddle/lib/riddle.rb +1 -1
  49. data/vendor/riddle/lib/riddle/client.rb +3 -0
  50. data/vendor/riddle/lib/riddle/client/message.rb +4 -3
  51. data/vendor/riddle/lib/riddle/configuration/section.rb +1 -1
  52. data/vendor/riddle/lib/riddle/controller.rb +17 -7
  53. metadata +63 -83
  54. data/lib/thinking_sphinx/active_record/search.rb +0 -57
  55. data/lib/thinking_sphinx/collection.rb +0 -148
  56. data/lib/thinking_sphinx/facet_collection.rb +0 -59
  57. data/lib/thinking_sphinx/search/facets.rb +0 -104
  58. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +0 -107
  59. data/spec/unit/thinking_sphinx/collection_spec.rb +0 -15
  60. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +0 -64
  61. data/spec/unit/thinking_sphinx/search_spec.rb +0 -228
@@ -1,57 +0,0 @@
1
- module ThinkingSphinx
2
- module ActiveRecord
3
- # This module covers the specific model searches - but the syntax is
4
- # exactly the same as the core Search class - so use that as your refence
5
- # point.
6
- #
7
- module Search
8
- def self.included(base)
9
- base.class_eval do
10
- class << self
11
- # Searches for results that match the parameters provided. Will only
12
- # return the ids for the matching objects. See
13
- # ThinkingSphinx::Search#search for syntax examples.
14
- #
15
- def search_for_ids(*args)
16
- options = args.extract_options!
17
- options[:class] = self
18
- args << options
19
- ThinkingSphinx::Search.search_for_ids(*args)
20
- end
21
-
22
- # Searches for results limited to a single model. See
23
- # ThinkingSphinx::Search#search for syntax examples.
24
- #
25
- def search(*args)
26
- options = args.extract_options!
27
- options[:class] = self
28
- args << options
29
- ThinkingSphinx::Search.search(*args)
30
- end
31
-
32
- def search_count(*args)
33
- options = args.extract_options!
34
- options[:class] = self
35
- args << options
36
- ThinkingSphinx::Search.count(*args)
37
- end
38
-
39
- def search_for_id(*args)
40
- options = args.extract_options!
41
- options[:class] = self
42
- args << options
43
- ThinkingSphinx::Search.search_for_id(*args)
44
- end
45
-
46
- def facets(*args)
47
- options = args.extract_options!
48
- options[:class] = self
49
- args << options
50
- ThinkingSphinx::Search.facets(*args)
51
- end
52
- end
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,148 +0,0 @@
1
- module ThinkingSphinx
2
- class Collection < ::Array
3
- attr_reader :total_entries, :total_pages, :current_page, :per_page
4
- attr_accessor :results
5
-
6
- # Compatibility with older versions of will_paginate
7
- alias_method :page_count, :total_pages
8
-
9
- def initialize(page, per_page, entries, total_entries)
10
- @current_page, @per_page, @total_entries = page, per_page, total_entries
11
-
12
- @total_pages = (entries / @per_page.to_f).ceil
13
- end
14
-
15
- def self.ids_from_results(results, page, limit, options)
16
- collection = self.new(page, limit,
17
- results[:total] || 0, results[:total_found] || 0
18
- )
19
- collection.results = results
20
- collection.replace results[:matches].collect { |match|
21
- match[:attributes]["sphinx_internal_id"]
22
- }
23
- return collection
24
- end
25
-
26
- def self.create_from_results(results, page, limit, options)
27
- collection = self.new(page, limit,
28
- results[:total] || 0, results[:total_found] || 0
29
- )
30
- collection.results = results
31
- collection.replace instances_from_matches(results[:matches], options)
32
- return collection
33
- end
34
-
35
- def self.instances_from_matches(matches, options = {})
36
- if klass = options[:class]
37
- instances_from_class klass, matches, options
38
- else
39
- instances_from_classes matches, options
40
- end
41
- end
42
-
43
- def self.instances_from_class(klass, matches, options = {})
44
- index_options = klass.sphinx_index_options
45
-
46
- ids = matches.collect { |match| match[:attributes]["sphinx_internal_id"] }
47
- instances = ids.length > 0 ? klass.find(
48
- :all,
49
- :joins => options[:joins],
50
- :conditions => {klass.primary_key.to_sym => ids},
51
- :include => (options[:include] || index_options[:include]),
52
- :select => (options[:select] || index_options[:select]),
53
- :order => (options[:sql_order] || index_options[:sql_order])
54
- ) : []
55
-
56
- # Raise an exception if we find records in Sphinx but not in the DB, so
57
- # the search method can retry without them. See
58
- # ThinkingSphinx::Search.retry_search_on_stale_index.
59
- if options[:raise_on_stale] && instances.length < ids.length
60
- stale_ids = ids - instances.map {|i| i.id }
61
- raise StaleIdsException, stale_ids
62
- end
63
-
64
- # if the user has specified an SQL order, return the collection
65
- # without rearranging it into the Sphinx order
66
- return instances if options[:sql_order]
67
-
68
- ids.collect { |obj_id|
69
- instances.detect { |obj| obj.id == obj_id }
70
- }
71
- end
72
-
73
- # Group results by class and call #find(:all) once for each group to reduce
74
- # the number of #find's in multi-model searches.
75
- #
76
- def self.instances_from_classes(matches, options = {})
77
- groups = matches.group_by { |match| match[:attributes]["class_crc"] }
78
- groups.each do |crc, group|
79
- group.replace(
80
- instances_from_class(class_from_crc(crc), group, options)
81
- )
82
- end
83
-
84
- matches.collect do |match|
85
- groups.detect { |crc, group|
86
- crc == match[:attributes]["class_crc"]
87
- }[1].detect { |obj|
88
- obj.id == match[:attributes]["sphinx_internal_id"]
89
- }
90
- end
91
- end
92
-
93
- def self.class_from_crc(crc)
94
- @@models_by_crc ||= ThinkingSphinx.indexed_models.inject({}) do |hash, model|
95
- hash[model.constantize.to_crc32] = model
96
- model.constantize.subclasses.each { |subclass|
97
- hash[subclass.to_crc32] = subclass.name
98
- }
99
- hash
100
- end
101
- @@models_by_crc[crc].constantize
102
- end
103
-
104
- def previous_page
105
- current_page > 1 ? (current_page - 1) : nil
106
- end
107
-
108
- def next_page
109
- current_page < total_pages ? (current_page + 1): nil
110
- end
111
-
112
- def offset
113
- (current_page - 1) * @per_page
114
- end
115
-
116
- def method_missing(method, *args, &block)
117
- super unless method.to_s[/^each_with_.*/]
118
-
119
- each_with_attribute method.to_s.gsub(/^each_with_/, ''), &block
120
- end
121
-
122
- def each_with_groupby_and_count(&block)
123
- results[:matches].each_with_index do |match, index|
124
- yield self[index], match[:attributes]["@groupby"], match[:attributes]["@count"]
125
- end
126
- end
127
-
128
- def each_with_attribute(attribute, &block)
129
- results[:matches].each_with_index do |match, index|
130
- yield self[index], (match[:attributes][attribute] || match[:attributes]["@#{attribute}"])
131
- end
132
- end
133
-
134
- def each_with_weighting(&block)
135
- results[:matches].each_with_index do |match, index|
136
- yield self[index], match[:weight]
137
- end
138
- end
139
-
140
- def inject_with_groupby_and_count(initial = nil, &block)
141
- index = -1
142
- results[:matches].inject(initial) do |memo, match|
143
- index += 1
144
- yield memo, self[index], match[:attributes]["@groupby"], match[:attributes]["@count"]
145
- end
146
- end
147
- end
148
- end
@@ -1,59 +0,0 @@
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
@@ -1,104 +0,0 @@
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
- facets = klass.sphinx_facets
26
- facets = Array(options.delete(:facets)).collect { |name|
27
- klass.sphinx_facets.detect { |facet| facet.name.to_s == name.to_s }
28
- }.compact if options[:facets]
29
-
30
- facets.inject(hash) do |hash, facet|
31
- unless facet.name == :class && !options[:class_facet]
32
- options[:group_by] = facet.attribute_name
33
- hash.add_from_results facet, search(*(args + [options]))
34
- end
35
-
36
- hash
37
- end
38
- end
39
-
40
- def facets_for_all_models(args, options)
41
- options = GlobalFacetOptions.merge(options)
42
- hash = ThinkingSphinx::FacetCollection.new args + [options]
43
- options = options.merge! facet_query_options
44
-
45
- facet_names(options).inject(hash) do |hash, name|
46
- options[:group_by] = name
47
- hash.add_from_results name, search(*(args + [options]))
48
- hash
49
- end
50
- end
51
-
52
- def facet_query_options
53
- config = ThinkingSphinx::Configuration.instance
54
- max = config.configuration.searchd.max_matches || 1000
55
-
56
- {
57
- :group_function => :attr,
58
- :limit => max,
59
- :max_matches => max,
60
- :page => 1
61
- }
62
- end
63
-
64
- def facet_classes(options)
65
- options[:classes] || ThinkingSphinx.indexed_models.collect { |model|
66
- model.constantize
67
- }
68
- end
69
-
70
- def facet_names(options)
71
- classes = facet_classes(options)
72
- names = options[:all_attributes] ?
73
- facet_names_for_all_classes(classes) :
74
- facet_names_common_to_all_classes(classes)
75
-
76
- names.delete "class_crc" unless options[:class_facet]
77
- names
78
- end
79
-
80
- def facet_names_for_all_classes(classes)
81
- all_facets = classes.collect { |klass| klass.sphinx_facets }.flatten
82
-
83
- all_facets.group_by { |facet|
84
- facet.name
85
- }.collect { |name, facets|
86
- if facets.collect { |facet| facet.type }.uniq.length > 1
87
- raise "Facet #{name} exists in more than one model with different types"
88
- end
89
- facets.first.attribute_name
90
- }
91
- end
92
-
93
- def facet_names_common_to_all_classes(classes)
94
- facet_names_for_all_classes(classes).select { |name|
95
- classes.all? { |klass|
96
- klass.sphinx_facets.detect { |facet|
97
- facet.attribute_name == name
98
- }
99
- }
100
- }
101
- end
102
- end
103
- end
104
- end
@@ -1,107 +0,0 @@
1
- require 'spec/spec_helper'
2
-
3
- describe "ThinkingSphinx::ActiveRecord::Search" do
4
- it "should add search_for_ids to ActiveRecord::Base" do
5
- ActiveRecord::Base.should respond_to("search_for_ids")
6
- end
7
-
8
- it "should add search_for_ids to ActiveRecord::Base" do
9
- ActiveRecord::Base.should respond_to("search")
10
- end
11
-
12
- it "should add search_count to ActiveRecord::Base" do
13
- ActiveRecord::Base.should respond_to("search_count")
14
- end
15
-
16
- it "should add search_for_id to ActiveRecord::Base" do
17
- ActiveRecord::Base.should respond_to("search_for_id")
18
- end
19
-
20
- describe "search_for_ids method" do
21
- before :each do
22
- ThinkingSphinx::Search.stub_method(:search_for_ids => true)
23
- end
24
-
25
- it "should call ThinkingSphinx::Search#search_for_ids with the class option set" do
26
- Person.search_for_ids("search")
27
-
28
- ThinkingSphinx::Search.should have_received(:search_for_ids).with(
29
- "search", :class => Person
30
- )
31
- end
32
-
33
- it "should override the class option" do
34
- Person.search_for_ids("search", :class => Friendship)
35
-
36
- ThinkingSphinx::Search.should have_received(:search_for_ids).with(
37
- "search", :class => Person
38
- )
39
- end
40
- end
41
-
42
- describe "search method" do
43
- before :each do
44
- ThinkingSphinx::Search.stub_method(:search => true)
45
- end
46
-
47
- it "should call ThinkingSphinx::Search#search with the class option set" do
48
- Person.search("search")
49
-
50
- ThinkingSphinx::Search.should have_received(:search).with(
51
- "search", :class => Person
52
- )
53
- end
54
-
55
- it "should override the class option" do
56
- Person.search("search", :class => Friendship)
57
-
58
- ThinkingSphinx::Search.should have_received(:search).with(
59
- "search", :class => Person
60
- )
61
- end
62
- end
63
-
64
- describe "search_for_id method" do
65
- before :each do
66
- ThinkingSphinx::Search.stub_method(:search_for_id => true)
67
- end
68
-
69
- it "should call ThinkingSphinx::Search#search with the class option set" do
70
- Person.search_for_id(10)
71
-
72
- ThinkingSphinx::Search.should have_received(:search_for_id).with(
73
- 10, :class => Person
74
- )
75
- end
76
-
77
- it "should override the class option" do
78
- Person.search_for_id(10, :class => Friendship)
79
-
80
- ThinkingSphinx::Search.should have_received(:search_for_id).with(
81
- 10, :class => Person
82
- )
83
- end
84
- end
85
-
86
- describe "search_count method" do
87
- before :each do
88
- ThinkingSphinx::Search.stub_method(:count => true)
89
- end
90
-
91
- it "should call ThinkingSphinx::Search#search with the class option set" do
92
- Person.search_count("search")
93
-
94
- ThinkingSphinx::Search.should have_received(:count).with(
95
- "search", :class => Person
96
- )
97
- end
98
-
99
- it "should override the class option" do
100
- Person.search_count("search", :class => Friendship)
101
-
102
- ThinkingSphinx::Search.should have_received(:count).with(
103
- "search", :class => Person
104
- )
105
- end
106
- end
107
- end