initforthe-thinking-sphinx 1.1.21

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 (91) hide show
  1. data/LICENCE +20 -0
  2. data/README.textile +141 -0
  3. data/lib/thinking_sphinx.rb +215 -0
  4. data/lib/thinking_sphinx/active_record.rb +278 -0
  5. data/lib/thinking_sphinx/active_record/attribute_updates.rb +48 -0
  6. data/lib/thinking_sphinx/active_record/delta.rb +87 -0
  7. data/lib/thinking_sphinx/active_record/has_many_association.rb +29 -0
  8. data/lib/thinking_sphinx/active_record/search.rb +57 -0
  9. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +42 -0
  10. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +54 -0
  11. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +135 -0
  12. data/lib/thinking_sphinx/association.rb +164 -0
  13. data/lib/thinking_sphinx/attribute.rb +268 -0
  14. data/lib/thinking_sphinx/class_facet.rb +15 -0
  15. data/lib/thinking_sphinx/collection.rb +148 -0
  16. data/lib/thinking_sphinx/configuration.rb +262 -0
  17. data/lib/thinking_sphinx/core/string.rb +15 -0
  18. data/lib/thinking_sphinx/deltas.rb +30 -0
  19. data/lib/thinking_sphinx/deltas/datetime_delta.rb +50 -0
  20. data/lib/thinking_sphinx/deltas/default_delta.rb +68 -0
  21. data/lib/thinking_sphinx/deltas/delayed_delta.rb +27 -0
  22. data/lib/thinking_sphinx/deltas/delayed_delta/delta_job.rb +24 -0
  23. data/lib/thinking_sphinx/deltas/delayed_delta/flag_as_deleted_job.rb +27 -0
  24. data/lib/thinking_sphinx/deltas/delayed_delta/job.rb +26 -0
  25. data/lib/thinking_sphinx/deploy/capistrano.rb +82 -0
  26. data/lib/thinking_sphinx/facet.rb +108 -0
  27. data/lib/thinking_sphinx/facet_collection.rb +59 -0
  28. data/lib/thinking_sphinx/field.rb +82 -0
  29. data/lib/thinking_sphinx/index.rb +99 -0
  30. data/lib/thinking_sphinx/index/builder.rb +287 -0
  31. data/lib/thinking_sphinx/index/faux_column.rb +110 -0
  32. data/lib/thinking_sphinx/property.rb +160 -0
  33. data/lib/thinking_sphinx/rails_additions.rb +136 -0
  34. data/lib/thinking_sphinx/search.rb +727 -0
  35. data/lib/thinking_sphinx/search/facets.rb +104 -0
  36. data/lib/thinking_sphinx/source.rb +150 -0
  37. data/lib/thinking_sphinx/source/internal_properties.rb +46 -0
  38. data/lib/thinking_sphinx/source/sql.rb +126 -0
  39. data/lib/thinking_sphinx/tasks.rb +162 -0
  40. data/rails/init.rb +14 -0
  41. data/spec/unit/thinking_sphinx/active_record/delta_spec.rb +136 -0
  42. data/spec/unit/thinking_sphinx/active_record/has_many_association_spec.rb +53 -0
  43. data/spec/unit/thinking_sphinx/active_record/search_spec.rb +107 -0
  44. data/spec/unit/thinking_sphinx/active_record_spec.rb +329 -0
  45. data/spec/unit/thinking_sphinx/association_spec.rb +246 -0
  46. data/spec/unit/thinking_sphinx/attribute_spec.rb +338 -0
  47. data/spec/unit/thinking_sphinx/collection_spec.rb +15 -0
  48. data/spec/unit/thinking_sphinx/configuration_spec.rb +222 -0
  49. data/spec/unit/thinking_sphinx/core/string_spec.rb +9 -0
  50. data/spec/unit/thinking_sphinx/facet_collection_spec.rb +64 -0
  51. data/spec/unit/thinking_sphinx/facet_spec.rb +302 -0
  52. data/spec/unit/thinking_sphinx/field_spec.rb +154 -0
  53. data/spec/unit/thinking_sphinx/index/builder_spec.rb +355 -0
  54. data/spec/unit/thinking_sphinx/index/faux_column_spec.rb +30 -0
  55. data/spec/unit/thinking_sphinx/index_spec.rb +45 -0
  56. data/spec/unit/thinking_sphinx/rails_additions_spec.rb +191 -0
  57. data/spec/unit/thinking_sphinx/search_spec.rb +228 -0
  58. data/spec/unit/thinking_sphinx/source_spec.rb +217 -0
  59. data/spec/unit/thinking_sphinx_spec.rb +151 -0
  60. data/tasks/distribution.rb +67 -0
  61. data/tasks/rails.rake +1 -0
  62. data/tasks/testing.rb +78 -0
  63. data/vendor/after_commit/LICENSE +20 -0
  64. data/vendor/after_commit/README +16 -0
  65. data/vendor/after_commit/Rakefile +22 -0
  66. data/vendor/after_commit/init.rb +8 -0
  67. data/vendor/after_commit/lib/after_commit.rb +45 -0
  68. data/vendor/after_commit/lib/after_commit/active_record.rb +114 -0
  69. data/vendor/after_commit/lib/after_commit/connection_adapters.rb +103 -0
  70. data/vendor/after_commit/test/after_commit_test.rb +53 -0
  71. data/vendor/delayed_job/lib/delayed/job.rb +251 -0
  72. data/vendor/delayed_job/lib/delayed/message_sending.rb +7 -0
  73. data/vendor/delayed_job/lib/delayed/performable_method.rb +55 -0
  74. data/vendor/delayed_job/lib/delayed/worker.rb +54 -0
  75. data/vendor/riddle/lib/riddle.rb +30 -0
  76. data/vendor/riddle/lib/riddle/client.rb +619 -0
  77. data/vendor/riddle/lib/riddle/client/filter.rb +53 -0
  78. data/vendor/riddle/lib/riddle/client/message.rb +65 -0
  79. data/vendor/riddle/lib/riddle/client/response.rb +84 -0
  80. data/vendor/riddle/lib/riddle/configuration.rb +33 -0
  81. data/vendor/riddle/lib/riddle/configuration/distributed_index.rb +48 -0
  82. data/vendor/riddle/lib/riddle/configuration/index.rb +142 -0
  83. data/vendor/riddle/lib/riddle/configuration/indexer.rb +19 -0
  84. data/vendor/riddle/lib/riddle/configuration/remote_index.rb +17 -0
  85. data/vendor/riddle/lib/riddle/configuration/searchd.rb +25 -0
  86. data/vendor/riddle/lib/riddle/configuration/section.rb +43 -0
  87. data/vendor/riddle/lib/riddle/configuration/source.rb +23 -0
  88. data/vendor/riddle/lib/riddle/configuration/sql_source.rb +34 -0
  89. data/vendor/riddle/lib/riddle/configuration/xml_source.rb +28 -0
  90. data/vendor/riddle/lib/riddle/controller.rb +44 -0
  91. metadata +190 -0
