freelancing-god-thinking-sphinx 0.9.6 → 0.9.7

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.
@@ -37,6 +37,57 @@ module ThinkingSphinx
37
37
  initialize_from_builder(&block) if block_given?
38
38
  end
39
39
 
40
+ def to_config(index, database_conf, charset_type)
41
+ # Set up associations and joins
42
+ link!
43
+
44
+ attr_sources = attributes.collect { |attrib|
45
+ attrib.to_sphinx_clause
46
+ }.join("\n ")
47
+
48
+ db_adapter = case adapter
49
+ when :postgres
50
+ "pgsql"
51
+ when :mysql
52
+ "mysql"
53
+ else
54
+ raise "Unsupported Database Adapter: Sphinx only supports MySQL and PosgreSQL"
55
+ end
56
+
57
+ config = <<-SOURCE
58
+
59
+ source #{model.name.downcase}_#{index}_core
60
+ {
61
+ type = #{db_adapter}
62
+ sql_host = #{database_conf[:host] || "localhost"}
63
+ sql_user = #{database_conf[:username]}
64
+ sql_pass = #{database_conf[:password]}
65
+ sql_db = #{database_conf[:database]}
66
+
67
+ sql_query_pre = #{charset_type == "utf-8" && adapter == :mysql ? "SET NAMES utf8" : ""}
68
+ sql_query_pre = #{to_sql_query_pre}
69
+ sql_query = #{to_sql.gsub(/\n/, ' ')}
70
+ sql_query_range = #{to_sql_query_range}
71
+ sql_query_info = #{to_sql_query_info}
72
+ #{attr_sources}
73
+ }
74
+ SOURCE
75
+
76
+ if delta?
77
+ config += <<-SOURCE
78
+
79
+ source #{model.name.downcase}_#{index}_delta : #{model.name.downcase}_#{index}_core
80
+ {
81
+ sql_query_pre =
82
+ sql_query = #{to_sql(:delta => true).gsub(/\n/, ' ')}
83
+ sql_query_range = #{to_sql_query_range :delta => true}
84
+ }
85
+ SOURCE
86
+ end
87
+
88
+ config
89
+ end
90
+
40
91
  # Link all the fields and associations to their corresponding
41
92
  # associations and joins. This _must_ be called before interrogating
42
93
  # the index's fields and associations for anything that may reference
@@ -86,7 +137,7 @@ module ThinkingSphinx
86
137
  where_clause << " AND " << @conditions.join(" AND ")
87
138
  end
88
139
 
89
- <<-SQL
140
+ sql = <<-SQL
90
141
  SELECT #{ (
91
142
  ["#{@model.quoted_table_name}.#{quote_column(@model.primary_key)}"] +
92
143
  @fields.collect { |field| field.to_select_sql } +
@@ -102,8 +153,13 @@ GROUP BY #{ (
102
153
  @fields.collect { |field| field.to_group_sql }.compact +
103
154
  @attributes.collect { |attribute| attribute.to_group_sql }.compact
104
155
  ).join(", ") }
105
- ORDER BY NULL
106
156
  SQL
157
+
158
+ if @model.connection.class.name == "ActiveRecord::ConnectionAdapters::MysqlAdapter"
159
+ sql += " ORDER BY NULL"
160
+ end
161
+
162
+ sql
107
163
  end
108
164
 
109
165
  # Simple helper method for the query info SQL - which is a statement that
@@ -152,6 +208,14 @@ ORDER BY NULL
152
208
  end
153
209
  end
154
210
 
211
+ def prefix_fields
212
+ @fields.select { |field| field.prefixes }
213
+ end
214
+
215
+ def infix_fields
216
+ @fields.select { |field| field.infixes }
217
+ end
218
+
155
219
  private
156
220
 
157
221
  def quote_column(column)
@@ -182,6 +246,11 @@ ORDER BY NULL
182
246
  :type => :integer,
183
247
  :as => :class_crc
184
248
  )
249
+ @attributes << Attribute.new(
250
+ FauxColumn.new("0"),
251
+ :type => :integer,
252
+ :as => :sphinx_deleted
253
+ )
185
254
  end
186
255
 
187
256
  # Returns all associations used amongst all the fields and attributes.
@@ -48,14 +48,14 @@ module ThinkingSphinx
48
48
  # get access down the associations tree.
49
49
  #
50
50
  # indexes :id, :as => :my_id
51
+ # indexes :name, :sortable => true
51
52
  # indexes first_name, last_name, :sortable => true
