Commit 4e223943ab159d8fa7faf4b042ec3121d74ed32e

Authored by Daniela Feitosa
1 parent 4a1e090a

Revert "Merge branch 'merge-requests/25'"

This reverts commit 6409d6cc8e989f473f17b882d05367c4b9153528, reversing
changes made to 313f8a54d53a4fdfa651f02197813026e1660e3b.

Conflicts:

	features/step_definitions/noosfero_steps.rb
	test/unit/category_finder_test.rb

There are some problems with solr
(ActionItem1958)
Showing 235 changed files with 3871 additions and 9091 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 235 files displayed.

@@ -38,9 +38,6 @@ commands and make sure you understand what you are doing): @@ -38,9 +38,6 @@ commands and make sure you understand what you are doing):
38 cp config/database.yml.sqlite3 config/database.yml 38 cp config/database.yml.sqlite3 config/database.yml
39 # create tmp directory if it doesn't exist 39 # create tmp directory if it doesn't exist
40 mkdir tmp 40 mkdir tmp
41 - # download and start Solr  
42 - rake solr:download  
43 - rake solr:start  
44 # create the development database 41 # create the development database
45 rake db:schema:load 42 rake db:schema:load
46 # run pending migrations 43 # run pending migrations
@@ -13,7 +13,7 @@ You need to install some packages Noosfero depends on. On Debian GNU/Linux or @@ -13,7 +13,7 @@ You need to install some packages Noosfero depends on. On Debian GNU/Linux or
13 Debian-based systems, all of these packages are available through the Debian 13 Debian-based systems, all of these packages are available through the Debian
14 archive. You can install them with the following command: 14 archive. You can install them with the following command:
15 15
16 - # apt-get install ruby rake po4a libgettext-ruby-util libgettext-ruby-data libgettext-ruby1.8 libsqlite3-ruby rcov librmagick-ruby libredcloth-ruby libwill-paginate-ruby iso-codes libfeedparser-ruby openjdk-6-jre libdaemons-ruby mongrel mongrel-cluster tango-icon-theme libhpricot-ruby 16 + # apt-get install ruby rake po4a libgettext-ruby-util libgettext-ruby-data libgettext-ruby1.8 libsqlite3-ruby rcov librmagick-ruby libredcloth-ruby libwill-paginate-ruby iso-codes libfeedparser-ruby libferret-ruby libdaemons-ruby mongrel mongrel-cluster tango-icon-theme libhpricot-ruby
17 17
18 On other systems, they may or may not be available through your regular package 18 On other systems, they may or may not be available through your regular package
19 management system. Below are the links to their homepages. 19 management system. Below are the links to their homepages.
@@ -24,7 +24,7 @@ management system. Below are the links to their homepages. @@ -24,7 +24,7 @@ management system. Below are the links to their homepages.
24 * Ruby-GetText: http://www.yotabanana.com/hiki/ruby-gettext.html?ruby-gettext (at least version 1.9.0) 24 * Ruby-GetText: http://www.yotabanana.com/hiki/ruby-gettext.html?ruby-gettext (at least version 1.9.0)
25 * Ruby-sqlite3: http://rubyforge.org/projects/sqlite-ruby 25 * Ruby-sqlite3: http://rubyforge.org/projects/sqlite-ruby
26 * rcov: http://eigenclass.org/hiki/rcov 26 * rcov: http://eigenclass.org/hiki/rcov
27 -* Solr: http://lucene.apache.org/solr 27 +* Ferret: http://ferret.davebalmain.com/trac
28 * RMagick: http://rmagick.rubyforge.org/ 28 * RMagick: http://rmagick.rubyforge.org/
29 * RedCloth: http://redcloth.org/ 29 * RedCloth: http://redcloth.org/
30 * will_paginate: http://github.com/mislav/will_paginate/wikis 30 * will_paginate: http://github.com/mislav/will_paginate/wikis
@@ -115,11 +115,8 @@ $ tar -zxvf noosfero-0.27.1.tar.gz @@ -115,11 +115,8 @@ $ tar -zxvf noosfero-0.27.1.tar.gz
115 $ ln -s noosfero-0.27.1 current 115 $ ln -s noosfero-0.27.1 current
116 $ cd current 116 $ cd current
117 117
118 -Copy config/solr.yml.dist to config/solr.yml. You will 118 +Copy config/ferret_server.yml.dist to config/ferret_server.yml. You will
119 probably not need to customize this configuration, but have a look at it. 119 probably not need to customize this configuration, but have a look at it.
120 -Then you'll need to download Solr into noosfero:  
121 -  
122 -$ rake solr:download  
123 120
124 Create the mongrel configuration file: 121 Create the mongrel configuration file:
125 122
@@ -240,10 +237,6 @@ Create the database structure: @@ -240,10 +237,6 @@ Create the database structure:
240 237
241 $ RAILS_ENV=production rake db:schema:load 238 $ RAILS_ENV=production rake db:schema:load
242 239
243 -Run Solr:  
244 -  
245 -$ rake solr:start  
246 -  
247 Now we have to create some initial data. To create your default environment 240 Now we have to create some initial data. To create your default environment
248 (the first one), run the command below: 241 (the first one), run the command below:
249 242
@@ -7,6 +7,4 @@ require 'rake' @@ -7,6 +7,4 @@ require 'rake'
7 require 'rake/testtask' 7 require 'rake/testtask'
8 require 'rake/rdoctask' 8 require 'rake/rdoctask'
9 9
10 -ACTS_AS_SEARCHABLE_ENABLED = false if Rake.application.top_level_tasks.detect{|t| t == 'db:data:minimal'}  
11 -  
12 require 'tasks/rails' 10 require 'tasks/rails'
app/controllers/my_profile/profile_members_controller.rb
@@ -126,11 +126,11 @@ class ProfileMembersController < MyProfileController @@ -126,11 +126,11 @@ class ProfileMembersController < MyProfileController
126 if !params[:query] || params[:query].length <= 2 126 if !params[:query] || params[:query].length <= 2
127 @users_found = [] 127 @users_found = []
128 elsif params[:scope] == 'all_users' 128 elsif params[:scope] == 'all_users'
129 - @users_found = Person.find_by_contents(params[:query] + '*')[:results].select {|user| !profile.members.include?(user)} 129 + @users_found = Person.find_by_contents(params[:query] + '*').select {|user| !profile.members.include?(user)}
130 @button_alt = _('Add member') 130 @button_alt = _('Add member')
131 @add_action = {:action => 'add_member'} 131 @add_action = {:action => 'add_member'}
132 elsif params[:scope] == 'new_admins' 132 elsif params[:scope] == 'new_admins'
133 - @users_found = Person.find_by_contents(params[:query] + '*')[:results].select {|user| profile.members.include?(user) && !profile.admins.include?(user)} 133 + @users_found = Person.find_by_contents(params[:query] + '*').select {|user| profile.members.include?(user) && !profile.admins.include?(user)}
134 @button_alt = _('Add member') 134 @button_alt = _('Add member')
135 @add_action = {:action => 'add_admin'} 135 @add_action = {:action => 'add_admin'}
136 end 136 end
app/controllers/public/browse_controller.rb
@@ -19,10 +19,9 @@ class BrowseController &lt; PublicController @@ -19,10 +19,9 @@ class BrowseController &lt; PublicController
19 @results = @environment.people.visible.send(@filter) 19 @results = @environment.people.visible.send(@filter)
20 20
21 if !params[:query].blank? 21 if !params[:query].blank?
22 - @results = @results.find_by_contents(params[:query], {:per_page => per_page, :page => params[:page]})[:results]  
23 - else  
24 - @results = @results.compact.paginate(:per_page => per_page, :page => params[:page]) 22 + @results = @results.find_by_contents(params[:query])
25 end 23 end
  24 + @results = @results.compact.paginate(:per_page => per_page, :page => params[:page])
26 end 25 end
27 26
28 def communities 27 def communities
@@ -32,10 +31,9 @@ class BrowseController &lt; PublicController @@ -32,10 +31,9 @@ class BrowseController &lt; PublicController
32 @results = @environment.communities.visible.send(@filter) 31 @results = @environment.communities.visible.send(@filter)
33 32
34 if !params[:query].blank? 33 if !params[:query].blank?
35 - @results = @results.find_by_contents(params[:query], {:per_page => per_page, :page => params[:page]})[:results]  
36 - else  
37 - @results = @results.compact.paginate(:per_page => per_page, :page => params[:page]) 34 + @results = @results.find_by_contents(params[:query])
38 end 35 end
  36 + @results = @results.compact.paginate(:per_page => per_page, :page => params[:page])
39 end 37 end
40 38
41 protected 39 protected
app/controllers/public/profile_search_controller.rb
@@ -8,10 +8,11 @@ class ProfileSearchController &lt; PublicController @@ -8,10 +8,11 @@ class ProfileSearchController &lt; PublicController
8 def index 8 def index
9 @q = params[:q] 9 @q = params[:q]
10 unless @q.blank? 10 unless @q.blank?
  11 + @filtered_query = remove_stop_words(@q)
11 if params[:where] == 'environment' 12 if params[:where] == 'environment'
12 redirect_to :controller => 'search', :query => @q 13 redirect_to :controller => 'search', :query => @q
13 else 14 else
14 - @results = Article.find_by_contents(@q + " profile_id:#{profile.id} published:true")[:results].paginate(:per_page => 10, :page => params[:page]) 15 + @results = profile.articles.published.find_by_contents(@filtered_query).paginate(:per_page => 10, :page => params[:page])
15 end 16 end
16 end 17 end
17 end 18 end
app/controllers/public/search_controller.rb
@@ -89,8 +89,7 @@ class SearchController &lt; PublicController @@ -89,8 +89,7 @@ class SearchController &lt; PublicController
89 # REFACTOR DUPLICATED CODE inner loop doing the same thing that outter loop 89 # REFACTOR DUPLICATED CODE inner loop doing the same thing that outter loop
90 90
91 if !@query.blank? || @region && !params[:radius].blank? 91 if !@query.blank? || @region && !params[:radius].blank?
92 - ret = @noosfero_finder.find(asset, @query, calculate_find_options(asset, nil, params[:page], @product_category, @region, params[:radius], params[:year], params[:month]).merge({:limit => :all}))  
93 - @result_ids = ret.is_a?(Hash) ? ret[:results] : ret 92 + @result_ids = @noosfero_finder.find(asset, @filtered_query, calculate_find_options(asset, nil, params[:page], @product_category, @region, params[:radius], params[:year], params[:month]).merge({:limit => :all}))
94 end 93 end
95 94
96 end 95 end
@@ -150,6 +149,7 @@ class SearchController &lt; PublicController @@ -150,6 +149,7 @@ class SearchController &lt; PublicController
150 149
151 def index 150 def index
152 @query = params[:query] || '' 151 @query = params[:query] || ''
  152 + @filtered_query = remove_stop_words(@query)
153 @product_category = ProductCategory.find(params[:product_category]) if params[:product_category] 153 @product_category = ProductCategory.find(params[:product_category]) if params[:product_category]
154 154
155 @region = City.find_by_id(params[:city]) if !params[:city].blank? && params[:city] =~ /^\d+$/ 155 @region = City.find_by_id(params[:city]) if !params[:city].blank? && params[:city] =~ /^\d+$/
@@ -158,16 +158,12 @@ class SearchController &lt; PublicController @@ -158,16 +158,12 @@ class SearchController &lt; PublicController
158 number_of_result_assets = @searching.values.select{|v| v}.size 158 number_of_result_assets = @searching.values.select{|v| v}.size
159 159
160 @results = {} 160 @results = {}
161 - @facets = {}  
162 @order = [] 161 @order = []
163 @names = {} 162 @names = {}
164 163
165 where_to_search.select { |key,description| @searching[key] }.each do |key, description| 164 where_to_search.select { |key,description| @searching[key] }.each do |key, description|
166 @order << key 165 @order << key
167 - find_options = calculate_find_options(key, limit, params[:page], @product_category, @region, params[:radius], params[:year], params[:month]);  
168 - ret = @noosfero_finder.find(key, @query, find_options)  
169 - @results[key] = ret.is_a?(Hash) ? ret[:results] : ret  
170 - @facets[key] = ret.is_a?(Hash) ? ret[:facets] : {} 166 + @results[key] = @noosfero_finder.find(key, @filtered_query, calculate_find_options(key, limit, params[:page], @product_category, @region, params[:radius], params[:year], params[:month]))
171 @names[key] = getterm(description) 167 @names[key] = getterm(description)
172 end 168 end
173 169
app/helpers/application_helper.rb
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 # application. 2 # application.
3 module ApplicationHelper 3 module ApplicationHelper
4 4
5 - include PermissionNameHelper 5 + include PermissionName
6 6
7 include LightboxHelper 7 include LightboxHelper
8 8
app/helpers/search_helper.rb
@@ -3,12 +3,21 @@ module SearchHelper @@ -3,12 +3,21 @@ module SearchHelper
3 # FIXME remove it after search_controler refactored 3 # FIXME remove it after search_controler refactored
4 include EventsHelper 4 include EventsHelper
5 5
  6 + STOP_WORDS = {
  7 + 'pt_BR' => Ferret::Analysis::FULL_PORTUGUESE_STOP_WORDS,
  8 + 'en' => Ferret::Analysis::FULL_ENGLISH_STOP_WORDS,
  9 + }
  10 +
6 def relevance_for(hit) 11 def relevance_for(hit)
7 n = (hit.ferret_score if hit.respond_to?(:ferret_score)) 12 n = (hit.ferret_score if hit.respond_to?(:ferret_score))
8 n ||= 1.0 13 n ||= 1.0
9 (n * 100.0).round 14 (n * 100.0).round
10 end 15 end
11 16
  17 + def remove_stop_words(query)
  18 + (query.downcase.scan(/"[^"]*"?|'[^']*'?|[^'"\s]+/) - (STOP_WORDS[locale] || [])).join(' ')
  19 + end
  20 +
12 def display_results(use_map = true) 21 def display_results(use_map = true)
13 22
14 unless use_map && GoogleMaps.enabled?(environment.default_hostname) 23 unless use_map && GoogleMaps.enabled?(environment.default_hostname)
app/models/article.rb
@@ -388,7 +388,7 @@ class Article &lt; ActiveRecord::Base @@ -388,7 +388,7 @@ class Article &lt; ActiveRecord::Base
388 end 388 end
389 389
390 def comments_updated 390 def comments_updated
391 - solr_save 391 + ferret_update
392 end 392 end
393 393
394 def accept_category?(cat) 394 def accept_category?(cat)
app/models/category_finder.rb
@@ -28,12 +28,10 @@ class CategoryFinder @@ -28,12 +28,10 @@ class CategoryFinder
28 end 28 end
29 29
30 if query.blank? 30 if query.blank?
31 - options.delete(:facets)  
32 asset_class(asset).send(finder_method, :all, options_for_find(asset_class(asset), {:order => "#{asset_table(asset)}.name"}.merge(options), date_range)) 31 asset_class(asset).send(finder_method, :all, options_for_find(asset_class(asset), {:order => "#{asset_table(asset)}.name"}.merge(options), date_range))
33 else 32 else
34 - pg_options = {:page => options.delete(:page), :per_page => options.delete(:per_page)}  
35 - solr_options = {:facets => options.delete(:facets)}  
36 - asset_class(asset).find_by_contents(query, pg_options, solr_options, options_for_find(asset_class(asset), options, date_range))[:results] 33 + ferret_options = {:page => options.delete(:page), :per_page => options.delete(:per_page)}
  34 + asset_class(asset).find_by_contents(query, ferret_options, options_for_find(asset_class(asset), options, date_range))
37 end 35 end
38 end 36 end
39 37
app/models/enterprise.rb
@@ -71,7 +71,7 @@ class Enterprise &lt; Organization @@ -71,7 +71,7 @@ class Enterprise &lt; Organization
71 end 71 end
72 72
73 def product_updated 73 def product_updated
74 - solr_save 74 + ferret_update
75 end 75 end
76 76
77 after_save do |e| 77 after_save do |e|
app/models/environment_finder.rb
@@ -22,8 +22,6 @@ class EnvironmentFinder @@ -22,8 +22,6 @@ class EnvironmentFinder
22 end 22 end
23 23
24 if query.blank? 24 if query.blank?
25 - options.delete(:facets)  
26 -  
27 # FIXME this test is in more than one place 25 # FIXME this test is in more than one place
28 if finder_method == 'paginate' 26 if finder_method == 'paginate'
29 options = {:order => "#{asset_table(asset)}.name"}.merge(options) 27 options = {:order => "#{asset_table(asset)}.name"}.merge(options)
@@ -50,20 +48,14 @@ class EnvironmentFinder @@ -50,20 +48,14 @@ class EnvironmentFinder
50 end 48 end
51 end 49 end
52 else 50 else
53 - pg_options = {:page => options.delete(:page), :per_page => options.delete(:per_page)}  
54 - solr_options = {:facets => options.delete(:facets)} 51 + ferret_options = {:page => options.delete(:page), :per_page => options.delete(:per_page)}
55 if product_category && asset == :products 52 if product_category && asset == :products
56 # SECURITY no risk of SQL injection, since product_category_ids comes from trusted source 53 # SECURITY no risk of SQL injection, since product_category_ids comes from trusted source
57 - ret = @environment.send(asset).find_by_contents(query, pg_options, solr_options, options.merge({:include => 'product_categorizations', :conditions => 'product_categorizations.category_id = (%s)' % product_category.id })) 54 + @environment.send(asset).find_by_contents(query, ferret_options, options.merge({:include => 'product_categorizations', :conditions => 'product_categorizations.category_id = (%s)' % product_category.id }))
58 elsif product_category && asset == :enterprises 55 elsif product_category && asset == :enterprises
59 - ret = @environment.send(asset).find_by_contents(query, pg_options, solr_options, options.merge(:joins => 'inner join product_categorizations on (product_categorizations.product_id = products.id)', :include => 'products', :conditions => "product_categorizations.category_id = (#{product_category.id})"))  
60 - else  
61 - ret = @environment.send(asset).find_by_contents(query, pg_options, solr_options, options)  
62 - end  
63 - if solr_options[:facets].nil?  
64 - ret[:results] 56 + @environment.send(asset).find_by_contents(query, ferret_options, options.merge(:joins => 'inner join product_categorizations on (product_categorizations.product_id = products.id)', :include => 'products', :conditions => "product_categorizations.category_id = (#{product_category.id})"))
65 else 57 else
66 - ret 58 + @environment.send(asset).find_by_contents(query, ferret_options, options)
67 end 59 end
68 end 60 end
69 end 61 end
app/models/region.rb
@@ -5,9 +5,10 @@ class Region &lt; Category @@ -5,9 +5,10 @@ class Region &lt; Category
5 require_dependency 'enterprise' # enterprises can also be validators 5 require_dependency 'enterprise' # enterprises can also be validators
6 6
7 # searches for organizations that could become validators for this region. 7 # searches for organizations that could become validators for this region.
8 - # <tt>search</tt> is passed as is to find_by_contents on Organization. 8 + # <tt>search</tt> is passed as is to ferret's find_by_contents on Organizatio
  9 + # find_by_contents on Organization class.
9 def search_possible_validators(search) 10 def search_possible_validators(search)
10 - Organization.find_by_contents(search)[:results].reject {|item| self.validator_ids.include?(item.id) } 11 + Organization.find_by_contents(search).reject {|item| self.validator_ids.include?(item.id) }
11 end 12 end
12 13
13 def has_validator? 14 def has_validator?
config/environments/cucumber.rb
@@ -26,3 +26,4 @@ config.gem &#39;rspec-rails&#39;, :lib =&gt; &#39;spec/rails&#39;, :version =&gt; &#39;&gt;=1.2.7.1&#39; u @@ -26,3 +26,4 @@ config.gem &#39;rspec-rails&#39;, :lib =&gt; &#39;spec/rails&#39;, :version =&gt; &#39;&gt;=1.2.7.1&#39; u
26 config.gem 'Selenium', :lib => 'selenium', :version => '>= 1.1.14' unless File.directory?(File.join(Rails.root, 'vendor/plugins/selenium')) 26 config.gem 'Selenium', :lib => 'selenium', :version => '>= 1.1.14' unless File.directory?(File.join(Rails.root, 'vendor/plugins/selenium'))
27 config.gem 'selenium-client', :lib => 'selenium/client', :version => '>= 1.2.17' unless File.directory?(File.join(Rails.root, 'vendor/plugins/selenium-client')) 27 config.gem 'selenium-client', :lib => 'selenium/client', :version => '>= 1.2.17' unless File.directory?(File.join(Rails.root, 'vendor/plugins/selenium-client'))
28 config.gem 'database_cleaner', :lib => 'database_cleaner' 28 config.gem 'database_cleaner', :lib => 'database_cleaner'
  29 +
config/solr.yml.dist
@@ -1,20 +0,0 @@ @@ -1,20 +0,0 @@
1 -# Config file for the acts_as_solr plugin.  
2 -#  
3 -# If you change the host or port number here, make sure you update  
4 -# them in your Solr config file  
5 -  
6 -production:  
7 - url: http://127.0.0.1:8983/solr  
8 - jvm_options: -server -Xmx512M -Xms64M  
9 -  
10 -development:  
11 - url: http://0.0.0.0:8982/solr  
12 - jvm_options: -server -Xmx64M -Xms16M  
13 -  
14 -test:  
15 - url: http://0.0.0.0:8981/solr  
16 - jvm_options: -server -Xmx32M -Xms16M  
17 -  
18 -cucumber:  
19 - url: http://0.0.0.0:8980/solr  
20 - jvm_options: -server -Xmx32M -Xms16M  
debian/control
@@ -11,7 +11,7 @@ Vcs-Browser: http://git.colivre.coop.br/?p=noosfero.git @@ -11,7 +11,7 @@ Vcs-Browser: http://git.colivre.coop.br/?p=noosfero.git
11 11
12 Package: noosfero 12 Package: noosfero
13 Architecture: all 13 Architecture: all
14 -Depends: rails, ruby1.8, ruby, rake, libgettext-ruby-data, libsqlite3-ruby, libpgsql-ruby, libmysql-ruby, librmagick-ruby, libredcloth-ruby, libwill-paginate-ruby, iso-codes, libfeedparser-ruby, openjdk-6-jre, libdaemons-ruby, rcov, mongrel, mongrel-cluster, tango-icon-theme, libhpricot-ruby, iso-codes, memcached, debconf, dbconfig-common, postgresql, adduser, ${misc:Depends} 14 +Depends: rails, ruby1.8, ruby, rake, libgettext-ruby-data, libsqlite3-ruby, libpgsql-ruby, libmysql-ruby, librmagick-ruby, libredcloth-ruby, libwill-paginate-ruby, iso-codes, libfeedparser-ruby, libferret-ruby, libdaemons-ruby, rcov, mongrel, mongrel-cluster, tango-icon-theme, libhpricot-ruby, iso-codes, memcached, debconf, dbconfig-common, postgresql, adduser, ${misc:Depends}
15 Recommends: postgresql-client 15 Recommends: postgresql-client
16 Description: free web-based platform for social networks 16 Description: free web-based platform for social networks
17 Noosfero is a web platform for social and solidarity economy networks with 17 Noosfero is a web platform for social and solidarity economy networks with
debian/ferret_server.yml 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +production:
  2 + host: localhost
  3 + port: 51000
  4 + pid_file: tmp/pids/ferret.production.pid
