thinking-sphinx 4.4.1 → 5.0.0

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +12 -21
  3. data/Appraisals +1 -16
  4. data/CHANGELOG.markdown +20 -0
  5. data/README.textile +14 -16
  6. data/bin/loadsphinx +5 -0
  7. data/lib/thinking_sphinx/active_record/association_proxy/attribute_finder.rb +1 -1
  8. data/lib/thinking_sphinx/active_record/base.rb +2 -6
  9. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +1 -1
  10. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +3 -2
  11. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +1 -1
  12. data/lib/thinking_sphinx/active_record/interpreter.rb +4 -4
  13. data/lib/thinking_sphinx/active_record/sql_source.rb +12 -0
  14. data/lib/thinking_sphinx/active_record/sql_source/template.rb +2 -2
  15. data/lib/thinking_sphinx/callbacks.rb +9 -0
  16. data/lib/thinking_sphinx/callbacks/appender.rb +47 -0
  17. data/lib/thinking_sphinx/core/index.rb +1 -2
  18. data/lib/thinking_sphinx/deletion.rb +18 -17
  19. data/lib/thinking_sphinx/index_set.rb +7 -3
  20. data/lib/thinking_sphinx/middlewares/sphinxql.rb +1 -1
  21. data/lib/thinking_sphinx/railtie.rb +1 -1
  22. data/lib/thinking_sphinx/real_time/index.rb +4 -0
  23. data/lib/thinking_sphinx/real_time/interpreter.rb +8 -6
  24. data/lib/thinking_sphinx/real_time/transcriber.rb +35 -19
  25. data/spec/acceptance/big_integers_spec.rb +1 -1
  26. data/spec/acceptance/merging_spec.rb +1 -1
  27. data/spec/acceptance/real_time_updates_spec.rb +2 -2
  28. data/spec/acceptance/sql_deltas_spec.rb +3 -3
  29. data/spec/acceptance/suspended_deltas_spec.rb +3 -3
  30. data/spec/internal/app/models/admin/person.rb +3 -1
  31. data/spec/internal/app/models/album.rb +3 -1
  32. data/spec/internal/app/models/animal.rb +1 -0
  33. data/spec/internal/app/models/article.rb +2 -0
  34. data/spec/internal/app/models/bird.rb +1 -0
  35. data/spec/internal/app/models/book.rb +2 -0
  36. data/spec/internal/app/models/car.rb +1 -1
  37. data/spec/internal/app/models/city.rb +2 -0
  38. data/spec/internal/app/models/product.rb +1 -1
  39. data/spec/internal/app/models/tee.rb +2 -0
  40. data/spec/internal/app/models/user.rb +2 -0
  41. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +2 -1
  42. data/spec/thinking_sphinx/active_record/index_spec.rb +0 -12
  43. data/spec/thinking_sphinx/active_record/interpreter_spec.rb +15 -14
  44. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +38 -0
  45. data/spec/thinking_sphinx/deletion_spec.rb +5 -4
  46. data/spec/thinking_sphinx/index_set_spec.rb +28 -12
  47. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +2 -1
  48. data/spec/thinking_sphinx/real_time/index_spec.rb +38 -12
  49. data/spec/thinking_sphinx/real_time/interpreter_spec.rb +14 -14
  50. data/spec/thinking_sphinx/real_time/transcriber_spec.rb +9 -1
  51. data/thinking-sphinx.gemspec +3 -3
  52. metadata +8 -7
@@ -34,11 +34,11 @@ class ThinkingSphinx::IndexSet
34
34
  end
35
35
 
36
36
  def classes
37
- options[:classes] || []
37
+ options[:classes] || instances.collect(&:class)
38
38
  end
39
39
 
40
40
  def classes_specified?
41
- classes.any? || references_specified?
41
+ instances.any? || classes.any? || references_specified?
42
42
  end
43
43
 
44
44
  def classes_and_ancestors
@@ -68,6 +68,10 @@ class ThinkingSphinx::IndexSet
68
68
  all_indices.select { |index| references.include? index.reference }
69
69
  end
70
70
 
71
+ def instances
72
+ options[:instances] || []
73
+ end
74
+
71
75
  def mti_classes