52
- # indexes name, :sortable => true
53
53
  # indexes users.posts.content, :as => :post_content
54
54
  # indexes users(:id), :as => :user_ids
55
55
  #
56
- # Keep in mind that if any keywords for Ruby methods - such as id -
57
- # clash with your column names, you need to use the symbol version (see
58
- # the first and last examples above).
56
+ # Keep in mind that if any keywords for Ruby methods - such as id or
57
+ # name - clash with your column names, you need to use the symbol
58
+ # version (see the first, second and last examples above).
59
59
  #
60
60
  # If you specify multiple columns (example #2), a field will be created
61
61
  # for each. Don't use the :as option in this case. If you want to merge
@@ -194,4 +194,4 @@ module ThinkingSphinx
194
194
  end
195
195
  end
196
196
  end
197
- end
197
+ end
@@ -15,9 +15,12 @@ module ThinkingSphinx
15
15
  def search_for_ids(*args)
16
16
  results, client = search_results(*args.clone)
17
17
 
18
+ options = args.extract_options!
19
+ page = options[:page] ? options[:page].to_i : 1
20
+
18
21
  begin
19
22
  pager = WillPaginate::Collection.new(page,
20
- client.limit, results[:total])
23
+ client.limit, results[:total] || 0)
21
24
  pager.replace results[:matches].collect { |match| match[:doc] }
22
25
  rescue
23
26
  results[:matches].collect { |match| match[:doc] }
@@ -268,6 +271,9 @@ module ThinkingSphinx
268
271
 
269
272
  client.anchor = anchor_conditions(klass, options) || {} if client.anchor.empty?
270
273
 
274
+ client.filters << Riddle::Client::Filter.new(
275
+ "sphinx_deleted", [0]
276
+ )
271
277
  # class filters
272
278
  client.filters << Riddle::Client::Filter.new(
273
279
  "class_crc", options[:classes].collect { |klass| klass.to_crc32 }
@@ -17,6 +17,10 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
17
17
  :after_commit, [:toggle_delta]
18
18
  )
19
19
  end
20
+
21
+ it "should have an after_commit method by default" do
22
+ Person.instance_methods.should include("after_commit")
23
+ end
20
24
  end
21
25
 
22
26
  describe "save_with_after_commit_callback method" do
@@ -0,0 +1,63 @@
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.methods.should include("search_for_ids")
6
+ end
7
+
8
+ it "should add search_for_ids to ActiveRecord::Base" do
9
+ ActiveRecord::Base.methods.should include("search")
10
+ end
11
+
12
+ describe "search_for_ids method" do
13
+ before :each do
14
+ ThinkingSphinx::Search.stub_method(:search_for_ids => true)
15
+ end
16
+
17
+ after :each do
18
+ ThinkingSphinx::Search.unstub_method(:search_for_ids)
19
+ end
20
+
21
+ it "should call ThinkingSphinx::Search#search_for_ids with the class option set" do
22
+ Person.search_for_ids("search")
23
+
24
+ ThinkingSphinx::Search.should have_received(:search_for_ids).with(
25
+ "search", :class => Person
26
+ )
27
+ end
28
+
29
+ it "should override the class option" do
30
+ Person.search_for_ids("search", :class => Friendship)
31
+
32
+ ThinkingSphinx::Search.should have_received(:search_for_ids).with(
33
+ "search", :class => Person
34
+ )
35
+ end
36
+ end
37
+
38
+ describe "search method" do
39
+ before :each do
40
+ ThinkingSphinx::Search.stub_method(:search => true)
41
+ end
42
+
43
+ after :each do
44
+ ThinkingSphinx::Search.unstub_method(:search)
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
+ end
@@ -8,8 +8,9 @@ describe "ThinkingSphinx::ActiveRecord" do
8
8
  end
9
9
 
10
10
  TestModule::TestModel.stub_methods(
11
- :before_save => true,
12
- :after_commit => true
11
+ :before_save => true,
12
+ :after_commit => true,
13
+ :after_destroy => true
13
14
  )
14
15
 
15
16
  @index = ThinkingSphinx::Index.stub_instance(:delta? => false)
@@ -19,6 +20,8 @@ describe "ThinkingSphinx::ActiveRecord" do
19
20
  after :each do
20
21
  # Remove the class so we can redefine it
21
22
  TestModule.send(:remove_const, :TestModel)
23
+
24
+ ThinkingSphinx::Index.unstub_method(:new)
22
25
  end
23
26
 
