freelancing-god-thinking-sphinx 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -48,7 +48,7 @@ database, causing the next run to have failures. Let that run complete and then
48
48
  Since I first released this library, there's been quite a few people who have submitted patches, to my immense gratitude. Others have suggested syntax changes and general improvements. So my thanks to the following people:
49
49
 
50
50
  - Joost Hietbrink
51
- - Jonathon Conway
51
+ - Jonathan Conway
52
52
  - Gregory Mirzayantz
53
53
  - Tung Nguyen
54
54
  - Sean Cribbs
@@ -103,3 +103,5 @@ Since I first released this library, there's been quite a few people who have su
103
103
  - Lachie Cox
104
104
  - Lourens Naude
105
105
  - Tom Davies
106
+ - Dan Pickett
107
+ - Alex Caudill
@@ -13,6 +13,7 @@ require 'thinking_sphinx/attribute'
13
13
  require 'thinking_sphinx/collection'
14
14
  require 'thinking_sphinx/configuration'
15
15
  require 'thinking_sphinx/facet'
16
+ require 'thinking_sphinx/facet_collection'
16
17
  require 'thinking_sphinx/field'
17
18
  require 'thinking_sphinx/index'
18
19
  require 'thinking_sphinx/rails_additions'
@@ -33,7 +34,7 @@ module ThinkingSphinx
33
34
  module Version #:nodoc:
34
35
  Major = 1
35
36
  Minor = 1
36
- Tiny = 3
37
+ Tiny = 4
37
38
 
38
39
  String = [Major, Minor, Tiny].join('.')
39
40
  end
@@ -11,7 +11,8 @@ module ThinkingSphinx
11
11
 
12
12
  def self.detect(model)
13
13
  case model.connection.class.name
14
- when "ActiveRecord::ConnectionAdapters::MysqlAdapter"
14
+ when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
15
+ "ActiveRecord::ConnectionAdapters::MysqlplusAdapter"
15
16
  ThinkingSphinx::MysqlAdapter.new model
16
17
  when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
17
18
  ThinkingSphinx::PostgreSQLAdapter.new model
@@ -30,4 +31,4 @@ module ThinkingSphinx
30
31
  @connection ||= @model.connection
31
32
  end
32
33
  end
33
- end
34
+ end
@@ -11,7 +11,7 @@ module ThinkingSphinx
11
11
 
12
12
  def concatenate(clause, separator = ' ')
13
13
  clause.split(', ').collect { |field|
14
- "COALESCE(#{field}, '')"
14
+ "COALESCE(CAST(#{field} as varchar), '')"
15
15
  }.join(" || '#{separator}' || ")
16
16
  end
17
17
 
@@ -140,7 +140,7 @@ module ThinkingSphinx
140
140
  # actual column's datatype is and returns that.
141
141
  def type
142
142
  @type ||= case
143
- when is_many?
143
+ when is_many?, is_many_ints?
144
144
  :multi
145
145
  when @associations.values.flatten.length > 1
146
146
  :string
@@ -152,7 +152,7 @@ module ThinkingSphinx
152
152
  def to_facet
153
153
  return nil unless @faceted
154
154
 
155
- ThinkingSphinx::Facet.new(unique_name, @columns, self)
155
+ ThinkingSphinx::Facet.new(self)
156
156
  end
157
157
 
158
158
  private
@@ -206,6 +206,10 @@ module ThinkingSphinx
206
206
  associations.values.flatten.any? { |assoc| assoc.is_many? }
207
207
  end
208
208
 
209
+ def is_many_ints?
210
+ concat_ws? && all_ints?
211
+ end
212
+
209
213
  # Returns true if any of the columns are string values, instead of database
210
214
  # column references.
211
215
  def is_string?
@@ -130,5 +130,13 @@ module ThinkingSphinx
130
130
  yield self[index], match[:weight]
131
131
  end
132
132
  end
133
+
134
+ def inject_with_groupby_and_count(initial = nil, &block)
135
+ index = -1
136
+ results[:matches].inject(initial) do |memo, match|
137
+ index += 1
138
+ yield memo, self[index], match[:attributes]["@groupby"], match[:attributes]["@count"]
139
+ end
140
+ end
133
141
  end