72
76
  classes.reject { |klass|
73
77
  klass.column_names.include?(klass.inheritance_column)
@@ -76,7 +80,7 @@ class ThinkingSphinx::IndexSet
76
80
 
77
81
  def references
78
82
  options[:references] || classes_and_ancestors.collect { |klass|
79
- ThinkingSphinx::IndexSet.reference_name(klass)
83
+ self.class.reference_name(klass)
80
84
  }
81
85
  end
82
86
 
@@ -133,7 +133,7 @@ class ThinkingSphinx::Middlewares::SphinxQL <
133
133
 
134
134
  def indices_match_classes?
135
135
  indices.collect(&:reference).uniq.sort == classes.collect { |klass|
136
- ThinkingSphinx::IndexSet.reference_name(klass)
136
+ configuration.index_set_class.reference_name(klass)
137
137
  }.sort
138
138
  end
139
139
 
@@ -7,7 +7,7 @@ class ThinkingSphinx::Railtie < Rails::Railtie
7
7
 
8
8
  initializer 'thinking_sphinx.initialisation' do
9
9
  ActiveSupport.on_load(:active_record) do
10
- ActiveRecord::Base.send :include, ThinkingSphinx::ActiveRecord::Base
10
+ ActiveRecord::Base.include ThinkingSphinx::ActiveRecord::Base
11
11
  end
12
12
 
13
13
  if ActiveSupport::VERSION::MAJOR > 5
@@ -16,10 +16,14 @@ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
16
16
  end
17
17
 
18
18
  def add_attribute(attribute)
19
+ @attributes.delete_if { |existing| existing.name == attribute.name }
20
+
19
21
  @attributes << attribute
20
22
  end
21
23
 
22
24
  def add_field(field)
25
+ @fields.delete_if { |existing| existing.name == field.name }
26
+
23
27
  @fields << field
24
28
  end
25
29
 
@@ -5,16 +5,18 @@ class ThinkingSphinx::RealTime::Interpreter <
5
5
 
6
6
  def has(*columns)
7
7
  options = columns.extract_options!
8
- @index.attributes += columns.collect { |column|
8
+
9
+ columns.collect { |column|
9
10
  ::ThinkingSphinx::RealTime::Attribute.new column, options
10
- }
11
+ }.each { |attribute| @index.add_attribute attribute }
11
12
  end
12
13
 
13
14
  def indexes(*columns)
14
15
  options = columns.extract_options!
15
- @index.fields += columns.collect { |column|
16
+
17
+ columns.collect { |column|
16
18
  ::ThinkingSphinx::RealTime::Field.new column, options
17
- }
19
+ }.each { |field| @index.add_field field }
18
20
 
19
21
  append_sortable_attributes columns, options if options[:sortable]
20
22
  end
@@ -39,7 +41,7 @@ class ThinkingSphinx::RealTime::Interpreter <
39
41
  def append_sortable_attributes(columns, options)
40
42
  options = options.except(:sortable).merge(:type => :string)
41
43
 
42
- @index.attributes += columns.collect { |column|
44
+ columns.collect { |column|
43
45
  aliased_name = options[:as]
44
46
  aliased_name ||= column.__name.to_sym if column.respond_to?(:__name)
45
47
  aliased_name ||= column
@@ -47,6 +49,6 @@ class ThinkingSphinx::RealTime::Interpreter <
47
49
  options[:as] = "#{aliased_name}_sort".to_sym
48
50
 
49
51
  ::ThinkingSphinx::RealTime::Attribute.new column, options
50
- }
52
+ }.each { |attribute| @index.add_attribute attribute }
51
53
  end
52
54
  end
@@ -11,25 +11,8 @@ class ThinkingSphinx::RealTime::Transcriber
11
11
  }
12
12
  return unless items.present?
13
13
 
14
- values = []
15
- items.each do |instance|
16
- begin
17
- values << ThinkingSphinx::RealTime::TranscribeInstance.call(
18
- instance, index, properties
19
- )
20
- rescue ThinkingSphinx::TranscriptionError => error
21
- instrument 'error', :error => error
22
- end
23
- end
24
-
25
- insert = Riddle::Query::Insert.new index.name, columns, values
26
- sphinxql = insert.replace!.to_sql
27
-
28
- ThinkingSphinx::Logger.log :query, sphinxql do
29
- ThinkingSphinx::Connection.take do |connection|
30
- connection.execute sphinxql
31
- end
32
- end
14
+ delete_existing items
15
+ insert_replacements items
33
16
  end
34
17
 
35
18
  private
