ebeigarts-thinking-sphinx 1.1.22 → 1.2.10

Sign up to get free protection for your applications and to get access to all the features.
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