summaryrefslogtreecommitdiff
path: root/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/configuration.rb
blob: 31543f587365d8e57a6872509cf6486059900688 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
require 'erb'
require 'singleton'

module ThinkingSphinx
  # This class both keeps track of the configuration settings for Sphinx and
  # also generates the resulting file for Sphinx to use.
  # 
  # Here are the default settings, relative to RAILS_ROOT where relevant:
  #
  # config file::           config/#{environment}.sphinx.conf
  # searchd log file::      log/searchd.log
  # query log file::        log/searchd.query.log
  # pid file::              log/searchd.#{environment}.pid
  # searchd files::         db/sphinx/#{environment}/
  # address::               127.0.0.1
  # port::                  3312
  # allow star::            false
  # min prefix length::     1
  # min infix length::      1
  # mem limit::             64M
  # max matches::           1000
  # morphology::            stem_en
  # charset type::          utf-8
  # charset table::         nil
  # ignore chars::          nil
  # html strip::            false
  # html remove elements::  ''
  #
  # If you want to change these settings, create a YAML file at
  # config/sphinx.yml with settings for each environment, in a similar
  # fashion to database.yml - using the following keys: config_file,
  # searchd_log_file, query_log_file, pid_file, searchd_file_path, port,
  # allow_star, enable_star, min_prefix_len, min_infix_len, mem_limit,
  # max_matches, # morphology, charset_type, charset_table, ignore_chars,
  # html_strip, # html_remove_elements. I think you've got the idea.
  # 
  # Each setting in the YAML file is optional - so only put in the ones you
  # want to change.
  #
  # Keep in mind, if for some particular reason you're using a version of
  # Sphinx older than 0.9.8 r871 (that's prior to the proper 0.9.8 release),
  # don't set allow_star to true.
  # 
  class Configuration
    include Singleton
    
    SourceOptions = %w( mysql_connect_flags sql_range_step sql_query_pre
      sql_query_post sql_ranged_throttle sql_query_post_index )
    
    IndexOptions  = %w( charset_table charset_type docinfo enable_star
      exceptions html_index_attrs html_remove_elements html_strip ignore_chars
      min_infix_len min_prefix_len min_word_len mlock morphology ngram_chars
      ngram_len phrase_boundary phrase_boundary_step preopen stopwords
      wordforms )
        
    attr_accessor :config_file, :searchd_log_file, :query_log_file,
      :pid_file, :searchd_file_path, :address, :port, :allow_star,
      :database_yml_file, :app_root, :bin_path, :model_directories
    
    attr_accessor :source_options, :index_options
    
    attr_reader :environment, :configuration
    
    # Load in the configuration settings - this will look for config/sphinx.yml
    # and parse it according to the current environment.
    # 
    def initialize(app_root = Dir.pwd)
      self.reset
    end
    
    def reset
      self.app_root          = RAILS_ROOT if defined?(RAILS_ROOT)
      self.app_root          = Merb.root  if defined?(Merb)
      self.app_root        ||= app_root
      
      @configuration = Riddle::Configuration.new
      @configuration.searchd.address    = "127.0.0.1"
      @configuration.searchd.port       = 3312
      @configuration.searchd.pid_file   = "#{self.app_root}/log/searchd.#{environment}.pid"
      @configuration.searchd.log        = "#{self.app_root}/log/searchd.log"
      @configuration.searchd.query_log  = "#{self.app_root}/log/searchd.query.log"
      
      self.database_yml_file    = "#{self.app_root}/config/database.yml"
      self.config_file          = "#{self.app_root}/config/#{environment}.sphinx.conf"
      self.searchd_file_path    = "#{self.app_root}/db/sphinx/#{environment}"
      self.allow_star           = false
      self.bin_path             = ""
      self.model_directories    = ["#{app_root}/app/models/"] +
        Dir.glob("#{app_root}/vendor/plugins/*/app/models/")
      
      self.source_options  = {}
      self.index_options   = {
        :charset_type => "utf-8",
        :morphology   => "stem_en"
      }
            
      parse_config
      
      self
    end
    
    def self.environment
      @@environment ||= (
        defined?(Merb) ? Merb.environment : ENV['RAILS_ENV']
      ) || "development"
    end
    
    def environment
      self.class.environment
    end
    
    def controller
      @controller ||= Riddle::Controller.new(@configuration, self.config_file)
    end
    
    # Generate the config file for Sphinx by using all the settings defined and
    # looping through all the models with indexes to build the relevant
    # indexer and searchd configuration, and sources and indexes details.
    #
    def build(file_path=nil)
      load_models
      file_path ||= "#{self.config_file}"
      
      @configuration.indexes.clear
      
      ThinkingSphinx.indexed_models.each_with_index do |model, model_index|
        @configuration.indexes.concat model.constantize.to_riddle(model_index)
      end
      
      open(file_path, "w") do |file|
        file.write @configuration.render
      end
    end
    
    # Make sure all models are loaded - without reloading any that
    # ActiveRecord::Base is already aware of (otherwise we start to hit some
    # messy dependencies issues).
    # 
    def load_models
      self.model_directories.each do |base|
        Dir["#{base}**/*.rb"].each do |file|
          model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
        
          next if model_name.nil?
          next if ::ActiveRecord::Base.send(:subclasses).detect { |model|
            model.name == model_name
          }
        
          begin
            model_name.camelize.constantize
          rescue LoadError
            model_name.gsub!(/.*[\/\\]/, '').nil? ? next : retry
          rescue NameError
            next
          end
        end
      end
    end
    
    def address
      @configuration.searchd.address
    end
    
    def address=(address)
      @configuration.searchd.address = address
    end
    
    def port
      @configuration.searchd.port
    end
    
    def port=(port)
      @configuration.searchd.port = port
    end
    
    def pid_file
      @configuration.searchd.pid_file
    end
    
    def pid_file=(pid_file)
      @configuration.searchd.pid_file = pid_file
    end
    
    def searchd_log_file
      @configuration.searchd.log
    end
    
    def searchd_log_file=(file)
      @configuration.searchd.log = file
    end
    
    def query_log_file
      @configuration.searchd.query_log
    end
    
    def query_log_file=(file)
      @configuration.searchd.query_log = file
    end
    
    private
    
    # Parse the config/sphinx.yml file - if it exists - then use the attribute
    # accessors to set the appropriate values. Nothing too clever.
    # 
    def parse_config
      path = "#{app_root}/config/sphinx.yml"
      return unless File.exists?(path)
      
      conf = YAML::load(ERB.new(IO.read(path)).result)[environment]
      
      conf.each do |key,value|
        self.send("#{key}=", value) if self.methods.include?("#{key}=")
        
        set_sphinx_setting self.source_options, key, value, SourceOptions
        set_sphinx_setting self.index_options,  key, value, IndexOptions
        set_sphinx_setting @configuration.searchd, key, value
        set_sphinx_setting @configuration.indexer, key, value
      end unless conf.nil?
      
      self.bin_path += '/' unless self.bin_path.blank?
      
      if self.allow_star
        self.index_options[:enable_star]    = true
        self.index_options[:min_prefix_len] = 1
      end
    end
    
    def set_sphinx_setting(object, key, value, allowed = {})
      if object.is_a?(Hash)
        object[key.to_sym] = value if allowed.include?(key.to_s)
      else
        object.send("#{key}=", value) if object.methods.include?("#{key}")
        send("#{key}=", value) if self.methods.include?("#{key}")
      end
    end
  end
end