@@ -55,6 +38,27 @@ class ThinkingSphinx::RealTime::Transcriber
55
38
  }
56
39
  end
57
40
 
41
+ def delete_existing(instances)
42
+ ids = instances.collect(&index.primary_key.to_sym)
43
+
44
+ execute <<~SQL.strip
45
+ DELETE FROM #{@index.name} WHERE sphinx_internal_id IN (#{ids.join(', ')})
46
+ SQL
47
+ end
48
+
49
+ def execute(sphinxql)
50
+ ThinkingSphinx::Logger.log :query, sphinxql do
51
+ ThinkingSphinx::Connection.take do |connection|
52
+ connection.execute sphinxql
53
+ end
54
+ end
55
+ end
56
+
57
+ def insert_replacements(instances)
58
+ insert = Riddle::Query::Insert.new index.name, columns, values(instances)
59
+ execute insert.replace!.to_sql
60
+ end
61
+
58
62
  def instrument(message, options = {})
59
63
  ActiveSupport::Notifications.instrument(
60
64
  "#{message}.thinking_sphinx.real_time", options.merge(:index => index)
@@ -64,4 +68,16 @@ class ThinkingSphinx::RealTime::Transcriber
64
68
  def properties
65
69
  @properties ||= index.fields + index.attributes
66
70
  end
71
+
72
+ def values(instances)
73
+ instances.each_with_object([]) do |instance, array|
74
+ begin
75
+ array << ThinkingSphinx::RealTime::TranscribeInstance.call(
76
+ instance, index, properties
77
+ )
78
+ rescue ThinkingSphinx::TranscriptionError => error
79
+ instrument 'error', :error => error
80
+ end
81
+ end
82
+ end
67
83
  end
@@ -52,7 +52,7 @@ describe '64 bit document ids', :live => true do
52
52
  context 'with Real-Time' do
53
53
  it 'handles large 32 bit integers with an offset multiplier' do
54
54
  product = Product.create! :name => "Widget"
55
- product.update_attributes :id => 980190962
55
+ product.update :id => 980190962
56
56
  expect(
57
57
  Product.search('widget', :indices => ['product_core']).to_a
58
58
  ).to eq([product])
@@ -34,7 +34,7 @@ describe "Merging deltas", :live => true do
34
34
  Book.search("Space", :indices => ["book_core"]).to_a
35
35
  ).to eq([race])
36
36
 
37
- race.reload.update_attributes :title => "The Hate Race"
37
+ race.reload.update :title => "The Hate Race"
38
38
  sleep 0.25
