dpickett-thinking-sphinx 1.1.4 → 1.1.12
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.
- 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
|