debian/noosfero.install
@@ -28,7 +28,7 @@ public usr/share/noosfero @@ -28,7 +28,7 @@ public usr/share/noosfero
28 debian/default/noosfero etc/default 28 debian/default/noosfero etc/default
29 etc/init.d/noosfero etc/init.d 29 etc/init.d/noosfero etc/init.d
30 debian/mongrel_cluster.yml etc/noosfero 30 debian/mongrel_cluster.yml etc/noosfero
31 -debian/solr.yml etc/noosfero 31 +debian/ferret_server.yml etc/noosfero
32 etc/logrotate.d/noosfero etc/logrotate.d 32 etc/logrotate.d/noosfero etc/logrotate.d
33 debian/noosfero.yml etc/noosfero 33 debian/noosfero.yml etc/noosfero
34 34
debian/noosfero.links
@@ -3,7 +3,7 @@ var/tmp/noosfero usr/share/noosfero/tmp @@ -3,7 +3,7 @@ var/tmp/noosfero usr/share/noosfero/tmp
3 var/log/noosfero usr/share/noosfero/log 3 var/log/noosfero usr/share/noosfero/log
4 etc/noosfero/database.yml usr/share/noosfero/config/database.yml 4 etc/noosfero/database.yml usr/share/noosfero/config/database.yml
5 etc/noosfero/mongrel_cluster.yml usr/share/noosfero/config/mongrel_cluster.yml 5 etc/noosfero/mongrel_cluster.yml usr/share/noosfero/config/mongrel_cluster.yml
6 -etc/noosfero/solr.yml usr/share/noosfero/config/solr.yml 6 +etc/noosfero/ferret_server.yml usr/share/noosfero/config/ferret_server.yml
7 etc/noosfero/plugins usr/share/noosfero/config/plugins 7 etc/noosfero/plugins usr/share/noosfero/config/plugins
8 etc/noosfero/noosfero.yml usr/share/noosfero/config/noosfero.yml 8 etc/noosfero/noosfero.yml usr/share/noosfero/config/noosfero.yml
9 etc/noosfero/local.rb usr/share/noosfero/config/local.rb 9 etc/noosfero/local.rb usr/share/noosfero/config/local.rb
debian/solr.yml
@@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
1 -# Config file for the acts_as_solr plugin.  
2 -#  
3 -# If you change the host or port number here, make sure you update  
4 -# them in your Solr config file  
5 -  
6 -production:  
7 - url: http://127.0.0.1:8983/solr  
8 - jvm_options: -server -Xmx512M -Xms64M  
9 -  
etc/init.d/noosfero
@@ -43,14 +43,14 @@ if [ -z &quot;$NOOSFERO_DIR&quot; ] || [ -z &quot;$NOOSFERO_USER&quot; ]; then @@ -43,14 +43,14 @@ if [ -z &quot;$NOOSFERO_DIR&quot; ] || [ -z &quot;$NOOSFERO_USER&quot; ]; then
43 fi 43 fi
44 44
45 ###################### 45 ######################
46 -SOLR_PID_FILE=$NOOSFERO_DIR/tmp/pids/solr.production.pid 46 +FERRET_PID_FILE=$NOOSFERO_DIR/tmp/pids/ferret.production.pid
47 47
48 main_script() { 48 main_script() {
49 cd $NOOSFERO_DIR 49 cd $NOOSFERO_DIR
50 if [ "$NOOSFERO_USER" != "$USER" ]; then 50 if [ "$NOOSFERO_USER" != "$USER" ]; then
51 - su $NOOSFERO_USER -l -c "SOLR_DATA_PATH=/var/lib/noosfero-data/index ./script/production $1" 51 + su $NOOSFERO_USER -l -c "./script/production $1"
52 else 52 else
53 - SOLR_DATA_PATH=/var/lib/noosfero-data/index ./script/production $1 53 + ./script/production $1
54 fi 54 fi
55 } 55 }
56 56
@@ -76,13 +76,6 @@ do_setup() { @@ -76,13 +76,6 @@ do_setup() {
76 chmod 750 /var/tmp/noosfero 76 chmod 750 /var/tmp/noosfero
77 fi 77 fi
78 78
79 - # Solr directory  
80 - if [ ! -d /var/tmp/noosfero/solr ]; then  
81 - mkdir -p /var/tmp/noosfero/solr  
82 - chown $NOOSFERO_USER:root /var/tmp/noosfero/solr  
83 - chmod 750 /var/tmp/noosfero/solr  
84 - fi  
85 -  
86 # symlink the directories into Noosfero directory 79 # symlink the directories into Noosfero directory
87 if [ ! -e $NOOSFERO_DIR/tmp ]; then 80 if [ ! -e $NOOSFERO_DIR/tmp ]; then
88 ln -s /var/tmp/noosfero $NOOSFERO_DIR/tmp 81 ln -s /var/tmp/noosfero $NOOSFERO_DIR/tmp
@@ -97,8 +90,8 @@ do_setup() { @@ -97,8 +90,8 @@ do_setup() {
97 90
98 do_start() { 91 do_start() {
99 92
100 - # FIXME should not test for solr only  
101 - if [ -e $SOLR_PID_FILE ]; then 93 + # FIXME should not test for ferret only
  94 + if [ -e $FERRET_PID_FILE ]; then
102 echo 'noosfero already running, cannot start.' 95 echo 'noosfero already running, cannot start.'
103 exit 2 96 exit 2
104 fi 97 fi
@@ -111,8 +104,8 @@ do_start() { @@ -111,8 +104,8 @@ do_start() {
111 104
112 do_stop() { 105 do_stop() {
113 106
114 - # FIXME should not test for solr only  
115 - if [ ! -e $SOLR_PID_FILE ]; then 107 + # FIXME should not test for ferret only
  108 + if [ ! -e $FERRET_PID_FILE ]; then
116 echo 'noosfero not running, cannot stop' 109 echo 'noosfero not running, cannot stop'
117 exit 2 110 exit 2
118 fi 111 fi
features/browse.feature
@@ -4,7 +4,6 @@ Feature: browse @@ -4,7 +4,6 @@ Feature: browse
4 4
5 Background: 5 Background:
6 Given I am on the homepage 6 Given I am on the homepage
7 - And the search index is empty  
8 And the following users 7 And the following users
9 | login | name | 8 | login | name |
10 | joaosilva | Joao Silva | 9 | joaosilva | Joao Silva |
features/profile_search.feature
@@ -4,8 +4,7 @@ Feature: search inside a profile @@ -4,8 +4,7 @@ Feature: search inside a profile
4 In order to find stuff from a profile 4 In order to find stuff from a profile
5 5
6 Background: 6 Background:
7 - Given the search index is empty  
8 - And the following users 7 + Given the following users
9 | login | name | 8 | login | name |
10 | joaosilva | Joao Silva | 9 | joaosilva | Joao Silva |
11 And the following articles 10 And the following articles
features/search.feature
@@ -3,9 +3,6 @@ Feature: search @@ -3,9 +3,6 @@ Feature: search
3 I want to search 3 I want to search
4 In order to find stuff 4 In order to find stuff
5 5
6 - Background:  
7 - Given the search index is empty  
8 -  
9 Scenario: simple search for person 6 Scenario: simple search for person
10 Given the following users 7 Given the following users
11 | login | name | 8 | login | name |
features/step_definitions/noosfero_steps.rb
@@ -413,10 +413,6 @@ Given /^the environment domain is &quot;([^\&quot;]*)&quot;$/ do |domain| @@ -413,10 +413,6 @@ Given /^the environment domain is &quot;([^\&quot;]*)&quot;$/ do |domain|
413 d.save(false) 413 d.save(false)
414 end 414 end
415 415
416 -Given /^the search index is empty$/ do  
417 - ActsAsSolr::Post.execute(Solr::Request::Delete.new(:query => '*:*'))  
418 -end  
419 -  
420 Given /^skip comments captcha$/ do 416 Given /^skip comments captcha$/ do
421 Comment.any_instance.stubs(:skip_captcha?).returns(true) 417 Comment.any_instance.stubs(:skip_captcha?).returns(true)
422 end 418 end
lib/acts_as_searchable.rb
1 module ActsAsSearchable 1 module ActsAsSearchable
2 2
3 module ClassMethods 3 module ClassMethods
4 - ACTS_AS_SEARCHABLE_ENABLED = true unless defined? ACTS_AS_SEARCHABLE_ENABLED  
5 -  
6 def acts_as_searchable(options = {}) 4 def acts_as_searchable(options = {})
7 - if ACTS_AS_SEARCHABLE_ENABLED  
8 - if (!options[:fields])  
9 - options[:additional_fields] |= [{:schema_name => :string}]  
10 - else  
11 - options[:fields] << {:schema_name => :string}  
12 - end  
13 - acts_as_solr options  
14 - extend FindByContents  
15 - send :include, InstanceMethods 5 + if Noosfero::MultiTenancy.on? and ActiveRecord::Base.postgresql?
  6 + options[:additional_fields] ||= {}
  7 + options[:additional_fields] = Hash[*options[:additional_fields].collect{ |v| [v, {}] }.flatten] if options[:additional_fields].is_a?(Array)
  8 + options[:additional_fields].merge!(:schema_name => { :index => :untokenized })
16 end 9 end
  10 + acts_as_ferret({ :remote => true }.merge(options))
  11 + extend FindByContents
  12 + send :include, InstanceMethods
17 end 13 end
18 14
19 module InstanceMethods 15 module InstanceMethods
20 def schema_name 16 def schema_name
21 - (Noosfero::MultiTenancy.on? and ActiveRecord::Base.postgresql?) ? ActiveRecord::Base.connection.schema_search_path : '' 17 + ActiveRecord::Base.connection.schema_search_path
22 end 18 end
23 end 19 end
24 20
25 module FindByContents 21 module FindByContents
26 22
27 def schema_name 23 def schema_name
28 - (Noosfero::MultiTenancy.on? and ActiveRecord::Base.postgresql?) ? ActiveRecord::Base.connection.schema_search_path : '' 24 + ActiveRecord::Base.connection.schema_search_path
29 end 25 end
30 26
31 - def find_by_contents(query, pg_options = {}, options = {}, db_options = {})  
32 - pg_options[:page] ||= 1  
33 - pg_options[:per_page] ||= 20  
34 - options[:limit] = pg_options[:per_page].to_i*pg_options[:page].to_i  
35 - options[:scores] = true;  
36 -  
37 - query = !schema_name.empty? ? "+schema_name:\"#{schema_name}\" AND #{query}" : query  
38 - solr_result = find_by_solr(query, options)  
39 - if solr_result.nil?  
40 - results = facets = []  
41 - else  
42 - facets = options.include?(:facets) ? solr_result.facets : [] 27 + def find_by_contents(query, ferret_options = {}, db_options = {})
  28 + pg_options = {}
  29 + if ferret_options[:page]
  30 + pg_options[:page] = ferret_options.delete(:page)
  31 + end
  32 + if ferret_options[:per_page]
  33 + pg_options[:per_page] = ferret_options.delete(:per_page)
  34 + end
43 35
44 - if db_options.empty?  
45 - results = solr_result.results  
46 - else  
47 - ids = solr_result.results.map{|r|r[:id].to_i}  
48 - if ids.empty?  
49 - ids << -1  
50 - end 36 + ferret_options[:limit] = :all
51 37
52 - if db_options[:conditions]  
53 - db_options[:conditions] = sanitize_sql_for_conditions(db_options[:conditions]) + " and #{table_name}.id in (#{ids.join(', ')})"  
54 - else  
55 - db_options[:conditions] = "#{table_name}.id in (#{ids.join(', ')})"  
56 - end 38 + ferret_query = (Noosfero::MultiTenancy.on? and ActiveRecord::Base.postgresql?) ? "+schema_name:\"#{schema_name}\" AND #{query}" : query
  39 + # FIXME this is a HORRIBLE HACK
  40 + ids = find_ids_with_ferret(ferret_query, ferret_options)[1][0..8000].map{|r|r[:id].to_i}
57 41
58 - results = find(:all, db_options)  
59 - end 42 + if ids.empty?
  43 + ids << -1
  44 + end
60 45
61 - results = results.paginate(pg_options.merge(:total_entries => solr_result.total)) 46 + if db_options[:conditions]
  47 + db_options[:conditions] = sanitize_sql_for_conditions(db_options[:conditions]) + " and #{table_name}.id in (#{ids.join(', ')})"
  48 + else
  49 + db_options[:conditions] = "#{table_name}.id in (#{ids.join(', ')})"
62 end 50 end
63 51
64 - {:results => results, :facets => facets} 52 + pg_options[:page] ||= 1
  53 + result = find(:all, db_options)
  54 + result.paginate(pg_options)
65 end 55 end
66 end 56 end
67 end 57 end
lib/create_thumbnails_job.rb
1 class CreateThumbnailsJob < Struct.new(:class_name, :file_id) 1 class CreateThumbnailsJob < Struct.new(:class_name, :file_id)
2 def perform 2 def perform
3 return unless class_name.constantize.exists?(file_id) 3 return unless class_name.constantize.exists?(file_id)
  4 + Article.disable_ferret # acts_as_ferret sucks
4 file = class_name.constantize.find(file_id) 5 file = class_name.constantize.find(file_id)
5 file.create_thumbnails 6 file.create_thumbnails
  7 + Article.enable_ferret # acts_as_ferret sucks
6 end 8 end
7 end 9 end
lib/tasks/cucumber.rake
@@ -11,13 +11,6 @@ begin @@ -11,13 +11,6 @@ begin
11 vendored_cucumber_binary = Dir["#{RAILS_ROOT}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 11 vendored_cucumber_binary = Dir["#{RAILS_ROOT}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
12 12
13 namespace :cucumber do 13 namespace :cucumber do
14 - task :solr_start do  
15 - ENV['RAILS_ENV'] = 'cucumber'  
16 - Rake::Task['solr:stop'].invoke  
17 - Rake::Task['solr:download'].invoke  
18 - Rake::Task['solr:start'].invoke  
19 - end  
20 -  
21 Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| 14 Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
22 t.binary = vendored_cucumber_binary 15 t.binary = vendored_cucumber_binary
23 t.fork = true # You may get faster startup if you set this to false 16 t.fork = true # You may get faster startup if you set this to false
@@ -36,17 +29,11 @@ begin @@ -36,17 +29,11 @@ begin
36 t.cucumber_opts = "--color -p selenium --format #{ENV['CUCUMBER_FORMAT'] || 'pretty'}" 29 t.cucumber_opts = "--color -p selenium --format #{ENV['CUCUMBER_FORMAT'] || 'pretty'}"
37 end 30 end
38 31
39 - task :solr_stop do  
40 - ENV['RAILS_ENV'] = 'cucumber'  
41 - Rake::Task['solr:stop'].invoke  
42 - end  
43 -  
44 desc 'Run all features' 32 desc 'Run all features'
45 - task :all => [:solr_start, :ok, :wip, :solr_stop] do  
46 - end 33 + task :all => [:ok, :wip]
47 end 34 end
48 desc 'Alias for cucumber:ok' 35 desc 'Alias for cucumber:ok'
49 - task :cucumber => ['cucumber:solr_start', 'cucumber:ok', 'cucumber:solr_stop'] 36 + task :cucumber => 'cucumber:ok'
50 37
51 task :features => :cucumber do 38 task :features => :cucumber do
52 STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" 39 STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
lib/tasks/package.rake
@@ -8,22 +8,10 @@ task :package =&gt; &#39;package:clobber&#39; do @@ -8,22 +8,10 @@ task :package =&gt; &#39;package:clobber&#39; do
8 puts "** The `package` task only works from within #{Noosfero::PROJECT}'s git repository." 8 puts "** The `package` task only works from within #{Noosfero::PROJECT}'s git repository."
9 fail 9 fail
10 end 10 end
11 - begin  
12 - sh 'test -f vendor/plugins/acts_as_solr_reloaded/solr/start.jar'  
13 - rescue  
14 - puts "** The `package` task needs Solr installed within #{Noosfero::PROJECT}. Run 'rake solr:download'."  
15 - fail  
16 - end  
17 release = "#{Noosfero::PROJECT}-#{Noosfero::VERSION}" 11 release = "#{Noosfero::PROJECT}-#{Noosfero::VERSION}"
18 target = "pkg/#{release}" 12 target = "pkg/#{release}"
19 mkdir_p target 13 mkdir_p target
20 sh "git archive HEAD | (cd #{target} && tar x)" 14 sh "git archive HEAD | (cd #{target} && tar x)"
21 -  
22 - #solr inclusion  
23 - cp_r "vendor/plugins/acts_as_solr_reloaded/solr", "#{target}/vendor/plugins/acts_as_solr_reloaded", :verbose => true  
24 - rm_r "#{target}/vendor/plugins/acts_as_solr_reloaded/solr/work"  
25 - mkdir_p "#{target}/vendor/plugins/acts_as_solr_reloaded/solr/work"  
26 -  
27 sh "cd pkg && tar czf #{release}.tar.gz #{release}" 15 sh "cd pkg && tar czf #{release}.tar.gz #{release}"
28 end 16 end
29 17
lib/tasks/test.rake
@@ -7,10 +7,6 @@ else @@ -7,10 +7,6 @@ else
7 end 7 end
8 8
9 task :test do 9 task :test do
10 - ENV['RAILS_ENV'] = 'test'  
11 - Rake::Task['solr:stop'].invoke  
12 - Rake::Task['solr:download'].invoke  
13 - Rake::Task['solr:start'].invoke  
14 errors = %w(test:units test:functionals test:integration cucumber selenium).collect do |task| 10 errors = %w(test:units test:functionals test:integration cucumber selenium).collect do |task|
15 begin 11 begin
16 Rake::Task[task].invoke 12 Rake::Task[task].invoke
@@ -19,7 +15,6 @@ task :test do @@ -19,7 +15,6 @@ task :test do
19 task 15 task
20 end 16 end
21 end.compact 17 end.compact
22 - Rake::Task['solr:stop'].invoke  
23 abort "Errors running #{errors.to_sentence}!" if errors.any? 18 abort "Errors running #{errors.to_sentence}!" if errors.any?
24 end 19 end
25 20
script/development
1 #!/bin/sh 1 #!/bin/sh
2 2
3 -export RAILS_ENV=development  
4 -  
5 stop() { 3 stop() {
6 ./script/delayed_job stop 4 ./script/delayed_job stop
7 ./script/feed-updater stop 5 ./script/feed-updater stop
8 - rake -s solr:stop  
9 } 6 }
10 7
11 start() { 8 start() {
12 ./script/feed-updater start 9 ./script/feed-updater start
13 ./script/delayed_job start 10 ./script/delayed_job start
14 - rake -s solr:download  
15 - rake -s solr:start  
16 trap stop INT TERM 11 trap stop INT TERM
17 ./script/server $@ 12 ./script/server $@
18 } 13 }
script/ferret_server 0 → 100755
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +#!/usr/bin/env ruby
  2 +
  3 +begin
  4 + require File.join(File.dirname(__FILE__), '../vendor/plugins/acts_as_ferret/lib/server_manager')
  5 +rescue LoadError
  6 + # try the gem
  7 + require 'rubygems'
  8 + gem 'acts_as_ferret'
  9 + require 'server_manager'
  10 +end
script/production
@@ -22,7 +22,7 @@ do_start() { @@ -22,7 +22,7 @@ do_start() {
22 fi 22 fi
23 23
24 clear_cache 24 clear_cache
25 - rake -s solr:start 25 + ./script/ferret_server -e $RAILS_ENV start
26 environments_loop 26 environments_loop
27 mongrel_rails cluster::start 27 mongrel_rails cluster::start
28 } 28 }
@@ -31,7 +31,7 @@ do_stop() { @@ -31,7 +31,7 @@ do_stop() {
31 mongrel_rails cluster::stop 31 mongrel_rails cluster::stop
32 ./script/delayed_job stop 32 ./script/delayed_job stop
33 ./script/feed-updater stop 33 ./script/feed-updater stop
34 - rake -s solr:stop 34 + ./script/ferret_server -e $RAILS_ENV stop
35 } 35 }
36 36
37 environments_loop() { 37 environments_loop() {
test/factories.rb
@@ -22,7 +22,7 @@ module Noosfero::Factory @@ -22,7 +22,7 @@ module Noosfero::Factory
22 end 22 end
23 end 23 end
24 if options[:search] 24 if options[:search]
25 - obj.solr_save 25 + obj.ferret_create
26 end 26 end
27 obj 27 obj
28 end 28 end
test/functional/browse_controller_test.rb
@@ -7,7 +7,6 @@ class BrowseController; def rescue_action(e) raise e end; end @@ -7,7 +7,6 @@ class BrowseController; def rescue_action(e) raise e end; end
7 class BrowseControllerTest < Test::Unit::TestCase 7 class BrowseControllerTest < Test::Unit::TestCase
8 8
9 def setup 9 def setup
10 - Test::Unit::TestCase::setup  
11 @controller = BrowseController.new 10 @controller = BrowseController.new
12 @request = ActionController::TestRequest.new 11 @request = ActionController::TestRequest.new
13 @request.stubs(:ssl?).returns(false) 12 @request.stubs(:ssl?).returns(false)
test/functional/profile_members_controller_test.rb
@@ -6,7 +6,6 @@ class ProfileMembersController; def rescue_action(e) raise e end; end @@ -6,7 +6,6 @@ class ProfileMembersController; def rescue_action(e) raise e end; end
6 6
7 class ProfileMembersControllerTest < Test::Unit::TestCase 7 class ProfileMembersControllerTest < Test::Unit::TestCase
8 def setup 8 def setup
9 - Test::Unit::TestCase::setup  
10 @controller = ProfileMembersController.new 9 @controller = ProfileMembersController.new
11 @request = ActionController::TestRequest.new 10 @request = ActionController::TestRequest.new
12 @request.stubs(:ssl?).returns(true) 11 @request.stubs(:ssl?).returns(true)
@@ -235,12 +234,12 @@ class ProfileMembersControllerTest &lt; Test::Unit::TestCase @@ -235,12 +234,12 @@ class ProfileMembersControllerTest &lt; Test::Unit::TestCase
235 end 234 end
236 235
237 should 'find users' do 236 should 'find users' do
238 - ent = fast_create(Enterprise, {:name => 'Test Ent', :identifier => 'test_ent'}, :search => true) 237 + ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent')
239 user = create_user_full('test_user').person 238 user = create_user_full('test_user').person
240 person = create_user_with_permission('ent_user', 'manage_memberships', ent) 239 person = create_user_with_permission('ent_user', 'manage_memberships', ent)
241 login_as :ent_user 240 login_as :ent_user
242 241
243 - get :find_users, :profile => ent.identifier, :query => 'test', :scope => 'all_users' 242 + get :find_users, :profile => ent.identifier, :query => 'test*', :scope => 'all_users'
244 243
245 assert_includes assigns(:users_found), user 244 assert_includes assigns(:users_found), user
246 end 245 end
test/functional/profile_search_controller_test.rb
@@ -6,7 +6,6 @@ class ProfileSearchController; def rescue_action(e) raise e end; end @@ -6,7 +6,6 @@ class ProfileSearchController; def rescue_action(e) raise e end; end
6 6
7 class ProfileSearchControllerTest < Test::Unit::TestCase 7 class ProfileSearchControllerTest < Test::Unit::TestCase
8 def setup 8 def setup
9 - Test::Unit::TestCase::setup  
10 @controller = ProfileSearchController.new 9 @controller = ProfileSearchController.new
11 @request = ActionController::TestRequest.new 10 @request = ActionController::TestRequest.new
12 @response = ActionController::TestResponse.new 11 @response = ActionController::TestResponse.new
@@ -15,6 +14,14 @@ class ProfileSearchControllerTest &lt; Test::Unit::TestCase @@ -15,6 +14,14 @@ class ProfileSearchControllerTest &lt; Test::Unit::TestCase
15 end 14 end
16 attr_reader :person 15 attr_reader :person
17 16
  17 + should 'filter stop words' do
  18 + @controller.expects(:locale).returns('en').at_least_once
  19 + get 'index', :profile => person.identifier, :q => 'an article about something'
  20 + assert_response :success
  21 + assert_template 'index'
  22 + assert_equal 'article something', assigns('filtered_query')
  23 + end
  24 +
18 should 'espape xss attack' do 25 should 'espape xss attack' do
19 @controller.expects(:profile).returns(person).at_least_once 26 @controller.expects(:profile).returns(person).at_least_once
20 get 'index', :profile => person.identifier, :q => '<wslite>' 27 get 'index', :profile => person.identifier, :q => '<wslite>'
@@ -34,8 +41,8 @@ class ProfileSearchControllerTest &lt; Test::Unit::TestCase @@ -34,8 +41,8 @@ class ProfileSearchControllerTest &lt; Test::Unit::TestCase
34 end 41 end
35 42
36 should 'display search results' do 43 should 'display search results' do
37 - article1 = fast_create(Article, {:body => '<p>Article to test profile search</p>', :profile_id => person.id}, :search => true)  
38 - article2 = fast_create(Article, {:body => '<p>Another article to test profile search</p>', :profile_id => person.id}, :search => true) 44 + article1 = fast_create(Article, :body => '<p>Article to test profile search</p>', :profile_id => person.id)
  45 + article2 = fast_create(Article, :body => '<p>Another article to test profile search</p>', :profile_id => person.id)
39 46
40 get 'index', :profile => person.identifier, :q => 'article' 47 get 'index', :profile => person.identifier, :q => 'article'
41 48
test/functional/search_controller_test.rb
@@ -6,7 +6,6 @@ class SearchController; def rescue_action(e) raise e end; end @@ -6,7 +6,6 @@ class SearchController; def rescue_action(e) raise e end; end
6 6
7 class SearchControllerTest < Test::Unit::TestCase 7 class SearchControllerTest < Test::Unit::TestCase
8 def setup 8 def setup
9 - Test::Unit::TestCase::setup  
10 @controller = SearchController.new 9 @controller = SearchController.new
11 @request = ActionController::TestRequest.new 10 @request = ActionController::TestRequest.new
12 @response = ActionController::TestResponse.new 11 @response = ActionController::TestResponse.new
@@ -37,6 +36,21 @@ class SearchControllerTest &lt; Test::Unit::TestCase @@ -37,6 +36,21 @@ class SearchControllerTest &lt; Test::Unit::TestCase
37 assert_valid_xhtml 36 assert_valid_xhtml
38 end 37 end
39 38
  39 + should 'filter stop words' do
  40 + @controller.expects(:locale).returns('pt_BR').at_least_once
  41 + get 'index', :query => 'a carne da vaca'
  42 + assert_response :success
  43 + assert_template 'index'
  44 + assert_equal 'carne vaca', assigns('filtered_query')
  45 + end
  46 +
  47 + should 'search with filtered query' do
  48 + @controller.expects(:locale).returns('pt_BR').at_least_once
  49 + get 'index', :query => 'a carne da vaca'
  50 +
  51 + assert_equal 'carne vaca', assigns('filtered_query')
  52 + end
  53 +
40 should 'espape xss attack' do 54 should 'espape xss attack' do
41 get 'index', :query => '<wslite>' 55 get 'index', :query => '<wslite>'
42 assert_no_tag :tag => 'wslite' 56 assert_no_tag :tag => 'wslite'
test/test_helper.rb
@@ -47,11 +47,6 @@ class Test::Unit::TestCase @@ -47,11 +47,6 @@ class Test::Unit::TestCase
47 include AuthenticatedTestHelper 47 include AuthenticatedTestHelper
48 48
49 fixtures :environments, :roles 49 fixtures :environments, :roles
50 -  
51 - def self.setup  
52 - # clean up index db before each test  
53 - ActsAsSolr::Post.execute(Solr::Request::Delete.new(:query => '*:*'))  
54 - end  
55 50
56 def self.all_fixtures 51 def self.all_fixtures
57 Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.yml')).each do |item| 52 Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.yml')).each do |item|
@@ -195,6 +190,7 @@ class Test::Unit::TestCase @@ -195,6 +190,7 @@ class Test::Unit::TestCase
195 adapter.any_instance.stubs(:adapter_name).returns('PostgreSQL') 190 adapter.any_instance.stubs(:adapter_name).returns('PostgreSQL')
196 adapter.any_instance.stubs(:schema_search_path).returns(schema_name) 191 adapter.any_instance.stubs(:schema_search_path).returns(schema_name)
197 Noosfero::MultiTenancy.stubs(:on?).returns(true) 192 Noosfero::MultiTenancy.stubs(:on?).returns(true)
  193 + reload_for_ferret
198 end 194 end
199 195
200 def uses_sqlite 196 def uses_sqlite
@@ -203,6 +199,20 @@ class Test::Unit::TestCase @@ -203,6 +199,20 @@ class Test::Unit::TestCase
203 Noosfero::MultiTenancy.stubs(:on?).returns(false) 199 Noosfero::MultiTenancy.stubs(:on?).returns(false)
204 end 200 end
205 201
  202 + def reload_for_ferret
  203 + ActsAsFerret.send(:remove_const, :DEFAULT_FIELD_OPTIONS)
  204 + load File.join(RAILS_ROOT, 'lib', 'acts_as_searchable.rb')
  205 + load File.join(RAILS_ROOT, 'vendor', 'plugins', 'acts_as_ferret', 'lib', 'acts_as_ferret.rb')
  206 + [Article, Profile, Product].each do |clazz|
  207 + inst_meth = clazz.instance_methods.reject{ |m| m =~ /_to_ferret$/ }
  208 + clazz.stubs(:instance_methods).returns(inst_meth)
  209 + end
  210 + #FIXME Is there a way to avoid this replication from model code?
  211 + Article.acts_as_searchable :additional_fields => [ :comment_data ]
  212 + Profile.acts_as_searchable :additional_fields => [ :extra_data_for_index ]
  213 + Product.acts_as_searchable :fields => [ :name, :description, :category_full_name ]
  214 + end
  215 +
206 end 216 end
207 217
208 module NoosferoTestHelper 218 module NoosferoTestHelper
test/unit/article_test.rb
@@ -5,7 +5,6 @@ class ArticleTest &lt; Test::Unit::TestCase @@ -5,7 +5,6 @@ class ArticleTest &lt; Test::Unit::TestCase
5 fixtures :environments 5 fixtures :environments
6 6
7 def setup 7 def setup
8 - Test::Unit::TestCase::setup  
9 @profile = create_user('testing').person 8 @profile = create_user('testing').person
10 Comment.skip_captcha! 9 Comment.skip_captcha!
11 end 10 end
@@ -358,7 +357,7 @@ class ArticleTest &lt; Test::Unit::TestCase @@ -358,7 +357,7 @@ class ArticleTest &lt; Test::Unit::TestCase
358 357
359 should 'reindex when comments are changed' do 358 should 'reindex when comments are changed' do
360 a = Article.new 359 a = Article.new
361 - a.expects(:solr_save) 360 + a.expects(:ferret_update)
362 a.comments_updated 361 a.comments_updated
363 end 362 end
364 363
@@ -367,7 +366,7 @@ class ArticleTest &lt; Test::Unit::TestCase @@ -367,7 +366,7 @@ class ArticleTest &lt; Test::Unit::TestCase
367 art = owner.articles.build(:name => 'ytest'); art.save! 366 art = owner.articles.build(:name => 'ytest'); art.save!
368 c1 = art.comments.build(:title => 'a nice comment', :body => 'anything', :author => owner); c1.save! 367 c1 = art.comments.build(:title => 'a nice comment', :body => 'anything', :author => owner); c1.save!
369 368
370 - assert_includes Article.find_by_contents('nice')[:results], art 369 + assert_includes Article.find_by_contents('nice'), art
371 end 370 end
372 371
373 should 'index comments body together with article' do 372 should 'index comments body together with article' do
@@ -375,7 +374,7 @@ class ArticleTest &lt; Test::Unit::TestCase @@ -375,7 +374,7 @@ class ArticleTest &lt; Test::Unit::TestCase
375 art = owner.articles.build(:name => 'ytest'); art.save! 374 art = owner.articles.build(:name => 'ytest'); art.save!
376 c1 = art.comments.build(:title => 'test comment', :body => 'anything', :author => owner); c1.save! 375 c1 = art.comments.build(:title => 'test comment', :body => 'anything', :author => owner); c1.save!
377 376
378 - assert_includes Article.find_by_contents('anything')[:results], art 377 + assert_includes Article.find_by_contents('anything'), art
379 end 378 end
380 379
381 should 'cache children count' do 380 should 'cache children count' do
@@ -1508,24 +1507,24 @@ class ArticleTest &lt; Test::Unit::TestCase @@ -1508,24 +1507,24 @@ class ArticleTest &lt; Test::Unit::TestCase
1508 should 'index by schema name when database is postgresql' do 1507 should 'index by schema name when database is postgresql' do
1509 uses_postgresql 'schema_one' 1508 uses_postgresql 'schema_one'
1510 art1 = Article.create!(:name => 'some thing', :profile_id => @profile.id) 1509 art1 = Article.create!(:name => 'some thing', :profile_id => @profile.id)
1511 - assert_equal Article.find_by_contents('thing')[:results], [art1] 1510 + assert_equal Article.find_by_contents('thing'), [art1]
1512 uses_postgresql 'schema_two' 1511 uses_postgresql 'schema_two'
1513 art2 = Article.create!(:name => 'another thing', :profile_id => @profile.id) 1512 art2 = Article.create!(:name => 'another thing', :profile_id => @profile.id)
1514 - assert_not_includes Article.find_by_contents('thing')[:results], art1  
1515 - assert_includes Article.find_by_contents('thing')[:results], art2 1513 + assert_not_includes Article.find_by_contents('thing'), art1
  1514 + assert_includes Article.find_by_contents('thing'), art2
1516 uses_postgresql 'schema_one' 1515 uses_postgresql 'schema_one'
1517 - assert_includes Article.find_by_contents('thing')[:results], art1  
1518 - assert_not_includes Article.find_by_contents('thing')[:results], art2 1516 + assert_includes Article.find_by_contents('thing'), art1
  1517 + assert_not_includes Article.find_by_contents('thing'), art2
1519 uses_sqlite 1518 uses_sqlite
1520 end 1519 end
1521 1520
1522 should 'not index by schema name when database is not postgresql' do 1521 should 'not index by schema name when database is not postgresql' do
1523 uses_sqlite 1522 uses_sqlite
1524 art1 = Article.create!(:name => 'some thing', :profile_id => @profile.id) 1523 art1 = Article.create!(:name => 'some thing', :profile_id => @profile.id)
1525 - assert_equal Article.find_by_contents('thing')[:results], [art1] 1524 + assert_equal Article.find_by_contents('thing'), [art1]
1526 art2 = Article.create!(:name => 'another thing', :profile_id => @profile.id) 1525 art2 = Article.create!(:name => 'another thing', :profile_id => @profile.id)
1527 - assert_includes Article.find_by_contents('thing')[:results], art1  
1528 - assert_includes Article.find_by_contents('thing')[:results], art2 1526 + assert_includes Article.find_by_contents('thing'), art1
  1527 + assert_includes Article.find_by_contents('thing'), art2
1529 end 1528 end
1530 1529
1531 should 'get images paths in article body' do 1530 should 'get images paths in article body' do
test/unit/category_finder_test.rb
@@ -3,12 +3,10 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -3,12 +3,10 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 class CategoryFinderTest < ActiveSupport::TestCase 3 class CategoryFinderTest < ActiveSupport::TestCase
4 4
5 def setup 5 def setup
6 - Test::Unit::TestCase::setup  
7 @category = Category.create!(:name => 'my category', :environment => Environment.default) 6 @category = Category.create!(:name => 'my category', :environment => Environment.default)
8 @finder = CategoryFinder.new(@category) 7 @finder = CategoryFinder.new(@category)
9 @product_category = fast_create(ProductCategory, :name => 'Products') 8 @product_category = fast_create(ProductCategory, :name => 'Products')
10 9
11 - Profile.rebuild_index  
12 Comment.skip_captcha! 10 Comment.skip_captcha!
13 end 11 end
14 12
test/unit/enterprise_test.rb
@@ -4,7 +4,6 @@ class EnterpriseTest &lt; Test::Unit::TestCase @@ -4,7 +4,6 @@ class EnterpriseTest &lt; Test::Unit::TestCase
4 fixtures :profiles, :environments, :users 4 fixtures :profiles, :environments, :users
5 5
6 def setup 6 def setup
7 - Test::Unit::TestCase::setup  
8 @product_category = fast_create(ProductCategory, :name => 'Products') 7 @product_category = fast_create(ProductCategory, :name => 'Products')
9 end 8 end
10 9
@@ -92,7 +91,7 @@ class EnterpriseTest &lt; Test::Unit::TestCase @@ -92,7 +91,7 @@ class EnterpriseTest &lt; Test::Unit::TestCase
92 91
93 ent2 = fast_create(Enterprise, :name => 'test2', :identifier => 'test2') 92 ent2 = fast_create(Enterprise, :name => 'test2', :identifier => 'test2')
94 93
95 - result = Enterprise.find_by_contents(prod_cat.name)[:results] 94 + result = Enterprise.find_by_contents(prod_cat.name)
96 95
97 assert_includes result, ent1 96 assert_includes result, ent1
98 assert_not_includes result, ent2 97 assert_not_includes result, ent2
@@ -106,7 +105,7 @@ class EnterpriseTest &lt; Test::Unit::TestCase @@ -106,7 +105,7 @@ class EnterpriseTest &lt; Test::Unit::TestCase
106 105
107 ent2 = fast_create(Enterprise, :name => 'test2', :identifier => 'test2') 106 ent2 = fast_create(Enterprise, :name => 'test2', :identifier => 'test2')
108 107
109 - result = Enterprise.find_by_contents(prod_cat.name)[:results] 108 + result = Enterprise.find_by_contents(prod_cat.name)
110 109
111 assert_includes result, ent1 110 assert_includes result, ent1
112 assert_not_includes result, ent2 111 assert_not_includes result, ent2
@@ -407,12 +406,6 @@ class EnterpriseTest &lt; Test::Unit::TestCase @@ -407,12 +406,6 @@ class EnterpriseTest &lt; Test::Unit::TestCase
407 406
408 assert_equal product.inputs, enterprise.inputs 407 assert_equal product.inputs, enterprise.inputs
409 end 408 end
410 -  
411 - should 'reindex when products are changed' do  
412 - a = Enterprise.new  
413 - a.expects(:solr_save)  
414 - a.product_updated  
415 - end  
416 409
417 should "the followed_by? be true only to members" do 410 should "the followed_by? be true only to members" do
418 e = fast_create(Enterprise) 411 e = fast_create(Enterprise)
test/unit/environment_finder_test.rb
@@ -3,7 +3,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -3,7 +3,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 class EnvironmentFinderTest < ActiveSupport::TestCase 3 class EnvironmentFinderTest < ActiveSupport::TestCase
4 4
5 def setup 5 def setup
6 - Test::Unit::TestCase::setup  
7 @product_category = fast_create(ProductCategory, :name => 'Products') 6 @product_category = fast_create(ProductCategory, :name => 'Products')
8 end 7 end
9 8
@@ -78,10 +77,10 @@ class EnvironmentFinderTest &lt; ActiveSupport::TestCase @@ -78,10 +77,10 @@ class EnvironmentFinderTest &lt; ActiveSupport::TestCase
78 finder = EnvironmentFinder.new(Environment.default) 77 finder = EnvironmentFinder.new(Environment.default)
79 78
80 region = fast_create(Region, :name => 'r-test', :environment_id => Environment.default.id, :lat => 45.0, :lng => 45.0) 79 region = fast_create(Region, :name => 'r-test', :environment_id => Environment.default.id, :lat => 45.0, :lng => 45.0)
81 - ent1 = fast_create(Enterprise, {:name => 'test 1', :identifier => 'test1', :lat => 45.0, :lng => 45.0}, :search => true) 80 + ent1 = fast_create(Enterprise, :name => 'test 1', :identifier => 'test1', :lat => 45.0, :lng => 45.0)
82 p1 = create_user('test2').person 81 p1 = create_user('test2').person
83 p1.name = 'test 2'; p1.lat = 45.0; p1.lng = 45.0; p1.save! 82 p1.name = 'test 2'; p1.lat = 45.0; p1.lng = 45.0; p1.save!
84 - ent2 = fast_create(Enterprise, {:name => 'test 3', :identifier => 'test3', :lat => 30.0, :lng => 30.0}, :search => true) 83 + ent2 = fast_create(Enterprise, :name => 'test 3', :identifier => 'test3', :lat => 30.0, :lng => 30.0)
85 p2 = create_user('test4').person 84 p2 = create_user('test4').person
86 p2.name = 'test 4'; p2.lat = 30.0; p2.lng = 30.0; p2.save! 85 p2.name = 'test 4'; p2.lat = 30.0; p2.lng = 30.0; p2.save!
87 86
test/unit/environment_test.rb
@@ -3,10 +3,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -3,10 +3,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 class EnvironmentTest < Test::Unit::TestCase 3 class EnvironmentTest < Test::Unit::TestCase
4 fixtures :environments 4 fixtures :environments
5 5
6 - def setup  
7 - Test::Unit::TestCase::setup  
8 - end  
9 -  
10 def test_exists_default_and_it_is_unique 6 def test_exists_default_and_it_is_unique
11 Environment.delete_all 7 Environment.delete_all
12 vc = Environment.new(:name => 'Test Community') 8 vc = Environment.new(:name => 'Test Community')
@@ -454,7 +450,7 @@ class EnvironmentTest &lt; Test::Unit::TestCase @@ -454,7 +450,7 @@ class EnvironmentTest &lt; Test::Unit::TestCase
454 should 'find by contents from articles' do 450 should 'find by contents from articles' do
455 environment = fast_create(Environment) 451 environment = fast_create(Environment)
456 assert_nothing_raised do 452 assert_nothing_raised do
457 - environment.articles.find_by_contents('')[:results] 453 + environment.articles.find_by_contents('')
458 end 454 end
459 end 455 end
460 456
@@ -571,7 +567,7 @@ class EnvironmentTest &lt; Test::Unit::TestCase @@ -571,7 +567,7 @@ class EnvironmentTest &lt; Test::Unit::TestCase
571 Enterprise.create!(:name => 'test ' + n, :identifier => 'test_' + n) 567 Enterprise.create!(:name => 'test ' + n, :identifier => 'test_' + n)
572 end 568 end
573 569
574 - assert_equal 20, env.enterprises.find_by_contents('test')[:results].total_entries 570 + assert_equal 20, env.enterprises.find_by_contents('test').total_entries
575 end 571 end
576 572
577 should 'set replace_enterprise_template_when_enable on environment' do 573 should 'set replace_enterprise_template_when_enable on environment' do
test/unit/event_test.rb
@@ -2,10 +2,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -2,10 +2,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
2 2
3 class EventTest < ActiveSupport::TestCase 3 class EventTest < ActiveSupport::TestCase
4 4
5 - def setup  
6 - Test::Unit::TestCase::setup  
7 - end  
8 -  
9 should 'be an article' do 5 should 'be an article' do
10 assert_kind_of Article, Event.new 6 assert_kind_of Article, Event.new
11 end 7 end
@@ -63,13 +59,13 @@ class EventTest &lt; ActiveSupport::TestCase @@ -63,13 +59,13 @@ class EventTest &lt; ActiveSupport::TestCase
63 should 'be indexed by title' do 59 should 'be indexed by title' do
64 profile = create_user('testuser').person 60 profile = create_user('testuser').person
65 e = Event.create!(:name => 'my surprisingly nice event', :start_date => Date.new(2008, 06, 06), :profile => profile) 61 e = Event.create!(:name => 'my surprisingly nice event', :start_date => Date.new(2008, 06, 06), :profile => profile)
66 - assert_includes Event.find_by_contents('surprisingly')[:results], e 62 + assert_includes Event.find_by_contents('surprisingly'), e
67 end 63 end
68 64
69 should 'be indexed by body' do 65 should 'be indexed by body' do
70 profile = create_user('testuser').person 66 profile = create_user('testuser').person
71 e = Event.create!(:name => 'bli', :start_date => Date.new(2008, 06, 06), :profile => profile, :body => 'my surprisingly long description about my freaking nice event') 67 e = Event.create!(:name => 'bli', :start_date => Date.new(2008, 06, 06), :profile => profile, :body => 'my surprisingly long description about my freaking nice event')
72 - assert_includes Event.find_by_contents('surprisingly')[:results], e 68 + assert_includes Event.find_by_contents('surprisingly'), e
73 end 69 end
74 70
75 should 'use its own icon' do 71 should 'use its own icon' do
test/unit/forum_helper_test.rb
@@ -51,14 +51,14 @@ class ForumHelperTest &lt; Test::Unit::TestCase @@ -51,14 +51,14 @@ class ForumHelperTest &lt; Test::Unit::TestCase
51 some_post.comments << Comment.new(:title => 'test', :body => 'test', :author => a2) 51 some_post.comments << Comment.new(:title => 'test', :body => 'test', :author => a2)
52 c = Comment.last 52 c = Comment.last
53 assert_equal 2, some_post.comments.count 53 assert_equal 2, some_post.comments.count
54 - assert_match(/#{Regexp.escape(c.created_at.to_s)} ago by <a href='[^']+'>a2<\/a>/, last_topic_update(some_post)) 54 + assert_match /#{c.created_at.to_s} ago by <a href='[^']+'>a2<\/a>/, last_topic_update(some_post)
55 end 55 end
56 56
57 should "return last comment author's name from unauthenticated user" do 57 should "return last comment author's name from unauthenticated user" do
58 some_post = TextileArticle.create!(:name => 'First post', :profile => profile, :parent => forum, :published => true) 58 some_post = TextileArticle.create!(:name => 'First post', :profile => profile, :parent => forum, :published => true)
59 some_post.comments << Comment.new(:name => 'John', :email => 'lenon@example.com', :title => 'test', :body => 'test') 59 some_post.comments << Comment.new(:name => 'John', :email => 'lenon@example.com', :title => 'test', :body => 'test')
60 c = Comment.last 60 c = Comment.last
61 - assert_match(/#{Regexp.escape(c.created_at.to_s)} ago by John/m, last_topic_update(some_post)) 61 + assert_match /#{c.created_at.to_s} ago by John/m, last_topic_update(some_post)
62 end 62 end
63 63
64 protected 64 protected
test/unit/product_test.rb
@@ -3,7 +3,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -3,7 +3,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 class ProductTest < Test::Unit::TestCase 3 class ProductTest < Test::Unit::TestCase
4 4
5 def setup 5 def setup
6 - Test::Unit::TestCase::setup  
7 @product_category = fast_create(ProductCategory, :name => 'Products') 6 @product_category = fast_create(ProductCategory, :name => 'Products')
8 end 7 end
9 8
@@ -93,7 +92,7 @@ class ProductTest &lt; Test::Unit::TestCase @@ -93,7 +92,7 @@ class ProductTest &lt; Test::Unit::TestCase
93 p.stubs(:category_full_name).returns('interesting category') 92 p.stubs(:category_full_name).returns('interesting category')
94 p.save! 93 p.save!
95 94
96 - assert_includes Product.find_by_contents('interesting')[:results], p 95 + assert_includes Product.find_by_contents('interesting'), p
97 end 96 end
98 97
99 should 'have same lat and lng of its enterprise' do 98 should 'have same lat and lng of its enterprise' do
@@ -363,24 +362,24 @@ class ProductTest &lt; Test::Unit::TestCase @@ -363,24 +362,24 @@ class ProductTest &lt; Test::Unit::TestCase
363 should 'index by schema name when database is postgresql' do 362 should 'index by schema name when database is postgresql' do
364 uses_postgresql 'schema_one' 363 uses_postgresql 'schema_one'
365 p1 = Product.create!(:name => 'some thing', :product_category => @product_category) 364 p1 = Product.create!(:name => 'some thing', :product_category => @product_category)
366 - assert_equal Product.find_by_contents('thing')[:results], [p1] 365 + assert_equal Product.find_by_contents('thing'), [p1]
367 uses_postgresql 'schema_two' 366 uses_postgresql 'schema_two'
368 p2 = Product.create!(:name => 'another thing', :product_category => @product_category) 367 p2 = Product.create!(:name => 'another thing', :product_category => @product_category)
369 - assert_not_includes Product.find_by_contents('thing')[:results], p1  
370 - assert_includes Product.find_by_contents('thing')[:results], p2 368 + assert_not_includes Product.find_by_contents('thing'), p1
  369 + assert_includes Product.find_by_contents('thing'), p2
371 uses_postgresql 'schema_one' 370 uses_postgresql 'schema_one'
372 - assert_includes Product.find_by_contents('thing')[:results], p1  
373 - assert_not_includes Product.find_by_contents('thing')[:results], p2 371 + assert_includes Product.find_by_contents('thing'), p1
  372 + assert_not_includes Product.find_by_contents('thing'), p2
374 uses_sqlite 373 uses_sqlite
375 end 374 end
376 375
377 should 'not index by schema name when database is not postgresql' do 376 should 'not index by schema name when database is not postgresql' do
378 uses_sqlite 377 uses_sqlite
379 p1 = Product.create!(:name => 'some thing', :product_category => @product_category) 378 p1 = Product.create!(:name => 'some thing', :product_category => @product_category)
380 - assert_equal Product.find_by_contents('thing')[:results], [p1] 379 + assert_equal Product.find_by_contents('thing'), [p1]
381 p2 = Product.create!(:name => 'another thing', :product_category => @product_category) 380 p2 = Product.create!(:name => 'another thing', :product_category => @product_category)
382 - assert_includes Product.find_by_contents('thing')[:results], p1  
383 - assert_includes Product.find_by_contents('thing')[:results], p2 381 + assert_includes Product.find_by_contents('thing'), p1
  382 + assert_includes Product.find_by_contents('thing'), p2
384 end 383 end
385 384
386 end 385 end
test/unit/profile_test.rb
@@ -3,10 +3,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -3,10 +3,6 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 class ProfileTest < Test::Unit::TestCase 3 class ProfileTest < Test::Unit::TestCase
4 fixtures :profiles, :environments, :users, :roles, :domains 4 fixtures :profiles, :environments, :users, :roles, :domains
5 5
6 - def setup  
7 - Test::Unit::TestCase::setup  
8 - end  
9 -  
10 def test_identifier_validation 6 def test_identifier_validation
11 p = Profile.new 7 p = Profile.new
12 p.valid? 8 p.valid?
@@ -104,8 +100,8 @@ class ProfileTest &lt; Test::Unit::TestCase @@ -104,8 +100,8 @@ class ProfileTest &lt; Test::Unit::TestCase
104 def test_find_by_contents 100 def test_find_by_contents
105 p = create(Profile, :name => 'wanted') 101 p = create(Profile, :name => 'wanted')
106 102
107 - assert Profile.find_by_contents('wanted')[:results].include?(p)  
108 - assert ! Profile.find_by_contents('not_wanted')[:results].include?(p) 103 + assert Profile.find_by_contents('wanted').include?(p)
  104 + assert ! Profile.find_by_contents('not_wanted').include?(p)
109 end 105 end
110 106
111 should 'remove pages when removing profile' do 107 should 'remove pages when removing profile' do
@@ -196,10 +192,10 @@ class ProfileTest &lt; Test::Unit::TestCase @@ -196,10 +192,10 @@ class ProfileTest &lt; Test::Unit::TestCase
196 small = create(Profile, :name => 'A small profile for testing') 192 small = create(Profile, :name => 'A small profile for testing')
197 big = create(Profile, :name => 'A big profile for testing') 193 big = create(Profile, :name => 'A big profile for testing')
198 194
199 - assert Profile.find_by_contents('small')[:results].include?(small)  
200 - assert Profile.find_by_contents('big')[:results].include?(big) 195 + assert Profile.find_by_contents('small').include?(small)
  196 + assert Profile.find_by_contents('big').include?(big)
201 197
202 - both = Profile.find_by_contents('profile testing')[:results] 198 + both = Profile.find_by_contents('profile testing')
203 assert both.include?(small) 199 assert both.include?(small)
204 assert both.include?(big) 200 assert both.include?(big)
205 end 201 end
@@ -521,18 +517,18 @@ class ProfileTest &lt; Test::Unit::TestCase @@ -521,18 +517,18 @@ class ProfileTest &lt; Test::Unit::TestCase
521 should 'actually index by results of extra_data_for_index' do 517 should 'actually index by results of extra_data_for_index' do
522 profile = TestingExtraDataForIndex.create!(:name => 'testprofile', :identifier => 'testprofile') 518 profile = TestingExtraDataForIndex.create!(:name => 'testprofile', :identifier => 'testprofile')
523 519
524 - assert_includes TestingExtraDataForIndex.find_by_contents('sample')[:results], profile 520 + assert_includes TestingExtraDataForIndex.find_by_contents('sample'), profile
525 end 521 end
526 522
527 should 'index profile identifier for searching' do 523 should 'index profile identifier for searching' do
528 Profile.destroy_all 524 Profile.destroy_all
529 p = create(Profile, :identifier => 'lalala') 525 p = create(Profile, :identifier => 'lalala')
530 - assert_includes Profile.find_by_contents('lalala')[:results], p 526 + assert_includes Profile.find_by_contents('lalala'), p
531 end 527 end
532 528
533 should 'index profile name for searching' do 529 should 'index profile name for searching' do
534 p = create(Profile, :name => 'Interesting Profile') 530 p = create(Profile, :name => 'Interesting Profile')
535 - assert_includes Profile.find_by_contents('interesting')[:results], p 531 + assert_includes Profile.find_by_contents('interesting'), p
536 end 532 end
537 533
538 should 'enabled by default on creation' do 534 should 'enabled by default on creation' do
@@ -1669,24 +1665,24 @@ class ProfileTest &lt; Test::Unit::TestCase @@ -1669,24 +1665,24 @@ class ProfileTest &lt; Test::Unit::TestCase
1669 should 'index by schema name when database is postgresql' do 1665 should 'index by schema name when database is postgresql' do
1670 uses_postgresql 'schema_one' 1666 uses_postgresql 'schema_one'
1671 p1 = Profile.create!(:name => 'some thing', :identifier => 'some-thing') 1667 p1 = Profile.create!(:name => 'some thing', :identifier => 'some-thing')
1672 - assert_equal Profile.find_by_contents('thing')[:results], [p1] 1668 + assert_equal Profile.find_by_contents('thing'), [p1]
1673 uses_postgresql 'schema_two' 1669 uses_postgresql 'schema_two'
1674 p2 = Profile.create!(:name => 'another thing', :identifier => 'another-thing') 1670 p2 = Profile.create!(:name => 'another thing', :identifier => 'another-thing')
1675 - assert_not_includes Profile.find_by_contents('thing')[:results], p1  
1676 - assert_includes Profile.find_by_contents('thing')[:results], p2 1671 + assert_not_includes Profile.find_by_contents('thing'), p1
  1672 + assert_includes Profile.find_by_contents('thing'), p2
1677 uses_postgresql 'schema_one' 1673 uses_postgresql 'schema_one'
1678 - assert_includes Profile.find_by_contents('thing')[:results], p1  
1679 - assert_not_includes Profile.find_by_contents('thing')[:results], p2 1674 + assert_includes Profile.find_by_contents('thing'), p1
  1675 + assert_not_includes Profile.find_by_contents('thing'), p2
1680 uses_sqlite 1676 uses_sqlite
1681 end 1677 end
1682 1678
1683 should 'not index by schema name when database is not postgresql' do 1679 should 'not index by schema name when database is not postgresql' do
1684 uses_sqlite 1680 uses_sqlite
1685 p1 = Profile.create!(:name => 'some thing', :identifier => 'some-thing') 1681 p1 = Profile.create!(:name => 'some thing', :identifier => 'some-thing')
1686 - assert_equal Profile.find_by_contents('thing')[:results], [p1] 1682 + assert_equal Profile.find_by_contents('thing'), [p1]
1687 p2 = Profile.create!(:name => 'another thing', :identifier => 'another-thing') 1683 p2 = Profile.create!(:name => 'another thing', :identifier => 'another-thing')
1688 - assert_includes Profile.find_by_contents('thing')[:results], p1  
1689 - assert_includes Profile.find_by_contents('thing')[:results], p2 1684 + assert_includes Profile.find_by_contents('thing'), p1
  1685 + assert_includes Profile.find_by_contents('thing'), p2
1690 end 1686 end
1691 1687
1692 should 'know if url is the profile homepage' do 1688 should 'know if url is the profile homepage' do
test/unit/tiny_mce_article_test.rb
@@ -3,8 +3,7 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39; @@ -3,8 +3,7 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 class TinyMceArticleTest < Test::Unit::TestCase 3 class TinyMceArticleTest < Test::Unit::TestCase
4 4
5 def setup 5 def setup
6 - Test::Unit::TestCase::setup  
7 - Article.rebuild_solr_index 6 + Article.rebuild_index
8 @profile = create_user('zezinho').person 7 @profile = create_user('zezinho').person
9 end 8 end
10 attr_reader :profile 9 attr_reader :profile
@@ -24,8 +23,8 @@ class TinyMceArticleTest &lt; Test::Unit::TestCase @@ -24,8 +23,8 @@ class TinyMceArticleTest &lt; Test::Unit::TestCase
24 23
25 should 'be found when searching for articles by query' do 24 should 'be found when searching for articles by query' do
26 tma = TinyMceArticle.create!(:name => 'test tinymce article', :body => '---', :profile => profile) 25 tma = TinyMceArticle.create!(:name => 'test tinymce article', :body => '---', :profile => profile)
27 - assert_includes TinyMceArticle.find_by_contents('article')[:results], tma  
28 - assert_includes Article.find_by_contents('article')[:results], tma 26 + assert_includes TinyMceArticle.find_by_contents('article'), tma
  27 + assert_includes Article.find_by_contents('article'), tma
29 end 28 end
30 29
31 should 'not sanitize target attribute' do 30 should 'not sanitize target attribute' do
vendor/plugins/access_control/init.rb
@@ -2,5 +2,5 @@ require &#39;acts_as_accessor&#39; @@ -2,5 +2,5 @@ require &#39;acts_as_accessor&#39;
2 require 'acts_as_accessible' 2 require 'acts_as_accessible'
3 require 'permission_name_helper' 3 require 'permission_name_helper'
4 module ApplicationHelper 4 module ApplicationHelper
5 - include PermissionNameHelper 5 + include PermissionName
6 end 6 end
vendor/plugins/access_control/lib/permission_name_helper.rb
1 -module PermissionNameHelper 1 +module PermissionName
2 def permission_name(p) 2 def permission_name(p)
3 msgid = ActiveRecord::Base::PERMISSIONS.values.inject({}){|s,v| s.merge(v)}[p] 3 msgid = ActiveRecord::Base::PERMISSIONS.values.inject({}){|s,v| s.merge(v)}[p]
4 gettext(msgid) 4 gettext(msgid)
vendor/plugins/acts_as_ferret/LICENSE 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +Copyright (c) 2006 Kasper Weibel, Jens Kraemer
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
vendor/plugins/acts_as_ferret/README 0 → 100644
@@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
  1 += acts_as_ferret
  2 +
  3 +This ActiveRecord mixin adds full text search capabilities to any Rails model.
  4 +
  5 +It is heavily based on the original acts_as_ferret plugin done by
  6 +Kasper Weibel and a modified version done by Thomas Lockney, which
  7 +both can be found on http://ferret.davebalmain.com/trac/wiki/FerretOnRails
  8 +
  9 +== Installation
  10 +
  11 +=== Installation inside your Rails project via script/plugin
  12 +
  13 +script/plugin install svn://projects.jkraemer.net/acts_as_ferret/trunk/plugin/acts_as_ferret
  14 +
  15 +Aaf is is also available via git from Rubyforge:
  16 +git clone git://rubyforge.org/actsasferret.git
  17 +
  18 +=== System-wide installation with Rubygems
  19 +
  20 +<tt>sudo gem install acts_as_ferret</tt>
  21 +
  22 +To use acts_as_ferret in your project, add the following line to your
  23 +project's config/environment.rb:
  24 +
  25 +<tt>require 'acts_as_ferret'</tt>
  26 +
  27 +Call the aaf_install script inside your project directory to install the sample
  28 +config file and the drb server start/stop script.
  29 +
  30 +
  31 +== Usage
  32 +
  33 +include the following in your model class (specifiying the fields you want to get indexed):
  34 +
  35 +<tt>acts_as_ferret :fields => [ :title, :description ]</tt>
  36 +
  37 +now you can use ModelClass.find_by_contents(query) to find instances of your model
  38 +whose indexed fields match a given query. All query terms are required by default,
  39 +but explicit OR queries are possible. This differs from the ferret default, but
  40 +imho is the more often needed/expected behaviour (more query terms result in
  41 +less results).
  42 +
  43 +Please see ActsAsFerret::ActMethods#acts_as_ferret for more information.
  44 +
  45 +== License
  46 +
  47 +Released under the MIT license.
  48 +
  49 +== Authors
  50 +
  51 +* Kasper Weibel Nielsen-Refs (original author)
  52 +* Jens Kraemer <jk@jkraemer.net> (current maintainer)
  53 +
vendor/plugins/acts_as_ferret/bin/aaf_install 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +# acts_as_ferret gem install script
  2 +# Use inside the root of your Rails project
  3 +require 'fileutils'
  4 +
  5 +@basedir = File.join(File.dirname(__FILE__), '..')
  6 +
  7 +def install(dir, file, executable=false)
  8 + puts "Installing: #{file}"
  9 + target = File.join('.', dir, file)
  10 + if File.exists?(target)
  11 + puts "#{target} already exists, skipping"
  12 + else
  13 + FileUtils.cp File.join(@basedir, dir, file), target
  14 + FileUtils.chmod 0755, target if executable
  15 + end
  16 +end
  17 +
  18 +
  19 +install 'script', 'ferret_server', true
  20 +install 'config', 'ferret_server.yml'
  21 +
  22 +puts IO.read(File.join(@basedir, 'README'))
  23 +
vendor/plugins/acts_as_ferret/config/ferret_server.yml 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +# configuration for the acts_as_ferret DRb server
  2 +# host: where to reach the DRb server (used by application processes to contact the server)
  3 +# port: which port the server should listen on
  4 +# socket: where the DRb server should create the socket (absolute path), this setting overrides host:port configuration
  5 +# pid_file: location of the server's pid file (relative to RAILS_ROOT)
  6 +# log_file: log file (default: RAILS_ROOT/log/ferret_server.log
  7 +# log_level: log level for the server's logger
  8 +production:
  9 + host: localhost
  10 + port: 9010
  11 + pid_file: log/ferret.pid
  12 + log_file: log/ferret_server.log
  13 + log_level: warn
  14 +
  15 +# aaf won't try to use the DRb server in environments that are not
  16 +# configured here.
  17 +#development:
  18 +# host: localhost
  19 +# port: 9010
  20 +# pid_file: log/ferret.pid
  21 +#test:
  22 +# host: localhost
  23 +# port: 9009
  24 +# pid_file: log/ferret.pid
vendor/plugins/acts_as_ferret/doc/README.win32 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +Credits
  2 +=======
  3 +
  4 +The Win32 service support scripts have been written by
  5 +Herryanto Siatono <herryanto@pluitsolutions.com>.
  6 +
  7 +See his accompanying blog posting at
  8 +http://www.pluitsolutions.com/2007/07/30/acts-as-ferret-drbserver-win32-service/
  9 +
  10 +
  11 +Usage
  12 +=====
  13 +
  14 +There are two scripts:
  15 +
  16 +script/ferret_service is used to install/remove/start/stop the win32 service.
  17 +
  18 +script/ferret_daemon is to be called by Win32 service to start/stop the
  19 +DRbServer.
  20 +
  21 +Run 'ruby script/ferret_service -h' for more info.
  22 +
  23 +
vendor/plugins/acts_as_ferret/doc/monit-example 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +# monit configuration snippet to watch the Ferret DRb server shipped with
  2 +# acts_as_ferret
  3 +check process ferret with pidfile /path/to/ferret.pid
  4 +
  5 + # username is the user the drb server should be running as (It's good practice
  6 + # to run such services as a non-privileged user)
  7 + start program = "/bin/su -c 'cd /path/to/your/app/current/ && script/ferret_server -e production start' username"
  8 + stop program = "/bin/su -c 'cd /path/to/your/app/current/ && script/ferret_server -e production stop' username"
  9 +
  10 + # cpu usage boundaries
  11 + if cpu > 60% for 2 cycles then alert
  12 + if cpu > 90% for 5 cycles then restart
  13 +
  14 + # memory usage varies with index size and usage scenarios, so check how
  15 + # much memory your DRb server uses up usually and add some spare to that
  16 + # before enabling this rule:
  17 + # if totalmem > 50.0 MB for 5 cycles then restart
  18 +
  19 + # adjust port numbers according to your setup:
  20 + if failed port 9010 then alert
  21 + if failed port 9010 for 2 cycles then restart
  22 + group ferret
vendor/plugins/acts_as_ferret/init.rb 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +# Copyright (c) 2006 Kasper Weibel Nielsen-Refs, Thomas Lockney, Jens Krämer
  2 +#
  3 +# Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +# of this software and associated documentation files (the "Software"), to deal
  5 +# in the Software without restriction, including without limitation the rights
  6 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +# copies of the Software, and to permit persons to whom the Software is
  8 +# furnished to do so, subject to the following conditions:
  9 +#
  10 +# The above copyright notice and this permission notice shall be included in all
  11 +# copies or substantial portions of the Software.
  12 +#
  13 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19 +# SOFTWARE.
  20 +
  21 +require 'acts_as_ferret'
  22 +
  23 +config.after_initialize { ActsAsFerret::load_config }
  24 +config.to_prepare { ActsAsFerret::load_config }
vendor/plugins/acts_as_ferret/install.rb 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +# acts_as_ferret install script
  2 +require 'fileutils'
  3 +
  4 +def install(file)
  5 + puts "Installing: #{file}"
  6 + target = File.join(File.dirname(__FILE__), '..', '..', '..', file)
  7 + if File.exists?(target)
  8 + puts "target #{target} already exists, skipping"
  9 + else
  10 + FileUtils.cp File.join(File.dirname(__FILE__), file), target
  11 + end
  12 +end
  13 +
  14 +install File.join( 'script', 'ferret_server' )
  15 +install File.join( 'config', 'ferret_server.yml' )
  16 +
  17 +puts IO.read(File.join(File.dirname(__FILE__), 'README'))
  18 +
vendor/plugins/acts_as_ferret/lib/act_methods.rb 0 → 100644
@@ -0,0 +1,155 @@ @@ -0,0 +1,155 @@
  1 +module ActsAsFerret #:nodoc:
  2 +
  3 + # This module defines the acts_as_ferret method and is included into
  4 + # ActiveRecord::Base
  5 + module ActMethods
  6 +
  7 +
  8 + def reloadable?; false end
  9 +
  10 + # declares a class as ferret-searchable.
  11 + #
  12 + # ====options:
  13 + # fields:: names all fields to include in the index. If not given,
  14 + # all attributes of the class will be indexed. You may also give
  15 + # symbols pointing to instance methods of your model here, i.e.
  16 + # to retrieve and index data from a related model.
  17 + #
  18 + # additional_fields:: names fields to include in the index, in addition
  19 + # to those derived from the db scheme. use if you want
  20 + # to add custom fields derived from methods to the db
  21 + # fields (which will be picked by aaf). This option will
  22 + # be ignored when the fields option is given, in that
  23 + # case additional fields get specified there.
  24 + #
  25 + # if:: Can be set to a block that will be called with the record in question
  26 + # to determine if it should be indexed or not.
  27 + #
  28 + # index_dir:: declares the directory where to put the index for this class.
  29 + # The default is RAILS_ROOT/index/RAILS_ENV/CLASSNAME.
  30 + # The index directory will be created if it doesn't exist.
  31 + #
  32 + # reindex_batch_size:: reindexing is done in batches of this size, default is 1000
  33 + # mysql_fast_batches:: set this to false to disable the faster mysql batching
  34 + # algorithm if this model uses a non-integer primary key named
  35 + # 'id' on MySQL.
  36 + #
  37 + # ferret:: Hash of Options that directly influence the way the Ferret engine works. You
  38 + # can use most of the options the Ferret::I class accepts here, too. Among the
  39 + # more useful are:
  40 + #
  41 + # or_default:: whether query terms are required by
  42 + # default (the default, false), or not (true)
  43 + #
  44 + # analyzer:: the analyzer to use for query parsing (default: nil,
  45 + # which means the ferret StandardAnalyzer gets used)
  46 + #
  47 + # default_field:: use to set one or more fields that are searched for query terms
  48 + # that don't have an explicit field list. This list should *not*
  49 + # contain any untokenized fields. If it does, you're asking
  50 + # for trouble (i.e. not getting results for queries having
  51 + # stop words in them). Aaf by default initializes the default field
  52 + # list to contain all tokenized fields. If you use :single_index => true,
  53 + # you really should set this option specifying your default field
  54 + # list (which should be equal in all your classes sharing the index).
  55 + # Otherwise you might get incorrect search results and you won't get
  56 + # any lazy loading of stored field data.
  57 + #
  58 + # For downwards compatibility reasons you can also specify the Ferret options in the
  59 + # last Hash argument.
  60 + def acts_as_ferret(options={})
  61 +
  62 + extend ClassMethods
  63 +
  64 + include InstanceMethods
  65 + include MoreLikeThis::InstanceMethods
  66 +
  67 + if options[:rdig]
  68 + cattr_accessor :rdig_configuration
  69 + self.rdig_configuration = options[:rdig]
  70 + require 'rdig_adapter'
  71 + include ActsAsFerret::RdigAdapter
  72 + end
  73 +
  74 + unless included_modules.include?(ActsAsFerret::WithoutAR)
  75 + # set up AR hooks
  76 + after_create :ferret_create
  77 + after_update :ferret_update
  78 + after_destroy :ferret_destroy
  79 + end
  80 +
  81 + cattr_accessor :aaf_configuration
  82 +
  83 + # apply default config for rdig based models
  84 + if options[:rdig]
  85 + options[:fields] ||= { :title => { :boost => 3, :store => :yes },
  86 + :content => { :store => :yes } }
  87 + end
  88 +
  89 + # name of this index
  90 + index_name = options.delete(:index) || self.name.underscore
  91 +
  92 + index = ActsAsFerret::register_class_with_index(self, index_name, options)
  93 + self.aaf_configuration = index.index_definition.dup
  94 + logger.debug "configured index for class #{self.name}:\n#{aaf_configuration.inspect}"
  95 +
  96 + # update our copy of the global index config with options local to this class
  97 + aaf_configuration[:class_name] ||= self.name
  98 + aaf_configuration[:if] ||= options[:if]
  99 +
  100 + # add methods for retrieving field values
  101 + add_fields options[:fields]
  102 + add_fields options[:additional_fields]
  103 + add_fields aaf_configuration[:fields]
  104 + add_fields aaf_configuration[:additional_fields]
  105 +
  106 + # not good at class level, index might get initialized too early
  107 + #if options[:remote]
  108 + # aaf_index.ensure_index_exists
  109 + #end
  110 + end
  111 +
  112 +
  113 + protected
  114 +
  115 +
  116 + # helper to defines a method which adds the given field to a ferret
  117 + # document instance
  118 + def define_to_field_method(field, options = {})
  119 + method_name = "#{field}_to_ferret"
  120 + return if instance_methods.include?(method_name) # already defined
  121 + aaf_configuration[:defined_fields] ||= {}
  122 + aaf_configuration[:defined_fields][field] = options
  123 + dynamic_boost = options[:boost] if options[:boost].is_a?(Symbol)
  124 + via = options[:via] || field
  125 + define_method(method_name.to_sym) do
  126 + val = begin
  127 + content_for_field_name(field, via, dynamic_boost)
  128 + rescue
  129 + logger.warn("Error retrieving value for field #{field}: #{$!}")
  130 + ''
  131 + end
  132 + logger.debug("Adding field #{field} with value '#{val}' to index")
  133 + val
  134 + end
  135 + end
  136 +
  137 + def add_fields(field_config)
  138 + # TODO
  139 + #field_config.each do |*args|
  140 + # define_to_field_method *args
  141 + #end
  142 + if field_config.is_a? Hash
  143 + field_config.each_pair do |field, options|
  144 + define_to_field_method field, options
  145 + end
  146 + elsif field_config.respond_to?(:each)
  147 + field_config.each do |field|
  148 + define_to_field_method field
  149 + end
  150 + end
  151 + end
  152 +
  153 + end
  154 +
  155 +end
vendor/plugins/acts_as_ferret/lib/acts_as_ferret.rb 0 → 100644
@@ -0,0 +1,567 @@ @@ -0,0 +1,567 @@
  1 +# Copyright (c) 2006 Kasper Weibel Nielsen-Refs, Thomas Lockney, Jens Krämer
  2 +#
  3 +# Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +# of this software and associated documentation files (the "Software"), to deal
  5 +# in the Software without restriction, including without limitation the rights
  6 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +# copies of the Software, and to permit persons to whom the Software is
  8 +# furnished to do so, subject to the following conditions:
  9 +#
  10 +# The above copyright notice and this permission notice shall be included in all
  11 +# copies or substantial portions of the Software.
  12 +#
  13 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19 +# SOFTWARE.
  20 +
  21 +require 'active_support'
  22 +require 'active_record'
  23 +require 'set'
  24 +require 'enumerator'
  25 +require 'ferret'
  26 +
  27 +require 'ferret_find_methods'
  28 +require 'remote_functions'
  29 +require 'blank_slate'
  30 +require 'bulk_indexer'
  31 +require 'ferret_extensions'
  32 +require 'act_methods'
  33 +require 'search_results'
  34 +require 'class_methods'
  35 +require 'ferret_result'
  36 +require 'instance_methods'
  37 +require 'without_ar'
  38 +
  39 +require 'multi_index'
  40 +require 'remote_multi_index'
  41 +require 'more_like_this'
  42 +
  43 +require 'index'
  44 +require 'local_index'
  45 +require 'remote_index'
  46 +
  47 +require 'ferret_server'
  48 +
  49 +require 'rdig_adapter'
  50 +
  51 +# The Rails ActiveRecord Ferret Mixin.
  52 +#
  53 +# This mixin adds full text search capabilities to any Rails model.
  54 +#
  55 +# The current version emerged from on the original acts_as_ferret plugin done by
  56 +# Kasper Weibel and a modified version done by Thomas Lockney, which both can be
  57 +# found on the Ferret Wiki: http://ferret.davebalmain.com/trac/wiki/FerretOnRails.
  58 +#
  59 +# basic usage:
  60 +# include the following in your model class (specifiying the fields you want to get indexed):
  61 +# acts_as_ferret :fields => [ :title, :description ]
  62 +#
  63 +# now you can use ModelClass.find_by_contents(query) to find instances of your model
  64 +# whose indexed fields match a given query. All query terms are required by default, but
  65 +# explicit OR queries are possible. This differs from the ferret default, but imho is the more
  66 +# often needed/expected behaviour (more query terms result in less results).
  67 +#
  68 +# Released under the MIT license.
  69 +#
  70 +# Authors:
  71 +# Kasper Weibel Nielsen-Refs (original author)
  72 +# Jens Kraemer <jk@jkraemer.net> (active maintainer since 2006)
  73 +#
  74 +#
  75 +# == Global properties
  76 +#
  77 +# raise_drb_errors:: Set this to true if you want aaf to raise Exceptions
  78 +# in case the DRb server cannot be reached (in other word - behave like
  79 +# versions up to 0.4.3). Defaults to false so DRb exceptions
  80 +# are logged but not raised. Be sure to set up some
  81 +# monitoring so you still detect when your DRb server died for
  82 +# whatever reason.
  83 +#
  84 +# remote:: Set this to false to force acts_as_ferret into local (non-DRb) mode even if
  85 +# config/ferret_server.yml contains a section for the current RAILS_ENV
  86 +# Usually you won't need to touch this option - just configure DRb for
  87 +# production mode in ferret_server.yml.
  88 +#
  89 +module ActsAsFerret
  90 +
  91 + class ActsAsFerretError < StandardError; end
  92 + class IndexNotDefined < ActsAsFerretError; end
  93 + class IndexAlreadyDefined < ActsAsFerretError; end
  94 +
  95 + # global Hash containing all multi indexes created by all classes using the plugin
  96 + # key is the concatenation of alphabetically sorted names of the classes the
  97 + # searcher searches.
  98 + @@multi_indexes = Hash.new
  99 + def self.multi_indexes; @@multi_indexes end
  100 +
  101 + # global Hash containing the ferret indexes of all classes using the plugin
  102 + # key is the index name.
  103 + @@ferret_indexes = Hash.new
  104 + def self.ferret_indexes; @@ferret_indexes end
  105 +
  106 + # mapping from class name to index name
  107 + @@index_using_classes = {}
  108 +
  109 + @@logger = Logger.new "#{RAILS_ROOT}/log/acts_as_ferret.log"
  110 + @@logger.level = ActiveRecord::Base.logger.level rescue Logger::DEBUG
  111 + mattr_accessor :logger
  112 +
  113 +
  114 + # Default ferret configuration for index fields
  115 + DEFAULT_FIELD_OPTIONS = {
  116 + :store => :no,
  117 + :highlight => :yes,
  118 + :index => :yes,
  119 + :term_vector => :with_positions_offsets,
  120 + :boost => 1.0
  121 + }
  122 +
  123 + @@raise_drb_errors = false
  124 + mattr_writer :raise_drb_errors
  125 + def self.raise_drb_errors?; @@raise_drb_errors end
  126 +
  127 + @@remote = nil
  128 + mattr_accessor :remote
  129 + def self.remote?
  130 + if @@remote.nil?
  131 + if ENV["FERRET_USE_LOCAL_INDEX"] || ActsAsFerret::Remote::Server.running
  132 + @@remote = false
  133 + else
  134 + @@remote = ActsAsFerret::Remote::Config.new.uri rescue false
  135 + end
  136 + if @@remote
  137 + logger.info "Will use remote index server which should be available at #{@@remote}"
  138 + else
  139 + logger.info "Will use local index."
  140 + end
  141 + end
  142 + @@remote
  143 + end
  144 + remote?
  145 +
  146 +
  147 + # Globally declares an index.
  148 + #
  149 + # This method is also used to implicitly declare an index when you use the
  150 + # acts_as_ferret call in your class. Returns the created index instance.
  151 + #
  152 + # === Options are:
  153 + #
  154 + # +models+:: Hash of model classes and their per-class option hashes which should
  155 + # use this index. Any models mentioned here will automatically use
  156 + # the index, there is no need to explicitly call +acts_as_ferret+ in the
  157 + # model class definition.
  158 + def self.define_index(name, options = {})
  159 + name = name.to_sym
  160 + pending_classes = nil
  161 + if ferret_indexes.has_key?(name)
  162 + # seems models have been already loaded. remove that index for now,
  163 + # re-register any already loaded classes later on.
  164 + idx = get_index(name)
  165 + pending_classes = idx.index_definition[:registered_models]
  166 + pending_classes_configs = idx.registered_models_config
  167 + idx.close
  168 + ferret_indexes.delete(name)
  169 + end
  170 +
  171 + index_definition = {
  172 + :index_dir => "#{ActsAsFerret::index_dir}/#{name}",
  173 + :name => name,
  174 + :single_index => false,
  175 + :reindex_batch_size => 1000,
  176 + :ferret => {},
  177 + :ferret_fields => {}, # list of indexed fields that will be filled later
  178 + :enabled => true, # used for class-wide disabling of Ferret
  179 + :mysql_fast_batches => true, # turn off to disable the faster, id based batching mechanism for MySQL
  180 + :raise_drb_errors => false # handle DRb connection errors by default
  181 + }.update( options )
  182 +
  183 + index_definition[:registered_models] = []
  184 +
  185 + # build ferret configuration
  186 + index_definition[:ferret] = {
  187 + :or_default => false,
  188 + :handle_parse_errors => true,
  189 + :default_field => nil, # will be set later on
  190 + #:max_clauses => 512,
  191 + #:analyzer => Ferret::Analysis::StandardAnalyzer.new,
  192 + # :wild_card_downcase => true
  193 + }.update( options[:ferret] || {} )
  194 +
  195 + index_definition[:user_default_field] = index_definition[:ferret][:default_field]
  196 +
  197 + unless remote?
  198 + ActsAsFerret::ensure_directory index_definition[:index_dir]
  199 + index_definition[:index_base_dir] = index_definition[:index_dir]
  200 + index_definition[:index_dir] = find_last_index_version(index_definition[:index_dir])
  201 + logger.debug "using index in #{index_definition[:index_dir]}"
  202 + end
  203 +
  204 + # these properties are somewhat vital to the plugin and shouldn't
  205 + # be overwritten by the user:
  206 + index_definition[:ferret].update(
  207 + :key => ((Noosfero::MultiTenancy.on? and ActiveRecord::Base.postgresql?) ? [:id, :class_name, :schema_name] : [:id, :class_name]),
  208 + :path => index_definition[:index_dir],
  209 + :auto_flush => true, # slower but more secure in terms of locking problems TODO disable when running in drb mode?
  210 + :create_if_missing => true
  211 + )
  212 +
  213 + # field config
  214 + index_definition[:ferret_fields] = build_field_config( options[:fields] )
  215 + index_definition[:ferret_fields].update build_field_config( options[:additional_fields] )
  216 +
  217 + idx = ferret_indexes[name] = create_index_instance( index_definition )
  218 +
  219 + # re-register early loaded classes
  220 + if pending_classes
  221 + pending_classes.each { |clazz| idx.register_class clazz, { :force_re_registration => true }.merge(pending_classes_configs[clazz]) }
  222 + end
  223 +
  224 + if models = options[:models]
  225 + models.each do |clazz, config|
  226 + clazz.send :include, ActsAsFerret::WithoutAR unless clazz.respond_to?(:acts_as_ferret)
  227 + clazz.acts_as_ferret config.merge(:index => name)
  228 + end
  229 + end
  230 +
  231 + return idx
  232 + end
  233 +
  234 + # called internally by the acts_as_ferret method
  235 + #
  236 + # returns the index
  237 + def self.register_class_with_index(clazz, index_name, options = {})
  238 + index_name = index_name.to_sym
  239 + @@index_using_classes[clazz.name] = index_name
  240 + unless index = ferret_indexes[index_name]
  241 + # index definition on the fly
  242 + # default to all attributes of this class
  243 + options[:fields] ||= clazz.new.attributes.keys.map { |k| k.to_sym }
  244 + index = define_index index_name, options
  245 + end
  246 + index.register_class(clazz, options)
  247 + return index
  248 + end
  249 +
  250 + def self.load_config
  251 + # using require_dependency to make the reloading in dev mode work.
  252 + require_dependency "#{RAILS_ROOT}/config/aaf.rb"
  253 + ActsAsFerret::logger.info "loaded configuration file aaf.rb"
  254 + rescue LoadError
  255 + ensure
  256 + @aaf_config_loaded = true
  257 + end
  258 +
  259 + # returns the index with the given name.
  260 + def self.get_index(name)
  261 + name = name.to_sym rescue nil
  262 + unless ferret_indexes.has_key?(name)
  263 + if @aaf_config_loaded
  264 + raise IndexNotDefined.new(name.to_s)
  265 + else
  266 + load_config and return get_index name
  267 + end
  268 + end
  269 + ferret_indexes[name]
  270 + end
  271 +
  272 + # count hits for a query with multiple models
  273 + def self.total_hits(query, models, options = {})
  274 + find_index(models).total_hits query, options.merge( :models => models )
  275 + end
  276 +
  277 + # find ids of records with multiple models
  278 + # TODO pagination logic?
  279 + def self.find_ids(query, models, options = {}, &block)
  280 + find_index(models).find_ids query, options.merge( :models => models ), &block
  281 + end
  282 +
  283 + def self.find_index(models_or_index_name)
  284 + case models_or_index_name
  285 + when Symbol
  286 + get_index models_or_index_name
  287 + when String
  288 + get_index models_or_index_name.to_sym
  289 + #when Array
  290 + # get_index_for models_or_index_name
  291 + else
  292 + get_index_for models_or_index_name
  293 + end
  294 + end
  295 +
  296 + def self.find(query, models_or_index_name, options = {}, ar_options = {})
  297 + # TODO generalize local/remote index so we can remove the workaround below
  298 + # (replace logic in class_methods#find_with_ferret)
  299 + # maybe put pagination stuff in a module to be included by all index
  300 + # implementations
  301 + models = [ models_or_index_name ] if Class === models_or_index_name
  302 + if models && models.size == 1
  303 + return models.shift.find_with_ferret query, options, ar_options
  304 + end
  305 + index = find_index(models_or_index_name)
  306 + multi = (MultiIndex === index or index.shared?)
  307 + if options[:per_page]
  308 + options[:page] = options[:page] ? options[:page].to_i : 1
  309 + limit = options[:per_page]
  310 + offset = (options[:page] - 1) * limit
  311 + if ar_options[:conditions] && !multi
  312 + ar_options[:limit] = limit
  313 + ar_options[:offset] = offset
  314 + options[:limit] = :all
  315 + options.delete :offset
  316 + else
  317 + # do pagination with ferret (or after everything is done in the case
  318 + # of multi_search)
  319 + options[:limit] = limit
  320 + options[:offset] = offset
  321 + end
  322 + elsif ar_options[:conditions]
  323 + if multi
  324 + # multisearch ignores find_options limit and offset
  325 + options[:limit] ||= ar_options.delete(:limit)
  326 + options[:offset] ||= ar_options.delete(:offset)
  327 + else
  328 + # let the db do the limiting and offsetting for single-table searches
  329 + unless options[:limit] == :all
  330 + ar_options[:limit] ||= options.delete(:limit)
  331 + end
  332 + ar_options[:offset] ||= options.delete(:offset)
  333 + options[:limit] = :all
  334 + end
  335 + end
  336 +
  337 + total_hits, result = index.find_records query, options.merge(:models => models), ar_options
  338 + logger.debug "Query: #{query}\ntotal hits: #{total_hits}, results delivered: #{result.size}"
  339 + SearchResults.new(result, total_hits, options[:page], options[:per_page])
  340 + end
  341 +
  342 + # returns the index used by the given class.
  343 + #
  344 + # If multiple classes are given, either the single index shared by these
  345 + # classes, or a multi index (to be used for search only) across the indexes
  346 + # of all models, is returned.
  347 + def self.get_index_for(*classes)
  348 + classes.flatten!
  349 + raise ArgumentError.new("no class specified") unless classes.any?
  350 + classes.map!(&:constantize) unless Class === classes.first
  351 + logger.debug "index_for #{classes.inspect}"
  352 + index = if classes.size > 1
  353 + indexes = classes.map { |c| get_index_for c }.uniq
  354 + indexes.size > 1 ? multi_index(indexes) : indexes.first
  355 + else
  356 + clazz = classes.first
  357 + clazz = clazz.superclass while clazz && !@@index_using_classes.has_key?(clazz.name)
  358 + get_index @@index_using_classes[clazz.name]
  359 + end
  360 + raise IndexNotDefined.new("no index found for class: #{classes.map(&:name).join(',')}") if index.nil?
  361 + return index
  362 + end
  363 +
  364 +
  365 + # creates a new Index instance.
  366 + def self.create_index_instance(definition)
  367 + (remote? ? RemoteIndex : LocalIndex).new(definition)
  368 + end
  369 +
  370 + def self.rebuild_index(name)
  371 + get_index(name).rebuild_index
  372 + end
  373 +
  374 + def self.change_index_dir(name, new_dir)
  375 + get_index(name).change_index_dir new_dir
  376 + end
  377 +
  378 + # find the most recent version of an index
  379 + def self.find_last_index_version(basedir)
  380 + # check for versioned index
  381 + versions = Dir.entries(basedir).select do |f|
  382 + dir = File.join(basedir, f)
  383 + File.directory?(dir) && File.file?(File.join(dir, 'segments')) && f =~ /^\d+(_\d+)?$/
  384 + end
  385 + if versions.any?
  386 + # select latest version
  387 + versions.sort!
  388 + File.join basedir, versions.last
  389 + else
  390 + basedir
  391 + end
  392 + end
  393 +
  394 + # returns a MultiIndex instance operating on a MultiReader
  395 + def self.multi_index(indexes)
  396 + index_names = indexes.dup
  397 + index_names = index_names.map(&:to_s) if Symbol === index_names.first
  398 + if String === index_names.first
  399 + indexes = index_names.map{ |name| get_index name }
  400 + else
  401 + index_names = index_names.map{ |i| i.index_name.to_s }
  402 + end
  403 + key = index_names.sort.join(",")
  404 + ActsAsFerret::multi_indexes[key] ||= (remote? ? ActsAsFerret::RemoteMultiIndex : ActsAsFerret::MultiIndex).new(indexes)
  405 + end
  406 +
  407 + # check for per-model conditions and return these if provided
  408 + def self.conditions_for_model(model, conditions = {})
  409 + if Hash === conditions
  410 + key = model.name.underscore.to_sym
  411 + conditions = conditions[key]
  412 + end
  413 + return conditions
  414 + end
  415 +
  416 + # retrieves search result records from a data structure like this:
  417 + # { 'Model1' => { '1' => [ rank, score ], '2' => [ rank, score ] }
  418 + #
  419 + # TODO: in case of STI AR will filter out hits from other
  420 + # classes for us, but this
  421 + # will lead to less results retrieved --> scoping of ferret query
  422 + # to self.class is still needed.
  423 + # from the ferret ML (thanks Curtis Hatter)
  424 + # > I created a method in my base STI class so I can scope my query. For scoping
  425 + # > I used something like the following line:
  426 + # >
  427 + # > query << " role:#{self.class.eql?(Contents) '*' : self.class}"
  428 + # >
  429 + # > Though you could make it more generic by simply asking
  430 + # > "self.descends_from_active_record?" which is how rails decides if it should
  431 + # > scope your "find" query for STI models. You can check out "base.rb" in
  432 + # > activerecord to see that.
  433 + # but maybe better do the scoping in find_ids_with_ferret...
  434 + def self.retrieve_records(id_arrays, find_options = {})
  435 + result = []
  436 + # get objects for each model
  437 + id_arrays.each do |model, id_array|
  438 + next if id_array.empty?
  439 + model_class = model.constantize
  440 +
  441 + # merge conditions
  442 + conditions = conditions_for_model model_class, find_options[:conditions]
  443 + conditions = combine_conditions([ "#{model_class.table_name}.#{model_class.primary_key} in (?)",
  444 + id_array.keys ],
  445 + conditions)
  446 +
  447 + # check for include association that might only exist on some models in case of multi_search
  448 + filtered_include_options = []
  449 + if include_options = find_options[:include]
  450 + include_options = [ include_options ] unless include_options.respond_to?(:each)
  451 + include_options.each do |include_option|
  452 + filtered_include_options << include_option if model_class.reflections.has_key?(include_option.is_a?(Hash) ? include_option.keys[0].to_sym : include_option.to_sym)
  453 + end
  454 + end
  455 + filtered_include_options = nil if filtered_include_options.empty?
  456 +
  457 + # fetch
  458 + tmp_result = model_class.find(:all, find_options.merge(:conditions => conditions,
  459 + :include => filtered_include_options))
  460 +
  461 + # set scores and rank
  462 + tmp_result.each do |record|
  463 + record.ferret_rank, record.ferret_score = id_array[record.id.to_s]
  464 + end
  465 + # merge with result array
  466 + result += tmp_result
  467 + end
  468 +
  469 + # order results as they were found by ferret, unless an AR :order
  470 + # option was given
  471 + result.sort! { |a, b| a.ferret_rank <=> b.ferret_rank } unless find_options[:order]
  472 + return result
  473 + end
  474 +
  475 + # combine our conditions with those given by user, if any
  476 + def self.combine_conditions(conditions, additional_conditions = [])
  477 + returning conditions do
  478 + if additional_conditions && additional_conditions.any?
  479 + cust_opts = (Array === additional_conditions) ? additional_conditions.dup : [ additional_conditions ]
  480 + logger.debug "cust_opts: #{cust_opts.inspect}"
  481 + conditions.first << " and " << cust_opts.shift
  482 + conditions.concat(cust_opts)
  483 + end
  484 + end
  485 + end
  486 +
  487 + def self.build_field_config(fields)
  488 + field_config = {}
  489 + case fields
  490 + when Array
  491 + fields.each { |name| field_config[name] = field_config_for name }
  492 + when Hash
  493 + fields.each { |name, options| field_config[name] = field_config_for name, options }
  494 + else raise InvalidArgumentError.new(":fields option must be Hash or Array")
  495 + end if fields
  496 + return field_config
  497 + end
  498 +
  499 + def self.ensure_directory(dir)
  500 + FileUtils.mkdir_p dir unless (File.directory?(dir) || File.symlink?(dir))
  501 + end
  502 +
  503 +
  504 + # make sure the default index base dir exists. by default, all indexes are created
  505 + # under RAILS_ROOT/index/RAILS_ENV
  506 + def self.init_index_basedir
  507 + index_base = "#{RAILS_ROOT}/index"
  508 + @@index_dir = "#{index_base}/#{RAILS_ENV}"
  509 + end
  510 +
  511 + mattr_accessor :index_dir
  512 + init_index_basedir
  513 +
  514 + def self.append_features(base)
  515 + super
  516 + base.extend(ClassMethods)
  517 + end
  518 +
  519 + # builds a FieldInfos instance for creation of an index
  520 + def self.field_infos(index_definition)
  521 + # default attributes for fields
  522 + fi = Ferret::Index::FieldInfos.new(:store => :no,
  523 + :index => :yes,
  524 + :term_vector => :no,
  525 + :boost => 1.0)
  526 + # primary key
  527 + fi.add_field(:id, :store => :yes, :index => :untokenized)
  528 + # class_name
  529 + fi.add_field(:class_name, :store => :yes, :index => :untokenized)
  530 +
  531 + # other fields
  532 + index_definition[:ferret_fields].each_pair do |field, options|
  533 + options = options.dup
  534 + options.delete :via
  535 + options.delete :boost if options[:boost].is_a?(Symbol) # dynamic boost
  536 + fi.add_field(field, options)
  537 + end
  538 + return fi
  539 + end
  540 +
  541 + def self.close_multi_indexes
  542 + # close combined index readers, just in case
  543 + # this seems to fix a strange test failure that seems to relate to a
  544 + # multi_index looking at an old version of the content_base index.
  545 + multi_indexes.each_pair do |key, index|
  546 + # puts "#{key} -- #{self.name}"
  547 + # TODO only close those where necessary (watch inheritance, where
  548 + # self.name is base class of a class where key is made from)
  549 + index.close #if key =~ /#{self.name}/
  550 + end
  551 + multi_indexes.clear
  552 + end
  553 +
  554 + protected
  555 +
  556 + def self.field_config_for(fieldname, options = {})
  557 + config = DEFAULT_FIELD_OPTIONS.merge options
  558 + config[:via] ||= fieldname
  559 + config[:term_vector] = :no if config[:index] == :no
  560 + return config
  561 + end
  562 +
  563 +end
  564 +
  565 +# include acts_as_ferret method into ActiveRecord::Base
  566 +ActiveRecord::Base.extend ActsAsFerret::ActMethods
  567 +
vendor/plugins/acts_as_ferret/lib/ar_mysql_auto_reconnect_patch.rb 0 → 100644
@@ -0,0 +1,41 @@ @@ -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 +
vendor/plugins/acts_as_ferret/lib/blank_slate.rb 0 → 100644
@@ -0,0 +1,53 @@ @@ -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 +
vendor/plugins/acts_as_ferret/lib/bulk_indexer.rb 0 → 100644
@@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
  1 +module ActsAsFerret
  2 + class BulkIndexer
  3 + def initialize(args = {})
  4 + @batch_size = args[:batch_size] || 1000
  5 + @logger = args[:logger]
  6 + @model = args[:model]
  7 + @work_done = 0
  8 + @index = args[:index]
  9 + if args[:reindex]
  10 + @reindex = true
  11 + @model_count = @model.count.to_f
  12 + else
  13 + @model_count = args[:total]
  14 + end
  15 + end
  16 +
  17 + def index_records(records, offset)
  18 + batch_time = measure_time {
  19 + records.each { |rec| @index.add_document(rec.to_doc, rec.ferret_analyzer) if rec.ferret_enabled?(true) }
  20 + }.to_f
  21 + @work_done = offset.to_f / @model_count * 100.0 if @model_count > 0
  22 + remaining_time = ( batch_time / @batch_size ) * ( @model_count - offset + @batch_size )
  23 + @logger.info "#{@reindex ? 're' : 'bulk '}index model #{@model.name} : #{'%.2f' % @work_done}% complete : #{'%.2f' % remaining_time} secs to finish"
  24 +
  25 + end
  26 +
  27 + def measure_time
  28 + t1 = Time.now
  29 + yield
  30 + Time.now - t1
  31 + end
  32 +
  33 + end
  34 +
  35 +end
vendor/plugins/acts_as_ferret/lib/class_methods.rb 0 → 100644
@@ -0,0 +1,295 @@ @@ -0,0 +1,295 @@
  1 +module ActsAsFerret
  2 +
  3 + module ClassMethods
  4 +
  5 + # Disables ferret index updates for this model. When a block is given,
  6 + # Ferret will be re-enabled again after executing the block.
  7 + def disable_ferret
  8 + aaf_configuration[:enabled] = false
  9 + if block_given?
  10 + yield
  11 + enable_ferret
  12 + end
  13 + end
  14 +
  15 + def enable_ferret
  16 + aaf_configuration[:enabled] = true
  17 + end
  18 +
  19 + def ferret_enabled?
  20 + aaf_configuration[:enabled]
  21 + end
  22 +
  23 + # rebuild the index from all data stored for this model, and any other
  24 + # model classes associated with the same index.
  25 + # This is called automatically when no index exists yet.
  26 + #
  27 + def rebuild_index
  28 + aaf_index.rebuild_index
  29 + end
  30 +
  31 + # re-index a number records specified by the given ids. Use for large
  32 + # indexing jobs i.e. after modifying a lot of records with Ferret disabled.
  33 + # Please note that the state of Ferret (enabled or disabled at class or
  34 + # record level) is not checked by this method, so if you need to do so
  35 + # (e.g. because of a custom ferret_enabled? implementation), you have to do
  36 + # so yourself.
  37 + def bulk_index(*ids)
  38 + options = Hash === ids.last ? ids.pop : {}
  39 + ids = ids.first if ids.size == 1 && ids.first.is_a?(Enumerable)
  40 + aaf_index.bulk_index(self.name, ids, options)
  41 + end
  42 +
  43 + # true if our db and table appear to be suitable for the mysql fast batch
  44 + # hack (see
  45 + # http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord)
  46 + def use_fast_batches?
  47 + if connection.class.name =~ /Mysql/ && primary_key == 'id' && aaf_configuration[:mysql_fast_batches]
  48 + logger.info "using mysql specific batched find :all. Turn off with :mysql_fast_batches => false if you encounter problems (i.e. because of non-integer UUIDs in the id column)"
  49 + true
  50 + end
  51 + end
  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 +
  72 + # runs across all records yielding those to be indexed when the index is rebuilt
  73 + def records_for_rebuild(batch_size = 1000)
  74 + transaction do
  75 + if use_fast_batches?
  76 + offset = 0
  77 + while (rows = find :all, :conditions => [ "#{table_name}.id > ?", offset ], :limit => batch_size).any?
  78 + offset = rows.last.id
  79 + yield rows, offset
  80 + end
  81 + else
  82 + order = "#{primary_key} ASC" # fixes #212
  83 + 0.step(self.count, batch_size) do |offset|
  84 + yield find( :all, :limit => batch_size, :offset => offset, :order => order ), offset
  85 + end
  86 + end
  87 + end
  88 + end
  89 +
  90 + # yields the records with the given ids, in batches of batch_size
  91 + def records_for_bulk_index(ids, batch_size = 1000)
  92 + transaction do
  93 + offset = 0
  94 + ids.each_slice(batch_size) do |id_slice|
  95 + records = find( :all, :conditions => ["id in (?)", id_slice] )
  96 + #yield records, offset
  97 + yield find( :all, :conditions => ["id in (?)", id_slice] ), offset
  98 + offset += batch_size
  99 + end
  100 + end
  101 + end
  102 +
  103 + # Retrieve the index instance for this model class. This can either be a
  104 + # LocalIndex, or a RemoteIndex instance.
  105 + #
  106 + def aaf_index
  107 + @index ||= ActsAsFerret::get_index(aaf_configuration[:name])
  108 + end
  109 +
  110 + # Finds instances by searching the Ferret index. Terms are ANDed by default, use
  111 + # OR between terms for ORed queries. Or specify +:or_default => true+ in the
  112 + # +:ferret+ options hash of acts_as_ferret.
  113 + #
  114 + # You may either use the +offset+ and +limit+ options to implement your own
  115 + # pagination logic, or use the +page+ and +per_page+ options to use the
  116 + # built in pagination support which is compatible with will_paginate's view
  117 + # helpers. If +page+ and +per_page+ are given, +offset+ and +limit+ will be
  118 + # ignored.
  119 + #
  120 + # == options:
  121 + # page:: page of search results to retrieve
  122 + # per_page:: number of search results that are displayed per page
  123 + # offset:: first hit to retrieve (useful for paging)
  124 + # limit:: number of hits to retrieve, or :all to retrieve
  125 + # all results
  126 + # lazy:: Array of field names whose contents should be read directly
  127 + # from the index. Those fields have to be marked
  128 + # +:store => :yes+ in their field options. Give true to get all
  129 + # stored fields. Note that if you have a shared index, you have
  130 + # to explicitly state the fields you want to fetch, true won't
  131 + # work here)
  132 + #
  133 + # +find_options+ is a hash passed on to active_record's find when
  134 + # retrieving the data from db, useful to i.e. prefetch relationships with
  135 + # :include or to specify additional filter criteria with :conditions.
  136 + #
  137 + # This method returns a +SearchResults+ instance, which really is an Array that has
  138 + # been decorated with a total_hits attribute holding the total number of hits.
  139 + # Additionally, SearchResults is compatible with the pagination helper
  140 + # methods of the will_paginate plugin.
  141 + #
  142 + # Please keep in mind that the number of results delivered might be less than
  143 + # +limit+ if you specify any active record conditions that further limit
  144 + # the result. Use +limit+ and +offset+ as AR find_options instead.
  145 + # +page+ and +per_page+ are supposed to work regardless of any
  146 + # +conitions+ present in +find_options+.
  147 + def find_with_ferret(q, options = {}, find_options = {})
  148 + aaf_index.register_class(self, {})
  149 +
  150 + if respond_to?(:scope) && scope(:find, :conditions)
  151 + if find_options[:conditions]
  152 + find_options[:conditions] = "(#{find_options[:conditions]}) AND (#{scope(:find, :conditions)})"
  153 + else
  154 + find_options[:conditions] = scope(:find, :conditions)
  155 + end
  156 + end
  157 +
  158 + if options[:per_page]
  159 + options[:page] = options[:page] ? options[:page].to_i : 1
  160 + limit = options[:per_page]
  161 + offset = (options[:page] - 1) * limit
  162 + if find_options[:conditions]
  163 + find_options[:limit] = limit
  164 + find_options[:offset] = offset
  165 + options[:limit] = :all
  166 + options.delete :offset
  167 + else
  168 + # do pagination with ferret
  169 + options[:limit] = limit
  170 + options[:offset] = offset
  171 + end
  172 + elsif find_options[:conditions]
  173 + find_options[:limit] ||= options.delete(:limit) unless options[:limit] == :all
  174 + find_options[:offset] ||= options.delete(:offset)
  175 + options[:limit] = :all
  176 + end
  177 +
  178 + total_hits, result = aaf_index.find_records q, options.merge(:models => [self]), find_options
  179 + logger.debug "Query: #{q}\ntotal hits: #{total_hits}, results delivered: #{result.size}"
  180 + SearchResults.new(result, total_hits, options[:page], options[:per_page])
  181 + end
  182 +
  183 +
  184 + # Returns the total number of hits for the given query
  185 + #
  186 + # Note that since we don't query the database here, this method won't deliver
  187 + # the expected results when used on an AR association.
  188 + #
  189 + def total_hits(q, options={})
  190 + aaf_index.total_hits(q, options)
  191 + end
  192 +
  193 + # Finds instance model name, ids and scores by contents.
  194 + # Useful e.g. if you want to search across models or do not want to fetch
  195 + # all result records (yet).
  196 + #
  197 + # Options are the same as for find_by_contents
  198 + #
  199 + # A block can be given too, it will be executed with every result:
  200 + # find_ids_with_ferret(q, options) do |model, id, score|
  201 + # id_array << id
  202 + # scores_by_id[id] = score
  203 + # end
  204 + # NOTE: in case a block is given, only the total_hits value will be returned
  205 + # instead of the [total_hits, results] array!
  206 + #
  207 + def find_ids_with_ferret(q, options = {}, &block)
  208 + aaf_index.find_ids(q, options, &block)
  209 + end
  210 +
  211 +
  212 + protected
  213 +
  214 +# def find_records_lazy_or_not(q, options = {}, find_options = {})
  215 +# if options[:lazy]
  216 +# logger.warn "find_options #{find_options} are ignored because :lazy => true" unless find_options.empty?
  217 +# lazy_find_by_contents q, options
  218 +# else
  219 +# ar_find_by_contents q, options, find_options
  220 +# end
  221 +# end
  222 +#
  223 +# def ar_find_by_contents(q, options = {}, find_options = {})
  224 +# result_ids = {}
  225 +# total_hits = find_ids_with_ferret(q, options) do |model, id, score, data|
  226 +# # stores ids, index and score of each hit for later ordering of
  227 +# # results
  228 +# result_ids[id] = [ result_ids.size + 1, score ]
  229 +# end
  230 +#
  231 +# result = ActsAsFerret::retrieve_records( { self.name => result_ids }, find_options )
  232 +#
  233 +# # count total_hits via sql when using conditions or when we're called
  234 +# # from an ActiveRecord association.
  235 +# if find_options[:conditions] or caller.find{ |call| call =~ %r{active_record/associations} }
  236 +# # chances are the ferret result count is not our total_hits value, so
  237 +# # we correct this here.
  238 +# if options[:limit] != :all || options[:page] || options[:offset] || find_options[:limit] || find_options[:offset]
  239 +# # our ferret result has been limited, so we need to re-run that
  240 +# # search to get the full result set from ferret.
  241 +# result_ids = {}
  242 +# find_ids_with_ferret(q, options.update(:limit => :all, :offset => 0)) do |model, id, score, data|
  243 +# result_ids[id] = [ result_ids.size + 1, score ]
  244 +# end
  245 +# # Now ask the database for the total size of the final result set.
  246 +# total_hits = count_records( { self.name => result_ids }, find_options )
  247 +# else
  248 +# # what we got from the database is our full result set, so take
  249 +# # it's size
  250 +# total_hits = result.length
  251 +# end
  252 +# end
  253 +#
  254 +# [ total_hits, result ]
  255 +# end
  256 +#
  257 +# def lazy_find_by_contents(q, options = {})
  258 +# logger.debug "lazy_find_by_contents: #{q}"
  259 +# result = []
  260 +# rank = 0
  261 +# total_hits = find_ids_with_ferret(q, options) do |model, id, score, data|
  262 +# logger.debug "model: #{model}, id: #{id}, data: #{data}"
  263 +# result << FerretResult.new(model, id, score, rank += 1, data)
  264 +# end
  265 +# [ total_hits, result ]
  266 +# end
  267 +
  268 +
  269 + def model_find(model, id, find_options = {})
  270 + model.constantize.find(id, find_options)
  271 + end
  272 +
  273 +
  274 +# def count_records(id_arrays, find_options = {})
  275 +# count_options = find_options.dup
  276 +# count_options.delete :limit
  277 +# count_options.delete :offset
  278 +# count = 0
  279 +# id_arrays.each do |model, id_array|
  280 +# next if id_array.empty?
  281 +# model = model.constantize
  282 +# # merge conditions
  283 +# conditions = ActsAsFerret::combine_conditions([ "#{model.table_name}.#{model.primary_key} in (?)", id_array.keys ],
  284 +# find_options[:conditions])
  285 +# opts = find_options.merge :conditions => conditions
  286 +# opts.delete :limit; opts.delete :offset
  287 +# count += model.count opts
  288 +# end
  289 +# count
  290 +# end
  291 +
  292 + end
  293 +
  294 +end
  295 +
vendor/plugins/acts_as_ferret/lib/ferret_extensions.rb 0 → 100644
@@ -0,0 +1,115 @@ @@ -0,0 +1,115 @@
  1 +module Ferret
  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
  34 +
  35 + class Index::Index
  36 + attr_accessor :batch_size, :logger
  37 +
  38 + def index_models(models)
  39 + models.each { |model| index_model model }
  40 + flush
  41 + optimize
  42 + close
  43 + ActsAsFerret::close_multi_indexes
  44 + end
  45 +
  46 + def index_model(model)
  47 + bulk_indexer = ActsAsFerret::BulkIndexer.new(:batch_size => @batch_size, :logger => logger,
  48 + :model => model, :index => self, :reindex => true)
  49 + logger.info "reindexing model #{model.name}"
  50 +
  51 + model.records_for_rebuild(@batch_size) do |records, offset|
  52 + bulk_indexer.index_records(records, offset)
  53 + end
  54 + end
  55 +
  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
  73 + end
  74 +
  75 + end
  76 +
  77 + # add marshalling support to SortFields
  78 + class Search::SortField
  79 + def _dump(depth)
  80 + to_s
  81 + end
  82 +
  83 + def self._load(string)
  84 + case string
  85 + when /<DOC(_ID)?>!/ : Ferret::Search::SortField::DOC_ID_REV
  86 + when /<DOC(_ID)?>/ : Ferret::Search::SortField::DOC_ID
  87 + when '<SCORE>!' : Ferret::Search::SortField::SCORE_REV
  88 + when '<SCORE>' : Ferret::Search::SortField::SCORE
  89 + when /^(\w+):<(\w+)>(!)?$/ : new($1.to_sym, :type => $2.to_sym, :reverse => !$3.nil?)
  90 + else raise "invalid value: #{string}"
  91 + end
  92 + end
  93 + end
  94 +
  95 + # add marshalling support to Sort
  96 + class Search::Sort
  97 + def _dump(depth)
  98 + to_s
  99 + end
  100 +
  101 + def self._load(string)
  102 + # we exclude the last <DOC> sorting as it is appended by new anyway
  103 + if string =~ /^Sort\[(.*?)(<DOC>(!)?)?\]$/
  104 + sort_fields = $1.split(',').map do |value|
  105 + value.strip!
  106 + Ferret::Search::SortField._load value unless value.blank?
  107 + end
  108 + new sort_fields.compact
  109 + else
  110 + raise "invalid value: #{string}"
  111 + end
  112 + end
  113 + end
  114 +
  115 +end
vendor/plugins/acts_as_ferret/lib/ferret_find_methods.rb 0 → 100644
@@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
  1 +module ActsAsFerret
  2 + # Ferret search logic common to single-class indexes, shared indexes and
  3 + # multi indexes.
  4 + module FerretFindMethods
  5 +
  6 + def find_records(q, options = {}, ar_options = {})
  7 + if options[:lazy]
  8 + logger.warn "find_options #{ar_options} are ignored because :lazy => true" unless ar_options.empty?
  9 + lazy_find q, options
  10 + else
  11 + ar_find q, options, ar_options
  12 + end
  13 + end
  14 +
  15 + def lazy_find(q, options = {})
  16 + logger.debug "lazy_find: #{q}"
  17 + result = []
  18 + rank = 0
  19 + total_hits = find_ids(q, options) do |model, id, score, data|
  20 + logger.debug "model: #{model}, id: #{id}, data: #{data}"
  21 + result << FerretResult.new(model, id, score, rank += 1, data)
  22 + end
  23 + [ total_hits, result ]
  24 + end
  25 +
  26 + def ar_find(q, options = {}, ar_options = {})
  27 + total_hits, id_arrays = find_id_model_arrays q, options
  28 + result = ActsAsFerret::retrieve_records(id_arrays, ar_options)
  29 +
  30 + # count total_hits via sql when using conditions, multiple models, or when we're called
  31 + # from an ActiveRecord association.
  32 + if id_arrays.size > 1 or ar_options[:conditions]
  33 + # chances are the ferret result count is not our total_hits value, so
  34 + # we correct this here.
  35 + if options[:limit] != :all || options[:page] || options[:offset] || ar_options[:limit] || ar_options[:offset]
  36 + # our ferret result has been limited, so we need to re-run that
  37 + # search to get the full result set from ferret.
  38 + new_th, id_arrays = find_id_model_arrays( q, options.merge(:limit => :all, :offset => 0) )
  39 + # Now ask the database for the total size of the final result set.
  40 + total_hits = count_records( id_arrays, ar_options )
  41 + else
  42 + # what we got from the database is our full result set, so take
  43 + # it's size
  44 + total_hits = result.length
  45 + end
  46 + end
  47 + [ total_hits, result ]
  48 + end
  49 +
  50 + def count_records(id_arrays, ar_options = {})
  51 + count_options = ar_options.dup
  52 + count_options.delete :limit
  53 + count_options.delete :offset
  54 + count = 0
  55 + id_arrays.each do |model, id_array|
  56 + next if id_array.empty?
  57 + model = model.constantize
  58 + # merge conditions
  59 + conditions = ActsAsFerret::conditions_for_model model, ar_options[:conditions]
  60 + count_options[:conditions] = ActsAsFerret::combine_conditions([ "#{model.table_name}.#{model.primary_key} in (?)", id_array.keys ], conditions)
  61 + count += model.count count_options
  62 + end
  63 + count
  64 + end
  65 +
  66 + def find_id_model_arrays(q, options)
  67 + id_arrays = {}
  68 + rank = 0
  69 + total_hits = find_ids(q, options) do |model, id, score, data|
  70 + id_arrays[model] ||= {}
  71 + id_arrays[model][id] = [ rank += 1, score ]
  72 + end
  73 + [total_hits, id_arrays]
  74 + end
  75 +
  76 + # Queries the Ferret index to retrieve model class, id, score and the
  77 + # values of any fields stored in the index for each hit.
  78 + # If a block is given, these are yielded and the number of total hits is
  79 + # returned. Otherwise [total_hits, result_array] is returned.
  80 + def find_ids(query, options = {})
  81 +
  82 + result = []
  83 + stored_fields = determine_stored_fields options
  84 +
  85 + q = process_query(query, options)
  86 + q = scope_query_to_models q, options[:models] #if shared?
  87 + logger.debug "query: #{query}\n-->#{q}"
  88 + s = searcher
  89 + total_hits = s.search_each(q, options) do |hit, score|
  90 + doc = s[hit]
  91 + model = doc[:class_name]
  92 + # fetch stored fields if lazy loading
  93 + data = extract_stored_fields(doc, stored_fields)
  94 + if block_given?
  95 + yield model, doc[:id], score, data
  96 + else
  97 + result << { :model => model, :id => doc[:id], :score => score, :data => data }
  98 + end
  99 + end
  100 + #logger.debug "id_score_model array: #{result.inspect}"
  101 + return block_given? ? total_hits : [total_hits, result]
  102 + end
  103 +
  104 + def scope_query_to_models(query, models)
  105 + return query if models.nil? or models == :all
  106 + models = [ models ] if Class === models
  107 + q = Ferret::Search::BooleanQuery.new
  108 + q.add_query(query, :must)
  109 + model_query = Ferret::Search::BooleanQuery.new
  110 + models.each do |model|
  111 + model_query.add_query(Ferret::Search::TermQuery.new(:class_name, model.name), :should)
  112 + end
  113 + q.add_query(model_query, :must)
  114 + return q
  115 + end
  116 +
  117 + end
  118 +end
vendor/plugins/acts_as_ferret/lib/ferret_result.rb 0 → 100644
@@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
  1 +module ActsAsFerret
  2 +
  3 + # mixed into the FerretResult and AR classes calling acts_as_ferret
  4 + module ResultAttributes
  5 + # holds the score this record had when it was found via
  6 + # acts_as_ferret
  7 + attr_accessor :ferret_score
  8 +
  9 + attr_accessor :ferret_rank
  10 + end
  11 +
  12 + class FerretResult < ActsAsFerret::BlankSlate
  13 + include ResultAttributes
  14 + attr_accessor :id
  15 + reveal :methods
  16 +
  17 + def initialize(model, id, score, rank, data = {})
  18 + @model = model.constantize
  19 + @id = id
  20 + @ferret_score = score
  21 + @ferret_rank = rank
  22 + @data = data
  23 + @use_record = false
  24 + end
  25 +
  26 + def inspect
  27 + "#<FerretResult wrapper for #{@model} with id #{@id}"
  28 + end
  29 +
  30 + def method_missing(method, *args, &block)
  31 + if (@ar_record && @use_record) || !@data.has_key?(method)
  32 + to_record.send method, *args, &block
  33 + else
  34 + @data[method]
  35 + end
  36 + end
  37 +
  38 + def respond_to?(name)
  39 + methods.include?(name.to_s) || @data.has_key?(name.to_sym) || to_record.respond_to?(name)
  40 + end
  41 +
  42 + def to_record
  43 + unless @ar_record
  44 + @ar_record = @model.find(id)
  45 + @ar_record.ferret_rank = ferret_rank
  46 + @ar_record.ferret_score = ferret_score
  47 + # don't try to fetch attributes from RDig based records
  48 + @use_record = !@ar_record.class.included_modules.include?(ActsAsFerret::RdigAdapter)
  49 + end
  50 + @ar_record
  51 + end
  52 + end
  53 +end
vendor/plugins/acts_as_ferret/lib/ferret_server.rb 0 → 100644
@@ -0,0 +1,238 @@ @@ -0,0 +1,238 @@
  1 +require 'drb'
  2 +require 'thread'
  3 +require 'yaml'
  4 +require 'erb'
  5 +
  6 +################################################################################
  7 +module ActsAsFerret
  8 + module Remote
  9 +
  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 + 'socket' => nil,
  22 + 'script' => nil
  23 + }
  24 +
  25 + ################################################################################
  26 + # load the configuration file and apply default settings
  27 + def initialize (file=DEFAULTS['cf'])
  28 + @everything = YAML.load(ERB.new(IO.read(file)).result)
  29 + raise "malformed ferret server config" unless @everything.is_a?(Hash)
  30 + @config = DEFAULTS.merge(@everything[RAILS_ENV] || {})
  31 + if @everything[RAILS_ENV]
  32 + @config['uri'] = socket.nil? ? "druby://#{host}:#{port}" : "drbunix:#{socket}"
  33 + end
  34 + end
  35 +
  36 + ################################################################################
  37 + # treat the keys of the config data as methods
  38 + def method_missing (name, *args)
  39 + @config.has_key?(name.to_s) ? @config[name.to_s] : super
  40 + end
  41 +
  42 + end
  43 +
  44 + #################################################################################
  45 + # This class acts as a drb server listening for indexing and
  46 + # search requests from models declared to 'acts_as_ferret :remote => true'
  47 + #
  48 + # Usage:
  49 + # - modify RAILS_ROOT/config/ferret_server.yml to suit your needs.
  50 + # - environments for which no section in the config file exists will use
  51 + # the index locally (good for unit tests/development mode)
  52 + # - run script/ferret_server to start the server:
  53 + # script/ferret_server -e production start
  54 + # - to stop the server run
  55 + # script/ferret_server -e production stop
  56 + #
  57 + class Server
  58 +
  59 + #################################################################################
  60 + # FIXME include detection of OS and include the correct file
  61 + require 'unix_daemon'
  62 + include(ActsAsFerret::Remote::UnixDaemon)
  63 +
  64 +
  65 + ################################################################################
  66 + cattr_accessor :running
  67 +
  68 + ################################################################################
  69 + def initialize
  70 + ActiveRecord::Base.allow_concurrency = true
  71 + require 'ar_mysql_auto_reconnect_patch'
  72 + @cfg = ActsAsFerret::Remote::Config.new
  73 + ActiveRecord::Base.logger = @logger = Logger.new(@cfg.log_file)
  74 + ActiveRecord::Base.logger.level = Logger.const_get(@cfg.log_level.upcase) rescue Logger::DEBUG
  75 + if @cfg.script
  76 + path = File.join(RAILS_ROOT, @cfg.script)
  77 + load path
  78 + @logger.info "loaded custom startup script from #{path}"
  79 + end
  80 + end
  81 +
  82 + ################################################################################
  83 + # start the server as a daemon process
  84 + def start
  85 + raise "ferret_server not configured for #{RAILS_ENV}" unless (@cfg.uri rescue nil)
  86 + platform_daemon { run_drb_service }
  87 + end
  88 +
  89 + ################################################################################
  90 + # run the server and block until it exits
  91 + def run
  92 + raise "ferret_server not configured for #{RAILS_ENV}" unless (@cfg.uri rescue nil)
  93 + run_drb_service
  94 + end
  95 +
  96 + def run_drb_service
  97 + $stdout.puts("starting ferret server...")
  98 + self.class.running = true
  99 + DRb.start_service(@cfg.uri, self)
  100 + DRb.thread.join
  101 + rescue Exception => e
  102 + @logger.error(e.to_s)
  103 + raise
  104 + end
  105 +
  106 + #################################################################################
  107 + # handles all incoming method calls, and sends them on to the correct local index
  108 + # instance.
  109 + #
  110 + # Calls are not queued, so this will block until the call returned.
  111 + #
  112 + def method_missing(name, *args)
  113 + @logger.debug "\#method_missing(#{name.inspect}, #{args.inspect})"
  114 +
  115 +
  116 + index_name = args.shift
  117 + index = if name.to_s =~ /^multi_(.+)/
  118 + name = $1
  119 + ActsAsFerret::multi_index(index_name)
  120 + else
  121 + ActsAsFerret::get_index(index_name)
  122 + end
  123 +
  124 + if index.nil?
  125 + @logger.error "\#index with name #{index_name} not found in call to #{name} with args #{args.inspect}"
  126 + raise ActsAsFerret::IndexNotDefined.new(index_name)
  127 + end
  128 +
  129 +
  130 + # TODO find another way to implement the reconnection logic (maybe in
  131 + # local_index or class_methods)
  132 + # reconnect_when_needed(clazz) do
  133 +
  134 + # using respond_to? here so we not have to catch NoMethodError
  135 + # which would silently catch those from deep inside the indexing
  136 + # code, too...
  137 +
  138 + if index.respond_to?(name)
  139 + index.send name, *args
  140 + # TODO check where we need this:
  141 + #elsif clazz.respond_to?(name)
  142 + # @logger.debug "no luck, trying to call class method instead"
  143 + # clazz.send name, *args
  144 + else
  145 + raise NoMethodError.new("method #{name} not supported by DRb server")
  146 + end
  147 + rescue => e
  148 + @logger.error "ferret server error #{$!}\n#{$!.backtrace.join "\n"}"
  149 + raise e
  150 + end
  151 +
  152 + def register_class(class_name)
  153 + @logger.debug "############ registerclass #{class_name}"
  154 + class_name.constantize
  155 + @logger.debug "index for class #{class_name}: #{ActsAsFerret::ferret_indexes[class_name.underscore.to_sym]}"
  156 +
  157 + end
  158 +
  159 + # make sure we have a versioned index in place, building one if necessary
  160 + def ensure_index_exists(index_name)
  161 + @logger.debug "DRb server: ensure_index_exists for index #{index_name}"
  162 + definition = ActsAsFerret::get_index(index_name).index_definition
  163 + dir = definition[:index_dir]
  164 + unless File.directory?(dir) && File.file?(File.join(dir, 'segments')) && dir =~ %r{/\d+(_\d+)?$}
  165 + rebuild_index(index_name)
  166 + end
  167 + end
  168 +
  169 + # disconnects the db connection for the class specified by class_name
  170 + # used only in unit tests to check the automatic reconnection feature
  171 + def db_disconnect!(class_name)
  172 + with_class class_name do |clazz|
  173 + clazz.connection.disconnect!
  174 + end
  175 + end
  176 +
  177 + # hides LocalIndex#rebuild_index to implement index versioning
  178 + def rebuild_index(index_name)
  179 + definition = ActsAsFerret::get_index(index_name).index_definition.dup
  180 + models = definition[:registered_models]
  181 + index = new_index_for(definition)
  182 + # TODO fix reconnection stuff
  183 + # reconnect_when_needed(clazz) do
  184 + # @logger.debug "DRb server: rebuild index for class(es) #{models.inspect} in #{index.options[:path]}"
  185 + index.index_models models
  186 + # end
  187 + new_version = File.join definition[:index_base_dir], Time.now.utc.strftime('%Y%m%d%H%M%S')
  188 + # create a unique directory name (needed for unit tests where
  189 + # multiple rebuilds per second may occur)
  190 + if File.exists?(new_version)
  191 + i = 0
  192 + i+=1 while File.exists?("#{new_version}_#{i}")
  193 + new_version << "_#{i}"
  194 + end
  195 +
  196 + File.rename index.options[:path], new_version
  197 + ActsAsFerret::change_index_dir index_name, new_version
  198 + end
  199 +
  200 +
  201 + protected
  202 +
  203 + def reconnect_when_needed(clazz)
  204 + retried = false
  205 + begin
  206 + yield
  207 + rescue ActiveRecord::StatementInvalid => e
  208 + if e.message =~ /MySQL server has gone away/
  209 + if retried
  210 + raise e
  211 + else
  212 + @logger.info "StatementInvalid caught, trying to reconnect..."
  213 + clazz.connection.reconnect!
  214 + retried = true
  215 + retry
  216 + end
  217 + else
  218 + @logger.error "StatementInvalid caught, but unsure what to do with it: #{e}"
  219 + raise e
  220 + end
  221 + end
  222 + end
  223 +
  224 + def new_index_for(index_definition)
  225 + ferret_cfg = index_definition[:ferret].dup
  226 + ferret_cfg.update :auto_flush => false,
  227 + :create => true,
  228 + :field_infos => ActsAsFerret::field_infos(index_definition),
  229 + :path => File.join(index_definition[:index_base_dir], 'rebuild')
  230 + returning Ferret::Index::Index.new(ferret_cfg) do |i|
  231 + i.batch_size = index_definition[:reindex_batch_size]
  232 + i.logger = @logger
  233 + end
  234 + end
  235 +
  236 + end
  237 + end
  238 +end
vendor/plugins/acts_as_ferret/lib/index.rb 0 → 100644
@@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
  1 +module ActsAsFerret
  2 +
  3 + class IndexLogger
  4 + def initialize(logger, name)
  5 + @logger = logger
  6 + @index_name = name
  7 + end
  8 + %w(debug info warn error).each do |m|
  9 + define_method(m) do |message|
  10 + @logger.send m, "[#{@index_name}] #{message}"
  11 + end
  12 + question = :"#{m}?"
  13 + define_method(question) do
  14 + @logger.send question
  15 + end
  16 + end
  17 + end
  18 +
  19 + # base class for local and remote indexes
  20 + class AbstractIndex
  21 + include FerretFindMethods
  22 +
  23 + attr_reader :logger, :index_name, :index_definition, :registered_models_config
  24 + def initialize(index_definition)
  25 + @index_definition = index_definition
  26 + @registered_models_config = {}
  27 + @index_name = index_definition[:name]
  28 + @logger = IndexLogger.new(ActsAsFerret::logger, @index_name)
  29 + end
  30 +
  31 + # TODO allow for per-class field configuration (i.e. different via, boosts
  32 + # for the same field among different models)
  33 + def register_class(clazz, options = {})
  34 + logger.info "register class #{clazz} with index #{index_name}"
  35 +
  36 + if force = options.delete(:force_re_registration)
  37 + index_definition[:registered_models].delete(clazz)
  38 + end
  39 +
  40 + if index_definition[:registered_models].map(&:name).include?(clazz.name)
  41 + logger.info("refusing re-registration of class #{clazz}")
  42 + else
  43 + index_definition[:registered_models] << clazz
  44 + @registered_models_config[clazz] = options
  45 +
  46 + # merge fields from this acts_as_ferret call with predefined fields
  47 + already_defined_fields = index_definition[:ferret_fields]
  48 + field_config = ActsAsFerret::build_field_config options[:fields]
  49 + field_config.update ActsAsFerret::build_field_config( options[:additional_fields] )
  50 + field_config.each do |field, config|
  51 + if already_defined_fields.has_key?(field)
  52 + logger.info "ignoring redefinition of ferret field #{field}" if shared?
  53 + else
  54 + already_defined_fields[field] = config
  55 + logger.info "adding new field #{field} from class #{clazz.name} to index #{index_name}"
  56 + end
  57 + end
  58 +
  59 + # update default field list to be used by the query parser, unless it
  60 + # was explicitly given by user.
  61 + #
  62 + # It will include all content fields *not* marked as :untokenized.
  63 + # This fixes the otherwise failing CommentTest#test_stopwords. Basically
  64 + # this means that by default only tokenized fields (which all fields are
  65 + # by default) will be searched. If you want to search inside the contents
  66 + # of an untokenized field, you'll have to explicitly specify it in your
  67 + # query.
  68 + unless index_definition[:user_default_field]
  69 + # grab all tokenized fields
  70 + ferret_fields = index_definition[:ferret_fields]
  71 + index_definition[:ferret][:default_field] = ferret_fields.keys.select do |field|
  72 + ferret_fields[field][:index] != :untokenized
  73 + end
  74 + logger.info "default field list for index #{index_name}: #{index_definition[:ferret][:default_field].inspect}"
  75 + end
  76 + end
  77 +
  78 + return index_definition
  79 + end
  80 +
  81 + # true if this index is used by more than one model class
  82 + def shared?
  83 + index_definition[:registered_models].size > 1
  84 + end
  85 +
  86 + # Switches the index to a new index directory.
  87 + # Used by the DRb server when switching to a new index version.
  88 + def change_index_dir(new_dir)
  89 + logger.debug "[#{index_name}] changing index dir to #{new_dir}"
  90 + index_definition[:index_dir] = index_definition[:ferret][:path] = new_dir
  91 + reopen!
  92 + logger.debug "[#{index_name}] index dir is now #{new_dir}"
  93 + end
  94 +
  95 + protected
  96 +
  97 + end
  98 +
  99 +end
vendor/plugins/acts_as_ferret/lib/instance_methods.rb 0 → 100644
@@ -0,0 +1,165 @@ @@ -0,0 +1,165 @@
  1 +module ActsAsFerret #:nodoc:
  2 +
  3 + module InstanceMethods
  4 + include ResultAttributes
  5 +
  6 + # Returns an array of strings with the matches highlighted. The +query+ can
  7 + # either be a String or a Ferret::Search::Query object.
  8 + #
  9 + # === Options
  10 + #
  11 + # field:: field to take the content from. This field has
  12 + # to have it's content stored in the index
  13 + # (:store => :yes in your call to aaf). If not
  14 + # given, all stored fields are searched, and the
  15 + # highlighted content found in all of them is returned.
  16 + # set :highlight => :no in the field options to
  17 + # avoid highlighting of contents from a :stored field.
  18 + # excerpt_length:: Default: 150. Length of excerpt to show. Highlighted
  19 + # terms will be in the centre of the excerpt.
  20 + # num_excerpts:: Default: 2. Number of excerpts to return.
  21 + # pre_tag:: Default: "<em>". Tag to place to the left of the
  22 + # match.
  23 + # post_tag:: Default: "</em>". This tag should close the
  24 + # +:pre_tag+.
  25 + # ellipsis:: Default: "...". This is the string that is appended
  26 + # at the beginning and end of excerpts (unless the
  27 + # excerpt hits the start or end of the field. You'll
  28 + # probably want to change this to a Unicode elipsis
  29 + # character.
  30 + def highlight(query, options = {})
  31 + self.class.aaf_index.highlight(self.send(self.class.primary_key), self.class.name, query, options)
  32 + end
  33 +
  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
  39 +
  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?) && (aaf_configuration[:if].nil? || aaf_configuration[:if].call(self))
  51 + end
  52 +
  53 + # Returns the analyzer to use when adding this record to the index.
  54 + #
  55 + # Override to return a specific analyzer for any record that is to be
  56 + # indexed, i.e. specify a different analyzer based on language. Returns nil
  57 + # by default so the global analyzer (specified with the acts_as_ferret
  58 + # call) is used.
  59 + def ferret_analyzer
  60 + nil
  61 + end
  62 +
  63 + # Disable Ferret for this record for a specified amount of time. ::once will
  64 + # disable Ferret for the next call to #save (this is the default), ::always
  65 + # will do so for all subsequent calls.
  66 + #
  67 + # Note that this will turn off only the create and update hooks, but not the
  68 + # destroy hook. I think that's reasonable, if you think the opposite, please
  69 + # tell me.
  70 + #
  71 + # To manually trigger reindexing of a record after you're finished modifying
  72 + # it, you can call #ferret_update directly instead of #save (remember to
  73 + # enable ferret again before).
  74 + #
  75 + # When given a block, this will be executed without any ferret indexing of
  76 + # this object taking place. The optional argument in this case can be used
  77 + # to indicate if the object should be indexed after executing the block
  78 + # (::index_when_finished). Automatic Ferret indexing of this object will be
  79 + # turned on after the block has been executed. If passed ::index_when_true,
  80 + # the index will only be updated if the block evaluated not to false or nil.
  81 + #
  82 + def disable_ferret(option = :once)
  83 + if block_given?
  84 + @ferret_disabled = :always
  85 + result = yield
  86 + ferret_enable
  87 + ferret_update if option == :index_when_finished || (option == :index_when_true && result)
  88 + result
  89 + elsif [:once, :always].include?(option)
  90 + @ferret_disabled = option
  91 + else
  92 + raise ArgumentError.new("Invalid Argument #{option}")
  93 + end
  94 + end
  95 +
  96 + # add to index
  97 + def ferret_create
  98 + if ferret_enabled?
  99 + logger.debug "ferret_create/update: #{self.class.name} : #{self.id}"
  100 + self.class.aaf_index << self
  101 + else
  102 + ferret_enable if @ferret_disabled == :once
  103 + end
  104 + true # signal success to AR
  105 + end
  106 + alias :ferret_update :ferret_create
  107 +
  108 +
  109 + # remove from index
  110 + def ferret_destroy
  111 + logger.debug "ferret_destroy: #{self.class.name} : #{self.id}"
  112 + begin
  113 + self.class.aaf_index.remove self.id, self.class.name
  114 + rescue
  115 + logger.warn("Could not find indexed value for this object: #{$!}\n#{$!.backtrace}")
  116 + end
  117 + true # signal success to AR
  118 + end
  119 +
  120 + # turn this instance into a ferret document (which basically is a hash of
  121 + # fieldname => value pairs)
  122 + def to_doc
  123 + logger.debug "creating doc for class: #{self.class.name}, id: #{self.id}"
  124 + returning Ferret::Document.new do |doc|
  125 + # store the id and class name of each item
  126 + doc[:id] = self.id
  127 + doc[:class_name] = self.class.name
  128 +
  129 + # iterate through the fields and add them to the document
  130 + aaf_configuration[:defined_fields].each_pair do |field, config|
  131 + doc[field] = self.send("#{field}_to_ferret") unless config[:ignore]
  132 + end
  133 + if aaf_configuration[:boost]
  134 + if self.respond_to?(aaf_configuration[:boost])
  135 + boost = self.send aaf_configuration[:boost]
  136 + doc.boost = boost.to_i if boost
  137 + else
  138 + logger.error "boost option should point to an instance method: #{aaf_configuration[:boost]}"
  139 + end
  140 + end
  141 + end
  142 + end
  143 +
  144 + def document_number
  145 + self.class.aaf_index.document_number(id, self.class.name)
  146 + end
  147 +
  148 + def query_for_record
  149 + self.class.aaf_index.query_for_record(id, self.class.name)
  150 + end
  151 +
  152 + def content_for_field_name(field, via = field, dynamic_boost = nil)
  153 + return nil if field.to_sym == :type
  154 + field_data = self.send(via) || self.instance_variable_get("@#{via}")
  155 + if (dynamic_boost && boost_value = self.send(dynamic_boost))
  156 + field_data = Ferret::Field.new(field_data)
  157 + field_data.boost = boost_value.to_i
  158 + end
  159 + field_data
  160 + end
  161 +
  162 +
  163 + end
  164 +
  165 +end
vendor/plugins/acts_as_ferret/lib/local_index.rb 0 → 100644
@@ -0,0 +1,199 @@ @@ -0,0 +1,199 @@
  1 +module ActsAsFerret
  2 + class LocalIndex < AbstractIndex
  3 + include MoreLikeThis::IndexMethods
  4 +
  5 + def initialize(index_name)
  6 + super
  7 + ensure_index_exists
  8 + end
  9 +
  10 + def reopen!
  11 + logger.debug "reopening index at #{index_definition[:ferret][:path]}"
  12 + close
  13 + ferret_index
  14 + end
  15 +
  16 + # The 'real' Ferret Index instance
  17 + def ferret_index
  18 + ensure_index_exists
  19 + returning @ferret_index ||= Ferret::Index::Index.new(index_definition[:ferret]) do
  20 + @ferret_index.batch_size = index_definition[:reindex_batch_size]
  21 + @ferret_index.logger = logger
  22 + end
  23 + end
  24 +
  25 + # Checks for the presence of a segments file in the index directory
  26 + # Rebuilds the index if none exists.
  27 + def ensure_index_exists
  28 + #logger.debug "LocalIndex: ensure_index_exists at #{index_definition[:index_dir]}"
  29 + unless File.file? "#{index_definition[:index_dir]}/segments"
  30 + ActsAsFerret::ensure_directory(index_definition[:index_dir])
  31 + rebuild_index
  32 + end
  33 + end
  34 +
  35 + # Closes the underlying index instance
  36 + def close
  37 + @ferret_index.close if @ferret_index
  38 + rescue StandardError
  39 + # is raised when index already closed
  40 + ensure
  41 + @ferret_index = nil
  42 + end
  43 +
  44 + # rebuilds the index from all records of the model classes associated with this index
  45 + def rebuild_index
  46 + models = index_definition[:registered_models]
  47 + logger.debug "rebuild index with models: #{models.inspect}"
  48 + close
  49 + index = Ferret::Index::Index.new(index_definition[:ferret].dup.update(:auto_flush => false,
  50 + :field_infos => ActsAsFerret::field_infos(index_definition),
  51 + :create => true))
  52 + index.batch_size = index_definition[:reindex_batch_size]
  53 + index.logger = logger
  54 + index.index_models models
  55 + reopen!
  56 + end
  57 +
  58 + def bulk_index(class_name, ids, options)
  59 + ferret_index.bulk_index(class_name.constantize, ids, options)
  60 + end
  61 +
  62 + # Parses the given query string into a Ferret Query object.
  63 + def process_query(query, options = {})
  64 + return query unless String === query
  65 + ferret_index.synchronize do
  66 + if options[:analyzer]
  67 + # use per-query analyzer if present
  68 + qp = Ferret::QueryParser.new ferret_index.instance_variable_get('@options').merge(options)
  69 + reader = ferret_index.reader
  70 + qp.fields =
  71 + reader.fields unless options[:all_fields] || options[:fields]
  72 + qp.tokenized_fields =
  73 + reader.tokenized_fields unless options[:tokenized_fields]
  74 + return qp.parse query
  75 + else
  76 + # work around ferret bug in #process_query (doesn't ensure the
  77 + # reader is open)
  78 + ferret_index.send(:ensure_reader_open)
  79 + return ferret_index.process_query(query)
  80 + end
  81 + end
  82 + end
  83 +
  84 + # Total number of hits for the given query.
  85 + def total_hits(query, options = {})
  86 + ferret_index.search(query, options).total_hits
  87 + end
  88 +
  89 + def searcher
  90 + ferret_index
  91 + end
  92 +
  93 +
  94 + ######################################
  95 + # methods working on a single record
  96 + # called from instance_methods, here to simplify interfacing with the
  97 + # remote ferret server
  98 + # TODO having to pass id and class_name around like this isn't nice
  99 + ######################################
  100 +
  101 + # add record to index
  102 + # record may be the full AR object, a Ferret document instance or a Hash
  103 + def add(record, analyzer = nil)
  104 + unless Hash === record || Ferret::Document === record
  105 + analyzer = record.ferret_analyzer
  106 + record = record.to_doc
  107 + end
  108 + ferret_index.add_document(record, analyzer)
  109 + end
  110 + alias << add
  111 +
  112 + # delete record from index
  113 + def remove(id, class_name)
  114 + ferret_index.query_delete query_for_record(id, class_name)
  115 + end
  116 +
  117 + # highlight search terms for the record with the given id.
  118 + def highlight(id, class_name, query, options = {})
  119 + logger.debug("highlight: #{class_name} / #{id} query: #{query}")
  120 + options.reverse_merge! :num_excerpts => 2, :pre_tag => '<em>', :post_tag => '</em>'
  121 + highlights = []
  122 + ferret_index.synchronize do
  123 + doc_num = document_number(id, class_name)
  124 +
  125 + if options[:field]
  126 + highlights << ferret_index.highlight(query, doc_num, options)
  127 + else
  128 + query = process_query(query) # process only once
  129 + index_definition[:ferret_fields].each_pair do |field, config|
  130 + next if config[:store] == :no || config[:highlight] == :no
  131 + options[:field] = field
  132 + highlights << ferret_index.highlight(query, doc_num, options)
  133 + end
  134 + end
  135 + end
  136 + return highlights.compact.flatten[0..options[:num_excerpts]-1]
  137 + end
  138 +
  139 + # retrieves the ferret document number of the record with the given id.
  140 + def document_number(id, class_name)
  141 + hits = ferret_index.search(query_for_record(id, class_name))
  142 + return hits.hits.first.doc if hits.total_hits == 1
  143 + raise "cannot determine document number for class #{class_name} / primary key: #{id}\nresult was: #{hits.inspect}"
  144 + end
  145 +
  146 + # build a ferret query matching only the record with the given id
  147 + # the class name only needs to be given in case of a shared index configuration
  148 + def query_for_record(id, class_name = nil)
  149 + if shared?
  150 + raise InvalidArgumentError.new("shared index needs class_name argument") if class_name.nil?
  151 + returning bq = Ferret::Search::BooleanQuery.new do
  152 + bq.add_query(Ferret::Search::TermQuery.new(:id, id.to_s), :must)
  153 + bq.add_query(Ferret::Search::TermQuery.new(:class_name, class_name), :must)
  154 + end
  155 + else
  156 + Ferret::Search::TermQuery.new(:id, id.to_s)
  157 + end
  158 + end
  159 +
  160 +
  161 +
  162 + def determine_stored_fields(options = {})
  163 + stored_fields = options[:lazy]
  164 + if stored_fields && !(Array === stored_fields)
  165 + stored_fields = index_definition[:ferret_fields].select { |field, config| config[:store] == :yes }.map(&:first)
  166 + end
  167 + logger.debug "stored_fields: #{stored_fields.inspect}"
  168 + return stored_fields
  169 + end
  170 +
  171 + # loads data for fields declared as :lazy from the Ferret document
  172 + def extract_stored_fields(doc, stored_fields)
  173 + fields = index_definition[:ferret_fields]
  174 + data = {}
  175 + logger.debug "extracting stored fields #{stored_fields.inspect} from document #{doc[:class_name]} / #{doc[:id]}"
  176 + stored_fields.each do |field|
  177 + if field_cfg = fields[field]
  178 + data[field_cfg[:via]] = doc[field]
  179 + end
  180 + end if stored_fields
  181 + logger.debug "done: #{data.inspect}"
  182 + return data
  183 + end
  184 +
  185 + protected
  186 +
  187 + # returns a MultiIndex instance operating on a MultiReader
  188 + #def multi_index(model_classes)
  189 + # model_classes.map!(&:constantize) if String === model_classes.first
  190 + # model_classes.sort! { |a, b| a.name <=> b.name }
  191 + # key = model_classes.inject("") { |s, clazz| s + clazz.name }
  192 + # multi_config = index_definition[:ferret].dup
  193 + # multi_config.delete :default_field # we don't want the default field list of *this* class for multi_searching
  194 + # ActsAsFerret::multi_indexes[key] ||= MultiIndex.new(model_classes, multi_config)
  195 + #end
  196 +
  197 + end
  198 +
  199 +end
vendor/plugins/acts_as_ferret/lib/more_like_this.rb 0 → 100644
@@ -0,0 +1,217 @@ @@ -0,0 +1,217 @@
  1 +module ActsAsFerret #:nodoc:
  2 +
  3 + module MoreLikeThis
  4 +
  5 + module InstanceMethods
  6 +
  7 + # returns other instances of this class, which have similar contents
  8 + # like this one. Basically works like this: find out n most interesting
  9 + # (i.e. characteristic) terms from this document, and then build a
  10 + # query from those which is run against the whole index. Which terms
  11 + # are interesting is decided on variour criteria which can be
  12 + # influenced by the given options.
  13 + #
  14 + # The algorithm used here is a quite straight port of the MoreLikeThis class
  15 + # from Apache Lucene.
  16 + #
  17 + # options are:
  18 + # :field_names : Array of field names to use for similarity search (mandatory)
  19 + # :min_term_freq => 2, # Ignore terms with less than this frequency in the source doc.
  20 + # :min_doc_freq => 5, # Ignore words which do not occur in at least this many docs
  21 + # :min_word_length => nil, # Ignore words shorter than this length (longer words tend to
  22 + # be more characteristic for the document they occur in).
  23 + # :max_word_length => nil, # Ignore words if greater than this len.
  24 + # :max_query_terms => 25, # maximum number of terms in the query built
  25 + # :max_num_tokens => 5000, # maximum number of tokens to examine in a single field
  26 + # :boost => false, # when true, a boost according to the relative score of
  27 + # a term is applied to this Term's TermQuery.
  28 + # :similarity => 'DefaultAAFSimilarity' # the similarity implementation to use (the default
  29 + # equals Ferret's internal similarity implementation)
  30 + # :analyzer => 'Ferret::Analysis::StandardAnalyzer' # class name of the analyzer to use
  31 + # :append_to_query => nil # proc taking a query object as argument, which will be called after generating the query. can be used to further manipulate the query used to find related documents, i.e. to constrain the search to a given class in single table inheritance scenarios
  32 + # ferret_options : Ferret options handed over to find_by_contents (i.e. for limits and sorting)
  33 + # ar_options : options handed over to find_by_contents for AR scoping
  34 + def more_like_this(options = {}, ferret_options = {}, ar_options = {})
  35 + options = {
  36 + :field_names => nil, # Default field names
  37 + :min_term_freq => 2, # Ignore terms with less than this frequency in the source doc.
  38 + :min_doc_freq => 5, # Ignore words which do not occur in at least this many docs
  39 + :min_word_length => 0, # Ignore words if less than this len. Default is not to ignore any words.
  40 + :max_word_length => 0, # Ignore words if greater than this len. Default is not to ignore any words.
  41 + :max_query_terms => 25, # maximum number of terms in the query built
  42 + :max_num_tokens => 5000, # maximum number of tokens to analyze when analyzing contents
  43 + :boost => false,
  44 + :similarity => 'ActsAsFerret::MoreLikeThis::DefaultAAFSimilarity', # class name of the similarity implementation to use
  45 + :analyzer => 'Ferret::Analysis::StandardAnalyzer', # class name of the analyzer to use
  46 + :append_to_query => nil,
  47 + :base_class => self.class # base class to use for querying, useful in STI scenarios where BaseClass.find_by_contents can be used to retrieve results from other classes, too
  48 + }.update(options)
  49 + #index.search_each('id:*') do |doc, score|
  50 + # puts "#{doc} == #{index[doc][:description]}"
  51 + #end
  52 + clazz = options[:base_class]
  53 + options[:base_class] = clazz.name
  54 + query = clazz.aaf_index.build_more_like_this_query(self.id, self.class.name, options)
  55 + options[:append_to_query].call(query) if options[:append_to_query]
  56 + clazz.find_with_ferret(query, ferret_options, ar_options)
  57 + end
  58 +
  59 + end
  60 +
  61 + module IndexMethods
  62 +
  63 + # TODO to allow morelikethis for unsaved records, we have to give the
  64 + # unsaved record's data to this method. check how this will work out
  65 + # via drb...
  66 + def build_more_like_this_query(id, class_name, options)
  67 + [:similarity, :analyzer].each { |sym| options[sym] = options[sym].constantize.new }
  68 + ferret_index.synchronize do # avoid that concurrent writes close our reader
  69 + ferret_index.send(:ensure_reader_open)
  70 + reader = ferret_index.send(:reader)
  71 + term_freq_map = retrieve_terms(id, class_name, reader, options)
  72 + priority_queue = create_queue(term_freq_map, reader, options)
  73 + create_query(id, class_name, priority_queue, options)
  74 + end
  75 + end
  76 +
  77 + protected
  78 +
  79 + def create_query(id, class_name, priority_queue, options={})
  80 + query = Ferret::Search::BooleanQuery.new
  81 + qterms = 0
  82 + best_score = nil
  83 + while(cur = priority_queue.pop)
  84 + term_query = Ferret::Search::TermQuery.new(cur.field, cur.word)
  85 +
  86 + if options[:boost]
  87 + # boost term according to relative score
  88 + # TODO untested
  89 + best_score ||= cur.score
  90 + term_query.boost = cur.score / best_score
  91 + end
  92 + begin
  93 + query.add_query(term_query, :should)
  94 + rescue Ferret::Search::BooleanQuery::TooManyClauses
  95 + break
  96 + end
  97 + qterms += 1
  98 + break if options[:max_query_terms] > 0 && qterms >= options[:max_query_terms]
  99 + end
  100 + # exclude the original record
  101 + query.add_query(query_for_record(id, class_name), :must_not)
  102 + return query
  103 + end
  104 +
  105 +
  106 +
  107 + # creates a term/term_frequency map for terms from the fields
  108 + # given in options[:field_names]
  109 + def retrieve_terms(id, class_name, reader, options)
  110 + raise "more_like_this atm only works on saved records" if id.nil?
  111 + document_number = document_number(id, class_name) rescue nil
  112 + field_names = options[:field_names]
  113 + max_num_tokens = options[:max_num_tokens]
  114 + term_freq_map = Hash.new(0)
  115 + doc = nil
  116 + record = nil
  117 + field_names.each do |field|
  118 + #puts "field: #{field}"
  119 + term_freq_vector = reader.term_vector(document_number, field) if document_number
  120 + #if false
  121 + if term_freq_vector
  122 + # use stored term vector
  123 + # puts 'using stored term vector'
  124 + term_freq_vector.terms.each do |term|
  125 + term_freq_map[term.text] += term.positions.size unless noise_word?(term.text, options)
  126 + end
  127 + else
  128 + # puts 'no stored term vector'
  129 + # no term vector stored, but we have stored the contents in the index
  130 + # -> extract terms from there
  131 + content = nil
  132 + if document_number
  133 + doc = reader[document_number]
  134 + content = doc[field]
  135 + end
  136 + unless content
  137 + # no term vector, no stored content, so try content from this instance
  138 + record ||= options[:base_class].constantize.find(id)
  139 + content = record.content_for_field_name(field.to_s)
  140 + end
  141 + puts "have doc: #{doc[:id]} with #{field} == #{content}"
  142 + token_count = 0
  143 +
  144 + ts = options[:analyzer].token_stream(field, content)
  145 + while token = ts.next
  146 + break if (token_count+=1) > max_num_tokens
  147 + next if noise_word?(token.text, options)
  148 + term_freq_map[token.text] += 1
  149 + end
  150 + end
  151 + end
  152 + term_freq_map
  153 + end
  154 +
  155 + # create an ordered(by score) list of word,fieldname,score
  156 + # structures
  157 + def create_queue(term_freq_map, reader, options)
  158 + pq = Array.new(term_freq_map.size)
  159 +
  160 + similarity = options[:similarity]
  161 + num_docs = reader.num_docs
  162 + term_freq_map.each_pair do |word, tf|
  163 + # filter out words that don't occur enough times in the source
  164 + next if options[:min_term_freq] && tf < options[:min_term_freq]
  165 +
  166 + # go through all the fields and find the largest document frequency
  167 + top_field = options[:field_names].first
  168 + doc_freq = 0
  169 + options[:field_names].each do |field_name|
  170 + freq = reader.doc_freq(field_name, word)
  171 + if freq > doc_freq
  172 + top_field = field_name
  173 + doc_freq = freq
  174 + end
  175 + end
  176 + # filter out words that don't occur in enough docs
  177 + next if options[:min_doc_freq] && doc_freq < options[:min_doc_freq]
  178 + next if doc_freq == 0 # index update problem ?
  179 +
  180 + idf = similarity.idf(doc_freq, num_docs)
  181 + score = tf * idf
  182 + pq << FrequencyQueueItem.new(word, top_field, score)
  183 + end
  184 + pq.compact!
  185 + pq.sort! { |a,b| a.score<=>b.score }
  186 + return pq
  187 + end
  188 +
  189 + def noise_word?(text, options)
  190 + len = text.length
  191 + (
  192 + (options[:min_word_length] > 0 && len < options[:min_word_length]) ||
  193 + (options[:max_word_length] > 0 && len > options[:max_word_length]) ||
  194 + (options[:stop_words] && options.include?(text))
  195 + )
  196 + end
  197 +
  198 + end
  199 +
  200 + class DefaultAAFSimilarity
  201 + def idf(doc_freq, num_docs)
  202 + return 0.0 if num_docs == 0
  203 + return Math.log(num_docs.to_f/(doc_freq+1)) + 1.0
  204 + end
  205 + end
  206 +
  207 +
  208 + class FrequencyQueueItem
  209 + attr_reader :word, :field, :score
  210 + def initialize(word, field, score)
  211 + @word = word; @field = field; @score = score
  212 + end
  213 + end
  214 +
  215 + end
  216 +end
  217 +
vendor/plugins/acts_as_ferret/lib/multi_index.rb 0 → 100644
@@ -0,0 +1,126 @@ @@ -0,0 +1,126 @@
  1 +module ActsAsFerret #:nodoc:
  2 +
  3 + # Base class for remote and local multi-indexes
  4 + class MultiIndexBase
  5 + include FerretFindMethods
  6 + attr_accessor :logger
  7 +
  8 + def initialize(indexes, options = {})
  9 + # ensure all models indexes exist
  10 + @indexes = indexes
  11 + indexes.each { |i| i.ensure_index_exists }
  12 + default_fields = indexes.inject([]) do |fields, idx|
  13 + fields + [ idx.index_definition[:ferret][:default_field] ]
  14 + end.flatten.uniq
  15 + @options = {
  16 + :default_field => default_fields
  17 + }.update(options)
  18 + @logger = IndexLogger.new(ActsAsFerret::logger, "multi: #{indexes.map(&:index_name).join(',')}")
  19 + end
  20 +
  21 + def ar_find(query, options = {}, ar_options = {})
  22 + limit = options.delete(:limit)
  23 + offset = options.delete(:offset) || 0
  24 + options[:limit] = :all
  25 + total_hits, result = super query, options, ar_options
  26 + total_hits = result.size if ar_options[:conditions]
  27 + if limit && limit != :all
  28 + result = result[offset..limit+offset-1]
  29 + end
  30 + [total_hits, result]
  31 + end
  32 +
  33 + def determine_stored_fields(options)
  34 + return nil unless options.has_key?(:lazy)
  35 + stored_fields = []
  36 + @indexes.each do |index|
  37 + stored_fields += index.determine_stored_fields(options)
  38 + end
  39 + return stored_fields.uniq
  40 + end
  41 +
  42 + def shared?
  43 + false
  44 + end
  45 +
  46 + end
  47 +
  48 + # This class can be used to search multiple physical indexes at once.
  49 + class MultiIndex < MultiIndexBase
  50 +
  51 + def extract_stored_fields(doc, stored_fields)
  52 + ActsAsFerret::get_index_for(doc[:class_name]).extract_stored_fields(doc, stored_fields) unless stored_fields.blank?
  53 + end
  54 +
  55 + def total_hits(q, options = {})
  56 + search(q, options).total_hits
  57 + end
  58 +
  59 + def search(query, options={})
  60 + query = process_query(query, options)
  61 + logger.debug "parsed query: #{query.to_s}"
  62 + searcher.search(query, options)
  63 + end
  64 +
  65 + def search_each(query, options = {}, &block)
  66 + query = process_query(query, options)
  67 + searcher.search_each(query, options, &block)
  68 + end
  69 +
  70 + # checks if all our sub-searchers still are up to date
  71 + def latest?
  72 + #return false unless @reader
  73 + # segfaults with 0.10.4 --> TODO report as bug @reader.latest?
  74 + @reader and @reader.latest?
  75 + #@sub_readers.each do |r|
  76 + # return false unless r.latest?
  77 + #end
  78 + #true
  79 + end
  80 +
  81 + def searcher
  82 + ensure_searcher
  83 + @searcher
  84 + end
  85 +
  86 + def doc(i)
  87 + searcher[i]
  88 + end
  89 + alias :[] :doc
  90 +
  91 + def query_parser
  92 + @query_parser ||= Ferret::QueryParser.new(@options)
  93 + end
  94 +
  95 + def process_query(query, options = {})
  96 + query = query_parser.parse(query) if query.is_a?(String)
  97 + return query
  98 + end
  99 +
  100 + def close
  101 + @searcher.close if @searcher
  102 + @reader.close if @reader
  103 + end
  104 +
  105 + protected
  106 +
  107 + def ensure_searcher
  108 + unless latest?
  109 + @sub_readers = @indexes.map { |idx|
  110 + begin
  111 + reader = Ferret::Index::IndexReader.new(idx.index_definition[:index_dir])
  112 + logger.debug "sub-reader opened: #{reader}"
  113 + reader
  114 + rescue Exception
  115 + raise "error opening reader on index for class #{clazz.inspect}: #{$!}"
  116 + end
  117 + }
  118 + close
  119 + @reader = Ferret::Index::IndexReader.new(@sub_readers)
  120 + @searcher = Ferret::Search::Searcher.new(@reader)
  121 + end
  122 + end
  123 +
  124 + end # of class MultiIndex
  125 +
  126 +end
vendor/plugins/acts_as_ferret/lib/rdig_adapter.rb 0 → 100644
@@ -0,0 +1,141 @@ @@ -0,0 +1,141 @@
  1 +begin
  2 + require 'rdig'
  3 +rescue LoadError
  4 +end
  5 +module ActsAsFerret
  6 +
  7 + # The RdigAdapter is automatically included into your model if you specify
  8 + # the +:rdig+ options hash in your call to acts_as_ferret. It overrides
  9 + # several methods declared by aaf to retrieve documents with the help of
  10 + # RDig's http crawler when you call rebuild_index.
  11 + module RdigAdapter
  12 +
  13 + if defined?(RDig)
  14 +
  15 + def self.included(target)
  16 + target.extend ClassMethods
  17 + target.send :include, InstanceMethods
  18 + end
  19 +
  20 + # Indexer class to replace RDig's original indexer
  21 + class Indexer
  22 + include MonitorMixin
  23 + def initialize(batch_size, model_class, &block)
  24 + @batch_size = batch_size
  25 + @model_class = model_class
  26 + @documents = []
  27 + @offset = 0
  28 + @block = block
  29 + super()
  30 + end
  31 +
  32 + def add(doc)
  33 + synchronize do
  34 + @documents << @model_class.new(doc.uri.to_s, doc)
  35 + process_batch if @documents.size >= @batch_size
  36 + end
  37 + end
  38 + alias << add
  39 +
  40 + def close
  41 + synchronize do
  42 + process_batch
  43 + end
  44 + end
  45 +
  46 + protected
  47 + def process_batch
  48 + ActsAsFerret::logger.info "RdigAdapter::Indexer#process_batch: #{@documents.size} docs in queue, offset #{@offset}"
  49 + @block.call @documents, @offset
  50 + @offset += @documents.size
  51 + @documents = []
  52 + end
  53 + end
  54 +
  55 + module ClassMethods
  56 + # overriding aaf to return the documents fetched via RDig
  57 + def records_for_rebuild(batch_size = 1000, &block)
  58 + indexer = Indexer.new(batch_size, self, &block)
  59 + configure_rdig do
  60 + crawler = RDig::Crawler.new RDig.configuration, ActsAsFerret::logger
  61 + crawler.instance_variable_set '@indexer', indexer
  62 + ActsAsFerret::logger.debug "now crawling..."
  63 + crawler.crawl
  64 + end
  65 + rescue => e
  66 + ActsAsFerret::logger.error e
  67 + ActsAsFerret::logger.debug e.backtrace.join("\n")
  68 + ensure
  69 + indexer.close if indexer
  70 + end
  71 +
  72 + # overriding aaf to skip reindexing records changed during the rebuild
  73 + # when rebuilding with the rake task
  74 + def records_modified_since(time)
  75 + []
  76 + end
  77 +
  78 + # unfortunately need to modify global RDig.configuration because it's
  79 + # used everywhere in RDig
  80 + def configure_rdig
  81 + # back up original config
  82 + old_logger = RDig.logger
  83 + old_cfg = RDig.configuration.dup
  84 + RDig.logger = ActsAsFerret.logger
  85 + rdig_configuration[:crawler].each { |k,v| RDig.configuration.crawler.send :"#{k}=", v } if rdig_configuration[:crawler]
  86 + if ce_config = rdig_configuration[:content_extraction]
  87 + RDig.configuration.content_extraction = OpenStruct.new( :hpricot => OpenStruct.new( ce_config ) )
  88 + end
  89 + yield
  90 + ensure
  91 + # restore original config
  92 + RDig.configuration.crawler = old_cfg.crawler
  93 + RDig.configuration.content_extraction = old_cfg.content_extraction
  94 + RDig.logger = old_logger
  95 + end
  96 +
  97 + # overriding aaf to enforce loading page title and content from the
  98 + # ferret index
  99 + def find_with_ferret(q, options = {}, find_options = {})
  100 + options[:lazy] = true
  101 + super
  102 + end
  103 +
  104 + def find_for_id(id)
  105 + new id
  106 + end
  107 + end
  108 +
  109 + module InstanceMethods
  110 + def initialize(uri, rdig_document = nil)
  111 + @id = uri
  112 + @rdig_document = rdig_document
  113 + end
  114 +
  115 + # Title of the document.
  116 + # Use the +:title_tag_selector+ option to declare the hpricot expression
  117 + # that should be used for selecting the content for this field.
  118 + def title
  119 + @rdig_document.title
  120 + end
  121 +
  122 + # Content of the document.
  123 + # Use the +:content_tag_selector+ option to declare the hpricot expression
  124 + # that should be used for selecting the content for this field.
  125 + def content
  126 + @rdig_document.body
  127 + end
  128 +
  129 + # Url of this document.
  130 + def id
  131 + @id
  132 + end
  133 +
  134 + def to_s
  135 + "Page at #{id}, title: #{title}"
  136 + end
  137 + end
  138 + end
  139 + end
  140 +
  141 +end
vendor/plugins/acts_as_ferret/lib/remote_functions.rb 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +module ActsAsFerret
  2 + module RemoteFunctions
  3 +
  4 + private
  5 +
  6 + def yield_results(total_hits, results)
  7 + results.each do |result|
  8 + yield result[:model], result[:id], result[:score], result[:data]
  9 + end
  10 + total_hits
  11 + end
  12 +
  13 +
  14 + def handle_drb_error(return_value_in_case_of_error = false)
  15 + yield
  16 + rescue DRb::DRbConnError => e
  17 + logger.error "DRb connection error: #{e}"
  18 + logger.warn e.backtrace.join("\n")
  19 + raise e if ActsAsFerret::raise_drb_errors?
  20 + return_value_in_case_of_error
  21 + end
  22 + end
  23 +end
vendor/plugins/acts_as_ferret/lib/remote_index.rb 0 → 100644
@@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
  1 +require 'drb'
  2 +module ActsAsFerret
  3 +
  4 + # This index implementation connects to a remote ferret server instance. It
  5 + # basically forwards all calls to the remote server.
  6 + class RemoteIndex < AbstractIndex
  7 + include RemoteFunctions
  8 +
  9 + def initialize(config)
  10 + super
  11 + @server = DRbObject.new(nil, ActsAsFerret::remote)
  12 + end
  13 +
  14 + # Cause model classes to be loaded (and indexes get declared) on the DRb
  15 + # side of things.
  16 + def register_class(clazz, options)
  17 + handle_drb_error { @server.register_class clazz.name }
  18 + end
  19 +
  20 + def method_missing(method_name, *args)
  21 + args.unshift index_name
  22 + handle_drb_error { @server.send(method_name, *args) }
  23 + end
  24 +
  25 + # Proxy any methods that require special return values in case of errors
  26 + {
  27 + :highlight => []
  28 + }.each do |method_name, default_result|
  29 + define_method method_name do |*args|
  30 + args.unshift index_name
  31 + handle_drb_error(default_result) { @server.send method_name, *args }
  32 + end
  33 + end
  34 +
  35 + def find_ids(q, options = {}, &proc)
  36 + total_hits, results = handle_drb_error([0, []]) { @server.find_ids(index_name, q, options) }
  37 + block_given? ? yield_results(total_hits, results, &proc) : [ total_hits, results ]
  38 + end
  39 +
  40 + # add record to index
  41 + def add(record)
  42 + handle_drb_error { @server.add index_name, record.to_doc }
  43 + end
  44 + alias << add
  45 +
  46 + private
  47 +
  48 + #def model_class_name
  49 + # index_definition[:class_name]
  50 + #end
  51 +
  52 + end
  53 +
  54 +end
vendor/plugins/acts_as_ferret/lib/remote_multi_index.rb 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +module ActsAsFerret
  2 + class RemoteMultiIndex < MultiIndexBase
  3 + include RemoteFunctions
  4 +
  5 + def initialize(indexes, options = {})
  6 + @index_names = indexes.map(&:index_name)
  7 + @server = DRbObject.new(nil, ActsAsFerret::remote)
  8 + super
  9 + end
  10 +
  11 + def find_ids(query, options, &proc)
  12 + total_hits, results = handle_drb_error([0, []]) { @server.multi_find_ids(@index_names, query, options) }
  13 + block_given? ? yield_results(total_hits, results, &proc) : [ total_hits, results ]
  14 + end
  15 +
  16 + def method_missing(name, *args)
  17 + handle_drb_error { @server.send(:"multi_#{name}", @index_names, *args) }
  18 + end
  19 + end
  20 +end
vendor/plugins/acts_as_ferret/lib/search_results.rb 0 → 100644
@@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
  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 < ActsAsFerret::BlankSlate
  6 + reveal :methods
  7 + attr_reader :current_page, :per_page, :total_hits, :total_pages
  8 + alias total_entries total_hits # will_paginate compatibility
  9 + alias page_count total_pages # will_paginate backwards compatibility
  10 +
  11 + def initialize(results, total_hits, current_page = 1, per_page = nil)
  12 + @results = results
  13 + @total_hits = total_hits
  14 + @current_page = current_page
  15 + @per_page = (per_page || total_hits)
  16 + @total_pages = @per_page > 0 ? (@total_hits / @per_page.to_f).ceil : 0
  17 + end
  18 +
  19 + def method_missing(symbol, *args, &block)
  20 + @results.send(symbol, *args, &block)
  21 + end
  22 +
  23 + def respond_to?(name)
  24 + methods.include?(name.to_s) || @results.respond_to?(name)
  25 + end
  26 +
  27 +
  28 + # code from here on was directly taken from will_paginate's collection.rb
  29 +
  30 + # Current offset of the paginated collection. If we're on the first page,
  31 + # it is always 0. If we're on the 2nd page and there are 30 entries per page,
  32 + # the offset is 30. This property is useful if you want to render ordinals
  33 + # besides your records: simply start with offset + 1.
  34 + #
  35 + def offset
  36 + (current_page - 1) * per_page
  37 + end
  38 +
  39 + # current_page - 1 or nil if there is no previous page
  40 + def previous_page
  41 + current_page > 1 ? (current_page - 1) : nil
  42 + end
  43 +
  44 + # current_page + 1 or nil if there is no next page
  45 + def next_page
  46 + current_page < total_pages ? (current_page + 1) : nil
  47 + end
  48 + end
  49 +
  50 +end
vendor/plugins/acts_as_ferret/lib/server_manager.rb 0 → 100644
@@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
  1 +################################################################################
  2 +require 'optparse'
  3 +
  4 +################################################################################
  5 +$ferret_server_options = {
  6 + 'environment' => nil,
  7 + 'debug' => nil,
  8 + 'root' => nil
  9 +}
  10 +
  11 +################################################################################
  12 +OptionParser.new do |optparser|
  13 + optparser.banner = "Usage: #{File.basename($0)} [options] {start|stop|run}"
  14 +
  15 + optparser.on('-h', '--help', "This message") do
  16 + puts optparser
  17 + exit
  18 + end
  19 +
  20 + optparser.on('-R', '--root=PATH', 'Set RAILS_ROOT to the given string') do |r|
  21 + $ferret_server_options['root'] = r
  22 + end
  23 +
  24 + optparser.on('-e', '--environment=NAME', 'Set RAILS_ENV to the given string') do |e|
  25 + $ferret_server_options['environment'] = e
  26 + end
  27 +
  28 + optparser.on('--debug', 'Include full stack traces on exceptions') do
  29 + $ferret_server_options['debug'] = true
  30 + end
  31 +
  32 + $ferret_server_action = optparser.permute!(ARGV)
  33 + (puts optparser; exit(1)) unless $ferret_server_action.size == 1
  34 +
  35 + $ferret_server_action = $ferret_server_action.first
  36 + (puts optparser; exit(1)) unless %w(start stop run).include?($ferret_server_action)
  37 +end
  38 +
  39 +################################################################################
  40 +begin
  41 + ENV['FERRET_USE_LOCAL_INDEX'] = 'true'
  42 + ENV['RAILS_ENV'] = $ferret_server_options['environment']
  43 +
  44 + # determine RAILS_ROOT unless already set
  45 + RAILS_ROOT = $ferret_server_options['root'] || File.join(File.dirname(__FILE__), *(['..']*4)) unless defined? RAILS_ROOT
  46 + # check if environment.rb is present
  47 + rails_env_file = File.join(RAILS_ROOT, 'config', 'environment')
  48 + raise "Unable to find Rails environment.rb at \n#{rails_env_file}.rb\nPlease use the --root option of ferret_server to point it to your RAILS_ROOT." unless File.exists?(rails_env_file+'.rb')
  49 + # load it
  50 + require rails_env_file
  51 +
  52 + require 'acts_as_ferret'
  53 + ActsAsFerret::Remote::Server.new.send($ferret_server_action)
  54 +rescue Exception => e
  55 + $stderr.puts(e.message)
  56 + $stderr.puts(e.backtrace.join("\n")) if $ferret_server_options['debug']
  57 + exit(1)
  58 +end
vendor/plugins/acts_as_ferret/lib/unix_daemon.rb 0 → 100644
@@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
  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 + raise "ferret_server may already be running, a pid file exists: #{@cfg.pid_file}" if read_pid_file
  53 + open(@cfg.pid_file, "w") {|f| f << Process.pid << "\n"}
  54 + at_exit { File.unlink(@cfg.pid_file) if read_pid_file == Process.pid }
  55 + end
  56 +
  57 + #################################################################################
  58 + def read_pid_file
  59 + File.read(@cfg.pid_file).to_i if File.exist?(@cfg.pid_file)
  60 + end
  61 +
  62 + end
  63 + end
  64 +end
vendor/plugins/acts_as_ferret/lib/without_ar.rb 0 → 100644
@@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
  1 +module ActsAsFerret
  2 +
  3 + # Include this module to use acts_as_ferret with model classes
  4 + # not based on ActiveRecord.
  5 + #
  6 + # Implement the find_for_id(id) class method in your model class in
  7 + # order to make search work.
  8 + module WithoutAR
  9 + def self.included(target)
  10 + target.extend ClassMethods
  11 + target.extend ActsAsFerret::ActMethods
  12 + target.send :include, InstanceMethods
  13 + end
  14 +
  15 + module ClassMethods
  16 + def logger
  17 + RAILS_DEFAULT_LOGGER
  18 + end
  19 + def table_name
  20 + self.name.underscore
  21 + end
  22 + def primary_key
  23 + 'id'
  24 + end
  25 + def find(what, args = {})
  26 + case what
  27 + when :all
  28 + ids = args[:conditions][1]
  29 + ids.map { |id| find id }
  30 + else
  31 + find_for_id what
  32 + end
  33 + end
  34 + def find_for_id(id)
  35 + raise NotImplementedError.new("implement find_for_id in class #{self.name}")
  36 + end
  37 + def count
  38 + 0
  39 + end
  40 + end
  41 +
  42 + module InstanceMethods
  43 + def logger
  44 + self.class.logger
  45 + end
  46 + end
  47 + end
  48 +
  49 +end
vendor/plugins/acts_as_ferret/rakefile 0 → 100644
@@ -0,0 +1,134 @@ @@ -0,0 +1,134 @@
  1 +# rakefile for acts_as_ferret.
  2 +# use to create a gem or generate rdoc api documentation.
  3 +#
  4 +# RELEASE creation:
  5 +# rake release REL=x.y.z
  6 +
  7 +require 'rake'
  8 +require 'rake/rdoctask'
  9 +require 'rake/packagetask'
  10 +require 'rake/gempackagetask'
  11 +require 'rake/testtask'
  12 +require 'rake/contrib/rubyforgepublisher'
  13 +
  14 +def announce(msg='')
  15 + STDERR.puts msg
  16 +end
  17 +
  18 +
  19 +PKG_NAME = 'acts_as_ferret'
  20 +PKG_VERSION = ENV['REL']
  21 +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
  22 +RUBYFORGE_PROJECT = 'actsasferret'
  23 +RUBYFORGE_USER = 'jkraemer'
  24 +
  25 +desc 'Default: run unit tests.'
  26 +task :default => :test
  27 +
  28 +desc 'Test the acts_as_ferret plugin.'
  29 +Rake::TestTask.new(:test) do |t|
  30 + t.libs << 'lib'
  31 + t.pattern = 'test/**/*_test.rb'
  32 + t.verbose = true
  33 +end
  34 +
  35 +desc 'Generate documentation for the acts_as_ferret plugin.'
  36 +Rake::RDocTask.new(:rdoc) do |rdoc|
  37 + rdoc.rdoc_dir = 'html'
  38 + rdoc.title = "acts_as_ferret - Ferret based full text search for any ActiveRecord model"
  39 + rdoc.options << '--line-numbers' << '--inline-source'
  40 + rdoc.options << '--main' << 'README'
  41 + rdoc.rdoc_files.include('README', 'LICENSE')
  42 + rdoc.template = "#{ENV['template']}.rb" if ENV['template']
  43 + rdoc.rdoc_files.include('lib/**/*.rb')
  44 +end
  45 +
  46 +desc "Publish the API documentation"
  47 +task :pdoc => [:rdoc] do
  48 + Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, RUBYFORGE_USER).upload
  49 +end
  50 +
  51 +if PKG_VERSION
  52 + spec = Gem::Specification.new do |s|
  53 + s.name = PKG_NAME
  54 + s.version = PKG_VERSION
  55 + s.platform = Gem::Platform::RUBY
  56 + s.summary = "acts_as_ferret - Ferret based full text search for any ActiveRecord model"
  57 + s.files = Dir.glob('**/*', File::FNM_DOTMATCH).reject do |f|
  58 + [ /\.$/, /sqlite$/, /\.log$/, /^pkg/, /\.svn/, /\.\w+\.sw.$/,
  59 + /^html/, /\~$/, /\/\._/, /\/#/ ].any? {|regex| f =~ regex }
  60 + end
  61 + #s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG)
  62 + # s.files.delete ...
  63 + s.require_path = 'lib'
  64 + s.bindir = "bin"
  65 + s.executables = ["aaf_install"]
  66 + s.default_executable = "aaf_install"
  67 + s.autorequire = 'acts_as_ferret'
  68 + s.has_rdoc = true
  69 + # s.test_files = Dir['test/**/*_test.rb']
  70 + s.author = "Jens Kraemer"
  71 + s.email = "jk@jkraemer.net"
  72 + s.homepage = "http://projects.jkraemer.net/acts_as_ferret"
  73 + end
  74 +
  75 + package_task = Rake::GemPackageTask.new(spec) do |pkg|
  76 + pkg.need_tar = true
  77 + end
  78 +
  79 + # Validate that everything is ready to go for a release.
  80 + task :prerelease do
  81 + announce
  82 + announce "**************************************************************"
  83 + announce "* Making RubyGem Release #{PKG_VERSION}"
  84 + announce "**************************************************************"
  85 + announce
  86 + # Are all source files checked in?
  87 + if ENV['RELTEST']
  88 + announce "Release Task Testing, skipping checked-in file test"
  89 + else
  90 + announce "Pulling in svn..."
  91 + `svk pull .`
  92 + announce "Checking for unchecked-in files..."
  93 + data = `svk st`
  94 + unless data =~ /^$/
  95 + fail "SVK status is not clean ... do you have unchecked-in files?"
  96 + end
  97 + announce "No outstanding checkins found ... OK"
  98 +# announce "Pushing to svn..."
  99 +# `svk push .`
  100 + end
  101 + end
  102 +
  103 +
  104 + desc "tag the new release"
  105 + task :tag => [ :prerelease ] do
  106 + reltag = "REL_#{PKG_VERSION.gsub(/\./, '_')}"
  107 + reltag << ENV['REUSE'].gsub(/\./, '_') if ENV['REUSE']
  108 + announce "Tagging with [#{PKG_VERSION}]"
  109 + if ENV['RELTEST']
  110 + announce "Release Task Testing, skipping tagging"
  111 + else
  112 + `svn copy -m 'tagging version #{PKG_VERSION}' svn://projects.jkraemer.net/acts_as_ferret/trunk/plugin svn://projects.jkraemer.net/acts_as_ferret/tags/#{PKG_VERSION}`
  113 + `svn del -m 'remove old stable' svn://projects.jkraemer.net/acts_as_ferret/tags/stable`
  114 + `svn copy -m 'tagging version #{PKG_VERSION} as stable' svn://projects.jkraemer.net/acts_as_ferret/tags/#{PKG_VERSION} svn://projects.jkraemer.net/acts_as_ferret/tags/stable`
  115 + end
  116 + end
  117 +
  118 + # Upload release to rubyforge
  119 + desc "Upload release to rubyforge"
  120 + task :prel => [ :tag, :prerelease, :package ] do
  121 + `rubyforge login`
  122 + release_command = "rubyforge add_release #{RUBYFORGE_PROJECT} #{PKG_NAME} '#{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.gem"
  123 + puts release_command
  124 + system(release_command)
  125 + `rubyforge config #{RUBYFORGE_PROJECT}`
  126 + release_command = "rubyforge add_file #{RUBYFORGE_PROJECT} #{PKG_NAME} '#{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.tgz"
  127 + puts release_command
  128 + system(release_command)
  129 + end
  130 +
  131 + desc 'Publish the gem and API docs'
  132 + task :release => [:pdoc, :prel ]
  133 +
  134 +end
vendor/plugins/acts_as_ferret/recipes/aaf_recipes.rb 0 → 100644
@@ -0,0 +1,97 @@ @@ -0,0 +1,97 @@
  1 +# Ferret DRb server Capistrano tasks
  2 +#
  3 +# Usage:
  4 +# in your Capfile, add acts_as_ferret's recipes directory to your load path and
  5 +# load the ferret tasks:
  6 +#
  7 +# load_paths << 'vendor/plugins/acts_as_ferret/recipes'
  8 +# load 'aaf_recipes'
  9 +#
  10 +# This will hook aaf's DRb start/stop tasks into the standard
  11 +# deploy:{start|restart|stop} tasks so the server will be restarted along with
  12 +# the rest of your application.
  13 +# Also an index directory in the shared folder will be created and symlinked
  14 +# into current/ when you deploy.
  15 +#
  16 +# In order to use the ferret:index:rebuild task, declare the indexes you intend to
  17 +# rebuild remotely in config/deploy.rb:
  18 +#
  19 +# set :ferret_indexes, %w( model another_model shared )
  20 +#
  21 +# HINT: To be very sure that your DRb server and application are always using
  22 +# the same model and schema versions, and you never lose any index updates because
  23 +# of the DRb server being restarted in that moment, use the following sequence
  24 +# to update your application:
  25 +#
  26 +# cap deploy:stop deploy:update deploy:migrate deploy:start
  27 +#
  28 +# That will stop the DRb server after stopping your application, and bring it
  29 +# up before starting the application again. Plus they'll never use different
  30 +# versions of model classes (which might happen otherwise)
  31 +# Downside: Your downtime is a bit longer than with the usual deploy, so be sure to
  32 +# put up some maintenance page for the meantime. Obviously this won't work if
  33 +# your migrations need acts_as_ferret (i.e. if you update model instances which
  34 +# would lead to index updates). In this case bring up the DRb server before
  35 +# running your migrations:
  36 +#
  37 +# cap deploy:stop deploy:update ferret:start deploy:migrate ferret:stop deploy:start
  38 +#
  39 +# Chances are that you're still not safe if your migrations not only modify the index,
  40 +# but also change the structure of your models. So just don't do both things in
  41 +# one go - I can't think of an easy way to handle this case automatically.
  42 +# Suggestions and patches are of course very welcome :-)
  43 +
  44 +namespace :ferret do
  45 +
  46 + desc "Stop the Ferret DRb server"
  47 + task :stop, :roles => :app do
  48 + rails_env = fetch(:rails_env, 'production')
  49 + run "cd #{current_path}; script/ferret_server -e #{rails_env} stop || true"
  50 + end
  51 +
  52 + desc "Start the Ferret DRb server"
  53 + task :start, :roles => :app do
  54 + rails_env = fetch(:rails_env, 'production')
  55 + run "cd #{current_path}; script/ferret_server -e #{rails_env} start"
  56 + end
  57 +
  58 + desc "Restart the Ferret DRb server"
  59 + task :restart, :roles => :app do
  60 + top.ferret.stop
  61 + sleep 1
  62 + top.ferret.start
  63 + end
  64 +
  65 + namespace :index do
  66 +
  67 + desc "Rebuild the Ferret index. See aaf_recipes.rb for instructions."
  68 + task :rebuild => :environment, :roles => :app do
  69 + rake = fetch(:rake, 'rake')
  70 + rails_env = fetch(:rails_env, 'production')
  71 + indexes = fetch(:ferret_indexes, nil)
  72 + if indexes and indexes.any?
  73 + run "cd #{current_path}; RAILS_ENV=#{rails_env} INDEXES='#{indexes.join(' ')}' #{rake} ferret:rebuild"
  74 + end
  75 + end
  76 +
  77 + desc "purges all indexes for the current environment"
  78 + task :purge, :roles => :app do
  79 + run "rm -fr #{shared_path}/index/#{rails_env}"
  80 + end
  81 +
  82 + desc "symlinks index folder"
  83 + task :symlink, :roles => :app do
  84 + run "mkdir -p #{shared_path}/index && rm -rf #{release_path}/index && ln -nfs #{shared_path}/index #{release_path}/index"
  85 + end
  86 +
  87 + end
  88 +
  89 +end
  90 +
  91 +after "deploy:stop", "ferret:stop"
  92 +before "deploy:start", "ferret:start"
  93 +
  94 +before "deploy:restart", "ferret:stop"
  95 +after "deploy:restart", "ferret:start"
  96 +after "deploy:symlink", "ferret:index:symlink"
  97 +
vendor/plugins/acts_as_ferret/script/ferret_daemon 0 → 100644
@@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
  1 +# Ferret Win32 Service Daemon, called by Win 32 service,
  2 +# created by Herryanto Siatono <herryanto@pluitsolutions.com>
  3 +#
  4 +# see doc/README.win32 for usage instructions
  5 +#
  6 +require 'optparse'
  7 +require 'win32/service'
  8 +include Win32
  9 +
  10 +# Read options
  11 +options = {}
  12 +ARGV.options do |opts|
  13 + opts.banner = 'Usage: ferret_daemon [options]'
  14 + opts.on("-l", "--log FILE", "Daemon log file") {|file| options[:log] = file }
  15 + opts.on("-c","--console","Run Ferret server on console.") {options[:console] = true}
  16 + opts.on_tail("-h","--help", "Show this help message") {puts opts; exit}
  17 + opts.on("-e", "--environment ENV ", "Rails environment") {|env|
  18 + options[:environment] = env
  19 + ENV['RAILS_ENV'] = env
  20 + }
  21 + opts.parse!
  22 +end
  23 +
  24 +require File.dirname(__FILE__) + '/../config/environment'
  25 +
  26 +# Ferret Win32 Service Daemon, called by Win 32 service,
  27 +# to run on the console, use -c or --console option.
  28 +module Ferret
  29 + class FerretDaemon < Daemon
  30 + # Standard logger to redirect STDOUT and STDERR to a log file
  31 + class FerretStandardLogger
  32 + def initialize(logger)
  33 + @logger = logger
  34 + end
  35 +
  36 + def write(s)
  37 + @logger.info s
  38 + end
  39 + end
  40 +
  41 + def initialize(options={})
  42 + @options = options
  43 +
  44 + # initialize logger
  45 + if options[:log]
  46 + @logger = Logger.new @options[:log]
  47 + else
  48 + @logger = Logger.new RAILS_ROOT + "/log/ferret_service_#{RAILS_ENV}.log"
  49 + end
  50 +
  51 + # redirect stout and stderr to Ferret logger if running as windows service
  52 + $stdout = $stderr = FerretStandardLogger.new(@logger) unless @options[:console]
  53 +
  54 + log "Initializing FerretDaemon..."
  55 + if @options[:console]
  56 + self.service_init
  57 + self.service_main
  58 + end
  59 + end
  60 +
  61 + def service_main
  62 + log "Service main enterred..."
  63 +
  64 + while running?
  65 + log "Listening..."
  66 + sleep
  67 + end
  68 +
  69 + log "Service main exit..."
  70 + end
  71 +
  72 + def service_init
  73 + log "Starting Ferret DRb server..."
  74 + ActsAsFerret::Remote::Server.start
  75 + log "FerretDaemon started."
  76 + end
  77 +
  78 + def service_stop
  79 + log "Stopping service..."
  80 + DRb.stop_service
  81 + log "FerretDaemon stopped."
  82 + end
  83 +
  84 + def log(msg)
  85 + @logger.info msg
  86 + puts msg if @options[:console]
  87 + end
  88 + end
  89 +end
  90 +
  91 +if __FILE__ == $0
  92 + d = Ferret::FerretDaemon.new(options)
  93 + d.mainloop
  94 +end
vendor/plugins/acts_as_ferret/script/ferret_server 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +#!/usr/bin/env ruby
  2 +
  3 +begin
  4 + require File.join(File.dirname(__FILE__), '../vendor/plugins/acts_as_ferret/lib/server_manager')
  5 +rescue LoadError
  6 + # try the gem
  7 + require 'rubygems'
  8 + gem 'acts_as_ferret'
  9 + require 'server_manager'
  10 +end
vendor/plugins/acts_as_ferret/script/ferret_service 0 → 100644
@@ -0,0 +1,178 @@ @@ -0,0 +1,178 @@
  1 +# Ferret Win32 Service Daemon install script
  2 +# created by Herryanto Siatono <herryanto@pluitsolutions.com>
  3 +#
  4 +# see doc/README.win32 for usage instructions
  5 +#
  6 +require 'optparse'
  7 +require 'win32/service'
  8 +include Win32
  9 +
  10 +module Ferret
  11 + # Parse and validate service command and options
  12 + class FerretServiceCommand
  13 + COMMANDS = ['install', 'remove', 'start', 'stop', 'help']
  14 + BANNER = "Usage: ruby script/ferret_service <command> [options]"
  15 +
  16 + attr_reader :options, :command
  17 +
  18 + def initialize
  19 + @options = {}
  20 + end
  21 +
  22 + def valid_command?
  23 + COMMANDS.include?@command
  24 + end
  25 +
  26 + def valid_options?
  27 + @options[:name] and !@options[:name].empty?
  28 + end
  29 +
  30 + def print_command_list
  31 + puts BANNER
  32 + puts "\nAvailable commands:\n"
  33 + puts COMMANDS.map {|cmd| " - #{cmd}\n"}
  34 + puts "\nUse option -h for each command to help."
  35 + exit
  36 + end
  37 +
  38 + def validate_options
  39 + errors = []
  40 + errors << "Service name is required." unless @options[:name]
  41 +
  42 + if (errors.size > 0)
  43 + errors << "Error found. Use: 'ruby script/ferret_service #{@command} -h' for to get help."
  44 + puts errors.join("\n")
  45 + exit
  46 + end
  47 + end
  48 +
  49 + def run(args)
  50 + @command = args.shift
  51 + @command = @command.dup.downcase if @command
  52 +
  53 + # validate command and options
  54 + print_command_list unless valid_command? or @command == 'help'
  55 +
  56 + opts_parser = create_options_parser
  57 + begin
  58 + opts_parser.parse!(args)
  59 + rescue OptionParser::ParseError => e
  60 + puts e
  61 + puts opts_parser
  62 + end
  63 +
  64 + # validate required options
  65 + validate_options
  66 + end
  67 +
  68 + def create_options_parser
  69 + opts_parser = OptionParser.new
  70 + opts_parser.banner = BANNER
  71 + opts_parser.on("-n", "--name=NAME", "Service name") {|name| @options[:name] = name }
  72 + opts_parser.on_tail("-t", "--trace", "Display stack trace when exception thrown") { @options[:trace] = true }
  73 + opts_parser.on_tail("-h", "--help", "Show this help message") { puts opts_parser; exit }
  74 +
  75 + if ['install'].include?@command
  76 + opts_parser.on("-d", "--display=NAME", "Service display name") {|name| @options[:display] = name }
  77 +
  78 + opts_parser.on("-l", "--log FILE", "Service log file") {|file| @options[:log] = file }
  79 + opts_parser.on("-e", "--environment ENV ", "Rails environment") { |env|
  80 + @options[:environment] = env
  81 + ENV['RAILS_ENV'] = env
  82 + }
  83 + end
  84 + opts_parser
  85 + end
  86 + end
  87 +
  88 + # Install, Remove, Start and Stop Ferret DRb server Win32 service
  89 + class FerretService
  90 + FERRET_DAEMON = 'ferret_daemon'
  91 +
  92 + def initialize
  93 + end
  94 +
  95 + def install
  96 + svc = Service.new
  97 +
  98 + begin
  99 + if Service.exists?(@options[:name])
  100 + puts "Service name '#{@options[:name]}' already exists."
  101 + return
  102 + end
  103 +
  104 + svc.create_service do |s|
  105 + s.service_name = @options[:name]
  106 + s.display_name = @options[:display]
  107 + s.binary_path_name = binary_path_name
  108 + s.dependencies = []
  109 + end
  110 +
  111 + svc.close
  112 + puts "'#{@options[:name]}' service installed."
  113 + rescue => e
  114 + handle_error(e)
  115 + end
  116 + end
  117 +
  118 + def remove
  119 + begin
  120 + Service.stop(@options[:name])
  121 + rescue
  122 + end
  123 +
  124 + begin
  125 + Service.delete(@options[:name])
  126 + puts "'#{@options[:name]}' service removed."
  127 + rescue => e
  128 + handle_error(e)
  129 + end
  130 + end
  131 +
  132 + def start
  133 + begin
  134 + Service.start(@options[:name])
  135 + puts "'#{@options[:name]}' successfully started."
  136 + rescue => e
  137 + handle_error(e)
  138 + end
  139 + end
  140 +
  141 + def stop
  142 + begin
  143 + Service.stop(@options[:name])
  144 + puts "'#{@options[:name]}' successfully stopped.\n"
  145 + rescue => e
  146 + handle_error(e)
  147 + end
  148 + end
  149 +
  150 + def run(args)
  151 + svc_cmd = FerretServiceCommand.new
  152 + svc_cmd.run(args)
  153 + @options = svc_cmd.options
  154 + self.send(svc_cmd.command.to_sym)
  155 + end
  156 +
  157 + protected
  158 + def handle_error(e)
  159 + if @options[:trace]
  160 + raise e
  161 + else
  162 + puts e
  163 + end
  164 + end
  165 +
  166 + def binary_path_name
  167 + path = ""
  168 + path << "#{ENV['RUBY_HOME']}/bin/" if ENV['RUBY_HOME']
  169 + path << "ruby.exe "
  170 + path << File.expand_path("script/" + FERRET_DAEMON)
  171 + path << " -e #{@options[:environment]} " if @options[:environment]
  172 + path << " -l #{@options[:log]} " if @options[:log]
  173 + path
  174 + end
  175 + end
  176 +end
  177 +
  178 +Ferret::FerretService.new.run(ARGV)
vendor/plugins/acts_as_ferret/tasks/ferret.rake 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +namespace :ferret do
  2 +
  3 + # Rebuild index task. Declare the indexes to be rebuilt with the INDEXES
  4 + # environment variable:
  5 + #
  6 + # INDEXES="my_model shared" rake ferret:rebuild
  7 + desc "Rebuild a Ferret index. Specify what model to rebuild with the MODEL environment variable."
  8 + task :rebuild do
  9 + require File.join(RAILS_ROOT, 'config', 'environment')
  10 +
  11 + indexes = ENV['INDEXES'].split
  12 + indexes.each do |index_name|
  13 + start = 1.minute.ago
  14 + ActsAsFerret::rebuild_index index_name
  15 + idx = ActsAsFerret::get_index index_name
  16 + # update records that have changed since the rebuild started
  17 + idx.index_definition[:registered_models].each do |m|
  18 + m.records_modified_since(start).each do |object|
  19 + object.ferret_update
  20 + end
  21 + end
  22 + end
  23 + end
  24 +end
vendor/plugins/acts_as_solr_reloaded/.gitignore
@@ -1,17 +0,0 @@ @@ -1,17 +0,0 @@
1 -*.log  
2 -*.log  
3 -*_pid  
4 -coverage/*  
5 -coverage.data  
6 -solr/solr/data/*  
7 -.svn  
8 -test/db/*.db  
9 -test/log/*.log  
10 -pkg/*  
11 -*.sw?  
12 -.DS_Store  
13 -coverage  
14 -rdoc  
15 -pkg  
16 -acts_as_solr_reloaded-*.gem  
17 -tags  
vendor/plugins/acts_as_solr_reloaded/LICENSE
@@ -1,22 +0,0 @@ @@ -1,22 +0,0 @@
1 -(The MIT License)  
2 -  
3 -Copyright © 2010  
4 -  
5 -Permission is hereby granted, free of charge, to any person obtaining  
6 -a copy of this software and associated documentation files (the  
7 -‘Software’), to deal in the Software without restriction, including  
8 -without limitation the rights to use, copy, modify, merge, publish,  
9 -distribute, sublicense, and/or sell copies of the Software, and to  
10 -permit persons to whom the Software is furnished to do so, subject to  
11 -the following conditions:  
12 -  
13 -The above copyright notice and this permission notice shall be  
14 -included in all copies or substantial portions of the Software.  
15 -  
16 -THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND,  
17 -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF  
18 -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  
19 -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  
20 -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  
21 -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE  
22 -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  
vendor/plugins/acts_as_solr_reloaded/README.markdown
@@ -1,64 +0,0 @@ @@ -1,64 +0,0 @@
1 -Description  
2 -======  
3 -This plugin adds full text search capabilities and many other nifty features from Apache's [Solr](http://lucene.apache.org/solr/) to any Rails model.  
4 -It was based on the first draft by Erik Hatcher.  
5 -  
6 -This plugin is intended for use in old versions of Rails. For newer versions, I strongly advice using Sunspot!  
7 -Nevertheless, this plugin is used for Noosfero project in production. Any problem please open an issue.  
8 -  
9 -Installation  
10 -======  
11 -  
12 -Install as a plugin  
13 -  
14 - script/plugin install git://github.com/brauliobo/acts_as_solr_reloaded.git  
15 -  
16 -Download Solr 3.1  
17 -  
18 - rake solr:download  
19 -  
20 -Requirements  
21 -------  
22 -* Java Runtime Environment(JRE) 1.6 aka 6.0 or newer [http://www.java.com/en/download/index.jsp](http://www.java.com/en/download/index.jsp) (use default-jre for Debian like distribution)  
23 -* (Recommended) If you have libxml-ruby installed, make sure it's at least version 0.7  
24 -  
25 -Configuration  
26 -======  
27 -See config/solr.yml file.  
28 -  
29 -Basic Usage  
30 -======  
31 -<pre><code>  
32 -# Just include the line below to any of your ActiveRecord models:  
33 - acts_as_solr  
34 -  
35 -# Or if you want, you can specify only the fields that should be indexed:  
36 - acts_as_solr :fields => [:name, :author]  
37 -  
38 -# Then to find instances of your model, just do:  
39 - Model.search(query) #query is a string representing your query  
40 -  
41 -# Please see ActsAsSolr::ActsMethods for a complete info  
42 -  
43 -</code></pre>  
44 -  
45 -  
46 -`acts_as_solr` in your tests  
47 -======  
48 -To test code that uses `acts_as_solr` you must start a Solr server for the test environment. You can do that with `rake solr:start RAILS_ENV=test`  
49 -  
50 -However, if you would like to mock out Solr calls so that a Solr server is not needed (and your tests will run much faster), just add this to your `test_helper.rb` or similar:  
51 -  
52 -<pre><code>  
53 -class ActsAsSolr::Post  
54 - def self.execute(request)  
55 - true  
56 - end  
57 -end  
58 -</pre></code>  
59 -  
60 -([via](http://www.subelsky.com/2007/10/actsassolr-capistranhttpwwwbloggercomim.html#c1646308013209805416))  
61 -  
62 -Release Information  
63 -======  
64 -Released under the MIT license.  
vendor/plugins/acts_as_solr_reloaded/README.rdoc
@@ -1,96 +0,0 @@ @@ -1,96 +0,0 @@
1 -= DESCRIPTION  
2 -  
3 -This plugin adds full text search capabilities and many other nifty features from Apache's Solr[http://lucene.apache.org/solr/] to any Rails model, like:  
4 -  
5 -* faceting  
6 -* dynamic attributes  
7 -* integration with acts_as_taggable_on  
8 -* integration with will_paginate  
9 -* highlighting  
10 -* geolocation  
11 -* relevance  
12 -* suggest  
13 -  
14 -Watch this screencast for a short demo of the latests features:  
15 -  
16 -http://www.vimeo.com/8728276  
17 -  
18 -== INSTALLATION  
19 -  
20 - script/plugin install git://github.com/brauliobo/acts_as_solr_reloaded.git  
21 - rake solr:download  
22 -  
23 -== REQUIREMENTS  
24 -  
25 -* Java Runtime Environment(JRE) 1.5 aka 5.0 [http://www.java.com/en/download/index.jsp](http://www.java.com/en/download/index.jsp) (use default-jre for Debian like distribution)  
26 -* (Recommended) If you have libxml-ruby installed, make sure it's at least version 0.7  
27 -  
28 -== CONFIGURATION  
29 -  
30 -See config/solr.yml file.  
31 -  
32 -== USAGE  
33 -  
34 -Just include the line below to any of your ActiveRecord models:  
35 - acts_as_solr  
36 -  
37 -Or if you want, you can specify only the fields that should be indexed:  
38 - acts_as_solr :fields => [:name, :author]  
39 -  
40 -Then to find instances of your model, just do:  
41 - Model.search(query) #query is a string representing your query  
42 -  
43 -Case you want to use dynamic attributes or geolocalization, you can use this generators that setup the database:  
44 -  
45 - script/generate dynamic_attributes_migration  
46 - script/generate local_migration  
47 -  
48 -and then configure your model like this:  
49 -  
50 - acts_as_solr :dynamic_attributes => true,  
51 - :spatial => true  
52 -  
53 -If you want to integrate the model with acts_as_taggable_on, just add the option :taggable => true :  
54 -  
55 - acts_as_solr :taggable => true  
56 -  
57 -Please see ActsAsSolr::ActsMethods for a complete info  
58 -  
59 -== PAGINATION  
60 -  
61 -In your controller:  
62 -  
63 - @search = Product.search "beer", :page => 2, :per_page => 20  
64 -  
65 -And in your view:  
66 -  
67 - will_paginate @search  
68 -  
69 -== TESTING  
70 -  
71 -To test code that uses acts_as_solr_reloaded you must start a Solr server for the test environment and also start MongoDB.  
72 -  
73 -== LICENSE  
74 -  
75 -(The MIT License)  
76 -  
77 -Copyright © 2010  
78 -  
79 -Permission is hereby granted, free of charge, to any person obtaining  
80 -a copy of this software and associated documentation files (the  
81 -‘Software’), to deal in the Software without restriction, including  
82 -without limitation the rights to use, copy, modify, merge, publish,  
83 -distribute, sublicense, and/or sell copies of the Software, and to  
84 -permit persons to whom the Software is furnished to do so, subject to  
85 -the following conditions:  
86 -  
87 -The above copyright notice and this permission notice shall be  
88 -included in all copies or substantial portions of the Software.  
89 -  
90 -THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND,  
91 -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF  
92 -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  
93 -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  
94 -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  
95 -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE  
96 -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  
vendor/plugins/acts_as_solr_reloaded/Rakefile
@@ -1,71 +0,0 @@ @@ -1,71 +0,0 @@
1 -require 'rubygems'  
2 -require 'rake'  
3 -require 'rake/testtask'  
4 -require 'rake/rdoctask'  
5 -  
6 -Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each { |ext| load ext }  
7 -  
8 -desc "Default Task"  
9 -task :default => [:test]  
10 -  
11 -desc "Runs the unit tests"  
12 -task :test => "test:unit"  
13 -  
14 -namespace :test do  
15 - task :setup do  
16 - RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/test") unless defined? RAILS_ROOT  
17 - ENV['RAILS_ENV'] = "test"  
18 - ENV["ACTS_AS_SOLR_TEST"] = "true"  
19 - require File.expand_path("#{File.dirname(__FILE__)}/config/solr_environment")  
20 - puts "Using " + DB  
21 - %x(mysql -u#{MYSQL_USER} < #{File.dirname(__FILE__) + "/test/fixtures/db_definitions/mysql.sql"}) if DB == 'mysql'  
22 -  
23 - Rake::Task["test:migrate"].invoke  
24 - end  
25 -  
26 - desc 'Measures test coverage using rcov'  
27 - task :rcov => :setup do  
28 - rm_f "coverage"  
29 - rm_f "coverage.data"  
30 - rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib"  
31 -  
32 - system("#{rcov} --html #{Dir.glob('test/**/*_shoulda.rb').join(' ')}")  
33 - system("open coverage/index.html") if PLATFORM['darwin']  
34 - end  
35 -  
36 - desc 'Runs the functional tests, testing integration with Solr'  
37 - Rake::TestTask.new('functional' => :setup) do |t|  
38 - t.pattern = "test/functional/*_test.rb"  
39 - t.verbose = true  
40 - end  
41 -  
42 - desc "Unit tests"  
43 - Rake::TestTask.new(:unit) do |t|  
44 - t.libs << 'test/unit'  
45 - t.pattern = "test/unit/*_shoulda.rb"  
46 - t.verbose = true  
47 - end  
48 -end  
49 -  
50 -Rake::RDocTask.new do |rd|  
51 - rd.main = "README.rdoc"  
52 - rd.rdoc_dir = "rdoc"  
53 - rd.rdoc_files.exclude("lib/solr/**/*.rb", "lib/solr.rb")  
54 - rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")  
55 -end  
56 -  
57 -begin  
58 - require 'jeweler'  
59 - Jeweler::Tasks.new do |s|  
60 - s.name = "acts_as_solr_reloaded"  
61 - s.summary = "This gem adds full text search capabilities and many other nifty features from Apache Solr to any Rails model."  
62 - s.email = "dc.rec1@gmail.com"  
63 - s.homepage = "http://github.com/dcrec1/acts_as_solr_reloaded"  
64 - s.description = "This gem adds full text search capabilities and many other nifty features from Apache Solr to any Rails model."  
65 - s.authors = ["Diego Carrion"]  
66 - s.files = FileList["[A-Z]*", "{bin,generators,config,lib,solr}/**/*"] +  
67 - FileList["test/**/*"].reject {|f| f.include?("test/log")}.reject {|f| f.include?("test/tmp")}  
68 - end  
69 -rescue LoadError  
70 - puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"  
71 -end  
vendor/plugins/acts_as_solr_reloaded/TESTING_THE_PLUGIN
@@ -1,25 +0,0 @@ @@ -1,25 +0,0 @@
1 -acts_as_solr comes with a quick and fast unit test suite, and with a longer-running  
2 -functional test suite, the latter testing the actual integration with Solr.  
3 -  
4 -The unit test suite is written using Shoulda, so make sure you have a recent version  
5 -installed.  
6 -  
7 -Running `rake test` or just `rake` will run both test suites. Use `rake test:unit` to  
8 -just run the unit test suite.  
9 -  
10 -== How to run functional tests for this plugin:  
11 -To run the acts_as_solr's plugin tests run the following steps:  
12 -  
13 -- create a MySQL database called "actsassolr_test" (if you want to use MySQL)  
14 -  
15 -- create a new Rails project, if needed (the plugin can only be tested from within a Rails project); move/checkout acts_as_solr into its vendor/plugins/, as usual  
16 -  
17 -- copy vendor/plugins/acts_as_solr_reloaded/config/solr.yml to config/ (the Rails config folder)  
18 -  
19 -- rake solr:start RAILS_ENV=test  
20 -  
21 -- rake test:functional (Accepts the following arguments: DB=sqlite|mysql and MYSQL_USER=user)  
22 -  
23 -== Troubleshooting:  
24 -If for some reason the tests don't run and you get MySQL errors, make sure you edit the MYSQL_USER entry under  
25 -config/environment.rb. It's recommended to create or use a MySQL user with no password.  
vendor/plugins/acts_as_solr_reloaded/VERSION
@@ -1 +0,0 @@ @@ -1 +0,0 @@
1 -1.6.0  
2 \ No newline at end of file 0 \ No newline at end of file
vendor/plugins/acts_as_solr_reloaded/acts_as_solr_reloaded.gemspec
@@ -1,205 +0,0 @@ @@ -1,205 +0,0 @@
1 -# Generated by jeweler  
2 -# DO NOT EDIT THIS FILE DIRECTLY  
3 -# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'  
4 -# -*- encoding: utf-8 -*-  
5 -  
6 -Gem::Specification.new do |s|  
7 - s.name = %q{acts_as_solr_reloaded}  
8 - s.version = "1.6.0"  
9 -  
10 - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=  
11 - s.authors = ["Diego Carrion"]  
12 - s.date = %q{2011-03-20}  
13 - s.description = %q{This gem adds full text search capabilities and many other nifty features from Apache Solr to any Rails model.}  
14 - s.email = %q{dc.rec1@gmail.com}  
15 - s.extra_rdoc_files = [  
16 - "LICENSE",  
17 - "README.markdown",  
18 - "README.rdoc"  
19 - ]  
20 - s.files = [  
21 - "LICENSE",  
22 - "README.markdown",  
23 - "README.rdoc",  
24 - "Rakefile",  
25 - "TESTING_THE_PLUGIN",  
26 - "VERSION",  
27 - "config/solr.yml",  
28 - "config/solr_environment.rb",  
29 - "generators/dynamic_attributes_migration/dynamic_attributes_migration_generator.rb",  
30 - "generators/dynamic_attributes_migration/templates/migration.rb",  
31 - "generators/local_migration/local_migration_generator.rb",  
32 - "generators/local_migration/templates/migration.rb",  
33 - "lib/acts_as_solr.rb",  
34 - "lib/acts_as_solr/acts_methods.rb",  
35 - "lib/acts_as_solr/class_methods.rb",  
36 - "lib/acts_as_solr/common_methods.rb",  
37 - "lib/acts_as_solr/deprecation.rb",  
38 - "lib/acts_as_solr/dynamic_attribute.rb",  
39 - "lib/acts_as_solr/instance_methods.rb",  
40 - "lib/acts_as_solr/lazy_document.rb",  
41 - "lib/acts_as_solr/local.rb",  
42 - "lib/acts_as_solr/mongo_mapper.rb",  
43 - "lib/acts_as_solr/parser_methods.rb",  
44 - "lib/acts_as_solr/search_results.rb",  
45 - "lib/acts_as_solr/solr_fixtures.rb",  
46 - "lib/acts_as_solr/tasks.rb",  
47 - "lib/acts_as_solr_reloaded.rb",  
48 - "lib/solr.rb",  
49 - "lib/solr/connection.rb",  
50 - "lib/solr/document.rb",  
51 - "lib/solr/exception.rb",  
52 - "lib/solr/field.rb",  
53 - "lib/solr/importer.rb",  
54 - "lib/solr/importer/array_mapper.rb",  
55 - "lib/solr/importer/delimited_file_source.rb",  
56 - "lib/solr/importer/hpricot_mapper.rb",  
57 - "lib/solr/importer/mapper.rb",  
58 - "lib/solr/importer/solr_source.rb",  
59 - "lib/solr/importer/xpath_mapper.rb",  
60 - "lib/solr/indexer.rb",  
61 - "lib/solr/request.rb",  
62 - "lib/solr/request/add_document.rb",  
63 - "lib/solr/request/base.rb",  
64 - "lib/solr/request/commit.rb",  
65 - "lib/solr/request/delete.rb",  
66 - "lib/solr/request/dismax.rb",  
67 - "lib/solr/request/index_info.rb",  
68 - "lib/solr/request/modify_document.rb",  
69 - "lib/solr/request/optimize.rb",  
70 - "lib/solr/request/ping.rb",  
71 - "lib/solr/request/select.rb",  
72 - "lib/solr/request/spellcheck.rb",  
73 - "lib/solr/request/standard.rb",  
74 - "lib/solr/request/update.rb",  
75 - "lib/solr/response.rb",  
76 - "lib/solr/response/add_document.rb",  
77 - "lib/solr/response/base.rb",  
78 - "lib/solr/response/commit.rb",  
79 - "lib/solr/response/delete.rb",  
80 - "lib/solr/response/dismax.rb",  
81 - "lib/solr/response/index_info.rb",  
82 - "lib/solr/response/modify_document.rb",  
83 - "lib/solr/response/optimize.rb",  
84 - "lib/solr/response/ping.rb",  
85 - "lib/solr/response/ruby.rb",  
86 - "lib/solr/response/select.rb",  
87 - "lib/solr/response/spellcheck.rb",  
88 - "lib/solr/response/standard.rb",  
89 - "lib/solr/response/xml.rb",  
90 - "lib/solr/solrtasks.rb",  
91 - "lib/solr/util.rb",  
92 - "lib/solr/xml.rb",  
93 - "lib/tasks/database.rake",  
94 - "lib/tasks/solr.rake",  
95 - "lib/tasks/test.rake",  
96 - "test/config/solr.yml",  
97 - "test/db/connections/mysql/connection.rb",  
98 - "test/db/connections/sqlite/connection.rb",  
99 - "test/db/migrate/001_create_books.rb",  
100 - "test/db/migrate/002_create_movies.rb",  
101 - "test/db/migrate/003_create_categories.rb",  
102 - "test/db/migrate/004_create_electronics.rb",  
103 - "test/db/migrate/005_create_authors.rb",  
104 - "test/db/migrate/006_create_postings.rb",  
105 - "test/db/migrate/007_create_posts.rb",  
106 - "test/db/migrate/008_create_gadgets.rb",  
107 - "test/db/migrate/009_create_dynamic_attributes.rb",  
108 - "test/db/migrate/010_create_advertises.rb",  
109 - "test/db/migrate/011_create_locals.rb",  
110 - "test/db/test.db",  
111 - "test/fixtures/advertises.yml",  
112 - "test/fixtures/authors.yml",  
113 - "test/fixtures/books.yml",  
114 - "test/fixtures/categories.yml",  
115 - "test/fixtures/db_definitions/mysql.sql",  
116 - "test/fixtures/dynamic_attributes.yml",  
117 - "test/fixtures/electronics.yml",  
118 - "test/fixtures/locals.yml",  
119 - "test/fixtures/movies.yml",  
120 - "test/fixtures/postings.yml",  
121 - "test/functional/acts_as_solr_test.rb",  
122 - "test/functional/association_indexing_test.rb",  
123 - "test/functional/faceted_search_test.rb",  
124 - "test/functional/multi_solr_search_test.rb",  
125 - "test/models/advertise.rb",  
126 - "test/models/author.rb",  
127 - "test/models/book.rb",  
128 - "test/models/category.rb",  
129 - "test/models/document.rb",  
130 - "test/models/dynamic_attribute.rb",  
131 - "test/models/electronic.rb",  
132 - "test/models/gadget.rb",  
133 - "test/models/local.rb",  
134 - "test/models/movie.rb",  
135 - "test/models/novel.rb",  
136 - "test/models/post.rb",  
137 - "test/models/posting.rb",  
138 - "test/test_helper.rb",  
139 - "test/unit/acts_methods_shoulda.rb",  
140 - "test/unit/class_methods_shoulda.rb",  
141 - "test/unit/common_methods_shoulda.rb",  
142 - "test/unit/instance_methods_shoulda.rb",  
143 - "test/unit/lazy_document_shoulda.rb",  
144 - "test/unit/parser_instance.rb",  
145 - "test/unit/parser_methods_shoulda.rb",  
146 - "test/unit/solr_instance.rb",  
147 - "test/unit/test_helper.rb"  
148 - ]  
149 - s.homepage = %q{http://github.com/dcrec1/acts_as_solr_reloaded}  
150 - s.require_paths = ["lib"]  
151 - s.rubygems_version = %q{1.5.0}  
152 - s.summary = %q{This gem adds full text search capabilities and many other nifty features from Apache Solr to any Rails model.}  
153 - s.test_files = [  
154 - "test/db/connections/mysql/connection.rb",  
155 - "test/db/connections/sqlite/connection.rb",  
156 - "test/db/migrate/001_create_books.rb",  
157 - "test/db/migrate/002_create_movies.rb",  
158 - "test/db/migrate/003_create_categories.rb",  
159 - "test/db/migrate/004_create_electronics.rb",  
160 - "test/db/migrate/005_create_authors.rb",  
161 - "test/db/migrate/006_create_postings.rb",  
162 - "test/db/migrate/007_create_posts.rb",  
163 - "test/db/migrate/008_create_gadgets.rb",  
164 - "test/db/migrate/009_create_dynamic_attributes.rb",  
165 - "test/db/migrate/010_create_advertises.rb",  
166 - "test/db/migrate/011_create_locals.rb",  
167 - "test/functional/acts_as_solr_test.rb",  
168 - "test/functional/association_indexing_test.rb",  
169 - "test/functional/faceted_search_test.rb",  
170 - "test/functional/multi_solr_search_test.rb",  
171 - "test/models/advertise.rb",  
172 - "test/models/author.rb",  
173 - "test/models/book.rb",  
174 - "test/models/category.rb",  
175 - "test/models/document.rb",  
176 - "test/models/dynamic_attribute.rb",  
177 - "test/models/electronic.rb",  
178 - "test/models/gadget.rb",  
179 - "test/models/local.rb",  
180 - "test/models/movie.rb",  
181 - "test/models/novel.rb",  
182 - "test/models/post.rb",  
183 - "test/models/posting.rb",  
184 - "test/test_helper.rb",  
185 - "test/unit/acts_methods_shoulda.rb",  
186 - "test/unit/class_methods_shoulda.rb",  
187 - "test/unit/common_methods_shoulda.rb",  
188 - "test/unit/instance_methods_shoulda.rb",  
189 - "test/unit/lazy_document_shoulda.rb",  
190 - "test/unit/parser_instance.rb",  
191 - "test/unit/parser_methods_shoulda.rb",  
192 - "test/unit/solr_instance.rb",  
193 - "test/unit/test_helper.rb"  
194 - ]  
195 -  
196 - if s.respond_to? :specification_version then  
197 - s.specification_version = 3  
198 -  
199 - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then  
200 - else  
201 - end  
202 - else  
203 - end  
204 -end  
205 -  
vendor/plugins/acts_as_solr_reloaded/config/solr.yml
@@ -1,14 +0,0 @@ @@ -1,14 +0,0 @@
1 -# Config file for the acts_as_solr_reloaded plugin.  
2 -#  
3 -# If you change the host or port number here, make sure you update  
4 -# them in your Solr config file  
5 -  
6 -development:  
7 - url: http://127.0.0.1:8982/solr  
8 -  
9 -test:  
10 - url: http://127.0.0.1:8981/solr  
11 -  
12 -production:  
13 - url: http://127.0.0.1:8983/solr  
14 - jvm_options: -server -d64 -Xmx1024M -Xms64M  
15 \ No newline at end of file 0 \ No newline at end of file
vendor/plugins/acts_as_solr_reloaded/config/solr_environment.rb
@@ -1,43 +0,0 @@ @@ -1,43 +0,0 @@
1 -ENV['RAILS_ENV'] = (ENV['RAILS_ENV'] || 'development').dup  
2 -require "uri"  
3 -require "fileutils"  
4 -require "yaml"  
5 -dir = File.dirname(__FILE__)  
6 -SOLR_PATH = File.expand_path("#{dir}/../solr") unless defined? SOLR_PATH  
7 -  
8 -# RAILS_ROOT isn't defined yet, so figure it out.  
9 -unless defined? RAILS_ROOT  
10 - RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../test")  
11 -end  
12 -unless defined? SOLR_LOGS_PATH  
13 - SOLR_LOGS_PATH = ENV["SOLR_LOGS_PATH"] || "#{RAILS_ROOT}/log"  
14 -end  
15 -unless defined? SOLR_PIDS_PATH  
16 - SOLR_PIDS_PATH = ENV["SOLR_PIDS_PATH"] || "#{RAILS_ROOT}/tmp/pids"  
17 -end  
18 -unless defined? SOLR_DATA_PATH  
19 - SOLR_DATA_PATH = ENV["SOLR_DATA_PATH"] || "#{RAILS_ROOT}/solr/#{ENV['RAILS_ENV']}"  
20 -end  
21 -unless defined? SOLR_CONFIG_PATH  
22 - SOLR_CONFIG_PATH = ENV["SOLR_CONFIG_PATH"] || "#{SOLR_PATH}/solr"  
23 -end  
24 -unless defined? SOLR_PID_FILE  
25 - SOLR_PID_FILE="#{SOLR_PIDS_PATH}/solr.#{ENV['RAILS_ENV']}.pid"  
26 -end  
27 -  
28 -unless defined? SOLR_PORT  
29 - config = YAML::load_file(RAILS_ROOT+'/config/solr.yml')  
30 - raise("No solr environment defined for RAILS_ENV the #{ENV['RAILS_ENV'].inspect}") unless config[ENV['RAILS_ENV']]  
31 -  
32 - SOLR_HOST = ENV['HOST'] || URI.parse(config[ENV['RAILS_ENV']]['url']).host  
33 - SOLR_PORT = ENV['PORT'] || URI.parse(config[ENV['RAILS_ENV']]['url']).port  
34 -end  
35 -  
36 -SOLR_JVM_OPTIONS = config[ENV['RAILS_ENV']]['jvm_options'] unless defined? SOLR_JVM_OPTIONS  
37 -  
38 -if ENV["ACTS_AS_SOLR_TEST"]  
39 - require "activerecord"  
40 - DB = (ENV['DB'] ? ENV['DB'] : 'sqlite') unless defined?(DB)  
41 - MYSQL_USER = (ENV['MYSQL_USER'].nil? ? 'root' : ENV['MYSQL_USER']) unless defined? MYSQL_USER  
42 - require File.join(File.dirname(File.expand_path(__FILE__)), '..', 'test', 'db', 'connections', DB, 'connection.rb')  
43 -end  
vendor/plugins/acts_as_solr_reloaded/generators/dynamic_attributes_migration/dynamic_attributes_migration_generator.rb
@@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
1 -class DynamicAttributesMigrationGenerator < Rails::Generator::Base  
2 - def manifest  
3 - record do |m|  
4 - m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "dynamic_attributes_migration"  
5 - end  
6 - end  
7 -end  
8 \ No newline at end of file 0 \ No newline at end of file