sherpa99-thinking-sphinx 1.1.4

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 (145) hide show
  1. data/LICENCE +20 -0
  2. data/README +107 -0
  3. data/README.textile +107 -0
  4. data/Rakefile +4 -0
  5. data/contribute.rb +328 -0
  6. data/cucumber.yml +1 -0
  7. data/features/a.rb +17 -0
  8. data/features/attribute_transformation.feature +22 -0
  9. data/features/datetime_deltas.feature +55 -0
  10. data/features/delayed_delta_indexing.feature +37 -0
  11. data/features/deleting_instances.feature +52 -0
  12. data/features/facets.feature +26 -0
  13. data/features/handling_edits.feature +67 -0
  14. data/features/retry_stale_indexes.feature +24 -0
  15. data/features/searching_across_models.feature +20 -0
  16. data/features/searching_by_model.feature +118 -0
  17. data/features/searching_with_find_arguments.feature +56 -0
  18. data/features/sphinx_detection.feature +16 -0
  19. data/features/step_definitions/alpha_steps.rb +3 -0
  20. data/features/step_definitions/beta_steps.rb +11 -0
  21. data/features/step_definitions/cat_steps.rb +3 -0
  22. data/features/step_definitions/common_steps.rb +154 -0
  23. data/features/step_definitions/datetime_delta_steps.rb +11 -0
  24. data/features/step_definitions/delayed_delta_indexing_steps.rb +7 -0
  25. data/features/step_definitions/facet_steps.rb +30 -0
  26. data/features/step_definitions/find_arguments_steps.rb +36 -0
  27. data/features/step_definitions/gamma_steps.rb +15 -0
  28. data/features/step_definitions/search_steps.rb +66 -0
  29. data/features/step_definitions/sphinx_steps.rb +23 -0
  30. data/features/support/db/active_record.rb +40 -0
  31. data/features/support/db/database.example.yml +4 -0
  32. data/features/support/db/migrations/create_alphas.rb +18 -0
  33. data/features/support/db/migrations/create_animals.rb +9 -0
  34. data/features/support/db/migrations/create_betas.rb +15 -0
  35. data/features/support/db/migrations/create_boxes.rb +13 -0
  36. data/features/support/db/migrations/create_comments.rb +13 -0
  37. data/features/support/db/migrations/create_delayed_betas.rb +28 -0
  38. data/features/support/db/migrations/create_developers.rb +39 -0
  39. data/features/support/db/migrations/create_gammas.rb +14 -0
  40. data/features/support/db/migrations/create_people.rb +1014 -0
  41. data/features/support/db/migrations/create_posts.rb +6 -0
  42. data/features/support/db/migrations/create_thetas.rb +16 -0
  43. data/features/support/db/mysql.rb +4 -0
  44. data/features/support/db/postgresql.rb +4 -0
  45. data/features/support/env.rb +6 -0
  46. data/features/support/models/alpha.rb +9 -0
  47. data/features/support/models/animal.rb +5 -0
  48. data/features/support/models/beta.rb +7 -0
  49. data/features/support/models/box.rb +8 -0
  50. data/features/support/models/cat.rb +3 -0
  51. data/features/support/models/comment.rb +3 -0
  52. data/features/support/models/delayed_beta.rb +7 -0
  53. data/features/support/models/developer.rb +8 -0
  54. data/features/support/models/gamma.rb +5 -0
  55. data/features/support/models/person.rb +8 -0
  56. data/features/support/models/post.rb +8 -0
  57. data/features/support/models/theta.rb +7 -0
  58. data/features/support/post_database.rb +37 -0
  59. data/features/support/z.rb +19 -0
  60. data/ginger_scenarios.rb +24 -0
  61. data/init.rb +12 -0
  62. data/lib/thinking_sphinx.rb +144 -0
  63. data/lib/thinking_sphinx/active_record.rb +245 -0
  64. data/lib/thinking_sphinx/active_record/delta.rb +74 -0
  65. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  66. data/lib/thinking_sphinx/active_record/search.rb +57 -0
  67. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +34 -0
  68. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +53 -0
  69. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +129 -0
  70. data/lib/thinking_sphinx/association.rb +144 -0
  71. data/lib/thinking_sphinx/attribute.rb +258 -0
  72. data/lib/thinking_sphinx/collection.rb +142 -0
  73. data/lib/thinking_sphinx/configuration.rb +236 -0
  74. data/lib/thinking_sphinx/core/string.rb +22 -0
  75. data/lib/thinking_sphinx/deltas.rb +22 -0
  76. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  77. data/lib/thinking_sphinx/deltas/default_delta.rb +65 -0
  78. data/lib/thinking_sphinx/deltas/delayed_delta.rb +25 -0
  79. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  80. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  81. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  82. data/lib/thinking_sphinx/facet.rb +58 -0
  83. data/lib/thinking_sphinx/facet_collection.rb +44 -0
  84. data/lib/thinking_sphinx/field.rb +172 -0
  85. data/lib/thinking_sphinx/index.rb +414 -0
  86. data/lib/thinking_sphinx/index/builder.rb +233 -0
  87. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  88. data/lib/thinking_sphinx/rails_additions.rb +133 -0
  89. data/lib/thinking_sphinx/search.rb +638 -0
  90. data/lib/thinking_sphinx/tasks.rb +128 -0
  91. data/rails/init.rb +6 -0
  92. data/spec/fixtures/data.sql +32 -0
  93. data/spec/fixtures/database.yml.default +3 -0
  94. data/spec/fixtures/models.rb +81 -0
  95. data/spec/fixtures/structure.sql +84 -0
  96. data/spec/spec_helper.rb +54 -0
  97. data/spec/sphinx_helper.rb +109 -0
  98. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
  99. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
  100. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
  101. data/spec/unit/thinking_sphinx/active_record_spec.rb +256 -0
  102. data/spec/unit/thinking_sphinx/association_spec.rb +247 -0
  103. data/spec/unit/thinking_sphinx/attribute_spec.rb +212 -0
  104. data/spec/unit/thinking_sphinx/collection_spec.rb +14 -0
  105. data/spec/unit/thinking_sphinx/configuration_spec.rb +136 -0
  106. data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
  107. data/spec/unit/thinking_sphinx/field_spec.rb +145 -0
  108. data/spec/unit/thinking_sphinx/index/builder_spec.rb +5 -0
  109. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
  110. data/spec/unit/thinking_sphinx/index_spec.rb +54 -0
  111. data/spec/unit/thinking_sphinx/search_spec.rb +59 -0
  112. data/spec/unit/thinking_sphinx_spec.rb +129 -0
  113. data/tasks/distribution.rb +48 -0
  114. data/tasks/rails.rake +1 -0
  115. data/tasks/testing.rb +86 -0
  116. data/thinking-sphinx.gemspec +232 -0
  117. data/vendor/after_commit/LICENSE +20 -0
  118. data/vendor/after_commit/README +16 -0
  119. data/vendor/after_commit/Rakefile +22 -0
  120. data/vendor/after_commit/init.rb +5 -0
  121. data/vendor/after_commit/lib/after_commit.rb +42 -0
  122. data/vendor/after_commit/lib/after_commit/active_record.rb +91 -0
  123. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  124. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  125. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  126. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  127. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  128. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  129. data/vendor/riddle/lib/riddle.rb +30 -0
  130. data/vendor/riddle/lib/riddle/client.rb +619 -0
  131. data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
  132. data/vendor/riddle/lib/riddle/client/message.rb +65 -0
  133. data/vendor/riddle/lib/riddle/client/response.rb +84 -0
  134. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  135. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  136. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  137. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  138. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  139. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  140. data/vendor/riddle/lib/riddle/configuration/section.rb +37 -0
  141. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  142. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  143. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  144. data/vendor/riddle/lib/riddle/controller.rb +44 -0
  145. metadata +248 -0