39
39
  expect(
40
40
  Book.search("Race", :indices => ["book_delta"]).to_a
@@ -11,7 +11,7 @@ describe 'Updates to records in real-time indices', :live => true do
11
11
 
12
12
  it "handles attributes for sortable fields accordingly" do
13
13
  product = Product.create! :name => 'Red Fish'
14
- product.update_attributes :name => 'Blue Fish'
14
+ product.update :name => 'Blue Fish'
15
15
 
16
16
  expect(Product.search('blue fish', :indices => ['product_core']).to_a).
17
17
  to eq([product])
@@ -22,7 +22,7 @@ describe 'Updates to records in real-time indices', :live => true do
22
22
 
23
23
  expect(Admin::Person.search('Death').to_a).to eq([person])
24
24
 
25
- person.update_attributes :name => 'Mort'
25
+ person.update :name => 'Mort'
26
26
 
27
27
  expect(Admin::Person.search('Death').to_a).to be_empty
28
28
  expect(Admin::Person.search('Mort').to_a).to eq([person])
@@ -25,7 +25,7 @@ describe 'SQL delta indexing', :live => true do
25
25
 
26
26
  expect(Book.search('Harry').to_a).to eq([book])
27
27
 
28
- book.reload.update_attributes(:author => 'Terry Pratchett')
28
+ book.reload.update(:author => 'Terry Pratchett')
29
29
  sleep 0.25
30
30
 
31
31
  expect(Book.search('Terry').to_a).to eq([book])
@@ -37,7 +37,7 @@ describe 'SQL delta indexing', :live => true do
37
37
 
38
38
  expect(Book.search('Harry').to_a).to eq([book])
39
39
 
40
- book.reload.update_attributes(:author => 'Terry Pratchett')
40
+ book.reload.update(:author => 'Terry Pratchett')
41
41
  sleep 0.25
42
42
 
43
43
  expect(Book.search('Harry')).to be_empty
@@ -49,7 +49,7 @@ describe 'SQL delta indexing', :live => true do
49
49
 
50
50
  expect(Album.search('Whitloms').to_a).to eq([album])
51
51
 
52
- album.reload.update_attributes(:artist => 'The Whitlams')
52
+ album.reload.update(:artist => 'The Whitlams')
53
53
  sleep 0.25
54
54
 
55
55
  expect(Book.search('Whitloms')).to be_empty
@@ -10,7 +10,7 @@ describe 'Suspend deltas for a given action', :live => true do
10
10
  expect(Book.search('Harry').to_a).to eq([book])
11
11
 
12
12
  ThinkingSphinx::Deltas.suspend :book do
13
- book.reload.update_attributes(:author => 'Terry Pratchett')
13
+ book.reload.update(:author => 'Terry Pratchett')
14
14
  sleep 0.25
15
15
 
16
16
  expect(Book.search('Terry').to_a).to eq([])
@@ -27,7 +27,7 @@ describe 'Suspend deltas for a given action', :live => true do
27
27
  expect(Book.search('Harry').to_a).to eq([book])
28
28
 
29
29
  ThinkingSphinx::Deltas.suspend :book do
30
- book.reload.update_attributes(:author => 'Terry Pratchett')
30
+ book.reload.update(:author => 'Terry Pratchett')
31
31
  sleep 0.25
32
32
 
33
33
  expect(Book.search('Terry').to_a).to eq([])
@@ -44,7 +44,7 @@ describe 'Suspend deltas for a given action', :live => true do
44
44
  expect(Book.search('Harry').to_a).to eq([book])
45
45
 
46
46
  ThinkingSphinx::Deltas.suspend_and_update :book do
47
- book.reload.update_attributes(:author => 'Terry Pratchett')
47
+ book.reload.update(:author => 'Terry Pratchett')
48
48
  sleep 0.25
49
49
 
50
50
  expect(Book.search('Terry').to_a).to eq([])
@@ -3,5 +3,7 @@
3
3
  class Admin::Person < ActiveRecord::Base
4
4
  self.table_name = 'admin_people'
5
5
 
6
- after_save ThinkingSphinx::RealTime.callback_for('admin/person')
6
+ ThinkingSphinx::Callbacks.append(
7
+ self, 'admin/person', :behaviours => [:sql, :real_time]
8
+ )
7
9
  end
@@ -6,7 +6,9 @@ class Album < ActiveRecord::Base
6
6
  before_validation :set_id, :on => :create
7
7
  before_validation :set_integer_id, :on => :create
8
8
 
9
- after_save ThinkingSphinx::RealTime.callback_for(:album)
9
+ ThinkingSphinx::Callbacks.append(
10
+ self, :behaviours => [:sql, :real_time, :deltas]
11
+ )
10
12
 
11
13
  validates :id, :presence => true, :uniqueness => true
12
14
  validates :integer_id, :presence => true, :uniqueness => true
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Animal < ActiveRecord::Base
4
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
4
5
  end
@@ -4,4 +4,6 @@ class Article < ActiveRecord::Base
4
4
  belongs_to :user
5
5
  has_many :taggings
6
6
  has_many :tags, :through => :taggings
7
+
8
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql, :updates])
7
9
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Bird < Animal
4
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
4
5
  end
@@ -5,6 +5,8 @@ class Book < ActiveRecord::Base
5
5
 
6
6
  has_and_belongs_to_many :genres
7
7
 
8
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql, :deltas])
9
+
8
10
  sphinx_scope(:by_query) { |query| query }
9
11
  sphinx_scope(:by_year) do |year|
10
12
  {:with => {:year => year}}
@@ -3,5 +3,5 @@
3
3
  class Car < ActiveRecord::Base
4
4
  belongs_to :manufacturer
5
5
 
6
- after_save ThinkingSphinx::RealTime.callback_for(:car)
6
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:real_time])
7
7
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class City < ActiveRecord::Base
4
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
5
+
4
6
  scope :ordered, lambda { order(:name) }
5
7
  end
@@ -4,5 +4,5 @@ class Product < ActiveRecord::Base
4
4
  has_many :categorisations
5
5
  has_many :categories, :through => :categorisations
6
6
 
7
- after_save ThinkingSphinx::RealTime.callback_for(:product)
7
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:real_time])
8
8
  end