@@ -0,0 +1,24 @@
1
+ module ThinkingSphinx
2
+ module Deltas
3
+ class DeltaJob
4
+ attr_accessor :index
5
+
6
+ def initialize(index)
7
+ @index = index
8
+ end
9
+
10
+ def perform
11
+ return true unless ThinkingSphinx.updates_enabled? &&
12
+ ThinkingSphinx.deltas_enabled?
13
+
14
+ config = ThinkingSphinx::Configuration.instance
15
+ client = Riddle::Client.new config.address, config.port
16
+
17
+ output = `#{config.bin_path}indexer --config #{config.config_file} --rotate #{index}`
18
+ puts output unless ThinkingSphinx.suppress_delta_output?
19
+
20
+ true
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module ThinkingSphinx
2
+ module Deltas
3
+ class FlagAsDeletedJob
4
+ attr_accessor :index, :document_id
5
+
6
+ def initialize(index, document_id)
7
+ @index, @document_id = index, document_id
8
+ end
9
+
10
+ def perform
11
+ return true unless ThinkingSphinx.updates_enabled?
12
+
13
+ config = ThinkingSphinx::Configuration.instance
14
+ client = Riddle::Client.new config.address, config.port
15
+
16
+ client.update(
17
+ @index,
18
+ ['sphinx_deleted'],
19
+ {@document_id => [1]}
20
+ ) if ThinkingSphinx.sphinx_running? &&
21
+ ThinkingSphinx::Search.search_for_id(@document_id, @index)
22
+
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module ThinkingSphinx
2
+ module Deltas
3
+ class Job < Delayed::Job
4
+ def self.enqueue(object, priority = 0)
5
+ super unless duplicates_exist(object)
6
+ end
7
+
8
+ def self.cancel_thinking_sphinx_jobs
9
+ if connection.tables.include?("delayed_jobs")
10
+ delete_all("handler LIKE '--- !ruby/object:ThinkingSphinx::Deltas::%'")
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def self.duplicates_exist(object)
17
+ count(
18
+ :conditions => {
19
+ :handler => object.to_yaml,
20
+ :locked_at => nil
21
+ }
22
+ ) > 0
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,82 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :thinking_sphinx do
3
+ namespace :install do
4
+ desc "Install Sphinx by source"
5
+ task :sphinx do
6
+ with_postgres = false
7
+ run "which pg_config" do |channel, stream, data|
8
+ with_postgres = !(data.nil? || data == "")
9
+ end
10
+
11
+ args = []
12
+ if with_postgres
13
+ run "pg_config --pkgincludedir" do |channel, stream, data|
14
+ args << "--with-pgsql=#{data}"
15
+ end
16
+ end
17
+
18
+ commands = <<-CMD
19
+ wget -q http://www.sphinxsearch.com/downloads/sphinx-0.9.8.1.tar.gz >> sphinx.log
20
+ tar xzvf sphinx-0.9.8.1.tar.gz
21
+ cd sphinx-0.9.8.1
22
+ ./configure #{args.join(" ")}
23
+ make
24
+ sudo make install
25
+ rm -rf sphinx-0.9.8.1 sphinx-0.9.8.1.tar.gz
26
+ CMD
27
+ run commands.split(/\n\s+/).join(" && ")
28
+ end
29
+
30
+ desc "Install Thinking Sphinx as a gem from GitHub"
31
+ task :ts do
32
+ sudo "gem install freelancing-god-thinking-sphinx --source http://gems.github.com"
33
+ end
34
+ end
35
+
36
+ desc "Generate the Sphinx configuration file"
37
+ task :configure do
38
+ rake "thinking_sphinx:configure"
39
+ end
40
+
41
+ desc "Index data"
42
+ task :index do
43
+ rake "thinking_sphinx:index"
44
+ end
45
+
46
+ desc "Start the Sphinx daemon"
47
+ task :start do
48
+ configure
49
+ rake "thinking_sphinx:start"
50
+ end
51
+
52
+ desc "Stop the Sphinx daemon"
53
+ task :stop do
54
+ configure
55
+ rake "thinking_sphinx:stop"
56
+ end
57
+
58
+ desc "Stop and then start the Sphinx daemon"
59
+ task :restart do
60
+ stop
61
+ start
62
+ end
63
+
64
+ desc "Stop, re-index and then start the Sphinx daemon"
65
+ task :rebuild do
66
+ stop
67
+ index
68
+ start
69
+ end
70
+
71
+ desc "Add the shared folder for sphinx files for the production environment"
72
+ task :shared_sphinx_folder, :roles => :web do
73
+ sudo "mkdir -p #{shared_path}/db/sphinx/production"
74
+ end
75
+
76
+ def rake(*tasks)
77
+ tasks.each do |t|
78
+ run "cd #{current_path} && rake #{t} RAILS_ENV=production"
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,108 @@
1
+ module ThinkingSphinx
2
+ class Facet
3
+ attr_reader :property
4
+
5
+ def initialize(property)
6
+ @property = property
7
+
8
+ if property.columns.length != 1
9
+ raise "Can't translate Facets on multiple-column field or attribute"
10
+ end
11
+ end
12
+
13
+ def self.name_for(facet)
14
+ case facet
15
+ when Facet
16
+ facet.name
17
+ when String, Symbol
18
+ facet.to_s.gsub(/(_facet|_crc)$/,'').to_sym
19
+ end
20
+ end
21
+
22
+ def self.attribute_name_for(name)
23
+ name.to_s == 'class' ? 'class_crc' : "#{name}_facet"
24
+ end
25
+
26
+ def self.attribute_name_from_value(name, value)
27
+ case value
28
+ when String
29
+ attribute_name_for(name)
30
+ when Array
31
+ if value.all? { |val| val.is_a?(Integer) }
32
+ name
33
+ else
34
+ attribute_name_for(name)
35
+ end
36
+ else
37
+ name
38
+ end
39
+ end
40
+
41
+ def self.translate?(property)
42
+ return true if property.is_a?(Field)
43
+
44
+ case property.type
45
+ when :string
46
+ true
47
+ when :integer, :boolean, :datetime, :float
48
+ false
49
+ when :multi
50
+ !property.all_ints?
51
+ end
52
+ end
53
+
54
+ def name
55
+ property.unique_name
56
+ end
57
+
58
+ def attribute_name
59
+ if translate?
60
+ Facet.attribute_name_for(@property.unique_name)
61
+ else
62
+ @property.unique_name.to_s
63
+ end
64
+ end
65
+
66
+ def translate?
67
+ Facet.translate?(@property)
68
+ end
69
+
70
+ def type
71
+ @property.is_a?(Field) ? :string : @property.type
72
+ end
73
+
74
+ def value(object, attribute_value)
75
+ return translate(object, attribute_value) if translate?
76
+
77
+ case @property.type
78
+ when :datetime
79
+ Time.at(attribute_value)
80
+ when :boolean
81
+ attribute_value > 0
82
+ else
83
+ attribute_value
84
+ end
85
+ end
86
+
87
+ def to_s
88
+ name
89
+ end
90
+
91
+ private
92
+
93
+ def translate(object, attribute_value)
94
+ column.__stack.each { |method|
95
+ return nil unless object = object.send(method)
96
+ }
97
+ if object.is_a?(Array)
98
+ object.collect { |item| item.send(column.__name) }
99
+ else
100
+ object.send(column.__name)
101
+ end
102
+ end
103
+
104
+ def column
105
+ @property.columns.first
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,59 @@
1
+ module ThinkingSphinx
2
+ class FacetCollection < Hash
3
+ attr_accessor :arguments
4
+
5
+ def initialize(arguments)
6
+ @arguments = arguments.clone
7
+ @facet_names = []
8
+ end
9
+
10
+ def add_from_results(facet, results)
11
+ name = ThinkingSphinx::Facet.name_for(facet)
12
+
13
+ self[name] ||= {}
14
+ @facet_names << name
15
+
16
+ return if results.empty?
17
+
18
+ facet = facet_from_object(results.first, facet) if facet.is_a?(String)
19
+
20
+ results.each_with_groupby_and_count { |result, group, count|
21
+ facet_value = facet.value(result, group)
22
+
23
+ self[name][facet_value] ||= 0
24
+ self[name][facet_value] += count
25
+ }
26
+ end
27
+
28
+ def for(hash = {})
29
+ arguments = @arguments.clone
30
+ options = arguments.extract_options!
31
+ options[:with] ||= {}
32
+
33
+ hash.each do |key, value|
34
+ attrib = ThinkingSphinx::Facet.attribute_name_from_value(key, value)
35
+ options[:with][attrib] = underlying_value key, value
36
+ end
37
+
38
+ arguments << options
39
+ ThinkingSphinx::Search.search *arguments
40
+ end
41
+
42
+ private
43
+
44
+ def underlying_value(key, value)
45
+ case value
46
+ when Array
47
+ value.collect { |item| underlying_value(key, item) }
48
+ when String
49
+ value.to_crc32
50
+ else
51
+ value
52
+ end
53
+ end
54
+
55
+ def facet_from_object(object, name)
56
+ object.sphinx_facets.detect { |facet| facet.attribute_name == name }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,82 @@
1
+ module ThinkingSphinx
2
+ # Fields - holding the string data which Sphinx indexes for your searches.
3
+ # This class isn't really useful to you unless you're hacking around with the
4
+ # internals of Thinking Sphinx - but hey, don't let that stop you.
5
+ #
6
+ # One key thing to remember - if you're using the field manually to
7
+ # generate SQL statements, you'll need to set the base model, and all the
8
+ # associations. Which can get messy. Use Index.link!, it really helps.
9
+ #
10
+ class Field < ThinkingSphinx::Property
11
+ attr_accessor :sortable, :infixes, :prefixes
12
+
13
+ # To create a new field, you'll need to pass in either a single Column
14
+ # or an array of them, and some (optional) options. The columns are
15
+ # references to the data that will make up the field.
16
+ #
17
+ # Valid options are:
18
+ # - :as => :alias_name
19
+ # - :sortable => true
20
+ # - :infixes => true
21
+ # - :prefixes => true
22
+ #
23
+ # Alias is only required in three circumstances: when there's
24
+ # another attribute or field with the same name, when the column name is
25
+ # 'id', or when there's more than one column.
26
+ #
27
+ # Sortable defaults to false - but is quite useful when set to true, as
28
+ # it creates an attribute with the same string value (which Sphinx converts
29
+ # to an integer value), which can be sorted by. Thinking Sphinx is smart
30
+ # enough to realise that when you specify fields in sort statements, you
31
+ # mean their respective attributes.
32
+ #
33
+ # If you have partial matching enabled (ie: enable_star), then you can
34
+ # specify certain fields to have their prefixes and infixes indexed. Keep
35
+ # in mind, though, that Sphinx's default is _all_ fields - so once you
36
+ # highlight a particular field, no other fields in the index will have
37
+ # these partial indexes.
38
+ #
39
+ # Here's some examples:
40
+ #
41
+ # Field.new(
42
+ # Column.new(:name)
43
+ # )
44
+ #
45
+ # Field.new(
46
+ # [Column.new(:first_name), Column.new(:last_name)],
47
+ # :as => :name, :sortable => true
48
+ # )
49
+ #
50
+ # Field.new(
51
+ # [Column.new(:posts, :subject), Column.new(:posts, :content)],
52
+ # :as => :posts, :prefixes => true
53
+ # )
54
+ #
55
+ def initialize(source, columns, options = {})
56
+ super
57
+
58
+ @sortable = options[:sortable] || false
59
+ @infixes = options[:infixes] || false
60
+ @prefixes = options[:prefixes] || false
61
+
62
+ source.fields << self
63
+ end
64
+
65
+ # Get the part of the SELECT clause related to this field. Don't forget
66
+ # to set your model and associations first though.
67
+ #
68
+ # This will concatenate strings if there's more than one data source or
69
+ # multiple data values (has_many or has_and_belongs_to_many associations).
70
+ #
71
+ def to_select_sql
72
+ clause = @columns.collect { |column|
73
+ column_with_prefix(column)
74
+ }.join(', ')
75
+
76
+ clause = adapter.concatenate(clause) if concat_ws?
77
+ clause = adapter.group_concatenate(clause) if is_many?
78
+
79
+ "#{adapter.cast_to_string clause } AS #{quote_column(unique_name)}"
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,99 @@
1
+ require 'thinking_sphinx/index/builder'
2
+ require 'thinking_sphinx/index/faux_column'
3
+
4
+ module ThinkingSphinx
5
+ # The Index class is a ruby representation of a Sphinx source (not a Sphinx
6
+ # index - yes, I know it's a little confusing. You'll manage). This is
7
+ # another 'internal' Thinking Sphinx class - if you're using it directly,
8
+ # you either know what you're doing, or messing with things beyond your ken.
9
+ # Enjoy.
10
+ #
11
+ class Index
12
+ attr_accessor :model, :sources, :delta_object
13
+
14
+ # Create a new index instance by passing in the model it is tied to, and
15
+ # a block to build it with (optional but recommended). For documentation
16
+ # on the syntax for inside the block, the Builder class is what you want.
17
+ #
18
+ # Quick Example:
19
+ #
20
+ # Index.new(User) do
21
+ # indexes login, email
22
+ #
23
+ # has created_at
24
+ #
25
+ # set_property :delta => true
26
+ # end
27
+ #
28
+ def initialize(model, &block)
29
+ @model = model
30
+ @sources = []
31
+ @options = {}
32
+ @delta_object = nil
33
+ end
34
+
35
+ def fields
36
+ @sources.collect { |source| source.fields }.flatten
37
+ end
38
+
39
+ def attributes
40
+ @sources.collect { |source| source.attributes }.flatten
41
+ end
42
+
43
+ def name
44
+ self.class.name(@model)
45
+ end
46
+
47
+ def self.name(model)
48
+ model.name.underscore.tr(':/\\', '_')
49
+ end
50
+
51
+ def prefix_fields
52
+ fields.select { |field| field.prefixes }
53
+ end
54
+
55
+ def infix_fields
56
+ fields.select { |field| field.infixes }
57
+ end
58
+
59
+ def local_options
60
+ @options
61
+ end
62
+
63
+ def options
64
+ all_index_options = ThinkingSphinx::Configuration.instance.index_options.clone
65
+ @options.keys.select { |key|
66
+ ThinkingSphinx::Configuration::IndexOptions.include?(key.to_s) ||
67
+ ThinkingSphinx::Configuration::CustomOptions.include?(key.to_s)
68
+ }.each { |key| all_index_options[key.to_sym] = @options[key] }
69
+ all_index_options
70
+ end
71
+
72
+ def delta?
73
+ !@delta_object.nil?
74
+ end
75
+
76
+ private
77
+
78
+ def adapter
79
+ @adapter ||= @model.sphinx_database_adapter
80
+ end
81
+
82
+ def utf8?
83
+ options[:charset_type] == "utf-8"
84
+ end
85
+
86
+ # Does all the magic with the block provided to the base #initialize.
87
+ # Creates a new class subclassed from Builder, and evaluates the block
88
+ # on it, then pulls all relevant settings - fields, attributes, conditions,
89
+ # properties - into the new index.
90
+ #
91
+ def initialize_from_builder(&block)
92
+ #
93
+ end
94
+
95
+ def sql_query_pre_for_delta
96
+ [""]
97
+ end
98
+ end
99
+ end