acts_as_ferret 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +32 -6
- data/acts_as_ferret.gemspec +260 -0
- data/config/ferret_server.yml +1 -0
- data/doc/demo/README +154 -0
- data/doc/demo/README_DEMO +23 -0
- data/doc/demo/Rakefile +10 -0
- data/doc/demo/app/controllers/admin/backend_controller.rb +14 -0
- data/doc/demo/app/controllers/admin_area_controller.rb +4 -0
- data/doc/demo/app/controllers/application.rb +5 -0
- data/doc/demo/app/controllers/contents_controller.rb +49 -0
- data/doc/demo/app/controllers/searches_controller.rb +8 -0
- data/doc/demo/app/helpers/admin/backend_helper.rb +2 -0
- data/doc/demo/app/helpers/application_helper.rb +3 -0
- data/doc/demo/app/helpers/content_helper.rb +2 -0
- data/doc/demo/app/helpers/search_helper.rb +2 -0
- data/doc/demo/app/models/comment.rb +48 -0
- data/doc/demo/app/models/content.rb +12 -0
- data/doc/demo/app/models/content_base.rb +28 -0
- data/doc/demo/app/models/search.rb +19 -0
- data/doc/demo/app/models/shared_index1.rb +3 -0
- data/doc/demo/app/models/shared_index2.rb +3 -0
- data/doc/demo/app/models/special_content.rb +3 -0
- data/doc/demo/app/models/stats.rb +20 -0
- data/doc/demo/app/views/admin/backend/search.rhtml +18 -0
- data/doc/demo/app/views/contents/_form.rhtml +10 -0
- data/doc/demo/app/views/contents/edit.rhtml +9 -0
- data/doc/demo/app/views/contents/index.rhtml +24 -0
- data/doc/demo/app/views/contents/new.rhtml +8 -0
- data/doc/demo/app/views/contents/show.rhtml +8 -0
- data/doc/demo/app/views/layouts/application.html.erb +17 -0
- data/doc/demo/app/views/searches/_content.html.erb +2 -0
- data/doc/demo/app/views/searches/search.html.erb +20 -0
- data/doc/demo/config/boot.rb +109 -0
- data/doc/demo/config/database.yml +38 -0
- data/doc/demo/config/environment.rb +69 -0
- data/doc/demo/config/environments/development.rb +16 -0
- data/doc/demo/config/environments/production.rb +19 -0
- data/doc/demo/config/environments/test.rb +21 -0
- data/doc/demo/config/ferret_server.yml +18 -0
- data/doc/demo/config/lighttpd.conf +40 -0
- data/doc/demo/config/routes.rb +9 -0
- data/doc/demo/db/development_structure.sql +15 -0
- data/doc/demo/db/migrate/001_initial_migration.rb +18 -0
- data/doc/demo/db/migrate/002_add_type_to_contents.rb +9 -0
- data/doc/demo/db/migrate/003_create_shared_index1s.rb +11 -0
- data/doc/demo/db/migrate/004_create_shared_index2s.rb +11 -0
- data/doc/demo/db/migrate/005_special_field.rb +9 -0
- data/doc/demo/db/migrate/006_create_stats.rb +15 -0
- data/doc/demo/db/schema.sql +18 -0
- data/doc/demo/doc/README_FOR_APP +2 -0
- data/doc/demo/doc/howto.txt +70 -0
- data/doc/demo/public/.htaccess +40 -0
- data/doc/demo/public/404.html +8 -0
- data/doc/demo/public/500.html +8 -0
- data/doc/demo/public/dispatch.cgi +10 -0
- data/doc/demo/public/dispatch.fcgi +24 -0
- data/doc/demo/public/dispatch.rb +10 -0
- data/doc/demo/public/favicon.ico +0 -0
- data/doc/demo/public/images/rails.png +0 -0
- data/doc/demo/public/index.html +277 -0
- data/doc/demo/public/robots.txt +1 -0
- data/doc/demo/public/stylesheets/scaffold.css +74 -0
- data/doc/demo/script/about +3 -0
- data/doc/demo/script/breakpointer +3 -0
- data/doc/demo/script/console +3 -0
- data/doc/demo/script/destroy +3 -0
- data/doc/demo/script/ferret_server +10 -0
- data/doc/demo/script/generate +3 -0
- data/doc/demo/script/performance/benchmarker +3 -0
- data/doc/demo/script/performance/profiler +3 -0
- data/doc/demo/script/plugin +3 -0
- data/doc/demo/script/process/inspector +3 -0
- data/doc/demo/script/process/reaper +3 -0
- data/doc/demo/script/process/spawner +3 -0
- data/doc/demo/script/process/spinner +3 -0
- data/doc/demo/script/runner +3 -0
- data/doc/demo/script/server +3 -0
- data/doc/demo/test/fixtures/comments.yml +12 -0
- data/doc/demo/test/fixtures/contents.yml +13 -0
- data/doc/demo/test/fixtures/remote_contents.yml +9 -0
- data/doc/demo/test/fixtures/shared_index1s.yml +7 -0
- data/doc/demo/test/fixtures/shared_index2s.yml +7 -0
- data/doc/demo/test/functional/admin/backend_controller_test.rb +35 -0
- data/doc/demo/test/functional/contents_controller_test.rb +81 -0
- data/doc/demo/test/functional/searches_controller_test.rb +71 -0
- data/doc/demo/test/smoke/drb_smoke_test.rb +321 -0
- data/doc/demo/test/smoke/process_stats.rb +21 -0
- data/doc/demo/test/test_helper.rb +30 -0
- data/doc/demo/test/unit/comment_test.rb +217 -0
- data/doc/demo/test/unit/content_test.rb +705 -0
- data/doc/demo/test/unit/ferret_result_test.rb +24 -0
- data/doc/demo/test/unit/multi_index_test.rb +329 -0
- data/doc/demo/test/unit/remote_index_test.rb +23 -0
- data/doc/demo/test/unit/shared_index1_test.rb +108 -0
- data/doc/demo/test/unit/shared_index2_test.rb +13 -0
- data/doc/demo/test/unit/sort_test.rb +21 -0
- data/doc/demo/test/unit/special_content_test.rb +25 -0
- data/doc/demo/vendor/plugins/will_paginate/LICENSE +18 -0
- data/doc/demo/vendor/plugins/will_paginate/README +108 -0
- data/doc/demo/vendor/plugins/will_paginate/Rakefile +23 -0
- data/doc/demo/vendor/plugins/will_paginate/init.rb +21 -0
- data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/collection.rb +45 -0
- data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb +44 -0
- data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/finder.rb +159 -0
- data/doc/demo/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb +95 -0
- data/doc/demo/vendor/plugins/will_paginate/test/array_pagination_test.rb +23 -0
- data/doc/demo/vendor/plugins/will_paginate/test/boot.rb +27 -0
- data/doc/demo/vendor/plugins/will_paginate/test/console +10 -0
- data/doc/demo/vendor/plugins/will_paginate/test/finder_test.rb +219 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/admin.rb +3 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/companies.yml +24 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/company.rb +23 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/developer.rb +11 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/developers_projects.yml +13 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/project.rb +4 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/projects.yml +7 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/replies.yml +20 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/reply.rb +5 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/schema.sql +44 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/topic.rb +19 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/topics.yml +30 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/user.rb +2 -0
- data/doc/demo/vendor/plugins/will_paginate/test/fixtures/users.yml +35 -0
- data/doc/demo/vendor/plugins/will_paginate/test/helper.rb +42 -0
- data/doc/demo/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb +64 -0
- data/doc/demo/vendor/plugins/will_paginate/test/lib/load_fixtures.rb +10 -0
- data/doc/demo/vendor/plugins/will_paginate/test/pagination_test.rb +136 -0
- data/init.rb +2 -0
- data/lib/act_methods.rb +50 -157
- data/lib/acts_as_ferret.rb +457 -23
- data/lib/ar_mysql_auto_reconnect_patch.rb +41 -0
- data/lib/blank_slate.rb +53 -0
- data/lib/bulk_indexer.rb +4 -1
- data/lib/class_methods.rb +106 -295
- data/lib/ferret_extensions.rb +78 -5
- data/lib/ferret_find_methods.rb +141 -0
- data/lib/ferret_result.rb +26 -9
- data/lib/ferret_server.rb +100 -65
- data/lib/index.rb +83 -15
- data/lib/instance_methods.rb +32 -17
- data/lib/local_index.rb +106 -112
- data/lib/more_like_this.rb +13 -13
- data/lib/multi_index.rb +115 -72
- data/lib/rdig_adapter.rb +148 -0
- data/lib/remote_functions.rb +43 -0
- data/lib/remote_index.rb +25 -21
- data/lib/remote_multi_index.rb +20 -0
- data/lib/search_results.rb +7 -10
- data/lib/server_manager.rb +16 -4
- data/lib/unix_daemon.rb +23 -0
- data/lib/without_ar.rb +52 -0
- data/rakefile +23 -16
- data/recipes/aaf_recipes.rb +114 -0
- data/tasks/ferret.rake +22 -0
- metadata +233 -54
- data/lib/ferret_cap_tasks.rb +0 -21
- data/lib/shared_index.rb +0 -14
- data/lib/shared_index_class_methods.rb +0 -90
@@ -0,0 +1,41 @@
|
|
1
|
+
# Source: http://pastie.caboo.se/154842
|
2
|
+
#
|
3
|
+
# in /etc/my.cnf on the MySQL server, you can set the interactive-timeout parameter,
|
4
|
+
# for example, 12 hours = 28800 sec
|
5
|
+
# interactive-timeout=28800
|
6
|
+
|
7
|
+
# in ActiveRecord, setting the verification_timeout to something less than
|
8
|
+
# the interactive-timeout parameter; 14400 sec = 6 hours
|
9
|
+
ActiveRecord::Base.verification_timeout = 14400
|
10
|
+
ActiveRecord::Base.establish_connection
|
11
|
+
|
12
|
+
# Below is a monkey patch for keeping ActiveRecord connections alive.
|
13
|
+
# http://www.sparecycles.org/2007/7/2/saying-goodbye-to-lost-connections-in-rails
|
14
|
+
|
15
|
+
module ActiveRecord
|
16
|
+
module ConnectionAdapters
|
17
|
+
class MysqlAdapter
|
18
|
+
def execute(sql, name = nil) #:nodoc:
|
19
|
+
reconnect_lost_connections = true
|
20
|
+
begin
|
21
|
+
log(sql, name) { @connection.query(sql) }
|
22
|
+
rescue ActiveRecord::StatementInvalid => exception
|
23
|
+
if reconnect_lost_connections and exception.message =~ /(Lost connection to MySQL server during query
|
24
|
+
|MySQL server has gone away)/
|
25
|
+
reconnect_lost_connections = false
|
26
|
+
reconnect!
|
27
|
+
retry
|
28
|
+
elsif exception.message.split(":").first =~ /Packets out of order/
|
29
|
+
raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database.
|
30
|
+
Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hash
|
31
|
+
ing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql
|
32
|
+
bindings."
|
33
|
+
else
|
34
|
+
raise
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/lib/blank_slate.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
if defined?(BlankSlate)
|
2
|
+
# Rails 2.x has it already
|
3
|
+
module ActsAsFerret
|
4
|
+
class BlankSlate < ::BlankSlate
|
5
|
+
end
|
6
|
+
end
|
7
|
+
else
|
8
|
+
module ActsAsFerret
|
9
|
+
# 'backported' for Rails pre 2.0
|
10
|
+
#
|
11
|
+
#--
|
12
|
+
# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
|
13
|
+
# All rights reserved.
|
14
|
+
|
15
|
+
# Permission is granted for use, copying, modification, distribution,
|
16
|
+
# and distribution of modified versions of this work as long as the
|
17
|
+
# above copyright notice is included.
|
18
|
+
#++
|
19
|
+
|
20
|
+
######################################################################
|
21
|
+
# BlankSlate provides an abstract base class with no predefined
|
22
|
+
# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
|
23
|
+
# BlankSlate is useful as a base class when writing classes that
|
24
|
+
# depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
|
25
|
+
#
|
26
|
+
class BlankSlate
|
27
|
+
class << self
|
28
|
+
# Hide the method named +name+ in the BlankSlate class. Don't
|
29
|
+
# hide +instance_eval+ or any method beginning with "__".
|
30
|
+
def hide(name)
|
31
|
+
if instance_methods.include?(name.to_s) and name !~ /^(__|instance_eval|methods)/
|
32
|
+
@hidden_methods ||= {}
|
33
|
+
@hidden_methods[name.to_sym] = instance_method(name)
|
34
|
+
undef_method name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Redefine a previously hidden method so that it may be called on a blank
|
39
|
+
# slate object.
|
40
|
+
#
|
41
|
+
# no-op here since we don't hide the methods we reveal where this is
|
42
|
+
# used in this implementation
|
43
|
+
def reveal(name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
instance_methods.each { |m| hide(m) }
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
data/lib/bulk_indexer.rb
CHANGED
@@ -16,7 +16,10 @@ module ActsAsFerret
|
|
16
16
|
|
17
17
|
def index_records(records, offset)
|
18
18
|
batch_time = measure_time {
|
19
|
-
|
19
|
+
docs = []
|
20
|
+
records.each { |rec| docs << [rec.to_doc, rec.ferret_analyzer] if rec.ferret_enabled?(true) }
|
21
|
+
@index.update_batch(docs)
|
22
|
+
# records.each { |rec| @index.add_document(rec.to_doc, rec.ferret_analyzer) if rec.ferret_enabled?(true) }
|
20
23
|
}.to_f
|
21
24
|
@work_done = offset.to_f / @model_count * 100.0 if @model_count > 0
|
22
25
|
remaining_time = ( batch_time / @batch_size ) * ( @model_count - offset + @batch_size )
|
data/lib/class_methods.rb
CHANGED
@@ -20,18 +20,12 @@ module ActsAsFerret
|
|
20
20
|
aaf_configuration[:enabled]
|
21
21
|
end
|
22
22
|
|
23
|
-
# rebuild the index from all data stored for this model
|
23
|
+
# rebuild the index from all data stored for this model, and any other
|
24
|
+
# model classes associated with the same index.
|
24
25
|
# This is called automatically when no index exists yet.
|
25
26
|
#
|
26
|
-
|
27
|
-
|
28
|
-
# Useful when using the :single_index option.
|
29
|
-
# Note that attributes named the same in different models will share
|
30
|
-
# the same field options in the shared index.
|
31
|
-
def rebuild_index(*models)
|
32
|
-
models << self unless models.include?(self)
|
33
|
-
aaf_index.rebuild_index models.map(&:to_s)
|
34
|
-
index_dir = find_last_index_version(aaf_configuration[:index_base_dir]) unless aaf_configuration[:remote]
|
27
|
+
def rebuild_index
|
28
|
+
aaf_index.rebuild_index
|
35
29
|
end
|
36
30
|
|
37
31
|
# re-index a number records specified by the given ids. Use for large
|
@@ -43,7 +37,7 @@ module ActsAsFerret
|
|
43
37
|
def bulk_index(*ids)
|
44
38
|
options = Hash === ids.last ? ids.pop : {}
|
45
39
|
ids = ids.first if ids.size == 1 && ids.first.is_a?(Enumerable)
|
46
|
-
aaf_index.bulk_index(ids, options)
|
40
|
+
aaf_index.bulk_index(self.name, ids, options)
|
47
41
|
end
|
48
42
|
|
49
43
|
# true if our db and table appear to be suitable for the mysql fast batch
|
@@ -56,6 +50,25 @@ module ActsAsFerret
|
|
56
50
|
end
|
57
51
|
end
|
58
52
|
|
53
|
+
# Returns all records modified or created after the specified time.
|
54
|
+
# Used by the rake rebuild task to find models that need to be updated in
|
55
|
+
# the index after the rebuild finished because they changed while the
|
56
|
+
# rebuild was running.
|
57
|
+
# Override if your models don't stick to the created_at/updated_at
|
58
|
+
# convention.
|
59
|
+
def records_modified_since(time)
|
60
|
+
condition = []
|
61
|
+
%w(updated_at created_at).each do |col|
|
62
|
+
condition << "#{col} >= ?" if column_names.include? col
|
63
|
+
end
|
64
|
+
if condition.empty?
|
65
|
+
logger.warn "#{self.name}: Override records_modified_since(time) to keep the index up to date with records changed during rebuild."
|
66
|
+
[]
|
67
|
+
else
|
68
|
+
find :all, :conditions => [ condition.join(' AND '), *([time]*condition.size) ]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
59
72
|
# runs across all records yielding those to be indexed when the index is rebuilt
|
60
73
|
def records_for_rebuild(batch_size = 1000)
|
61
74
|
transaction do
|
@@ -66,8 +79,7 @@ module ActsAsFerret
|
|
66
79
|
yield rows, offset
|
67
80
|
end
|
68
81
|
else
|
69
|
-
|
70
|
-
order = "#{primary_key} ASC" if connection.class.name =~ /SQLServer/
|
82
|
+
order = "#{primary_key} ASC" # fixes #212
|
71
83
|
0.step(self.count, batch_size) do |offset|
|
72
84
|
yield find( :all, :limit => batch_size, :offset => offset, :order => order ), offset
|
73
85
|
end
|
@@ -80,9 +92,7 @@ module ActsAsFerret
|
|
80
92
|
transaction do
|
81
93
|
offset = 0
|
82
94
|
ids.each_slice(batch_size) do |id_slice|
|
83
|
-
logger.debug "########## slice: #{id_slice.join(',')}"
|
84
95
|
records = find( :all, :conditions => ["id in (?)", id_slice] )
|
85
|
-
logger.debug "########## slice records: #{records.inspect}"
|
86
96
|
#yield records, offset
|
87
97
|
yield find( :all, :conditions => ["id in (?)", id_slice] ), offset
|
88
98
|
offset += batch_size
|
@@ -90,23 +100,11 @@ module ActsAsFerret
|
|
90
100
|
end
|
91
101
|
end
|
92
102
|
|
93
|
-
# Switches this class to a new index located in dir.
|
94
|
-
# Used by the DRb server when switching to a new index version.
|
95
|
-
def index_dir=(dir)
|
96
|
-
logger.debug "changing index dir to #{dir}"
|
97
|
-
aaf_configuration[:index_dir] = aaf_configuration[:ferret][:path] = dir
|
98
|
-
aaf_index.reopen!
|
99
|
-
logger.debug "index dir is now #{dir}"
|
100
|
-
end
|
101
|
-
|
102
103
|
# Retrieve the index instance for this model class. This can either be a
|
103
104
|
# LocalIndex, or a RemoteIndex instance.
|
104
105
|
#
|
105
|
-
# Index instances are stored in a hash, using the index directory
|
106
|
-
# as the key. So model classes sharing a single index will share their
|
107
|
-
# Index object, too.
|
108
106
|
def aaf_index
|
109
|
-
ActsAsFerret::
|
107
|
+
@index ||= ActsAsFerret::get_index(aaf_configuration[:name])
|
110
108
|
end
|
111
109
|
|
112
110
|
# Finds instances by searching the Ferret index. Terms are ANDed by default, use
|
@@ -131,12 +129,6 @@ module ActsAsFerret
|
|
131
129
|
# stored fields. Note that if you have a shared index, you have
|
132
130
|
# to explicitly state the fields you want to fetch, true won't
|
133
131
|
# work here)
|
134
|
-
# models:: only for single_index scenarios: an Array of other Model classes to
|
135
|
-
# include in this search. Use :all to query all models.
|
136
|
-
# multi:: Specify additional model classes to search through. Each of
|
137
|
-
# these, as well as this class, has to have the
|
138
|
-
# :store_class_name option set to true. This option replaces the
|
139
|
-
# multi_search method.
|
140
132
|
#
|
141
133
|
# +find_options+ is a hash passed on to active_record's find when
|
142
134
|
# retrieving the data from db, useful to i.e. prefetch relationships with
|
@@ -153,61 +145,23 @@ module ActsAsFerret
|
|
153
145
|
# +page+ and +per_page+ are supposed to work regardless of any
|
154
146
|
# +conitions+ present in +find_options+.
|
155
147
|
def find_with_ferret(q, options = {}, find_options = {})
|
156
|
-
if
|
157
|
-
|
158
|
-
|
159
|
-
offset = (options[:page] - 1) * limit
|
160
|
-
if find_options[:conditions] && !options[:multi]
|
161
|
-
find_options[:limit] = limit
|
162
|
-
find_options[:offset] = offset
|
163
|
-
options[:limit] = :all
|
164
|
-
options.delete :offset
|
165
|
-
else
|
166
|
-
# do pagination with ferret (or after everything is done in the case
|
167
|
-
# of multi_search)
|
168
|
-
options[:limit] = limit
|
169
|
-
options[:offset] = offset
|
170
|
-
end
|
171
|
-
elsif find_options[:conditions]
|
172
|
-
if options[:multi]
|
173
|
-
# multisearch ignores find_options limit and offset
|
174
|
-
options[:limit] ||= find_options.delete(:limit)
|
175
|
-
options[:offset] ||= find_options.delete(:offset)
|
148
|
+
if respond_to?(:scope) && scope(:find, :conditions)
|
149
|
+
if find_options[:conditions]
|
150
|
+
find_options[:conditions] = "(#{find_options[:conditions]}) AND (#{scope(:find, :conditions)})"
|
176
151
|
else
|
177
|
-
|
178
|
-
find_options[:limit] ||= options.delete(:limit)
|
179
|
-
find_options[:offset] ||= options.delete(:offset)
|
180
|
-
options[:limit] = :all
|
152
|
+
find_options[:conditions] = scope(:find, :conditions)
|
181
153
|
end
|
182
154
|
end
|
183
|
-
|
184
|
-
total_hits, result = if options[:multi].blank?
|
185
|
-
find_records_lazy_or_not q, options, find_options
|
186
|
-
else
|
187
|
-
_multi_search q, options.delete(:multi), options, find_options
|
188
|
-
end
|
189
|
-
logger.debug "Query: #{q}\ntotal hits: #{total_hits}, results delivered: #{result.size}"
|
190
|
-
SearchResults.new(result, total_hits, options[:page], options[:per_page])
|
155
|
+
return ActsAsFerret::find q, self, options, find_options
|
191
156
|
end
|
192
|
-
alias find_by_contents find_with_ferret
|
193
157
|
|
194
|
-
|
195
158
|
|
196
159
|
# Returns the total number of hits for the given query
|
197
|
-
# To count the results of a query across multiple models, specify an array of
|
198
|
-
# class names with the :multi option.
|
199
160
|
#
|
200
161
|
# Note that since we don't query the database here, this method won't deliver
|
201
162
|
# the expected results when used on an AR association.
|
163
|
+
#
|
202
164
|
def total_hits(q, options={})
|
203
|
-
if options[:models]
|
204
|
-
# backwards compatibility
|
205
|
-
logger.warn "the :models option of total_hits is deprecated, please use :multi instead"
|
206
|
-
options[:multi] = options[:models]
|
207
|
-
end
|
208
|
-
if models = options[:multi]
|
209
|
-
options[:multi] = add_self_to_model_list_if_necessary(models).map(&:to_s)
|
210
|
-
end
|
211
165
|
aaf_index.total_hits(q, options)
|
212
166
|
end
|
213
167
|
|
@@ -215,243 +169,100 @@ module ActsAsFerret
|
|
215
169
|
# Useful e.g. if you want to search across models or do not want to fetch
|
216
170
|
# all result records (yet).
|
217
171
|
#
|
218
|
-
# Options are the same as for
|
172
|
+
# Options are the same as for find_with_ferret
|
219
173
|
#
|
220
174
|
# A block can be given too, it will be executed with every result:
|
221
|
-
#
|
175
|
+
# find_ids_with_ferret(q, options) do |model, id, score|
|
222
176
|
# id_array << id
|
223
177
|
# scores_by_id[id] = score
|
224
178
|
# end
|
225
179
|
# NOTE: in case a block is given, only the total_hits value will be returned
|
226
180
|
# instead of the [total_hits, results] array!
|
227
181
|
#
|
228
|
-
def
|
229
|
-
|
230
|
-
aaf_index.find_id_by_contents(q, options, &block)
|
182
|
+
def find_ids_with_ferret(q, options = {}, &block)
|
183
|
+
aaf_index.find_ids(q, options, &block)
|
231
184
|
end
|
232
185
|
|
233
186
|
|
234
|
-
# returns an array of hashes, each containing :class_name,
|
235
|
-
# :id and :score for a hit.
|
236
|
-
#
|
237
|
-
# if a block is given, class_name, id and score of each hit will
|
238
|
-
# be yielded, and the total number of hits is returned.
|
239
|
-
def id_multi_search(query, additional_models = [], options = {}, &proc)
|
240
|
-
deprecated_options_support(options)
|
241
|
-
models = add_self_to_model_list_if_necessary(additional_models)
|
242
|
-
aaf_index.id_multi_search(query, models.map(&:to_s), options, &proc)
|
243
|
-
end
|
244
|
-
|
245
|
-
|
246
187
|
protected
|
247
188
|
|
248
|
-
def
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
#
|
297
|
-
|
298
|
-
end
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
# count total_hits via sql when using conditions or when we're called
|
303
|
-
# from an ActiveRecord association.
|
304
|
-
if find_options[:conditions] or caller.find{ |call| call =~ %r{active_record/associations} }
|
305
|
-
# chances are the ferret result count is not our total_hits value, so
|
306
|
-
# we correct this here.
|
307
|
-
if options[:limit] != :all || options[:page] || options[:offset] || find_options[:limit] || find_options[:offset]
|
308
|
-
# our ferret result has been limited, so we need to re-run that
|
309
|
-
# search to get the full result set from ferret.
|
310
|
-
result_ids = {}
|
311
|
-
find_id_by_contents(q, options.update(:limit => :all, :offset => 0)) do |model, id, score, data|
|
312
|
-
result_ids[id] = [ result_ids.size + 1, score ]
|
313
|
-
end
|
314
|
-
# Now ask the database for the total size of the final result set.
|
315
|
-
total_hits = count_records( { self.name => result_ids }, find_options )
|
316
|
-
else
|
317
|
-
# what we got from the database is our full result set, so take
|
318
|
-
# it's size
|
319
|
-
total_hits = result.length
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
[ total_hits, result ]
|
324
|
-
end
|
325
|
-
|
326
|
-
def lazy_find_by_contents(q, options = {})
|
327
|
-
result = []
|
328
|
-
total_hits = find_id_by_contents(q, options) do |model, id, score, data|
|
329
|
-
result << FerretResult.new(model, id, score, data)
|
330
|
-
end
|
331
|
-
[ total_hits, result ]
|
332
|
-
end
|
189
|
+
# def find_records_lazy_or_not(q, options = {}, find_options = {})
|
190
|
+
# if options[:lazy]
|
191
|
+
# logger.warn "find_options #{find_options} are ignored because :lazy => true" unless find_options.empty?
|
192
|
+
# lazy_find_by_contents q, options
|
193
|
+
# else
|
194
|
+
# ar_find_by_contents q, options, find_options
|
195
|
+
# end
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# def ar_find_by_contents(q, options = {}, find_options = {})
|
199
|
+
# result_ids = {}
|
200
|
+
# total_hits = find_ids_with_ferret(q, options) do |model, id, score, data|
|
201
|
+
# # stores ids, index and score of each hit for later ordering of
|
202
|
+
# # results
|
203
|
+
# result_ids[id] = [ result_ids.size + 1, score ]
|
204
|
+
# end
|
205
|
+
#
|
206
|
+
# result = ActsAsFerret::retrieve_records( { self.name => result_ids }, find_options )
|
207
|
+
#
|
208
|
+
# # count total_hits via sql when using conditions or when we're called
|
209
|
+
# # from an ActiveRecord association.
|
210
|
+
# if find_options[:conditions] or caller.find{ |call| call =~ %r{active_record/associations} }
|
211
|
+
# # chances are the ferret result count is not our total_hits value, so
|
212
|
+
# # we correct this here.
|
213
|
+
# if options[:limit] != :all || options[:page] || options[:offset] || find_options[:limit] || find_options[:offset]
|
214
|
+
# # our ferret result has been limited, so we need to re-run that
|
215
|
+
# # search to get the full result set from ferret.
|
216
|
+
# result_ids = {}
|
217
|
+
# find_ids_with_ferret(q, options.update(:limit => :all, :offset => 0)) do |model, id, score, data|
|
218
|
+
# result_ids[id] = [ result_ids.size + 1, score ]
|
219
|
+
# end
|
220
|
+
# # Now ask the database for the total size of the final result set.
|
221
|
+
# total_hits = count_records( { self.name => result_ids }, find_options )
|
222
|
+
# else
|
223
|
+
# # what we got from the database is our full result set, so take
|
224
|
+
# # it's size
|
225
|
+
# total_hits = result.length
|
226
|
+
# end
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# [ total_hits, result ]
|
230
|
+
# end
|
231
|
+
#
|
232
|
+
# def lazy_find_by_contents(q, options = {})
|
233
|
+
# logger.debug "lazy_find_by_contents: #{q}"
|
234
|
+
# result = []
|
235
|
+
# rank = 0
|
236
|
+
# total_hits = find_ids_with_ferret(q, options) do |model, id, score, data|
|
237
|
+
# logger.debug "model: #{model}, id: #{id}, data: #{data}"
|
238
|
+
# result << FerretResult.new(model, id, score, rank += 1, data)
|
239
|
+
# end
|
240
|
+
# [ total_hits, result ]
|
241
|
+
# end
|
333
242
|
|
334
243
|
|
335
244
|
def model_find(model, id, find_options = {})
|
336
245
|
model.constantize.find(id, find_options)
|
337
246
|
end
|
338
247
|
|
339
|
-
# retrieves search result records from a data structure like this:
|
340
|
-
# { 'Model1' => { '1' => [ rank, score ], '2' => [ rank, score ] }
|
341
|
-
#
|
342
|
-
# TODO: in case of STI AR will filter out hits from other
|
343
|
-
# classes for us, but this
|
344
|
-
# will lead to less results retrieved --> scoping of ferret query
|
345
|
-
# to self.class is still needed.
|
346
|
-
# from the ferret ML (thanks Curtis Hatter)
|
347
|
-
# > I created a method in my base STI class so I can scope my query. For scoping
|
348
|
-
# > I used something like the following line:
|
349
|
-
# >
|
350
|
-
# > query << " role:#{self.class.eql?(Contents) '*' : self.class}"
|
351
|
-
# >
|
352
|
-
# > Though you could make it more generic by simply asking
|
353
|
-
# > "self.descends_from_active_record?" which is how rails decides if it should
|
354
|
-
# > scope your "find" query for STI models. You can check out "base.rb" in
|
355
|
-
# > activerecord to see that.
|
356
|
-
# but maybe better do the scoping in find_id_by_contents...
|
357
|
-
def retrieve_records(id_arrays, find_options = {})
|
358
|
-
result = []
|
359
|
-
# get objects for each model
|
360
|
-
id_arrays.each do |model, id_array|
|
361
|
-
next if id_array.empty?
|
362
|
-
begin
|
363
|
-
model = model.constantize
|
364
|
-
rescue
|
365
|
-
raise "Please use ':store_class_name => true' if you want to use multi_search.\n#{$!}"
|
366
|
-
end
|
367
248
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
:include => filtered_include_options))
|
386
|
-
|
387
|
-
# set scores and rank
|
388
|
-
tmp_result.each do |record|
|
389
|
-
record.ferret_rank, record.ferret_score = id_array[record.id.to_s]
|
390
|
-
end
|
391
|
-
# merge with result array
|
392
|
-
result.concat tmp_result
|
393
|
-
end
|
394
|
-
|
395
|
-
# order results as they were found by ferret, unless an AR :order
|
396
|
-
# option was given
|
397
|
-
result.sort! { |a, b| a.ferret_rank <=> b.ferret_rank } unless find_options[:order]
|
398
|
-
return result
|
399
|
-
end
|
400
|
-
|
401
|
-
def count_records(id_arrays, find_options = {})
|
402
|
-
count_options = find_options.dup
|
403
|
-
count_options.delete :limit
|
404
|
-
count_options.delete :offset
|
405
|
-
count = 0
|
406
|
-
id_arrays.each do |model, id_array|
|
407
|
-
next if id_array.empty?
|
408
|
-
begin
|
409
|
-
model = model.constantize
|
410
|
-
# merge conditions
|
411
|
-
conditions = combine_conditions([ "#{model.table_name}.#{model.primary_key} in (?)", id_array.keys ],
|
412
|
-
find_options[:conditions])
|
413
|
-
opts = find_options.merge :conditions => conditions
|
414
|
-
opts.delete :limit; opts.delete :offset
|
415
|
-
count += model.count opts
|
416
|
-
rescue TypeError
|
417
|
-
raise "#{model} must use :store_class_name option if you want to use multi_search against it.\n#{$!}"
|
418
|
-
end
|
419
|
-
end
|
420
|
-
count
|
421
|
-
end
|
422
|
-
|
423
|
-
def deprecated_options_support(options)
|
424
|
-
if options[:num_docs]
|
425
|
-
logger.warn ":num_docs is deprecated, use :limit instead!"
|
426
|
-
options[:limit] ||= options[:num_docs]
|
427
|
-
end
|
428
|
-
if options[:first_doc]
|
429
|
-
logger.warn ":first_doc is deprecated, use :offset instead!"
|
430
|
-
options[:offset] ||= options[:first_doc]
|
431
|
-
end
|
432
|
-
end
|
433
|
-
|
434
|
-
# creates a new Index instance.
|
435
|
-
def create_index_instance
|
436
|
-
if aaf_configuration[:remote]
|
437
|
-
RemoteIndex
|
438
|
-
elsif aaf_configuration[:single_index]
|
439
|
-
SharedIndex
|
440
|
-
else
|
441
|
-
LocalIndex
|
442
|
-
end.new(aaf_configuration)
|
443
|
-
end
|
444
|
-
|
445
|
-
# combine our conditions with those given by user, if any
|
446
|
-
def combine_conditions(conditions, additional_conditions = [])
|
447
|
-
returning conditions do
|
448
|
-
if additional_conditions && additional_conditions.any?
|
449
|
-
cust_opts = additional_conditions.respond_to?(:shift) ? additional_conditions.dup : [ additional_conditions ]
|
450
|
-
conditions.first << " and " << cust_opts.shift
|
451
|
-
conditions.concat(cust_opts)
|
452
|
-
end
|
453
|
-
end
|
454
|
-
end
|
249
|
+
# def count_records(id_arrays, find_options = {})
|
250
|
+
# count_options = find_options.dup
|
251
|
+
# count_options.delete :limit
|
252
|
+
# count_options.delete :offset
|
253
|
+
# count = 0
|
254
|
+
# id_arrays.each do |model, id_array|
|
255
|
+
# next if id_array.empty?
|
256
|
+
# model = model.constantize
|
257
|
+
# # merge conditions
|
258
|
+
# conditions = ActsAsFerret::combine_conditions([ "#{model.table_name}.#{model.primary_key} in (?)", id_array.keys ],
|
259
|
+
# find_options[:conditions])
|
260
|
+
# opts = find_options.merge :conditions => conditions
|
261
|
+
# opts.delete :limit; opts.delete :offset
|
262
|
+
# count += model.count opts
|
263
|
+
# end
|
264
|
+
# count
|
265
|
+
# end
|
455
266
|
|
456
267
|
end
|
457
268
|
|