24
27
  it "should return nil and do nothing if indexes are disabled" do
@@ -72,6 +75,20 @@ describe "ThinkingSphinx::ActiveRecord" do
72
75
  TestModule::TestModel.should_not have_received(:after_commit)
73
76
  end
74
77
 
78
+ it "should add an after_destroy hook with delta indexing enabled" do
79
+ @index.stub_method(:delta? => true)
80
+
81
+ TestModule::TestModel.define_index do; end
82
+
83
+ TestModule::TestModel.should have_received(:after_destroy).with(:toggle_deleted)
84
+ end
85
+
86
+ it "should add an after_destroy hook with delta indexing disabled" do
87
+ TestModule::TestModel.define_index do; end
88
+
89
+ TestModule::TestModel.should have_received(:after_destroy).with(:toggle_deleted)
90
+ end
91
+
75
92
  it "should return the new index" do
76
93
  TestModule::TestModel.define_index.should == @index
77
94
  end
@@ -82,4 +99,59 @@ describe "ThinkingSphinx::ActiveRecord" do
82
99
  Person.to_crc32.should be_a_kind_of(Integer)
83
100
  end
84
101
  end
102
+
103
+ describe "toggle_deleted method" do
104
+ before :each do
105
+ @configuration = ThinkingSphinx::Configuration.stub_instance(
106
+ :address => "an address",
107
+ :port => 123
108
+ )
109
+ @client = Riddle::Client.stub_instance(:update => true)
110
+ @person = Person.new
111
+
112
+ ThinkingSphinx::Configuration.stub_method(:new => @configuration)
113
+ Riddle::Client.stub_method(:new => @client)
114
+ Person.indexes.each { |index| index.stub_method(:delta? => false) }
115
+ end
116
+
117
+ after :each do
118
+ ThinkingSphinx::Configuration.unstub_method(:new)
119
+ Riddle::Client.unstub_method(:new)
120
+ Person.indexes.each { |index| index.unstub_method(:delta?) }
121
+ end
122
+
123
+ it "should create a client using the Configuration's address and port" do
124
+ @person.toggle_deleted
125
+
126
+ Riddle::Client.should have_received(:new).with(
127
+ @configuration.address, @configuration.port
128
+ )
129
+ end
130
+
131
+ it "should update the core index's deleted flag" do
132
+ @person.toggle_deleted
133
+
134
+ @client.should have_received(:update).with(
135
+ "person_core", ["sphinx_deleted"], {@person.id => 1}
136
+ )
137
+ end
138
+
139
+ it "should update the delta index's deleted flag if delta indexing is enabled" do
140
+ Person.indexes.each { |index| index.stub_method(:delta? => true) }
141
+
142
+ @person.toggle_deleted
143
+
144
+ @client.should have_received(:update).with(
145
+ "person_delta", ["sphinx_deleted"], {@person.id => 1}
146
+ )
147
+ end
148
+
149
+ it "shouldn't update the delta index if delta indexing is disabled" do
150
+ @person.toggle_deleted
151
+
152
+ @client.should_not have_received(:update).with(
153
+ "person_delta", ["sphinx_deleted"], {@person.id => 1}
154
+ )
155
+ end
156
+ end
85
157
  end
