acts_as_ferret 0.4.1 → 0.4.2
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.
- data/README +3 -0
- data/bin/aaf_install +23 -0
- data/config/ferret_server.yml +19 -8
- data/doc/README.win32 +23 -0
- data/doc/monit-example +22 -0
- data/install.rb +1 -2
- data/lib/act_methods.rb +16 -4
- data/lib/acts_as_ferret.rb +14 -23
- data/lib/bulk_indexer.rb +35 -0
- data/lib/class_methods.rb +192 -49
- data/lib/ferret_extensions.rb +52 -18
- data/lib/ferret_server.rb +109 -37
- data/lib/instance_methods.rb +45 -15
- data/lib/local_index.rb +7 -5
- data/lib/search_results.rb +53 -0
- data/lib/server_manager.rb +44 -0
- data/lib/shared_index_class_methods.rb +1 -1
- data/lib/unix_daemon.rb +63 -0
- data/rakefile +5 -2
- data/script/ferret_daemon +94 -0
- data/script/ferret_server +1 -17
- data/script/ferret_service +178 -0
- metadata +34 -26
- data/script/ferret_start +0 -72
- data/script/ferret_stop +0 -26
data/lib/ferret_extensions.rb
CHANGED
@@ -1,9 +1,39 @@
|
|
1
1
|
module Ferret
|
2
2
|
|
3
|
+
module Analysis
|
4
|
+
|
5
|
+
# = PerFieldAnalyzer
|
6
|
+
#
|
7
|
+
# This PerFieldAnalyzer is a workaround to a memory leak in
|
8
|
+
# ferret 0.11.4. It does basically do the same as the original
|
9
|
+
# Ferret::Analysis::PerFieldAnalyzer, but without the leak :)
|
10
|
+
#
|
11
|
+
# http://ferret.davebalmain.com/api/classes/Ferret/Analysis/PerFieldAnalyzer.html
|
12
|
+
#
|
13
|
+
# Thanks to Ben from omdb.org for tracking this down and creating this
|
14
|
+
# workaround.
|
15
|
+
# You can read more about the issue there:
|
16
|
+
# http://blog.omdb-beta.org/2007/7/29/tracking-down-a-memory-leak-in-ferret-0-11-4
|
17
|
+
class PerFieldAnalyzer < ::Ferret::Analysis::Analyzer
|
18
|
+
def initialize( default_analyzer = StandardAnalyzer.new )
|
19
|
+
@analyzers = {}
|
20
|
+
@default_analyzer = default_analyzer
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_field( field, analyzer )
|
24
|
+
@analyzers[field] = analyzer
|
25
|
+
end
|
26
|
+
alias []= add_field
|
27
|
+
|
28
|
+
def token_stream(field, string)
|
29
|
+
@analyzers.has_key?(field) ? @analyzers[field].token_stream(field, string) :
|
30
|
+
@default_analyzer.token_stream(field, string)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
3
34
|
|
4
35
|
class Index::Index
|
5
|
-
attr_accessor :batch_size
|
6
|
-
attr_accessor :logger
|
36
|
+
attr_accessor :batch_size, :logger
|
7
37
|
|
8
38
|
def index_models(models)
|
9
39
|
models.each { |model| index_model model }
|
@@ -14,32 +44,36 @@ module Ferret
|
|
14
44
|
end
|
15
45
|
|
16
46
|
def index_model(model)
|
17
|
-
@batch_size
|
18
|
-
|
19
|
-
batch_time = 0
|
47
|
+
bulk_indexer = ActsAsFerret::BulkIndexer.new(:batch_size => @batch_size, :logger => logger,
|
48
|
+
:model => model, :index => self, :reindex => true)
|
20
49
|
logger.info "reindexing model #{model.name}"
|
21
50
|
|
22
|
-
model_count = model.count.to_f
|
23
51
|
model.records_for_rebuild(@batch_size) do |records, offset|
|
24
|
-
|
25
|
-
batch_time = measure_time {
|
26
|
-
records.each { |rec| self << rec.to_doc if rec.ferret_enabled?(true) }
|
27
|
-
}.to_f
|
28
|
-
work_done = offset.to_f / model_count * 100.0 if model_count > 0
|
29
|
-
remaining_time = ( batch_time / @batch_size ) * ( model_count - offset + @batch_size )
|
30
|
-
logger.info "reindex model #{model.name} : #{'%.2f' % work_done}% complete : #{'%.2f' % remaining_time} secs to finish"
|
52
|
+
bulk_indexer.index_records(records, offset)
|
31
53
|
end
|
32
54
|
end
|
33
55
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
56
|
+
def bulk_index(model, ids, options = {})
|
57
|
+
options.reverse_merge! :optimize => true
|
58
|
+
orig_flush = @auto_flush
|
59
|
+
@auto_flush = false
|
60
|
+
bulk_indexer = ActsAsFerret::BulkIndexer.new(:batch_size => @batch_size, :logger => logger,
|
61
|
+
:model => model, :index => self, :total => ids.size)
|
62
|
+
model.records_for_bulk_index(ids, @batch_size) do |records, offset|
|
63
|
+
logger.debug "#{model} bulk indexing #{records.size} at #{offset}"
|
64
|
+
bulk_indexer.index_records(records, offset)
|
65
|
+
end
|
66
|
+
logger.info 'finishing bulk index...'
|
67
|
+
flush
|
68
|
+
if options[:optimize]
|
69
|
+
logger.info 'optimizing...'
|
70
|
+
optimize
|
71
|
+
end
|
72
|
+
@auto_flush = orig_flush
|
38
73
|
end
|
39
74
|
|
40
75
|
end
|
41
76
|
|
42
|
-
|
43
77
|
# add marshalling support to SortFields
|
44
78
|
class Search::SortField
|
45
79
|
def _dump(depth)
|
data/lib/ferret_server.rb
CHANGED
@@ -3,29 +3,41 @@ require 'thread'
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'erb'
|
5
5
|
|
6
|
-
|
6
|
+
################################################################################
|
7
7
|
module ActsAsFerret
|
8
|
-
|
9
8
|
module Remote
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
10
|
+
################################################################################
|
11
|
+
class Config
|
12
|
+
|
13
|
+
################################################################################
|
14
|
+
DEFAULTS = {
|
15
|
+
'host' => 'localhost',
|
16
|
+
'port' => '9009',
|
17
|
+
'cf' => "#{RAILS_ROOT}/config/ferret_server.yml",
|
18
|
+
'pid_file' => "#{RAILS_ROOT}/log/ferret_server.pid",
|
19
|
+
'log_file' => "#{RAILS_ROOT}/log/ferret_server.log",
|
20
|
+
'log_level' => 'debug',
|
21
|
+
}
|
22
|
+
|
23
|
+
################################################################################
|
24
|
+
# load the configuration file and apply default settings
|
25
|
+
def initialize (file=DEFAULTS['cf'])
|
26
|
+
@everything = YAML.load(ERB.new(IO.read(file)).result)
|
27
|
+
raise "malformed ferret server config" unless @everything.is_a?(Hash)
|
28
|
+
@config = DEFAULTS.merge(@everything[RAILS_ENV] || {})
|
29
|
+
@config['uri'] = "druby://#{host}:#{port}" if @everything[RAILS_ENV]
|
30
|
+
end
|
31
|
+
|
32
|
+
################################################################################
|
33
|
+
# treat the keys of the config data as methods
|
34
|
+
def method_missing (name, *args)
|
35
|
+
@config.has_key?(name.to_s) ? @config[name.to_s] : super
|
26
36
|
end
|
37
|
+
|
27
38
|
end
|
28
39
|
|
40
|
+
#################################################################################
|
29
41
|
# This class acts as a drb server listening for indexing and
|
30
42
|
# search requests from models declared to 'acts_as_ferret :remote => true'
|
31
43
|
#
|
@@ -33,25 +45,46 @@ module ActsAsFerret
|
|
33
45
|
# - modify RAILS_ROOT/config/ferret_server.yml to suit your needs.
|
34
46
|
# - environments for which no section in the config file exists will use
|
35
47
|
# the index locally (good for unit tests/development mode)
|
36
|
-
# - run script/
|
37
|
-
#
|
48
|
+
# - run script/ferret_server to start the server:
|
49
|
+
# script/ferret_server -e production start
|
50
|
+
# - to stop the server run
|
51
|
+
# script/ferret_server -e production stop
|
38
52
|
#
|
39
53
|
class Server
|
40
54
|
|
55
|
+
#################################################################################
|
56
|
+
# FIXME include detection of OS and include the correct file
|
57
|
+
require 'unix_daemon'
|
58
|
+
include(ActsAsFerret::Remote::UnixDaemon)
|
59
|
+
|
60
|
+
################################################################################
|
41
61
|
cattr_accessor :running
|
42
62
|
|
43
|
-
|
63
|
+
################################################################################
|
64
|
+
def initialize
|
65
|
+
@cfg = ActsAsFerret::Remote::Config.new
|
44
66
|
ActiveRecord::Base.allow_concurrency = true
|
45
|
-
ActiveRecord::Base.logger = Logger.new(
|
46
|
-
|
47
|
-
DRb.start_service(uri, ActsAsFerret::Remote::Server.new)
|
48
|
-
self.running = true
|
67
|
+
ActiveRecord::Base.logger = @logger = Logger.new(@cfg.log_file)
|
68
|
+
ActiveRecord::Base.logger.level = Logger.const_get(@cfg.log_level.upcase) rescue Logger::DEBUG
|
49
69
|
end
|
50
70
|
|
51
|
-
|
52
|
-
|
71
|
+
################################################################################
|
72
|
+
# start the server
|
73
|
+
def start
|
74
|
+
raise "ferret_server not configured for #{RAILS_ENV}" unless (@cfg.uri rescue nil)
|
75
|
+
$stdout.puts("starting ferret server...")
|
76
|
+
|
77
|
+
platform_daemon do
|
78
|
+
self.class.running = true
|
79
|
+
DRb.start_service(@cfg.uri, self)
|
80
|
+
DRb.thread.join
|
81
|
+
end
|
82
|
+
rescue Exception => e
|
83
|
+
@logger.error(e.to_s)
|
84
|
+
raise
|
53
85
|
end
|
54
86
|
|
87
|
+
#################################################################################
|
55
88
|
# handles all incoming method calls, and sends them on to the LocalIndex
|
56
89
|
# instance of the correct model class.
|
57
90
|
#
|
@@ -59,17 +92,25 @@ module ActsAsFerret
|
|
59
92
|
#
|
60
93
|
def method_missing(name, *args)
|
61
94
|
@logger.debug "\#method_missing(#{name.inspect}, #{args.inspect})"
|
95
|
+
retried = false
|
62
96
|
with_class args.shift do |clazz|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
clazz.
|
97
|
+
reconnect_when_needed(clazz) do
|
98
|
+
# using respond_to? here so we not have to catch NoMethodError
|
99
|
+
# which would silently catch those from deep inside the indexing
|
100
|
+
# code, too...
|
101
|
+
if clazz.aaf_index.respond_to?(name)
|
102
|
+
clazz.aaf_index.send name, *args
|
103
|
+
elsif clazz.respond_to?(name)
|
104
|
+
@logger.debug "no luck, trying to call class method instead"
|
105
|
+
clazz.send name, *args
|
106
|
+
else
|
107
|
+
raise NoMethodError.new("method #{name} not supported by DRb server")
|
108
|
+
end
|
68
109
|
end
|
69
110
|
end
|
70
|
-
rescue
|
71
|
-
@logger.error "ferret server error #{$!}\n#{$!.backtrace.join
|
72
|
-
raise
|
111
|
+
rescue => e
|
112
|
+
@logger.error "ferret server error #{$!}\n#{$!.backtrace.join "\n"}"
|
113
|
+
raise e
|
73
114
|
end
|
74
115
|
|
75
116
|
# make sure we have a versioned index in place, building one if necessary
|
@@ -83,14 +124,24 @@ module ActsAsFerret
|
|
83
124
|
end
|
84
125
|
end
|
85
126
|
|
127
|
+
# disconnects the db connection for the class specified by class_name
|
128
|
+
# used only in unit tests to check the automatic reconnection feature
|
129
|
+
def db_disconnect!(class_name)
|
130
|
+
with_class class_name do |clazz|
|
131
|
+
clazz.connection.disconnect!
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
86
135
|
# hides LocalIndex#rebuild_index to implement index versioning
|
87
136
|
def rebuild_index(clazz, *models)
|
88
137
|
with_class clazz do |clazz|
|
89
138
|
models = models.flatten.uniq.map(&:constantize)
|
90
139
|
models << clazz unless models.include?(clazz)
|
91
140
|
index = new_index_for(clazz, models)
|
92
|
-
|
93
|
-
|
141
|
+
reconnect_when_needed(clazz) do
|
142
|
+
@logger.debug "DRb server: rebuild index for class(es) #{models.inspect} in #{index.options[:path]}"
|
143
|
+
index.index_models models
|
144
|
+
end
|
94
145
|
new_version = File.join clazz.aaf_configuration[:index_base_dir], Time.now.utc.strftime('%Y%m%d%H%M%S')
|
95
146
|
# create a unique directory name (needed for unit tests where
|
96
147
|
# multiple rebuilds per second may occur)
|
@@ -113,6 +164,27 @@ module ActsAsFerret
|
|
113
164
|
yield clazz, *args
|
114
165
|
end
|
115
166
|
|
167
|
+
def reconnect_when_needed(clazz)
|
168
|
+
retried = false
|
169
|
+
begin
|
170
|
+
yield
|
171
|
+
rescue ActiveRecord::StatementInvalid => e
|
172
|
+
if e.message =~ /MySQL server has gone away/
|
173
|
+
if retried
|
174
|
+
raise e
|
175
|
+
else
|
176
|
+
@logger.info "StatementInvalid caught, trying to reconnect..."
|
177
|
+
clazz.connection.reconnect!
|
178
|
+
retried = true
|
179
|
+
retry
|
180
|
+
end
|
181
|
+
else
|
182
|
+
@logger.error "StatementInvalid caught, but unsure what to do with it: #{e}"
|
183
|
+
raise e
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
116
188
|
def new_index_for(clazz, models)
|
117
189
|
aaf_configuration = clazz.aaf_configuration
|
118
190
|
ferret_cfg = aaf_configuration[:ferret].dup
|
@@ -120,7 +192,7 @@ module ActsAsFerret
|
|
120
192
|
:create => true,
|
121
193
|
:field_infos => ActsAsFerret::field_infos(models),
|
122
194
|
:path => File.join(aaf_configuration[:index_base_dir], 'rebuild')
|
123
|
-
returning Ferret::Index::Index.new
|
195
|
+
returning Ferret::Index::Index.new(ferret_cfg) do |i|
|
124
196
|
i.batch_size = aaf_configuration[:reindex_batch_size]
|
125
197
|
i.logger = @logger
|
126
198
|
end
|
data/lib/instance_methods.rb
CHANGED
@@ -31,20 +31,36 @@ module ActsAsFerret #:nodoc:
|
|
31
31
|
self.class.aaf_index.highlight(id, self.class.name, query, options)
|
32
32
|
end
|
33
33
|
|
34
|
-
# re-eneable ferret indexing after a call to #disable_ferret
|
35
|
-
def
|
34
|
+
# re-eneable ferret indexing for this instance after a call to #disable_ferret
|
35
|
+
def enable_ferret
|
36
|
+
@ferret_disabled = nil
|
37
|
+
end
|
38
|
+
alias ferret_enable enable_ferret # compatibility
|
36
39
|
|
37
|
-
# returns true if ferret indexing is enabled
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
|
40
|
+
# returns true if ferret indexing is enabled for this record.
|
41
|
+
#
|
42
|
+
# The optional is_bulk_index parameter will be true if the method is called
|
43
|
+
# by rebuild_index or bulk_index, and false otherwise.
|
44
|
+
#
|
45
|
+
# If is_bulk_index is true, the class level ferret_enabled state will be
|
46
|
+
# ignored by this method (per-instance ferret_enabled checks however will
|
47
|
+
# take place, so if you override this method to forbid indexing of certain
|
48
|
+
# records you're still safe).
|
49
|
+
def ferret_enabled?(is_bulk_index = false)
|
50
|
+
@ferret_disabled.nil? && (is_bulk_index || self.class.ferret_enabled?)
|
51
|
+
end
|
42
52
|
|
43
|
-
# Disable Ferret for a specified amount of time. ::once will
|
44
|
-
# Ferret for the next call to #save (this is the default), ::always
|
45
|
-
# do so for all subsequent calls.
|
46
|
-
#
|
47
|
-
#
|
53
|
+
# Disable Ferret for this record for a specified amount of time. ::once will
|
54
|
+
# disable Ferret for the next call to #save (this is the default), ::always
|
55
|
+
# will do so for all subsequent calls.
|
56
|
+
#
|
57
|
+
# Note that this will turn off only the create and update hooks, but not the
|
58
|
+
# destroy hook. I think that's reasonable, if you think the opposite, please
|
59
|
+
# tell me.
|
60
|
+
#
|
61
|
+
# To manually trigger reindexing of a record after you're finished modifying
|
62
|
+
# it, you can call #ferret_update directly instead of #save (remember to
|
63
|
+
# enable ferret again before).
|
48
64
|
#
|
49
65
|
# When given a block, this will be executed without any ferret indexing of
|
50
66
|
# this object taking place. The optional argument in this case can be used
|
@@ -52,6 +68,7 @@ module ActsAsFerret #:nodoc:
|
|
52
68
|
# (::index_when_finished). Automatic Ferret indexing of this object will be
|
53
69
|
# turned on after the block has been executed. If passed ::index_when_true,
|
54
70
|
# the index will only be updated if the block evaluated not to false or nil.
|
71
|
+
#
|
55
72
|
def disable_ferret(option = :once)
|
56
73
|
if block_given?
|
57
74
|
@ferret_disabled = :always
|
@@ -94,7 +111,7 @@ module ActsAsFerret #:nodoc:
|
|
94
111
|
# fieldname => value pairs)
|
95
112
|
def to_doc
|
96
113
|
logger.debug "creating doc for class: #{self.class.name}, id: #{self.id}"
|
97
|
-
returning
|
114
|
+
returning Ferret::Document.new do |doc|
|
98
115
|
# store the id of each item
|
99
116
|
doc[:id] = self.id
|
100
117
|
|
@@ -105,6 +122,14 @@ module ActsAsFerret #:nodoc:
|
|
105
122
|
aaf_configuration[:ferret_fields].each_pair do |field, config|
|
106
123
|
doc[field] = self.send("#{field}_to_ferret") unless config[:ignore]
|
107
124
|
end
|
125
|
+
if aaf_configuration[:boost]
|
126
|
+
if self.respond_to?(aaf_configuration[:boost])
|
127
|
+
boost = self.send aaf_configuration[:boost]
|
128
|
+
doc.boost = boost.to_i if boost
|
129
|
+
else
|
130
|
+
logger.error "boost option should point to an instance method: #{aaf_configuration[:boost]}"
|
131
|
+
end
|
132
|
+
end
|
108
133
|
end
|
109
134
|
end
|
110
135
|
|
@@ -116,8 +141,13 @@ module ActsAsFerret #:nodoc:
|
|
116
141
|
self.class.aaf_index.query_for_record(id, self.class.name)
|
117
142
|
end
|
118
143
|
|
119
|
-
def content_for_field_name(field)
|
120
|
-
self[field] || self.instance_variable_get("@#{field.to_s}".to_sym) || self.send(field.to_sym)
|
144
|
+
def content_for_field_name(field, dynamic_boost = nil)
|
145
|
+
field_data = self[field] || self.instance_variable_get("@#{field.to_s}".to_sym) || self.send(field.to_sym)
|
146
|
+
if (dynamic_boost && boost_value = self.send(dynamic_boost))
|
147
|
+
field_data = Ferret::Field.new(field_data)
|
148
|
+
field_data.boost = boost_value.to_i
|
149
|
+
end
|
150
|
+
field_data
|
121
151
|
end
|
122
152
|
|
123
153
|
|
data/lib/local_index.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
module ActsAsFerret
|
2
|
-
|
3
2
|
class LocalIndex < AbstractIndex
|
4
3
|
include MoreLikeThis::IndexMethods
|
5
4
|
|
6
|
-
|
7
5
|
def initialize(aaf_configuration)
|
8
6
|
super
|
9
7
|
ensure_index_exists
|
@@ -62,6 +60,10 @@ module ActsAsFerret
|
|
62
60
|
index.index_models models
|
63
61
|
end
|
64
62
|
|
63
|
+
def bulk_index(ids, options)
|
64
|
+
ferret_index.bulk_index(aaf_configuration[:class_name].constantize, ids, options)
|
65
|
+
end
|
66
|
+
|
65
67
|
# Parses the given query string into a Ferret Query object.
|
66
68
|
def process_query(query)
|
67
69
|
# work around ferret bug in #process_query (doesn't ensure the
|
@@ -74,9 +76,9 @@ module ActsAsFerret
|
|
74
76
|
|
75
77
|
# Total number of hits for the given query.
|
76
78
|
# To count the results of a multi_search query, specify an array of
|
77
|
-
# class names with the :
|
79
|
+
# class names with the :multi option.
|
78
80
|
def total_hits(query, options = {})
|
79
|
-
index = (models = options.delete(:
|
81
|
+
index = (models = options.delete(:multi)) ? multi_index(models) : ferret_index
|
80
82
|
index.search(query, options).total_hits
|
81
83
|
end
|
82
84
|
|
@@ -96,7 +98,7 @@ module ActsAsFerret
|
|
96
98
|
def find_id_by_contents(query, options = {})
|
97
99
|
result = []
|
98
100
|
index = ferret_index
|
99
|
-
logger.debug "query: #{ferret_index.process_query query}"
|
101
|
+
logger.debug "query: #{ferret_index.process_query query}" if logger.debug?
|
100
102
|
lazy_fields = determine_lazy_fields options
|
101
103
|
|
102
104
|
total_hits = index.search_each(query, options) do |hit, score|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ActsAsFerret
|
2
|
+
|
3
|
+
# decorator that adds a total_hits accessor and will_paginate compatible
|
4
|
+
# paging support to search result arrays
|
5
|
+
class SearchResults
|
6
|
+
attr_reader :current_page, :per_page, :total_hits
|
7
|
+
|
8
|
+
def initialize(results, total_hits, current_page = 1, per_page = nil)
|
9
|
+
@results = results
|
10
|
+
@total_hits = total_hits
|
11
|
+
@current_page = current_page
|
12
|
+
@per_page = (per_page || total_hits)
|
13
|
+
@total_pages = @per_page > 0 ? (@total_hits / @per_page.to_f).ceil : 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(symbol, *args, &block)
|
17
|
+
@results.send(symbol, *args, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to?(name)
|
21
|
+
self.methods.include?(name) || @results.respond_to?(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# code from here on was directly taken from will_paginate's collection.rb
|
26
|
+
|
27
|
+
#
|
28
|
+
# The total number of pages.
|
29
|
+
def page_count
|
30
|
+
@total_pages
|
31
|
+
end
|
32
|
+
|
33
|
+
# Current offset of the paginated collection. If we're on the first page,
|
34
|
+
# it is always 0. If we're on the 2nd page and there are 30 entries per page,
|
35
|
+
# the offset is 30. This property is useful if you want to render ordinals
|
36
|
+
# besides your records: simply start with offset + 1.
|
37
|
+
#
|
38
|
+
def offset
|
39
|
+
(current_page - 1) * per_page
|
40
|
+
end
|
41
|
+
|
42
|
+
# current_page - 1 or nil if there is no previous page
|
43
|
+
def previous_page
|
44
|
+
current_page > 1 ? (current_page - 1) : nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# current_page + 1 or nil if there is no next page
|
48
|
+
def next_page
|
49
|
+
current_page < page_count ? (current_page + 1) : nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
################################################################################
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
################################################################################
|
5
|
+
$ferret_server_options = {
|
6
|
+
'environment' => nil,
|
7
|
+
'debug' => nil,
|
8
|
+
}
|
9
|
+
|
10
|
+
################################################################################
|
11
|
+
OptionParser.new do |optparser|
|
12
|
+
optparser.banner = "Usage: #{File.basename($0)} [options] {start|stop}"
|
13
|
+
|
14
|
+
optparser.on('-h', '--help', "This message") do
|
15
|
+
puts optparser
|
16
|
+
exit
|
17
|
+
end
|
18
|
+
|
19
|
+
optparser.on('-e', '--environment=NAME', 'Set RAILS_ENV to the given string') do |e|
|
20
|
+
$ferret_server_options['environment'] = e
|
21
|
+
end
|
22
|
+
|
23
|
+
optparser.on('--debug', 'Include full stack traces on exceptions') do
|
24
|
+
$ferret_server_options['debug'] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
$ferret_server_action = optparser.permute!(ARGV)
|
28
|
+
(puts optparser; exit(1)) unless $ferret_server_action.size == 1
|
29
|
+
|
30
|
+
$ferret_server_action = $ferret_server_action.first
|
31
|
+
(puts optparser; exit(1)) unless %w(start stop).include?($ferret_server_action)
|
32
|
+
end
|
33
|
+
|
34
|
+
################################################################################
|
35
|
+
begin
|
36
|
+
ENV['FERRET_USE_LOCAL_INDEX'] = 'true'
|
37
|
+
ENV['RAILS_ENV'] = $ferret_server_options['environment']
|
38
|
+
require(File.join(File.dirname(__FILE__), '../../../../config/environment'))
|
39
|
+
ActsAsFerret::Remote::Server.new.send($ferret_server_action)
|
40
|
+
rescue Exception => e
|
41
|
+
$stderr.puts(e.message)
|
42
|
+
$stderr.puts(e.backtrace.join("\n")) if $ferret_server_options['debug']
|
43
|
+
exit(1)
|
44
|
+
end
|
@@ -14,7 +14,7 @@ module ActsAsFerret
|
|
14
14
|
|
15
15
|
if original_query.is_a? String
|
16
16
|
model_query = options[:models].map(&:name).join '|'
|
17
|
-
q
|
17
|
+
q += %{ +class_name:"#{model_query}"}
|
18
18
|
else
|
19
19
|
q = Ferret::Search::BooleanQuery.new
|
20
20
|
q.add_query(original_query, :must)
|
data/lib/unix_daemon.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
################################################################################
|
2
|
+
module ActsAsFerret
|
3
|
+
module Remote
|
4
|
+
|
5
|
+
################################################################################
|
6
|
+
# methods for becoming a daemon on Unix-like operating systems
|
7
|
+
module UnixDaemon
|
8
|
+
|
9
|
+
################################################################################
|
10
|
+
def platform_daemon (&block)
|
11
|
+
safefork do
|
12
|
+
write_pid_file
|
13
|
+
trap("TERM") { exit(0) }
|
14
|
+
sess_id = Process.setsid
|
15
|
+
STDIN.reopen("/dev/null")
|
16
|
+
STDOUT.reopen("#{RAILS_ROOT}/log/ferret_server.out", "a")
|
17
|
+
STDERR.reopen(STDOUT)
|
18
|
+
block.call
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
################################################################################
|
23
|
+
# stop the daemon, nicely at first, and then forcefully if necessary
|
24
|
+
def stop
|
25
|
+
pid = read_pid_file
|
26
|
+
raise "ferret_server doesn't appear to be running" unless pid
|
27
|
+
$stdout.puts("stopping ferret server...")
|
28
|
+
Process.kill("TERM", pid)
|
29
|
+
30.times { Process.kill(0, pid); sleep(0.5) }
|
30
|
+
$stdout.puts("using kill -9 #{pid}")
|
31
|
+
Process.kill(9, pid)
|
32
|
+
rescue Errno::ESRCH => e
|
33
|
+
$stdout.puts("process #{pid} has stopped")
|
34
|
+
ensure
|
35
|
+
File.unlink(@cfg.pid_file) if File.exist?(@cfg.pid_file)
|
36
|
+
end
|
37
|
+
|
38
|
+
################################################################################
|
39
|
+
def safefork (&block)
|
40
|
+
@fork_tries ||= 0
|
41
|
+
fork(&block)
|
42
|
+
rescue Errno::EWOULDBLOCK
|
43
|
+
raise if @fork_tries >= 20
|
44
|
+
@fork_tries += 1
|
45
|
+
sleep 5
|
46
|
+
retry
|
47
|
+
end
|
48
|
+
|
49
|
+
#################################################################################
|
50
|
+
# create the PID file and install an at_exit handler
|
51
|
+
def write_pid_file
|
52
|
+
open(@cfg.pid_file, "w") {|f| f << Process.pid << "\n"}
|
53
|
+
at_exit { File.unlink(@cfg.pid_file) if read_pid_file == Process.pid }
|
54
|
+
end
|
55
|
+
|
56
|
+
#################################################################################
|
57
|
+
def read_pid_file
|
58
|
+
File.read(@cfg.pid_file).to_i if File.exist?(@cfg.pid_file)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/rakefile
CHANGED
@@ -61,6 +61,9 @@ if PKG_VERSION
|
|
61
61
|
#s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG)
|
62
62
|
# s.files.delete ...
|
63
63
|
s.require_path = 'lib'
|
64
|
+
s.bindir = "bin"
|
65
|
+
s.executables = ["aaf_install"]
|
66
|
+
s.default_executable = "aaf_install"
|
64
67
|
s.autorequire = 'acts_as_ferret'
|
65
68
|
s.has_rdoc = true
|
66
69
|
# s.test_files = Dir['test/**/*_test.rb']
|
@@ -92,8 +95,8 @@ if PKG_VERSION
|
|
92
95
|
fail "SVK status is not clean ... do you have unchecked-in files?"
|
93
96
|
end
|
94
97
|
announce "No outstanding checkins found ... OK"
|
95
|
-
announce "Pushing to svn..."
|
96
|
-
`svk push .`
|
98
|
+
# announce "Pushing to svn..."
|
99
|
+
# `svk push .`
|
97
100
|
end
|
98
101
|
end
|
99
102
|
|