134
- end
142
+ end
@@ -0,0 +1,58 @@
1
+ module ThinkingSphinx
2
+ class Facet
3
+ attr_reader :reference
4
+
5
+ def initialize(reference)
6
+ @reference = reference
7
+
8
+ if reference.columns.length != 1
9
+ raise "Can't translate Facets on multiple-column field or attribute"
10
+ end
11
+ end
12
+
13
+ def name
14
+ reference.unique_name
15
+ end
16
+
17
+ def attribute_name
18
+ @attribute_name ||= case @reference
19
+ when Attribute
20
+ @reference.unique_name.to_s
21
+ when Field
22
+ @reference.unique_name.to_s + "_sort"
23
+ end
24
+ end
25
+
26
+ def value(object, attribute_value)
27
+ return translate(object, attribute_value) if @reference.is_a?(Field)
28
+
29
+ case @reference.type
30
+ when :string, :multi
31
+ translate(object, attribute_value)
32
+ when :datetime
33
+ Time.at(attribute_value)
34
+ when :boolean
35
+ attribute_value > 0
36
+ else
37
+ attribute_value
38
+ end
39
+ end
40
+
41
+ def to_s
42
+ name
43
+ end
44
+
45
+ private
46
+
47
+ def translate(object, attribute_value)
48
+ column.__stack.each { |method|
49
+ object = object.send(method)
50
+ }
51
+ object.send(column.__name)
52
+ end
53
+
54
+ def column
55
+ @reference.columns.first
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,44 @@
1
+ module ThinkingSphinx
2
+ class FacetCollection < Hash
3
+ attr_accessor :arguments
4
+
5
+ def initialize(arguments)
6
+ @arguments = arguments.clone
7
+ @attribute_values = {}
8
+ @facets = []
9
+ end
10
+
11
+ def add_from_results(facet, results)
12
+ self[facet.name] = {}
13
+ @attribute_values[facet.name] = {}
14
+ @facets << facet
15
+
16
+ results.each_with_groupby_and_count { |result, group, count|
17
+ facet_value = facet.value(result, group)
18
+
19
+ self[facet.name][facet_value] = count
20
+ @attribute_values[facet.name][facet_value] = group
21
+ }
22
+ end
23
+
24
+ def for(hash = {})
25
+ arguments = @arguments.clone
26
+ options = arguments.extract_options!
27
+ options[:with] ||= {}
28
+
29
+ hash.each do |key, value|
30
+ attrib = facet_for_key(key).attribute_name
31
+ options[:with][attrib] = @attribute_values[key][value]
32
+ end
33
+
34
+ arguments << options
35
+ ThinkingSphinx::Search.search *arguments
36
+ end
37
+
38
+ private
39
+
40
+ def facet_for_key(key)
41
+ @facets.detect { |facet| facet.name == key }
42
+ end
43
+ end
44
+ end
@@ -115,7 +115,7 @@ module ThinkingSphinx
115
115
  def to_facet
116
116
  return nil unless @faceted
117
117
 
118
- ThinkingSphinx::Facet.new(unique_name, @columns, self)
118
+ ThinkingSphinx::Facet.new(self)
119
119
  end
120
120
 
121
121
  private
@@ -159,21 +159,22 @@ module ThinkingSphinx
159
159
  stored_class = @model.store_full_sti_class ? @model.name : @model.name.demodulize
160
160
  builder.where("#{@model.quoted_table_name}.#{quote_column(@model.inheritance_column)} = '#{stored_class}'")
161
161
  end
162
-
163
- @fields = builder.fields
164
- @attributes = builder.attributes
162
+
163
+ set_model = Proc.new { |item| item.model = @model }
164
+
165
+ @fields = builder.fields &set_model
166
+ @attributes = builder.attributes.each &set_model
165
167
  @conditions = builder.conditions
166
168
  @groupings = builder.groupings
167
169
  @delta_object = ThinkingSphinx::Deltas.parse self, builder.properties
168
170
  @options = builder.properties
169
171
 
