dpickett-thinking-sphinx 1.1.4 → 1.1.12
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +126 -0
- data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
- data/lib/thinking_sphinx/active_record/delta.rb +14 -1
- data/lib/thinking_sphinx/active_record.rb +23 -5
- data/lib/thinking_sphinx/adapters/abstract_adapter.rb +9 -1
- data/lib/thinking_sphinx/adapters/mysql_adapter.rb +3 -2
- data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +4 -3
- data/lib/thinking_sphinx/association.rb +17 -0
- data/lib/thinking_sphinx/attribute.rb +106 -95
- data/lib/thinking_sphinx/class_facet.rb +0 -5
- data/lib/thinking_sphinx/collection.rb +7 -1
- data/lib/thinking_sphinx/configuration.rb +9 -4
- data/lib/thinking_sphinx/core/string.rb +3 -10
- data/lib/thinking_sphinx/deltas/default_delta.rb +8 -5
- data/lib/thinking_sphinx/deltas/delayed_delta.rb +4 -2
- data/lib/thinking_sphinx/deltas.rb +7 -2
- data/lib/thinking_sphinx/deploy/capistrano.rb +80 -0
- data/lib/thinking_sphinx/facet.rb +22 -9
- data/lib/thinking_sphinx/facet_collection.rb +27 -12
- data/lib/thinking_sphinx/field.rb +4 -96
- data/lib/thinking_sphinx/index/builder.rb +46 -15
- data/lib/thinking_sphinx/index.rb +58 -66
- data/lib/thinking_sphinx/property.rb +133 -0
- data/lib/thinking_sphinx/rails_additions.rb +7 -4
- data/lib/thinking_sphinx/search.rb +181 -44
- data/lib/thinking_sphinx/tasks.rb +4 -4
- data/lib/thinking_sphinx.rb +47 -11
- data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +2 -2
- data/spec/unit/thinking_sphinx/active_record_spec.rb +64 -4
- data/spec/unit/thinking_sphinx/attribute_spec.rb +16 -1
- data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
- data/spec/unit/thinking_sphinx/facet_spec.rb +46 -0
- data/spec/unit/thinking_sphinx/index_spec.rb +90 -0
- data/spec/unit/thinking_sphinx/rails_additions_spec.rb +183 -0
- data/spec/unit/thinking_sphinx/search_spec.rb +44 -0
- data/spec/unit/thinking_sphinx_spec.rb +10 -6
- data/tasks/distribution.rb +1 -1
- data/tasks/testing.rb +7 -15
- data/vendor/after_commit/init.rb +3 -0
- data/vendor/after_commit/lib/after_commit/active_record.rb +27 -4
- data/vendor/after_commit/lib/after_commit/connection_adapters.rb +1 -1
- data/vendor/after_commit/lib/after_commit.rb +4 -1
- metadata +12 -3
- data/README +0 -107
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'
|
@@ -35,7 +36,7 @@ module ThinkingSphinx
|
|
35
36
|
module Version #:nodoc:
|
36
37
|
Major = 1
|
37
38
|
Minor = 1
|
38
|
-
Tiny =
|
39
|
+
Tiny = 12
|
39
40
|
|
40
41
|
String = [Major, Minor, Tiny].join('.')
|
41
42
|
end
|
@@ -61,6 +62,10 @@ module ThinkingSphinx
|
|
61
62
|
@@indexed_models ||= []
|
62
63
|
end
|
63
64
|
|
65
|
+
def self.unique_id_expression(offset = nil)
|
66
|
+
"* #{ThinkingSphinx.indexed_models.size} + #{offset || 0}"
|
67
|
+
end
|
68
|
+
|
64
69
|
# Check if index definition is disabled.
|
65
70
|
#
|
66
71
|
def self.define_indexes?
|
@@ -125,21 +130,52 @@ module ThinkingSphinx
|
|
125
130
|
# or if not using MySQL, this will return false.
|
126
131
|
#
|
127
132
|
def self.use_group_by_shortcut?
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
"SELECT @@global.sql_mode, @@session.sql_mode;"
|
134
|
-
).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
|
133
|
+
!!(
|
134
|
+
mysql? && ::ActiveRecord::Base.connection.select_all(
|
135
|
+
"SELECT @@global.sql_mode, @@session.sql_mode;"
|
136
|
+
).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
|
137
|
+
)
|
135
138
|
end
|
136
139
|
|
137
140
|
def self.sphinx_running?
|
138
|
-
!!sphinx_pid
|
141
|
+
!!sphinx_pid && pid_active?(sphinx_pid)
|
139
142
|
end
|
140
143
|
|
141
144
|
def self.sphinx_pid
|
142
|
-
pid_file
|
143
|
-
|
145
|
+
pid_file = ThinkingSphinx::Configuration.instance.pid_file
|
146
|
+
cat_command = 'cat'
|
147
|
+
return nil unless File.exists?(pid_file)
|
148
|
+
|
149
|
+
if microsoft?
|
150
|
+
pid_file.gsub!('/', '\\')
|
151
|
+
cat_command = 'type'
|
152
|
+
end
|
153
|
+
|
154
|
+
`#{cat_command} #{pid_file}`[/\d+/]
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.pid_active?(pid)
|
158
|
+
return true if microsoft?
|
159
|
+
|
160
|
+
begin
|
161
|
+
# In JRuby this returns -1 if the process doesn't exist
|
162
|
+
Process.getpgid(pid.to_i) != -1
|
163
|
+
rescue Exception => e
|
164
|
+
false
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.microsoft?
|
169
|
+
RUBY_PLATFORM =~ /mswin/
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.jruby?
|
173
|
+
defined?(JRUBY_VERSION)
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.mysql?
|
177
|
+
::ActiveRecord::Base.connection.class.name.demodulize == "MysqlAdapter" || (
|
178
|
+
jruby? && ::ActiveRecord::Base.connection.config[:adapter] == "jdbcmysql"
|
179
|
+
)
|
144
180
|
end
|
145
181
|
end
|
@@ -76,7 +76,7 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
|
|
76
76
|
|
77
77
|
@person = Person.new
|
78
78
|
@person.stub_method(
|
79
|
-
:
|
79
|
+
:in_both_indexes? => false,
|
80
80
|
:sphinx_document_id => 1
|
81
81
|
)
|
82
82
|
|
@@ -126,7 +126,7 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
|
|
126
126
|
end
|
127
127
|
|
128
128
|
it "should update the deleted attribute if in the core index" do
|
129
|
-
@person.stub_method(:
|
129
|
+
@person.stub_method(:in_both_indexes? => true)
|
130
130
|
|
131
131
|
@person.send(:index_delta)
|
132
132
|
|
@@ -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
|
@@ -93,6 +93,60 @@ describe "ThinkingSphinx::ActiveRecord" do
|
|
93
93
|
TestModule::TestModel.define_index.should == @index
|
94
94
|
end
|
95
95
|
end
|
96
|
+
|
97
|
+
describe "index methods" do
|
98
|
+
before(:all) do
|
99
|
+
@person = Person.find(:first)
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "in_both_indexes?" do
|
103
|
+
it "should return true if in core and delta indexes" do
|
104
|
+
@person.should_receive(:in_core_index?).and_return(true)
|
105
|
+
@person.should_receive(:in_delta_index?).and_return(true)
|
106
|
+
@person.in_both_indexes?.should be_true
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should return false if in one index and not the other" do
|
110
|
+
@person.should_receive(:in_core_index?).and_return(true)
|
111
|
+
@person.should_receive(:in_delta_index?).and_return(false)
|
112
|
+
@person.in_both_indexes?.should be_false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "in_core_index?" do
|
117
|
+
it "should call in_index? with core" do
|
118
|
+
@person.should_receive(:in_index?).with('core')
|
119
|
+
@person.in_core_index?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "in_delta_index?" do
|
124
|
+
it "should call in_index? with delta" do
|
125
|
+
@person.should_receive(:in_index?).with('delta')
|
126
|
+
@person.in_delta_index?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "in_index?" do
|
131
|
+
it "should return true if in the specified index" do
|
132
|
+
@person.should_receive(:sphinx_document_id).and_return(1)
|
133
|
+
@person.should_receive(:sphinx_index_name).and_return('person_core')
|
134
|
+
Person.should_receive(:search_for_id).with(1, 'person_core').and_return(true)
|
135
|
+
|
136
|
+
@person.in_index?('core').should be_true
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "source_of_sphinx_index method" do
|
142
|
+
it "should return self if model defines an index" do
|
143
|
+
Person.source_of_sphinx_index.should == Person
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should return the parent if model inherits an index" do
|
147
|
+
Parent.source_of_sphinx_index.should == Person
|
148
|
+
end
|
149
|
+
end
|
96
150
|
|
97
151
|
describe "to_crc32 method" do
|
98
152
|
it "should return an integer" do
|
@@ -100,6 +154,12 @@ describe "ThinkingSphinx::ActiveRecord" do
|
|
100
154
|
end
|
101
155
|
end
|
102
156
|
|
157
|
+
describe "to_crc32s method" do
|
158
|
+
it "should return an array" do
|
159
|
+
Person.to_crc32s.should be_a_kind_of(Array)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
103
163
|
describe "toggle_deleted method" do
|
104
164
|
before :each do
|
105
165
|
ThinkingSphinx.stub_method(:sphinx_running? => true)
|
@@ -157,7 +157,7 @@ describe ThinkingSphinx::Attribute do
|
|
157
157
|
end
|
158
158
|
|
159
159
|
it "should return :string if there's more than one association" do
|
160
|
-
@attribute.associations = {:a => :assoc, :b => :assoc}
|
160
|
+
@attribute.associations = {:a => [:assoc], :b => [:assoc]}
|
161
161
|
@attribute.send(:type).should == :string
|
162
162
|
end
|
163
163
|
|
@@ -209,4 +209,19 @@ describe ThinkingSphinx::Attribute do
|
|
209
209
|
attribute.send(:all_ints?).should be_false
|
210
210
|
end
|
211
211
|
end
|
212
|
+
|
213
|
+
describe "with custom queries" do
|
214
|
+
before :each do
|
215
|
+
index = CricketTeam.sphinx_indexes.first
|
216
|
+
@statement = index.to_riddle_for_core(0, 0).sql_attr_multi.first
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should track the query type accordingly" do
|
220
|
+
@statement.should match(/uint tags from query/)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should include the SQL statement" do
|
224
|
+
@statement.should match(/SELECT cricket_team_id, id FROM tags/)
|
225
|
+
end
|
226
|
+
end
|
212
227
|
end
|
@@ -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
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe ThinkingSphinx::Facet do
|
4
|
+
describe ".name_for" do
|
5
|
+
it "should remove '_facet' from provided string and return a symbol" do
|
6
|
+
ThinkingSphinx::Facet.name_for('attribute_facet').should == :attribute
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should remove '_facet' from provided symbol" do
|
10
|
+
ThinkingSphinx::Facet.name_for(:attribute_facet).should == :attribute
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return the name of the facet if a Facet is passed" do
|
14
|
+
facet = ThinkingSphinx::Facet.new(
|
15
|
+
ThinkingSphinx::Attribute.stub_instance(:unique_name => :attribute, :columns => ['attribute'])
|
16
|
+
)
|
17
|
+
ThinkingSphinx::Facet.name_for(facet).should == :attribute
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return 'class' for special case name 'class_crc'" do
|
21
|
+
ThinkingSphinx::Facet.name_for(:class_crc).should == :class
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should cycle" do
|
25
|
+
ThinkingSphinx::Facet.name_for(ThinkingSphinx::Facet.attribute_name_for(:attribute)).should == :attribute
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".attribute_name_for" do
|
30
|
+
it "should append '_facet' to provided string" do
|
31
|
+
ThinkingSphinx::Facet.attribute_name_for('attribute').should == 'attribute_facet'
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should append '_facet' to provided symbol and return a string" do
|
35
|
+
ThinkingSphinx::Facet.attribute_name_for(:attribute).should == 'attribute_facet'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return 'class_crc' for special case attribute 'class'" do
|
39
|
+
ThinkingSphinx::Facet.attribute_name_for(:class).should == 'class_crc'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should cycle" do
|
43
|
+
ThinkingSphinx::Facet.attribute_name_for(ThinkingSphinx::Facet.name_for('attribute_facet')).should == 'attribute_facet'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -51,4 +51,94 @@ describe ThinkingSphinx::Index do
|
|
51
51
|
@index.infix_fields.should_not include(@field_b)
|
52
52
|
end
|
53
53
|
end
|
54
|
+
|
55
|
+
describe "multi-value attribute as ranged-query with has-many association" do
|
56
|
+
before :each do
|
57
|
+
@index = ThinkingSphinx::Index.new(Person) do
|
58
|
+
has tags(:id), :as => :tag_ids, :source => :ranged_query
|
59
|
+
end
|
60
|
+
@index.link!
|
61
|
+
|
62
|
+
@sql = @index.to_riddle_for_core(0, 0).sql_query
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not include attribute in select-clause sql_query" do
|
66
|
+
@sql.should_not match(/tag_ids/)
|
67
|
+
@sql.should_not match(/GROUP_CONCAT\(`tags`.`id`/)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should not join with association table" do
|
71
|
+
@sql.should_not match(/LEFT OUTER JOIN `tags`/)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should include sql_attr_multi as ranged-query" do
|
75
|
+
attribute = @index.send(:attributes).first
|
76
|
+
attribute.send(:type_to_config).to_s.should == "sql_attr_multi"
|
77
|
+
|
78
|
+
declaration, query, range_query = attribute.send(:config_value).split('; ')
|
79
|
+
declaration.should == "uint tag_ids from ranged-query"
|
80
|
+
query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`id` AS `tag_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end"
|
81
|
+
range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "multi-value attribute as ranged-query with has-many-through association" do
|
86
|
+
before :each do
|
87
|
+
@index = ThinkingSphinx::Index.new(Person) do
|
88
|
+
has football_teams(:id), :as => :football_teams_ids, :source => :ranged_query
|
89
|
+
end
|
90
|
+
@index.link!
|
91
|
+
|
92
|
+
@sql = @index.to_riddle_for_core(0, 0).sql_query
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should not include attribute in select-clause sql_query" do
|
96
|
+
@sql.should_not match(/football_teams_ids/)
|
97
|
+
@sql.should_not match(/GROUP_CONCAT\(`tags`.`football_team_id`/)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should not join with association table" do
|
101
|
+
@sql.should_not match(/LEFT OUTER JOIN `tags`/)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should include sql_attr_multi as ranged-query" do
|
105
|
+
attribute = @index.send(:attributes).first
|
106
|
+
attribute.send(:type_to_config).to_s.should == "sql_attr_multi"
|
107
|
+
|
108
|
+
declaration, query, range_query = attribute.send(:config_value).split('; ')
|
109
|
+
declaration.should == "uint football_teams_ids from ranged-query"
|
110
|
+
query.should == "SELECT `tags`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `tags`.`football_team_id` AS `football_teams_ids` FROM `tags` WHERE `tags`.`person_id` >= $start AND `tags`.`person_id` <= $end"
|
111
|
+
range_query.should == "SELECT MIN(`tags`.`person_id`), MAX(`tags`.`person_id`) FROM `tags`"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "multi-value attribute as ranged-query with has-many-through association and foreign_key" do
|
116
|
+
before :each do
|
117
|
+
@index = ThinkingSphinx::Index.new(Person) do
|
118
|
+
has friends(:id), :as => :friend_ids, :source => :ranged_query
|
119
|
+
end
|
120
|
+
@index.link!
|
121
|
+
|
122
|
+
@sql = @index.to_riddle_for_core(0, 0).sql_query
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should not include attribute in select-clause sql_query" do
|
126
|
+
@sql.should_not match(/friend_ids/)
|
127
|
+
@sql.should_not match(/GROUP_CONCAT\(`friendships`.`friend_id`/)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should not join with association table" do
|
131
|
+
@sql.should_not match(/LEFT OUTER JOIN `friendships`/)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should include sql_attr_multi as ranged-query" do
|
135
|
+
attribute = @index.send(:attributes).first
|
136
|
+
attribute.send(:type_to_config).to_s.should == "sql_attr_multi"
|
137
|
+
|
138
|
+
declaration, query, range_query = attribute.send(:config_value).split('; ')
|
139
|
+
declaration.should == "uint friend_ids from ranged-query"
|
140
|
+
query.should == "SELECT `friendships`.`person_id` #{ThinkingSphinx.unique_id_expression} AS `id`, `friendships`.`friend_id` AS `friend_ids` FROM `friendships` WHERE `friendships`.`person_id` >= $start AND `friendships`.`person_id` <= $end"
|
141
|
+
range_query.should == "SELECT MIN(`friendships`.`person_id`), MAX(`friendships`.`person_id`) FROM `friendships`"
|
142
|
+
end
|
143
|
+
end
|
54
144
|
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe ThinkingSphinx::HashExcept do
|
4
|
+
before(:each) do
|
5
|
+
@hash = { :number => 20, :letter => 'b', :shape => 'rectangle' }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "except method" do
|
9
|
+
it "returns a hash without the specified keys" do
|
10
|
+
new_hash = @hash.except(:number)
|
11
|
+
new_hash.should_not have_key(:number)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "except! method" do
|
16
|
+
it "modifies hash removing specified keys" do
|
17
|
+
@hash.except!(:number)
|
18
|
+
@hash.should_not have_key(:number)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "extends Hash" do
|
23
|
+
it 'with except' do
|
24
|
+
Hash.instance_methods.include?('except').should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'with except!' do
|
28
|
+
Hash.instance_methods.include?('except!').should be_true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ThinkingSphinx::ArrayExtractOptions do
|
34
|
+
describe 'extract_options! method' do
|
35
|
+
it 'returns a hash' do
|
36
|
+
array = []
|
37
|
+
array.extract_options!.should be_kind_of(Hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns the last option if it is a hash' do
|
41
|
+
array = ['a', 'b', {:c => 'd'}]
|
42
|
+
array.extract_options!.should == {:c => 'd'}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "extends Array" do
|
47
|
+
it 'with extract_options!' do
|
48
|
+
Array.instance_methods.include?('extract_options!').should be_true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe ThinkingSphinx::AbstractQuotedTableName do
|
54
|
+
describe 'quote_table_name method' do
|
55
|
+
it 'calls quote_column_name' do
|
56
|
+
adapter = ActiveRecord::ConnectionAdapters::AbstractAdapter.new(defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql')
|
57
|
+
adapter.should_receive(:quote_column_name).with('messages')
|
58
|
+
adapter.quote_table_name('messages')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "extends ActiveRecord::ConnectionAdapters::AbstractAdapter" do
|
63
|
+
it 'with quote_table_name' do
|
64
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_methods.include?('quote_table_name').should be_true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe ThinkingSphinx::MysqlQuotedTableName do
|
70
|
+
describe "quote_table_name method" do
|
71
|
+
it 'correctly quotes' do
|
72
|
+
adapter = ActiveRecord::Base.connection
|
73
|
+
adapter.quote_table_name('thinking_sphinx.messages').should == "`thinking_sphinx`.`messages`"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "extends ActiveRecord::ConnectionAdapters::MysqlAdapter" do
|
78
|
+
it 'with quote_table_name' do
|
79
|
+
adapter = defined?(JRUBY_VERSION) ? :JdbcAdapter : :MysqlAdapter
|
80
|
+
ActiveRecord::ConnectionAdapters.const_get(adapter).instance_methods.include?("quote_table_name").should be_true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe ThinkingSphinx::ActiveRecordQuotedName do
|
86
|
+
describe "quoted_table_name method" do
|
87
|
+
it 'returns table name wrappd in quotes' do
|
88
|
+
Person.quoted_table_name.should == '`people`'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "extends ActiveRecord::Base" do
|
93
|
+
it 'with quoted_table_name' do
|
94
|
+
ActiveRecord::Base.respond_to?("quoted_table_name").should be_true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe ThinkingSphinx::ActiveRecordStoreFullSTIClass do
|
100
|
+
describe "store_full_sti_class method" do
|
101
|
+
it 'returns false' do
|
102
|
+
Person.store_full_sti_class.should be_false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "extends ActiveRecord::Base" do
|
107
|
+
it 'with store_full_sti_class' do
|
108
|
+
ActiveRecord::Base.respond_to?(:store_full_sti_class).should be_true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class TestModel
|
114
|
+
@@squares = 89
|
115
|
+
@@circles = 43
|
116
|
+
|
117
|
+
def number_of_polygons
|
118
|
+
@@polygons
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe ThinkingSphinx::ClassAttributeMethods do
|
123
|
+
describe "cattr_reader method" do
|
124
|
+
it 'creates getters' do
|
125
|
+
TestModel.cattr_reader :herbivores
|
126
|
+
test_model = TestModel.new
|
127
|
+
test_model.respond_to?(:herbivores).should be_true
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'sets the initial value to nil' do
|
131
|
+
TestModel.cattr_reader :carnivores
|
132
|
+
test_model = TestModel.new
|
133
|
+
test_model.carnivores.should be_nil
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'does not override an existing definition' do
|
137
|
+
TestModel.cattr_reader :squares
|
138
|
+
test_model = TestModel.new
|
139
|
+
test_model.squares.should == 89
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "cattr_writer method" do
|
144
|
+
it 'creates setters' do
|
145
|
+
TestModel.cattr_writer :herbivores
|
146
|
+
test_model = TestModel.new
|
147
|
+
test_model.respond_to?(:herbivores=).should be_true
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'does not override an existing definition' do
|
151
|
+
TestModel.cattr_writer :polygons
|
152
|
+
test_model = TestModel.new
|
153
|
+
test_model.polygons = 100
|
154
|
+
test_model.number_of_polygons.should == 100
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "cattr_accessor method" do
|
159
|
+
it 'calls cattr_reader' do
|
160
|
+
Class.should_receive(:cattr_reader).with('polygons')
|
161
|
+
Class.cattr_accessor('polygons')
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'calls cattr_writer' do
|
165
|
+
Class.should_receive(:cattr_writer).with('polygons')
|
166
|
+
Class.cattr_accessor('polygons')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "extends Class" do
|
171
|
+
it 'with cattr_reader' do
|
172
|
+
Class.respond_to?('cattr_reader').should be_true
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'with cattr_writer' do
|
176
|
+
Class.respond_to?('cattr_writer').should be_true
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'with cattr_accessor' do
|
180
|
+
Class.respond_to?('cattr_accessor').should be_true
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -50,6 +50,50 @@ describe ThinkingSphinx::Search do
|
|
50
50
|
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
describe "facets method" do
|
55
|
+
before :each do
|
56
|
+
@results = [Person.find(:first)]
|
57
|
+
@results.stub!(:each_with_groupby_and_count).
|
58
|
+
and_yield(@results.first, @results.first.city.to_crc32, 1)
|
59
|
+
ThinkingSphinx::Search.stub!(:search => @results)
|
60
|
+
|
61
|
+
@config = ThinkingSphinx::Configuration.instance
|
62
|
+
@config.configuration.searchd.max_matches = 10_000
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should use the system-set max_matches for limit on facet calls" do
|
66
|
+
ThinkingSphinx::Search.should_receive(:search) do |options|
|
67
|
+
options[:max_matches].should == 10_000
|
68
|
+
options[:limit].should == 10_000
|
69
|
+
end
|
70
|
+
|
71
|
+
ThinkingSphinx::Search.facets :all_attributes => true
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should use the default max-matches if there is no explicit setting" do
|
75
|
+
@config.configuration.searchd.max_matches = nil
|
76
|
+
ThinkingSphinx::Search.should_receive(:search) do |options|
|
77
|
+
options[:max_matches].should == 1000
|
78
|
+
options[:limit].should == 1000
|
79
|
+
end
|
80
|
+
|
81
|
+
ThinkingSphinx::Search.facets :all_attributes => true
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should ignore user-provided max_matches and limit on facet calls" do
|
85
|
+
ThinkingSphinx::Search.should_receive(:search) do |options|
|
86
|
+
options[:max_matches].should == 10_000
|
87
|
+
options[:limit].should == 10_000
|
88
|
+
end
|
89
|
+
|
90
|
+
ThinkingSphinx::Search.facets(
|
91
|
+
:all_attributes => true,
|
92
|
+
:max_matches => 500,
|
93
|
+
:limit => 200
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
53
97
|
end
|
54
98
|
|
55
99
|
describe ThinkingSphinx::Search, "playing nice with Search model" do
|