@@ -0,0 +1,6 @@
1
+ ActiveRecord::Base.connection.create_table :posts, :force => true do |t|
2
+ t.column :subject, :string, :null => false
3
+ t.column :content, :text
4
+ end
5
+
6
+ Post.create :subject => "Hello World", :content => "Um Text"
@@ -0,0 +1,16 @@
1
+ ActiveRecord::Base.connection.create_table :thetas, :force => true do |t|
2
+ t.column :name, :string, :null => false
3
+ t.column :created_at, :datetime, :null => false
4
+ t.column :updated_at, :datetime, :null => false
5
+ end
6
+
7
+ Theta.create :name => "one"
8
+ Theta.create :name => "two"
9
+ Theta.create :name => "three"
10
+ Theta.create :name => "four"
11
+ Theta.create :name => "five"
12
+ Theta.create :name => "six"
13
+ Theta.create :name => "seven"
14
+ Theta.create :name => "eight"
15
+ Theta.create :name => "nine"
16
+ Theta.create :name => "ten"
@@ -0,0 +1,4 @@
1
+ require 'active_record'
2
+ require 'active_record/connection_adapters/mysql_adapter'
3
+
4
+ Database = 'mysql'
@@ -0,0 +1,4 @@
1
+ require 'active_record'
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ Database = 'postgresql'
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'cucumber'
3
+ require 'spec'
4
+ require 'fileutils'
5
+ require 'ginger'
6
+ require 'will_paginate'
@@ -0,0 +1,9 @@
1
+ class Alpha < ActiveRecord::Base
2
+ define_index do
3
+ indexes :name, :sortable => true
4
+
5
+ has value, cost, created_at, created_on
6
+
7
+ set_property :field_weights => {"name" => 10}
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Animal < ActiveRecord::Base
2
+ define_index do
3
+ indexes name, :sortable => true
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class Beta < ActiveRecord::Base
2
+ define_index do
3
+ indexes :name, :sortable => true
4
+
5
+ set_property :delta => true
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ class Box < ActiveRecord::Base
2
+ define_index do
3
+ indexes width, :as => :width_field
4
+
5
+ has width, length, depth
6
+ has [width, length, depth], :as => :dimensions
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ class Cat < Animal
2
+ #
3
+ end
@@ -0,0 +1,3 @@
1
+ class Comment < ActiveRecord::Base
2
+ belongs_to :post
3
+ end
@@ -0,0 +1,7 @@
1
+ class DelayedBeta < ActiveRecord::Base
2
+ define_index do
3
+ indexes :name, :sortable => true
4
+
5
+ set_property :delta => :delayed
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ class Developer < ActiveRecord::Base
2
+ define_index do
3
+ indexes country, :facet => true
4
+ indexes state, :facet => true
5
+ has age, :facet => true
6
+ facet city
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class Gamma < ActiveRecord::Base
2
+ define_index do
3
+ indexes :name, :sortable => true
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class Person < ActiveRecord::Base
2
+ define_index do
3
+ indexes first_name, last_name, :sortable => true
4
+
5
+ has [first_name, middle_initial, last_name], :as => :name_sort
6
+ has birthday
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ class Post < ActiveRecord::Base
2
+ has_many :comments
3
+
4
+ define_index do
5
+ indexes subject
6
+ indexes content
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class Theta < ActiveRecord::Base
2
+ define_index do
3
+ indexes :name, :sortable => true
4
+
5
+ set_property :delta => :datetime, :threshold => 1.hour
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
2
+
3
+ require 'lib/thinking_sphinx'
4
+
5
+ %w( tmp/config tmp/log tmp/db/sphinx/development ).each do |path|
6
+ FileUtils.mkdir_p "#{Dir.pwd}/#{path}"
7
+ end
8
+
9
+ Kernel.const_set :RAILS_ROOT, "#{Dir.pwd}/tmp" unless defined?(RAILS_ROOT)
10
+
11
+ at_exit do
12
+ ThinkingSphinx::Configuration.instance.controller.stop
13
+ sleep(1) # Ensure Sphinx has shut down completely
14
+ FileUtils.rm_r "#{Dir.pwd}/tmp"
15
+ end
16
+
17
+ # Add log file
18
+ ActiveRecord::Base.logger = Logger.new open("tmp/active_record.log", "a")
19
+
20
+ ThinkingSphinx.deltas_enabled = false
21
+
22
+ # Load Models
23
+ Dir["features/support/models/*.rb"].sort.each do |file|
24
+ require file.gsub(/\.rb$/, '')
25
+ end
26
+
27
+ # Set up database tables and records
28
+ Dir["features/support/db/migrations/*.rb"].each do |file|
29
+ require file.gsub(/\.rb$/, '')
30
+ end
31
+
32
+ ThinkingSphinx.deltas_enabled = true
33
+ ThinkingSphinx.suppress_delta_output = true
34
+
35
+ ThinkingSphinx::Configuration.instance.build
36
+ ThinkingSphinx::Configuration.instance.controller.index
37
+ ThinkingSphinx::Configuration.instance.controller.start
@@ -0,0 +1,19 @@
1
+ # This file exists because Cucumber likes to auto-load all ruby files
2
+ puts <<-MESSAGE
3
+ Cucumber 0.1.13 defaults to loading all ruby files within the features folder,
4
+ with something approaching reverse-alphabetical order, and preferring the
5
+ features/support folder over everything else. This is annoying, because some
6
+ files need to be loaded before others (and others perhaps not at all, given
7
+ missing dependencies). Hence this place-holder imaginatively named 'z.rb', to
8
+ force this message.
9
+
10
+ A work-around is to use cucumber profiles. You will find the default profile in
11
+ cucumber.yml should serve your needs fine, unless you add new step definitions.
12
+ When you do that, you can regenerate the YAML file by running:
13
+ rake cucumber_defaults
14
+
15
+ And then run specific features as follows is slightly more verbose, but it
16
+ works, whereas this doesn't.
17
+ cucumber -p default features/something.feature
18
+ MESSAGE
19
+ exit 0
@@ -0,0 +1,24 @@
1
+ require 'ginger'
2
+
3
+ Ginger.configure do |config|
4
+ config.aliases["active_record"] = "activerecord"
5
+ config.aliases["active_support"] = "activesupport"
6
+
7
+ ar_1_2_6 = Ginger::Scenario.new
8
+ ar_1_2_6[/^active_?support$/] = "1.4.4"
9
+ ar_1_2_6[/^active_?record$/] = "1.15.6"
10
+
11
+ ar_2_0_4 = Ginger::Scenario.new
12
+ ar_2_0_4[/^active_?support$/] = "2.0.4"
13
+ ar_2_0_4[/^active_?record$/] = "2.0.4"
14
+
15
+ ar_2_1_2 = Ginger::Scenario.new
16
+ ar_2_1_2[/^active_?support$/] = "2.1.2"
17
+ ar_2_1_2[/^active_?record$/] = "2.1.2"
18
+
19
+ ar_2_2_0 = Ginger::Scenario.new
20
+ ar_2_2_0[/^active_?support$/] = "2.2.0"
21
+ ar_2_2_0[/^active_?record$/] = "2.2.0"
22
+
23
+ config.scenarios << ar_1_2_6 << ar_2_0_4 << ar_2_1_2 << ar_2_2_0
24
+ end
data/init.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'thinking_sphinx'
2
+
3
+ if Rails::VERSION::STRING.to_f < 2.1
4
+ ThinkingSphinx::Configuration.instance.load_models
5
+ end
6
+
7
+ if Rails::VERSION::STRING.to_f > 1.2
8
+ require 'action_controller/dispatcher'
9
+ ActionController::Dispatcher.to_prepare :thinking_sphinx do
10
+ ThinkingSphinx::Configuration.instance.load_models
11
+ end
12
+ end
@@ -0,0 +1,144 @@
1
+ Dir[File.join(File.dirname(__FILE__), '../vendor/*/lib')].each do |path|
2
+ $LOAD_PATH.unshift path
3
+ end
4
+
5
+ require 'active_record'
6
+ require 'riddle'
7
+ require 'after_commit'
8
+
9
+ require 'thinking_sphinx/core/string'
10
+ require 'thinking_sphinx/active_record'
11
+ require 'thinking_sphinx/association'
12
+ require 'thinking_sphinx/attribute'
13
+ require 'thinking_sphinx/collection'
14
+ require 'thinking_sphinx/configuration'
15
+ require 'thinking_sphinx/facet'
16
+ require 'thinking_sphinx/facet_collection'
17
+ require 'thinking_sphinx/field'
18
+ require 'thinking_sphinx/index'
19
+ require 'thinking_sphinx/rails_additions'
20
+ require 'thinking_sphinx/search'
21
+ require 'thinking_sphinx/deltas'
22
+
23
+ require 'thinking_sphinx/adapters/abstract_adapter'
24
+ require 'thinking_sphinx/adapters/mysql_adapter'
25
+ require 'thinking_sphinx/adapters/postgresql_adapter'
26
+
27
+ ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
28
+
29
+ Merb::Plugins.add_rakefiles(
30
+ File.join(File.dirname(__FILE__), "thinking_sphinx", "tasks")
31
+ ) if defined?(Merb)
32
+
33
+ module ThinkingSphinx
34
+ module Version #:nodoc:
35
+ Major = 1
36
+ Minor = 1
37
+ Tiny = 3
38
+
39
+ String = [Major, Minor, Tiny].join('.')
40
+ end
41
+
42
+ # A ConnectionError will get thrown when a connection to Sphinx can't be
43
+ # made.
44
+ class ConnectionError < StandardError
45
+ end
46
+
47
+ # A StaleIdsException is thrown by Collection.instances_from_matches if there
48
+ # are records in Sphinx but not in the database, so the search can be retried.
49
+ class StaleIdsException < StandardError
50
+ attr_accessor :ids
51
+ def initialize(ids)
52
+ self.ids = ids
53
+ end
54
+ end
55
+
56
+ # The collection of indexed models. Keep in mind that Rails lazily loads
57
+ # its classes, so this may not actually be populated with _all_ the models
58
+ # that have Sphinx indexes.
59
+ def self.indexed_models
60
+ @@indexed_models ||= []
61
+ end
62
+
63
+ # Check if index definition is disabled.
64
+ #
65
+ def self.define_indexes?
66
+ @@define_indexes = true unless defined?(@@define_indexes)
67
+ @@define_indexes == true
68
+ end
69
+
70
+ # Enable/disable indexes - you may want to do this while migrating data.
71
+ #
72
+ # ThinkingSphinx.define_indexes = false
73
+ #
74
+ def self.define_indexes=(value)
75
+ @@define_indexes = value
76
+ end
77
+
78
+ @@deltas_enabled = nil
79
+
80
+ # Check if delta indexing is enabled.
81
+ #
82
+ def self.deltas_enabled?
83
+ @@deltas_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@deltas_enabled.nil?
84
+ @@deltas_enabled
85
+ end
86
+
87
+ # Enable/disable all delta indexing.
88
+ #
89
+ # ThinkingSphinx.deltas_enabled = false
90
+ #
91
+ def self.deltas_enabled=(value)
92
+ @@deltas_enabled = value
93
+ end
94
+
95
+ @@updates_enabled = nil
96
+
97
+ # Check if updates are enabled. True by default, unless within the test
98
+ # environment.
99
+ #
100
+ def self.updates_enabled?
101
+ @@updates_enabled = (ThinkingSphinx::Configuration.environment != 'test') if @@updates_enabled.nil?
102
+ @@updates_enabled
103
+ end
104
+
105
+ # Enable/disable updates to Sphinx
106
+ #
107
+ # ThinkingSphinx.updates_enabled = false
108
+ #
109
+ def self.updates_enabled=(value)
110
+ @@updates_enabled = value
111
+ end
112
+
113
+ @@suppress_delta_output = false
114
+
115
+ def self.suppress_delta_output?
116
+ @@suppress_delta_output
117
+ end
118
+
119
+ def self.suppress_delta_output=(value)
120
+ @@suppress_delta_output = value
121
+ end
122
+
123
+ # Checks to see if MySQL will allow simplistic GROUP BY statements. If not,
124
+ # or if not using MySQL, this will return false.
125
+ #
126
+ def self.use_group_by_shortcut?
127
+ ::ActiveRecord::ConnectionAdapters.constants.include?("MysqlAdapter") &&
128
+ ::ActiveRecord::Base.connection.is_a?(
129
+ ::ActiveRecord::ConnectionAdapters::MysqlAdapter
130
+ ) &&
131
+ ::ActiveRecord::Base.connection.select_all(
132
+ "SELECT @@global.sql_mode, @@session.sql_mode;"
133
+ ).all? { |key,value| value.nil? || value[/ONLY_FULL_GROUP_BY/].nil? }
134
+ end
135
+
136
+ def self.sphinx_running?
137
+ !!sphinx_pid
138
+ end
139
+
140
+ def self.sphinx_pid
141
+ pid_file = ThinkingSphinx::Configuration.instance.pid_file
142
+ `cat #{pid_file}`[/\d+/] if File.exists?(pid_file)
143
+ end
144
+ end
@@ -0,0 +1,245 @@
1
+ require 'thinking_sphinx/active_record/delta'
2
+ require 'thinking_sphinx/active_record/search'
3
+ require 'thinking_sphinx/active_record/has_many_association'
4
+
5
+ module ThinkingSphinx
6
+ # Core additions to ActiveRecord models - define_index for creating indexes
7
+ # for models. If you want to interrogate the index objects created for the
8
+ # model, you can use the class-level accessor :sphinx_indexes.
9
+ #
10
+ module ActiveRecord
11
+ def self.included(base)
12
+ base.class_eval do
13
+ class_inheritable_array :sphinx_indexes, :sphinx_facets
14
+ class << self
15
+ # Allows creation of indexes for Sphinx. If you don't do this, there
16
+ # isn't much point trying to search (or using this plugin at all,
17
+ # really).
18
+ #
19
+ # An example or two:
20
+ #
21
+ # define_index
22
+ # indexes :id, :as => :model_id
23
+ # indexes name
24
+ # end
25
+ #
26
+ # You can also grab fields from associations - multiple levels deep
27
+ # if necessary.
28
+ #
29
+ # define_index do
30
+ # indexes tags.name, :as => :tag
31
+ # indexes articles.content
32
+ # indexes orders.line_items.product.name, :as => :product
33
+ # end
34
+ #
35
+ # And it will automatically concatenate multiple fields:
36
+ #
37
+ # define_index do
38
+ # indexes [author.first_name, author.last_name], :as => :author
39
+ # end
40
+ #
41
+ # The #indexes method is for fields - if you want attributes, use
42
+ # #has instead. All the same rules apply - but keep in mind that
43
+ # attributes are for sorting, grouping and filtering, not searching.
44
+ #
45
+ # define_index do
46
+ # # fields ...
47
+ #
48
+ # has created_at, updated_at
49
+ # end
50
+ #
51
+ # One last feature is the delta index. This requires the model to
52
+ # have a boolean field named 'delta', and is enabled as follows:
53
+ #
54
+ # define_index do
55
+ # # fields ...
56
+ # # attributes ...
57
+ #
58
+ # set_property :delta => true
59
+ # end
60
+ #
61
+ # Check out the more detailed documentation for each of these methods
62
+ # at ThinkingSphinx::Index::Builder.
63
+ #
64
+ def define_index(&block)
65
+ return unless ThinkingSphinx.define_indexes?
66
+
67
+ self.sphinx_indexes ||= []
68
+ index = Index.new(self, &block)
69
+
70
+ self.sphinx_indexes << index
71
+ unless ThinkingSphinx.indexed_models.include?(self.name)
72
+ ThinkingSphinx.indexed_models << self.name
73
+ end
74
+
75
+ if index.delta?
76
+ before_save :toggle_delta
77
+ after_commit :index_delta
78
+ end
79
+
80
+ after_destroy :toggle_deleted
81
+
82
+ index
83
+ end
84
+ alias_method :sphinx_index, :define_index
85
+
86
+ def sphinx_index_options
87
+ sphinx_indexes.last.options
88
+ end
89
+
90
+ # Generate a unique CRC value for the model's name, to use to
91
+ # determine which Sphinx documents belong to which AR records.
92
+ #
93
+ # Really only written for internal use - but hey, if it's useful to
94
+ # you in some other way, awesome.
95
+ #
96
+ def to_crc32
97
+ self.name.to_crc32
98
+ end
99
+
100
+ def to_crc32s
101
+ (subclasses << self).collect { |klass| klass.to_crc32 }
102
+ end
103
+
104
+ def source_of_sphinx_index
105
+ possible_models = self.sphinx_indexes.collect { |index| index.model }
106
+ return self if possible_models.include?(self)
107
+
108
+ parent = self.superclass
109
+ while !possible_models.include?(parent) && parent != ::ActiveRecord::Base
110
+ parent = parent.superclass
111
+ end
112
+
113
+ return parent
114
+ end
115
+
116
+ def to_riddle(offset)
117
+ sphinx_database_adapter.setup
118
+
119
+ indexes = [to_riddle_for_core(offset)]
120
+ indexes << to_riddle_for_delta(offset) if sphinx_delta?
121
+ indexes << to_riddle_for_distributed
122
+ end
123
+
124
+ def sphinx_database_adapter
125
+ @sphinx_database_adapter ||=
126
+ ThinkingSphinx::AbstractAdapter.detect(self)
127
+ end
128
+
129
+ private
130
+
131
+ def sphinx_name
132
+ self.name.underscore.tr(':/\\', '_')
133
+ end
134
+
135
+ def sphinx_delta?
136
+ self.sphinx_indexes.any? { |index| index.delta? }
137
+ end
138
+
139
+ def to_riddle_for_core(offset)
140
+ index = Riddle::Configuration::Index.new("#{sphinx_name}_core")
141
+ index.path = File.join(
142
+ ThinkingSphinx::Configuration.instance.searchd_file_path, index.name
143
+ )
144
+
145
+ set_configuration_options_for_indexes index
146
+ set_field_settings_for_indexes index
147
+
148
+ self.sphinx_indexes.select { |ts_index|
149
+ ts_index.model == self
150
+ }.each_with_index do |ts_index, i|
151
+ index.sources << ts_index.to_riddle_for_core(offset, i)
152
+ end
153
+
154
+ index
155
+ end
156
+
157
+ def to_riddle_for_delta(offset)
158
+ index = Riddle::Configuration::Index.new("#{sphinx_name}_delta")
159
+ index.parent = "#{sphinx_name}_core"
160
+ index.path = File.join(ThinkingSphinx::Configuration.instance.searchd_file_path, index.name)
161
+
162
+ self.sphinx_indexes.each_with_index do |ts_index, i|
163
+ index.sources << ts_index.to_riddle_for_delta(offset, i) if ts_index.delta?
164
+ end
165
+
166
+ index
167
+ end
168
+
169
+ def to_riddle_for_distributed
170
+ index = Riddle::Configuration::DistributedIndex.new(sphinx_name)
171
+ index.local_indexes << "#{sphinx_name}_core"
172
+ index.local_indexes.unshift "#{sphinx_name}_delta" if sphinx_delta?
173
+ index
174
+ end
175
+
176
+ def set_configuration_options_for_indexes(index)
177
+ ThinkingSphinx::Configuration.instance.index_options.each do |key, value|
178
+ index.send("#{key}=".to_sym, value)
179
+ end
180
+
181
+ self.sphinx_indexes.each do |ts_index|
182
+ ts_index.options.each do |key, value|
183
+ index.send("#{key}=".to_sym, value) if ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) && !value.nil?
184
+ end
185
+ end
186
+ end
187
+
188
+ def set_field_settings_for_indexes(index)
189
+ field_names = lambda { |field| field.unique_name.to_s }
190
+
191
+ self.sphinx_indexes.each do |ts_index|
192
+ index.prefix_field_names += ts_index.prefix_fields.collect(&field_names)
193
+ index.infix_field_names += ts_index.infix_fields.collect(&field_names)
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ base.send(:include, ThinkingSphinx::ActiveRecord::Delta)
200
+ base.send(:include, ThinkingSphinx::ActiveRecord::Search)
201
+
202
+ ::ActiveRecord::Associations::HasManyAssociation.send(
203
+ :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
204
+ )
205
+ ::ActiveRecord::Associations::HasManyThroughAssociation.send(
206
+ :include, ThinkingSphinx::ActiveRecord::HasManyAssociation
207
+ )
208
+ end
209
+
210
+ def in_core_index?
211
+ self.class.search_for_id(
212
+ self.sphinx_document_id,
213
+ "#{self.class.source_of_sphinx_index.name.underscore.tr(':/\\', '_')}_core"
214
+ )
215
+ end
216
+
217
+ def toggle_deleted
218
+ return unless ThinkingSphinx.updates_enabled? && ThinkingSphinx.sphinx_running?
219
+
220
+ config = ThinkingSphinx::Configuration.instance
221
+ client = Riddle::Client.new config.address, config.port
222
+
223
+ client.update(
224
+ "#{self.class.sphinx_indexes.first.name}_core",
225
+ ['sphinx_deleted'],
226
+ {self.sphinx_document_id => 1}
227
+ ) if self.in_core_index?
228
+
229
+ client.update(
230
+ "#{self.class.sphinx_indexes.first.name}_delta",
231
+ ['sphinx_deleted'],
232
+ {self.sphinx_document_id => 1}
233
+ ) if ThinkingSphinx.deltas_enabled? &&
234
+ self.class.sphinx_indexes.any? { |index| index.delta? } &&
235
+ self.delta
236
+ rescue ::ThinkingSphinx::ConnectionError
237
+ # nothing
238
+ end
239
+
240
+ def sphinx_document_id
241
+ (self.id * ThinkingSphinx.indexed_models.size) +
242
+ ThinkingSphinx.indexed_models.index(self.class.source_of_sphinx_index.name)
243
+ end
244
+ end
245
+ end