172
+ is_faceted = Proc.new { |item| item.faceted }
173
+ add_facet = Proc.new { |item| @model.sphinx_facets << item.to_facet }
174
+
170
175
  @model.sphinx_facets ||= []
171
- @fields.select { |field| field.faceted }.each { |field|
172
- @model.sphinx_facets << field.to_facet
173
- }
174
- @attributes.select { |attrib| attrib.faceted }.each { |attrib|
175
- @model.sphinx_facets << attrib.to_facet
176
- }
176
+ @fields.select( &is_faceted).each &add_facet
177
+ @attributes.select(&is_faceted).each &add_facet
177
178
 
178
179
  # We want to make sure that if the database doesn't exist, then Thinking
179
180
  # Sphinx doesn't mind when running non-TS tasks (like db:create, db:drop
@@ -277,7 +278,7 @@ module ThinkingSphinx
277
278
  config = @model.connection.instance_variable_get(:@config)
278
279
 
279
280
  source.sql_host = config[:host] || "localhost"
280
- source.sql_user = config[:username] || config[:user]
281
+ source.sql_user = config[:username] || config[:user] || ""
281
282
  source.sql_pass = (config[:password].to_s || "").gsub('#', '\#')
282
283
  source.sql_db = config[:database]
283
284
  source.sql_port = config[:port]
@@ -147,6 +147,15 @@ module ThinkingSphinx
147
147
  end
148
148
  alias_method :attribute, :has
149
149
 
150
+ def facet(*args)
151
+ options = args.extract_options!
152
+ options[:facet] = true
153
+
154
+ args.each do |columns|
155
+ attributes << Attribute.new(FauxColumn.coerce(columns), options)
156
+ end
157
+ end
158
+
150
159
  # Use this method to add some manual SQL conditions for your index
151
160
  # request. You can pass in as many strings as you like, they'll get
152
161
  # joined together with ANDs later on.
@@ -21,7 +21,7 @@ module ThinkingSphinx
21
21
 
22
22
  options = args.extract_options!
23
23
  page = options[:page] ? options[:page].to_i : 1
24
-
24
+
25
25
  ThinkingSphinx::Collection.ids_from_results(results, page, client.limit, options)
26
26
  end
27
27
 
@@ -353,18 +353,13 @@ module ThinkingSphinx
353
353
  end
354
354
 
355
355
  def facets(*args)
356
- options = args.extract_options!.merge! :group_function => :attr
356
+ hash = ThinkingSphinx::FacetCollection.new args
357
+ options = args.extract_options!.clone.merge! :group_function => :attr
357
358
 
358
- options[:class].sphinx_facets.inject({}) do |hash, facet|
359
- facet_result = {}
359
+ options[:class].sphinx_facets.inject(hash) do |hash, facet|
360
360
  options[:group_by] = facet.attribute_name
361
361
 
362
- results = search *(args + [options])
363
- results.each_with_groupby_and_count do |result, group, count|
364
- facet_result[facet.value(result, group)] = count
365
- end
366
- hash[facet.name] = facet_result
367
-
362
+ hash.add_from_results facet, search(*(args + [options]))
368
363
  hash
369
364
  end
370
365
  end
@@ -474,6 +469,13 @@ module ThinkingSphinx
474
469
  Riddle::Client::Filter.new attr.to_s, filter_value(val), true
475
470
  } if options[:without]
476
471
 
472
+ # every-match attribute filters
473
+ client.filters += options[:with_all].collect { |attr,vals|
474
+ Array(vals).collect { |val|
475
+ Riddle::Client::Filter.new attr.to_s, filter_value(val)
476
+ }
477
+ }.flatten if options[:with_all]
478
+
477
479
  # exclusive attribute filter on primary key