@@ -2,4 +2,6 @@
2
2
 
3
3
  class Tee < ActiveRecord::Base
4
4
  belongs_to :colour
5
+
6
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
5
7
  end
@@ -3,6 +3,8 @@
3
3
  class User < ActiveRecord::Base
4
4
  has_many :articles
5
5
 
6
+ ThinkingSphinx::Callbacks.append(self, :behaviours => [:sql])
7
+
6
8
  default_scope { order(:id) }
7
9
  scope :recent, lambda { where('created_at > ?', 1.week.ago) }
8
10
  end
@@ -19,12 +19,13 @@ describe ThinkingSphinx::ActiveRecord::Callbacks::UpdateCallbacks do
19
19
  let(:klass) { double(:name => 'Article') }
20
20
  let(:configuration) { double('configuration',
21
21
  :settings => {'attribute_updates' => true},
22
- :indices_for_references => [index]) }
22
+ :indices_for_references => [index], :index_set_class => set_class) }
23
23
  let(:connection) { double('connection', :execute => '') }
24
24
  let(:index) { double 'index', :name => 'article_core',
25
25
  :sources => [source], :document_id_for_key => 3, :distributed? => false,
26
26
  :type => 'plain', :primary_key => :id}
27
27
  let(:source) { double('source', :attributes => []) }
28
+ let(:set_class) { double(:reference_name => :article) }
28
29
 
29
30
  before :each do
30
31
  stub_const 'ThinkingSphinx::Configuration',
@@ -94,18 +94,6 @@ describe ThinkingSphinx::ActiveRecord::Index do
94
94
  end
95
95
  end
96
96
 
97
- describe '#docinfo' do
98
- it "defaults to extern" do
99
- expect(index.docinfo).to eq(:extern)
100
- end
101
-
102
- it "can be disabled" do
103
- config.settings["skip_docinfo"] = true
104
-
105
- expect(index.docinfo).to be_nil
106
- end
107
- end
108
-
109
97
  describe '#document_id_for_key' do
110
98
  it "calculates the document id based on offset and number of indices" do
111
99
  allow(config).to receive_message_chain(:indices, :count).and_return(5)
@@ -15,8 +15,13 @@ describe ThinkingSphinx::ActiveRecord::Interpreter do
15
15
  let(:block) { Proc.new { } }
16
16
 
17
17
  before :each do
18
- allow(ThinkingSphinx::ActiveRecord::SQLSource).to receive_messages :new => source
19
- allow(source).to receive_messages :model => model
18
+ allow(ThinkingSphinx::ActiveRecord::SQLSource).to receive_messages(
19
+ :new => source
20
+ )
21
+
22
+ allow(source).to receive_messages(
23
+ :model => model, :add_attribute => nil, :add_field => nil
24
+ )
20
25
  end
21
26
 
22
27
  describe '.translate!' do
@@ -94,17 +99,15 @@ describe ThinkingSphinx::ActiveRecord::Interpreter do
94
99
  end
95
100
 
96
101
  it "adds an attribute to the source" do
97
- instance.has column
102
+ expect(source).to receive(:add_attribute).with(attribute)
98
103
 
99
- expect(source.attributes).to include(attribute)
104
+ instance.has column
100
105
  end
101
106
 
102
107
  it "adds multiple attributes when passed multiple columns" do
103
- instance.has column, column
108
+ expect(source).to receive(:add_attribute).with(attribute).twice
104
109
 
105
- expect(source.attributes.select { |saved_attribute|
106
- saved_attribute == attribute
107
- }.length).to eq(2)
110
+ instance.has column, column
108
111
  end
109
112
  end
110
113
 
@@ -144,17 +147,15 @@ describe ThinkingSphinx::ActiveRecord::Interpreter do
144
147
  end
145
148
 
146
149
  it "adds a field to the source" do
147
- instance.indexes column
150
+ expect(source).to receive(:add_field).with(field)
148
151
 
149
- expect(source.fields).to include(field)
152
+ instance.indexes column
150
153
  end
151
154
 
152
155
  it "adds multiple fields when passed multiple columns" do
153
- instance.indexes column, column
156
+ expect(source).to receive(:add_field).with(field).twice
154
157
 
155
- expect(source.fields.select { |saved_field|
156
- saved_field == field
157
- }.length).to eq(2)
158
+ instance.indexes column, column
158
159
  end
159
160
  end
160
161