ferret_server.rb
4.42 KB
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
require 'drb'
require 'thread'
require 'yaml'
require 'erb'
module ActsAsFerret
module Remote
module Config
class << self
DEFAULTS = {
'host' => 'localhost',
'port' => '9009'
}
# read connection settings from config file
def load(file = "#{RAILS_ROOT}/config/ferret_server.yml")
config = DEFAULTS.merge(YAML.load(ERB.new(IO.read(file)).result))
if config = config[RAILS_ENV]
config[:uri] = "druby://#{config['host']}:#{config['port']}"
return config
end
{}
end
end
end
# This class acts as a drb server listening for indexing and
# search requests from models declared to 'acts_as_ferret :remote => true'
#
# Usage:
# - modify RAILS_ROOT/config/ferret_server.yml to suit your needs.
# - environments for which no section in the config file exists will use
# the index locally (good for unit tests/development mode)
# - run script/ferret_start to start the server:
# RAILS_ENV=production script/ferret_start
#
class Server
cattr_accessor :running
def self.start(uri = nil)
ActiveRecord::Base.allow_concurrency = true
ActiveRecord::Base.logger = Logger.new("#{RAILS_ROOT}/log/ferret_server.log")
uri ||= ActsAsFerret::Remote::Config.load[:uri]
DRb.start_service(uri, ActsAsFerret::Remote::Server.new)
self.running = true
end
def initialize
@logger = ActiveRecord::Base.logger
end
# handles all incoming method calls, and sends them on to the LocalIndex
# instance of the correct model class.
#
# Calls are not queued atm, so this will block until the call returned.
#
def method_missing(name, *args)
@logger.debug "\#method_missing(#{name.inspect}, #{args.inspect})"
with_class args.shift do |clazz|
begin
clazz.aaf_index.send name, *args
rescue NoMethodError
@logger.debug "no luck, trying to call class method instead"
clazz.send name, *args
end
end
rescue
@logger.error "ferret server error #{$!}\n#{$!.backtrace.join '\n'}"
raise
end
# make sure we have a versioned index in place, building one if necessary
def ensure_index_exists(class_name)
@logger.debug "DRb server: ensure_index_exists for class #{class_name}"
with_class class_name do |clazz|
dir = clazz.aaf_configuration[:index_dir]
unless File.directory?(dir) && File.file?(File.join(dir, 'segments')) && dir =~ %r{/\d+(_\d+)?$}
rebuild_index(clazz)
end
end
end
# hides LocalIndex#rebuild_index to implement index versioning
def rebuild_index(clazz, *models)
with_class clazz do |clazz|
models = models.flatten.uniq.map(&:constantize)
models << clazz unless models.include?(clazz)
index = new_index_for(clazz, models)
@logger.debug "DRb server: rebuild index for class(es) #{models.inspect} in #{index.options[:path]}"
index.index_models models
new_version = File.join clazz.aaf_configuration[:index_base_dir], Time.now.utc.strftime('%Y%m%d%H%M%S')
# create a unique directory name (needed for unit tests where
# multiple rebuilds per second may occur)
if File.exists?(new_version)
i = 0
i+=1 while File.exists?("#{new_version}_#{i}")
new_version << "_#{i}"
end
File.rename index.options[:path], new_version
clazz.index_dir = new_version
end
end
protected
def with_class(clazz, *args)
clazz = clazz.constantize if String === clazz
yield clazz, *args
end
def new_index_for(clazz, models)
aaf_configuration = clazz.aaf_configuration
ferret_cfg = aaf_configuration[:ferret].dup
ferret_cfg.update :auto_flush => false,
:create => true,
:field_infos => ActsAsFerret::field_infos(models),
:path => File.join(aaf_configuration[:index_base_dir], 'rebuild')
returning Ferret::Index::Index.new ferret_cfg do |i|
i.batch_size = aaf_configuration[:reindex_batch_size]
i.logger = @logger
end
end
end
end
end