478
480
  client.filters += Array(options[:without_ids]).collect { |id|
479
481
  Riddle::Client::Filter.new 'sphinx_internal_id', filter_value(id), true
@@ -0,0 +1,128 @@
1
+ require 'fileutils'
2
+
3
+ namespace :thinking_sphinx do
4
+ task :app_env do
5
+ Rake::Task[:environment].invoke if defined?(RAILS_ROOT)
6
+ Rake::Task[:merb_env].invoke if defined?(Merb)
7
+ end
8
+
9
+ desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
10
+ task :running_start => :app_env do
11
+ Rake::Task["thinking_sphinx:stop"].invoke if sphinx_running?
12
+ Rake::Task["thinking_sphinx:start"].invoke
13
+ end
14
+
15
+ desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings"
16
+ task :start => :app_env do
17
+ config = ThinkingSphinx::Configuration.instance
18
+
19
+ FileUtils.mkdir_p config.searchd_file_path
20
+ raise RuntimeError, "searchd is already running." if sphinx_running?
21
+
22
+ Dir["#{config.searchd_file_path}/*.spl"].each { |file| File.delete(file) }
23
+
24
+ cmd = "#{config.bin_path}searchd --pidfile --config #{config.config_file}"
25
+ puts cmd
26
+ system cmd
27
+
28
+ sleep(2)
29
+
30
+ if sphinx_running?
31
+ puts "Started successfully (pid #{sphinx_pid})."
32
+ else
33
+ puts "Failed to start searchd daemon. Check #{config.searchd_log_file}."
34
+ end
35
+ end
36
+
37
+ desc "Stop Sphinx using Thinking Sphinx's settings"
38
+ task :stop => :app_env do
39
+ raise RuntimeError, "searchd is not running." unless sphinx_running?
40
+ config = ThinkingSphinx::Configuration.instance
41
+ pid = sphinx_pid
42
+ system "searchd --stop --config #{config.config_file}"
43
+ puts "Stopped search daemon (pid #{pid})."
44
+ end
45
+
46
+ desc "Restart Sphinx"
47
+ task :restart => [:app_env, :stop, :start]
48
+
49
+ desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
50
+ task :configure => :app_env do
51
+ config = ThinkingSphinx::Configuration.instance
52
+ puts "Generating Configuration to #{config.config_file}"
53
+ config.build
54
+ end
55
+
56
+ desc "Index data for Sphinx using Thinking Sphinx's settings"
57
+ task :index => :app_env do
58
+ ThinkingSphinx::Deltas::Job.cancel_thinking_sphinx_jobs
59
+
60
+ config = ThinkingSphinx::Configuration.instance
61
+ unless ENV["INDEX_ONLY"] == "true"
62
+ puts "Generating Configuration to #{config.config_file}"
63
+ config.build
64
+ end
65
+
66
+ FileUtils.mkdir_p config.searchd_file_path
67
+ cmd = "#{config.bin_path}indexer --config #{config.config_file} --all"
68
+ cmd << " --rotate" if sphinx_running?
69
+ puts cmd
70
+ system cmd
71
+ end
72
+
73
+ namespace :index do
74
+ task :delta => :app_env do
75
+ ThinkingSphinx.indexed_models.select { |model|
76
+ model.constantize.sphinx_indexes.any? { |index| index.delta? }
77
+ }.each do |model|
78
+ model.constantize.sphinx_indexes.select { |index|
79
+ index.delta? && index.delta_object.respond_to?(:delayed_index)
80
+ }.each { |index|
81
+ index.delta_object.delayed_index(index.model)
82
+ }
83
+ end
84
+ end
85
+ end
86
+
87
+ desc "Process stored delta index requests"
88
+ task :delayed_delta => :app_env do
89
+ require 'delayed/worker'
90
+
91
+ Delayed::Worker.new(
92
+ :min_priority => ENV['MIN_PRIORITY'],
93
+ :max_priority => ENV['MAX_PRIORITY']
94
+ ).start
95
+ end
96
+ end
97
+
98
+ namespace :ts do
99
+ desc "Stop if running, then start a Sphinx searchd daemon using Thinking Sphinx's settings"
100
+ task :run => "thinking_sphinx:running_start"
101
+ desc "Start a Sphinx searchd daemon using Thinking Sphinx's settings"
102
+ task :start => "thinking_sphinx:start"
103
+ desc "Stop Sphinx using Thinking Sphinx's settings"
104
+ task :stop => "thinking_sphinx:stop"
105
+ desc "Index data for Sphinx using Thinking Sphinx's settings"
106
+ task :in => "thinking_sphinx:index"
107
+ namespace :in do
108
+ desc "Index Thinking Sphinx datetime delta indexes"
109
+ task :delta => "thinking_sphinx:index:delta"
110
+ end
111
+ task :index => "thinking_sphinx:index"
112
+ desc "Restart Sphinx"
113
+ task :restart => "thinking_sphinx:restart"
114
+ desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
115
+ task :conf => "thinking_sphinx:configure"
116
+ desc "Generate the Sphinx configuration file using Thinking Sphinx's settings"
117
+ task :config => "thinking_sphinx:configure"
118
+ desc "Process stored delta index requests"
119
+ task :dd => "thinking_sphinx:delayed_delta"
120
+ end
121
+
122
+ def sphinx_pid
123
+ ThinkingSphinx.sphinx_pid
124
+ end
125
+
126
+ def sphinx_running?
127
+ ThinkingSphinx.sphinx_running?
128
+ end
@@ -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
 
data/tasks/rails.rake ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '/../lib/thinking_sphinx/tasks')
@@ -18,7 +18,7 @@ module Riddle #:nodoc:
18
18
  Rev = 1533