@@ -0,0 +1,247 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ThinkingSphinx::Association do
4
+ describe "class-level children method" do
5
+ before :each do
6
+ @normal_reflection = ::ActiveRecord::Reflection::AssociationReflection.stub_instance(
7
+ :options => {:polymorphic => false}
8
+ )
9
+ @normal_association = ThinkingSphinx::Association.stub_instance
10
+ @poly_reflection = ::ActiveRecord::Reflection::AssociationReflection.stub_instance(
11
+ :options => {:polymorphic => true},
12
+ :macro => :has_many,
13
+ :name => "polly",
14
+ :active_record => "AR"
15
+ )
16
+ @non_poly_reflection = ::ActiveRecord::Reflection::AssociationReflection.stub_instance
17
+
18
+ Person.stub_method(:reflect_on_association => @normal_reflection)
19
+ ThinkingSphinx::Association.stub_methods(
20
+ :new => @normal_association,
21
+ :polymorphic_classes => [Person, Person],
22
+ :casted_options => {:casted => :options}
23
+ )
24
+ ::ActiveRecord::Reflection::AssociationReflection.stub_method(
25
+ :new => @non_poly_reflection
26
+ )
27
+ end
28
+
29
+ it "should return an empty array if no association exists" do
30
+ Person.stub_method(:reflect_on_association => nil)
31
+
32
+ ThinkingSphinx::Association.children(Person, :assoc).should == []
33
+ end
34
+
35
+ it "should return a single association instance in an array if assocation isn't polymorphic" do
36
+ ThinkingSphinx::Association.children(Person, :assoc).should == [@normal_association]
37
+ end
38
+
39
+ it "should return multiple association instances for polymorphic associations" do
40
+ Person.stub_method(:reflect_on_association => @poly_reflection)
41
+
42
+ ThinkingSphinx::Association.children(Person, :assoc).should ==
43
+ [@normal_association, @normal_association]
44
+ end
45
+
46
+ it "should generate non-polymorphic 'casted' associations for each polymorphic possibility" do
47
+ Person.stub_method(:reflect_on_association => @poly_reflection)
48
+
49
+ ThinkingSphinx::Association.children(Person, :assoc)
50
+
51
+ ThinkingSphinx::Association.should have_received(:casted_options).with(
52
+ Person, @poly_reflection
53
+ ).twice
54
+
55
+ ::ActiveRecord::Reflection::AssociationReflection.should have_received(:new).with(
56
+ :has_many, :polly_Person, {:casted => :options}, "AR"
57
+ ).twice
58
+
59
+ ThinkingSphinx::Association.should have_received(:new).with(
60
+ nil, @non_poly_reflection
61
+ ).twice
62
+ end
63
+ end
64
+
65
+ describe "instance-level children method" do
66
+ it "should return the children associations for the given association" do
67
+ @reflection = ::ActiveRecord::Reflection::AssociationReflection.stub_instance(
68
+ :klass => :klass
69
+ )
70
+ @association = ThinkingSphinx::Association.new(nil, @reflection)
71
+ ThinkingSphinx::Association.stub_method(:children => :result)
72
+
73
+ @association.children(:assoc).should == :result
74
+ ThinkingSphinx::Association.should have_received(:children).with(:klass, :assoc, @association)
75
+ end
76
+ end
77
+
78
+ describe "join_to method" do
79
+ before :each do
80
+ @parent_join = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.stub_instance
81
+ @join = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.stub_instance
82
+ @parent = ThinkingSphinx::Association.stub_instance(:join_to => true, :join => nil)
83
+ @base_join = Object.stub_instance(:joins => [:a, :b, :c])
84
+
85
+ ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.stub_method(:new => @join)
86
+ end
87
+
88
+ it "should call the parent's join_to if parent has no join" do
89
+ @assoc = ThinkingSphinx::Association.new(@parent, :ref)
90
+
91
+ @assoc.join_to(@base_join)
92
+
93
+ @parent.should have_received(:join_to).with(@base_join)
94
+ end
95
+
96
+ it "should not call the parent's join_to if it already has a join" do
97
+ @assoc = ThinkingSphinx::Association.new(@parent, :ref)
98
+ @parent.stub_method(:join => @parent_join)
99
+
100
+ @assoc.join_to(@base_join)
101
+
102
+ @parent.should_not have_received(:join_to)
103
+ end
104
+
105
+ it "should define the join association with a JoinAssociation instance" do
106
+ @assoc = ThinkingSphinx::Association.new(@parent, :ref)
107
+
108
+ @assoc.join_to(@base_join).should == @join
109
+ @assoc.join.should == @join
110
+ end
111
+ end
112
+
113
+ describe "to_sql method" do
114
+ before :each do
115
+ @reflection = ::ActiveRecord::Reflection::AssociationReflection.stub_instance(
116
+ :klass => Person
117
+ )
118
+ @association = ThinkingSphinx::Association.new(nil, @reflection)
119
+ @parent = Object.stub_instance(:aliased_table_name => "ALIAS TABLE NAME")
120
+ @join = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.stub_instance(
121
+ :association_join => "full association join SQL",
122
+ :parent => @parent
123
+ )
124
+ @association.join = @join
125
+ end
126
+
127
+ it "should return the join's association join value" do
128
+ @association.to_sql.should == "full association join SQL"
129
+ end
130
+
131
+ it "should replace ::ts_join_alias:: with the aliased table name" do
132
+ @join.stub_method(:association_join => "text with ::ts_join_alias:: gone")
133
+
134
+ @association.to_sql.should == "text with `ALIAS TABLE NAME` gone"
135
+ end
136
+ end
137
+
138
+ describe "is_many? method" do
139
+ before :each do
140
+ @parent = ThinkingSphinx::Association.stub_instance(
141
+ :is_many? => :parent_is_many
142
+ )
143
+ @reflection = ::ActiveRecord::Reflection::AssociationReflection.stub_instance(
144
+ :macro => :has_many
145
+ )
146
+ end
147
+
148
+ it "should return true if association is either a has_many or a habtm" do
149
+ association = ThinkingSphinx::Association.new(@parent, @reflection)
150
+ association.is_many?.should be_true
151
+
152
+ @reflection.stub_method(:macro => :has_and_belongs_to_many)
153
+ association.is_many?.should be_true
154
+ end
155
+
156
+ it "should return the parent value if not a has many or habtm and there is a parent" do
157
+ association = ThinkingSphinx::Association.new(@parent, @reflection)
158
+ @reflection.stub_method(:macro => :belongs_to)
159
+ association.is_many?.should == :parent_is_many
160
+ end
161
+
162
+ it "should return false if no parent and not a has many or habtm" do
163
+ association = ThinkingSphinx::Association.new(nil, @reflection)
164
+ @reflection.stub_method(:macro => :belongs_to)
165
+ association.is_many?.should be_false
166
+ end
167
+ end
168
+
169
+ describe "ancestors method" do
170
+ it "should return an array of associations - including all parents" do
171
+ parent = ThinkingSphinx::Association.stub_instance(:ancestors => [:all, :ancestors])
172
+ association = ThinkingSphinx::Association.new(parent, @reflection)
173
+ association.ancestors.should == [:all, :ancestors, association]
174
+ end
175
+ end
176
+
177
+ describe "polymorphic_classes method" do
178
+ it "should return all the polymorphic result types as classes" do
179
+ Person.connection.stub_method(:select_all => [
180
+ {"person_type" => "Person"},
181
+ {"person_type" => "Friendship"}
182
+ ])
183
+ ref = Object.stub_instance(
184
+ :active_record => Person,
185
+ :options => {:foreign_type => "person_type"}
186
+ )
187
+
188
+ ThinkingSphinx::Association.send(:polymorphic_classes, ref).should == [Person, Friendship]
189
+ end
190
+ end
191
+
192
+ describe "casted_options method" do
193
+ before :each do
194
+ @options = {
195
+ :foreign_key => "thing_id",
196
+ :foreign_type => "thing_type",
197
+ :polymorphic => true
198
+ }
199
+ @reflection = ::ActiveRecord::Reflection::AssociationReflection.stub_instance(
200
+ :options => @options
201
+ )
202
+ end
203
+
204
+ it "should return a new options set for a specific class" do
205
+ ThinkingSphinx::Association.send(:casted_options, Person, @reflection).should == {
206
+ :polymorphic => nil,
207
+ :class_name => "Person",
208
+ :foreign_key => "thing_id",
209
+ :foreign_type => "thing_type",
210
+ :conditions => "::ts_join_alias::.`thing_type` = 'Person'"
211
+ }
212
+ end
213
+
214
+ it "should append to existing Array of conditions" do
215
+ @options[:conditions] = ["first condition"]
216
+ ThinkingSphinx::Association.send(:casted_options, Person, @reflection).should == {
217
+ :polymorphic => nil,
218
+ :class_name => "Person",
219
+ :foreign_key => "thing_id",
220
+ :foreign_type => "thing_type",
221
+ :conditions => ["first condition", "::ts_join_alias::.`thing_type` = 'Person'"]
222
+ }
223
+ end
224
+
225
+ it "should merge to an existing Hash of conditions" do
226
+ @options[:conditions] = {"field" => "value"}
227
+ ThinkingSphinx::Association.send(:casted_options, Person, @reflection).should == {
228
+ :polymorphic => nil,
229
+ :class_name => "Person",
230
+ :foreign_key => "thing_id",
231
+ :foreign_type => "thing_type",
232
+ :conditions => {"field" => "value", "thing_type" => "Person"}
233
+ }
234
+ end
235
+
236
+ it "should append to an existing String of conditions" do
237
+ @options[:conditions] = "first condition"
238
+ ThinkingSphinx::Association.send(:casted_options, Person, @reflection).should == {
239
+ :polymorphic => nil,
240
+ :class_name => "Person",
241
+ :foreign_key => "thing_id",
242
+ :foreign_type => "thing_type",
243
+ :conditions => "first condition AND ::ts_join_alias::.`thing_type` = 'Person'"
244
+ }
245
+ end
246
+ end
247
+ end