19
19
  # Release number to mark my own fixes, beyond feature parity with
20
20
  # Sphinx itself.
21
- Release = 3
21
+ Release = 4
22
22
 
23
23
  String = [Major, Minor, Tiny].join('.')
24
24
  GemVersion = [Major, Minor, Tiny, Rev, Release].join('.')
@@ -456,7 +456,7 @@ module Riddle
456
456
  header = socket.recv(8)
457
457
  status, version, length = header.unpack('n2N')
458
458
 
459
- while response.length < length
459
+ while response.length < (length || 0)
460
460
  part = socket.recv(length - response.length)
461
461
  response << part if part
462
462
  end
@@ -20,13 +20,18 @@ module Riddle
20
20
  if send(setting) == ""
21
21
  conf = " #{setting} = "
22
22
  else
23
- conf = Array(send(setting)).collect { |set|
23
+ conf = setting_to_array(setting).collect { |set|
24
24
  " #{setting} = #{set}"
25
25
  }
26
26
  end
27
27
  conf.length == 0 ? nil : conf
28
28
  }.flatten.compact
29
29
  end
30
+
31
+ def setting_to_array(setting)
32
+ value = send(setting)
33
+ value.is_a?(Array) ? value : [value]
34
+ end
30
35
  end
31
36
  end
32
- end
37
+ end
@@ -14,7 +14,7 @@ module Riddle
14
14
  def start
15
15
  return if running?
16
16
 
17
- cmd = "searchd --config #{@path}"
17
+ cmd = "searchd --pidfile --config #{@path}"
18
18
  `#{cmd}`
19
19
 
20
20
  sleep(1)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: freelancing-god-thinking-sphinx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-17 00:00:00 -08:00
12
+ date: 2009-02-04 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -41,19 +41,21 @@ files:
41
41
  - lib/thinking_sphinx/deltas/delayed_delta/job.rb
42
42
  - lib/thinking_sphinx/deltas/delayed_delta.rb
43
43
  - lib/thinking_sphinx/deltas.rb
44
+ - lib/thinking_sphinx/facet.rb
45
+ - lib/thinking_sphinx/facet_collection.rb
44
46
  - lib/thinking_sphinx/field.rb
45
47
  - lib/thinking_sphinx/index/builder.rb
46
48
  - lib/thinking_sphinx/index/faux_column.rb
47
49
  - lib/thinking_sphinx/index.rb
48
50
  - lib/thinking_sphinx/rails_additions.rb
49
51
  - lib/thinking_sphinx/search.rb
52
+ - lib/thinking_sphinx/tasks.rb
50
53
  - lib/thinking_sphinx.rb
51
54
  - LICENCE
52
55
  - README
53
56
  - tasks/distribution.rb
54
57
  - tasks/testing.rb
55
- - tasks/thinking_sphinx_tasks.rb
56
- - tasks/thinking_sphinx_tasks.rake
58
+ - tasks/rails.rake
57
59
  - vendor/after_commit
58
60
  - vendor/after_commit/init.rb
59
61
  - vendor/after_commit/lib