diff --git a/.gitignore b/.gitignore
index ed85778f..2fb725ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,6 @@ index/**/*
coverage/**/*
config/environments/development.rb
nbproject
-db/schema.rb
\ No newline at end of file
+db/schema.rb
+**/.specification
+**/cache
\ No newline at end of file
diff --git a/app/sweepers/allocations_sweeper.rb b/app/sweepers/allocations_sweeper.rb
new file mode 100644
index 00000000..40f968d0
--- /dev/null
+++ b/app/sweepers/allocations_sweeper.rb
@@ -0,0 +1,49 @@
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+class AllocationsSweeper < ActionController::Caching::Sweeper
+ observe Allocation # This sweeper is going to keep an eye on the allocation model
+
+ # If our sweeper detects that an allocation was created call this
+ def after_save(allocation)
+ expire_cache_for(allocation)
+ end
+
+ # If our sweeper detects that an allocation was deleted call this
+ def after_destroy(allocation)
+ expire_cache_for(allocation)
+ end
+
+ def after_votes_create
+ expire_cache current_user
+ end
+
+ def after_votes_destroy
+ expire_cache current_user
+ end
+
+ def after_account_login
+ expire_cache current_user
+ end
+
+ def after_account_continue_openid
+ expire_cache current_user
+ end
+
+ private
+ def expire_cache_for(allocation)
+ if allocation.class.to_s == 'UserAllocation'
+ expire_cache(allocation.user)
+ else
+ for user in allocation.enterprise.users
+ expire_cache(user)
+ end
+ end
+ end
+
+ def expire_cache(user)
+ # Expire a fragment
+ expire_fragment(:controller => "allocations", :action => "my_allocations",
+ :user_id => (user == :false ? -1 : user.id))
+ end
+end
\ No newline at end of file
diff --git a/app/sweepers/announcements_sweeper.rb b/app/sweepers/announcements_sweeper.rb
new file mode 100644
index 00000000..b144254f
--- /dev/null
+++ b/app/sweepers/announcements_sweeper.rb
@@ -0,0 +1,27 @@
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+class AnnouncementsSweeper < ActionController::Caching::Sweeper
+ observe Announcement # This sweeper is going to keep an eye on the announcement model
+
+ # If our sweeper detects that an announcement was created call this
+ def after_save(announcement)
+ expire_cache_for(announcement)
+ end
+
+ # If our sweeper detects that an announcement was deleted call this
+ def after_destroy(announcement)
+ expire_cache_for(announcement)
+ end
+
+ def after_announcements_index
+ expire_fragment(:controller => 'announcements', :action => 'top_five',
+ :user_id => (logged_in? ? current_user.id : -1))
+ end
+
+ private
+ def expire_cache_for(record)
+ # Expire a fragment
+ expire_fragment(%r{announcements/top_five.user_id=*})
+ end
+end
\ No newline at end of file
diff --git a/app/sweepers/attachments_sweeper.rb b/app/sweepers/attachments_sweeper.rb
new file mode 100644
index 00000000..5dc47713
--- /dev/null
+++ b/app/sweepers/attachments_sweeper.rb
@@ -0,0 +1,28 @@
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+class AttachmentsSweeper < ActionController::Caching::Sweeper
+ observe Attachment # This sweeper is going to keep an eye on the Attachment model
+
+ # If our sweeper detects that an attachment was created call this
+ def after_save(attachment)
+ expire_cache_for(attachment)
+ end
+
+ # If our sweeper detects that an attachment was deleted call this
+ def after_destroy(attachment)
+ expire_cache_for(attachment)
+ end
+
+ def before_attachments_search
+ expire_fragment(%r{attachments/list_attachments\.action_type=search&page=(\d)+&user_id=#{current_user.id}})
+ end
+
+ private
+ def expire_cache_for(record)
+ # Expire a fragment
+ expire_fragment(%r{attachments/list_attachments.*})
+ # expire_fragment(:controller => 'attachments', :action => 'index',
+ # :page => params[:page] || 1)
+ end
+end
\ No newline at end of file
diff --git a/app/sweepers/comments_sweeper.rb b/app/sweepers/comments_sweeper.rb
new file mode 100644
index 00000000..48b03fcc
--- /dev/null
+++ b/app/sweepers/comments_sweeper.rb
@@ -0,0 +1,28 @@
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+class CommentsSweeper < ActionController::Caching::Sweeper
+ observe Comment # This sweeper is going to keep an eye on the comment model
+
+ # If our sweeper detects that a comment was created call this
+ def after_save(comment)
+ expire_cache_for(comment)
+ end
+
+ # If our sweeper detects that a comment was deleted call this
+ def after_destroy(comment)
+ expire_cache_for(comment)
+ end
+
+ private
+ def expire_cache_for(comment)
+ # Expire a fragment
+ if comment.class.to_s == 'TopicComment'
+ expire_fragment(%r{forums/list_forums.user_id=*})
+ expire_fragment(%r{forums/most_active.forum=-1&user_id=*})
+ expire_fragment(%r{forums/most_active.forum=#{comment.topic.forum.id}&user_id=*})
+ # expire_fragment(:controller => 'comments', :action => 'index',
+ # :page => params[:page] || 1)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/sweepers/forums_sweeper.rb b/app/sweepers/forums_sweeper.rb
new file mode 100644
index 00000000..618a45a9
--- /dev/null
+++ b/app/sweepers/forums_sweeper.rb
@@ -0,0 +1,41 @@
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+class ForumsSweeper < ActionController::Caching::Sweeper
+ observe Forum # This sweeper is going to keep an eye on the Forum model
+
+ # If our sweeper detects that a forum was created call this
+ def after_save(forum)
+ expire_cache_for(forum)
+ end
+
+ # If our sweeper detects that a forum was deleted call this
+ def after_destroy(forum)
+ expire_cache_for(forum)
+ end
+
+ def after_forums_mark_all_as_read
+ expire_fragment(%r{forums/list_forums.user_id=#{current_user.id}})
+ end
+
+ def after_watches_create_forum_watch
+ kill_list_forums_cache
+ end
+
+ def after_watches_destroy_forum_watch
+ kill_list_forums_cache
+ end
+
+ private
+ def expire_cache_for(record)
+ # Expire a fragment
+ kill_list_forums_cache
+ expire_fragment(%r{forums/most_active.forum=-1&user_id=*})
+ # expire_fragment(:controller => 'forums', :action => 'index',
+ # :page => params[:page] || 1)
+ end
+
+ def kill_list_forums_cache
+ expire_fragment(%r{forums/list_forums.user_id=*})
+ end
+end
\ No newline at end of file
diff --git a/app/sweepers/link_sets_sweeper.rb b/app/sweepers/link_sets_sweeper.rb
new file mode 100644
index 00000000..ce22146f
--- /dev/null
+++ b/app/sweepers/link_sets_sweeper.rb
@@ -0,0 +1,23 @@
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+class LinkSetsSweeper < ActionController::Caching::Sweeper
+ observe LinkSet # This sweeper is going to keep an eye on the link_set model
+
+ # If our sweeper detects that an link_set was created call this
+ def after_save(link_set)
+ expire_cache_for(link_set)
+ end
+
+ # If our sweeper detects that an link_set was deleted call this
+ def after_destroy(link_set)
+ expire_cache_for(link_set)
+ end
+
+ private
+ def expire_cache_for(link_set)
+ # Expire a fragment
+ expire_fragment(:controller => 'link_sets', :action => 'show_links',
+ :link_set_id => link_set.id)
+ end
+end
\ No newline at end of file
diff --git a/app/sweepers/topics_sweeper.rb b/app/sweepers/topics_sweeper.rb
new file mode 100644
index 00000000..54979adb
--- /dev/null
+++ b/app/sweepers/topics_sweeper.rb
@@ -0,0 +1,26 @@
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+class TopicsSweeper < ActionController::Caching::Sweeper
+ observe Topic # This sweeper is going to keep an eye on the Topic model
+
+ # If our sweeper detects that a topic was created call this
+ def after_save(topic)
+ expire_cache_for(topic)
+ end
+
+ # If our sweeper detects that a topic was deleted call this
+ def after_destroy(topic)
+ expire_cache_for(topic)
+ end
+
+ private
+ def expire_cache_for(topic)
+ # Expire a fragment
+ expire_fragment(%r{forums/list_forums.user_id=*})
+ expire_fragment(%r{forums/most_active.forum=-1&user_id=*})
+ expire_fragment(%r{forums/most_active.forum=#{topic.forum.id}&user_id=*})
+# expire_fragment(:controller => 'topics', :action => 'index',
+# :page => params[:page] || 1)
+ end
+end
\ No newline at end of file
diff --git a/db/migrate/20081229014808_create_topic_import.rb b/db/migrate/20081229014808_create_topic_import.rb
new file mode 100644
index 00000000..3e0fc571
--- /dev/null
+++ b/db/migrate/20081229014808_create_topic_import.rb
@@ -0,0 +1,19 @@
+class CreateTopicImport < ActiveRecord::Migration
+ def self.up
+ create_table :topic_imports, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8' do |t|
+ t.string :forum_name, :limit => 50, :null => false
+ t.string :topic_title, :limit => 200, :null => false
+ t.string :user_email, :null => false
+ t.text :comment_body, :null => false
+ t.timestamps
+ t.string :status
+ end
+ RunIntervalPeriodicJob.create(:job => 'TopicImport.process',
+ :interval => 600) #once every 10 minutes
+ end
+
+ def self.down
+ drop_table :topic_imports
+ RunIntervalPeriodicJob.find_by_job("TopicImport.process").destroy
+ end
+end
diff --git a/db/migrate/20090116033227_create_rates.rb b/db/migrate/20090116033227_create_rates.rb
new file mode 100644
index 00000000..5fe26f39
--- /dev/null
+++ b/db/migrate/20090116033227_create_rates.rb
@@ -0,0 +1,19 @@
+class CreateRates < ActiveRecord::Migration
+ def self.up
+ create_table :rates, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8' do |t|
+ t.references :user
+ t.references :rateable, :polymorphic => true
+ t.integer :stars
+ t.string :dimension
+
+ t.timestamps
+ end
+
+ add_index :rates, :user_id
+ add_index :rates, :rateable_id
+ end
+
+ def self.down
+ drop_table :rates
+ end
+end
diff --git a/db/migrate/20090116040949_add_rating_cache_column.rb b/db/migrate/20090116040949_add_rating_cache_column.rb
new file mode 100644
index 00000000..c1b11c64
--- /dev/null
+++ b/db/migrate/20090116040949_add_rating_cache_column.rb
@@ -0,0 +1,9 @@
+class AddRatingCacheColumn < ActiveRecord::Migration
+ def self.up
+ add_column :topics, :rating_average, :decimal, :default => 0
+ end
+
+ def self.down
+ remove_column :topics, :rating_average
+ end
+end
\ No newline at end of file
diff --git a/db/migrate/20090118024317_add_private_to_comments.rb b/db/migrate/20090118024317_add_private_to_comments.rb
new file mode 100644
index 00000000..ba5c6b56
--- /dev/null
+++ b/db/migrate/20090118024317_add_private_to_comments.rb
@@ -0,0 +1,13 @@
+class AddPrivateToComments < ActiveRecord::Migration
+ def self.up
+ change_table :comments do |t|
+ t.boolean :private, :default => false, :null => false
+ end
+ end
+
+ def self.down
+ change_table :comments do |t|
+ t.remove :private
+ end
+ end
+end
diff --git a/db/migrate/20090118042341_populate_rebuild_index_periodic_job.rb b/db/migrate/20090118042341_populate_rebuild_index_periodic_job.rb
new file mode 100644
index 00000000..6a1200a7
--- /dev/null
+++ b/db/migrate/20090118042341_populate_rebuild_index_periodic_job.rb
@@ -0,0 +1,11 @@
+class PopulateRebuildIndexPeriodicJob < ActiveRecord::Migration
+ def self.up
+ RunAtPeriodicJob.reset_column_information
+ # Regenerate SOLR indexes
+ RunAtPeriodicJob.create(:job => 'Topic.rebuild_solr_index; TopicComment.rebuild_solr_index', :run_at_minutes => 210) # run at 3:30AM
+ end
+
+ def self.down
+ RunIntervalPeriodicJob.find_by_job("Topic.rebuild_solr_index; TopicComment.rebuild_solr_index").destroy
+ end
+end
diff --git a/db/migrate/20090118171507_add_last_commented_at_to_topic.rb b/db/migrate/20090118171507_add_last_commented_at_to_topic.rb
new file mode 100644
index 00000000..5a45a783
--- /dev/null
+++ b/db/migrate/20090118171507_add_last_commented_at_to_topic.rb
@@ -0,0 +1,18 @@
+class AddLastCommentedAtToTopic < ActiveRecord::Migration
+ def self.up
+ change_table :topics do |t|
+ t.datetime :last_commented_at
+ end
+
+ Topic.reset_column_information
+ for topic in Topic.find(:all)
+ topic.update_attribute(:last_commented_at, topic.updated_at)
+ end
+ end
+
+ def self.down
+ change_table :topics do |t|
+ t.remove :last_commented_at
+ end
+ end
+end
diff --git a/db/migrate/20090118194132_add_power_user_group_to_forum.rb b/db/migrate/20090118194132_add_power_user_group_to_forum.rb
new file mode 100644
index 00000000..9a1b8a76
--- /dev/null
+++ b/db/migrate/20090118194132_add_power_user_group_to_forum.rb
@@ -0,0 +1,16 @@
+class AddPowerUserGroupToForum < ActiveRecord::Migration
+ extend MigrationHelpers
+
+ def self.up
+ change_table :forums do |t|
+ t.column 'power_user_group_id', :integer
+ end
+ add_foreign_key(:forums, :power_user_group_id, :groups)
+ end
+
+ def self.down
+ change_table :forums do |t|
+ t.remove :power_user_group_id
+ end
+ end
+end
diff --git a/db/migrate/20090119160141_add_published_at_to_comments.rb b/db/migrate/20090119160141_add_published_at_to_comments.rb
new file mode 100644
index 00000000..3cbbd8a0
--- /dev/null
+++ b/db/migrate/20090119160141_add_published_at_to_comments.rb
@@ -0,0 +1,18 @@
+class AddPublishedAtToComments < ActiveRecord::Migration
+ def self.up
+ change_table :comments do |t|
+ t.datetime :published_at
+ end
+
+ Comment.reset_column_information
+ for comment in TopicComment.find(:all)
+ comment.update_attribute(:published_at, comment.created_at) unless comment.private
+ end
+ end
+
+ def self.down
+ change_table :comments do |t|
+ t.remove :published_at
+ end
+ end
+end
diff --git a/db/migrate/20090121174906_add_public_to_attachments.rb b/db/migrate/20090121174906_add_public_to_attachments.rb
new file mode 100644
index 00000000..a465f0be
--- /dev/null
+++ b/db/migrate/20090121174906_add_public_to_attachments.rb
@@ -0,0 +1,13 @@
+class AddPublicToAttachments < ActiveRecord::Migration
+ def self.up
+ change_table :attachments do |t|
+ t.boolean :public, :default => true, :null => false
+ end
+ end
+
+ def self.down
+ change_table :attachments do |t|
+ t.remove :public
+ end
+ end
+end
diff --git a/db/migrate/20090129160804_lengthen_filename.rb b/db/migrate/20090129160804_lengthen_filename.rb
new file mode 100644
index 00000000..50c8d851
--- /dev/null
+++ b/db/migrate/20090129160804_lengthen_filename.rb
@@ -0,0 +1,21 @@
+class LengthenFilename < ActiveRecord::Migration
+ def self.up
+ change_table :attachments do |t|
+ # lengthen foreign keys
+ t.change :filename, :string, :limit => 200
+ end
+ end
+
+ def self.down
+ for attachment in Attachment.find(:all)
+ if attachment.filename.length > 50
+ attachment.filename = attachment.filename.slice(1..50)
+ attachment.save
+ end
+ end
+ change_table :attachments do |t|
+ # lengthen foreign keys
+ t.change :filename, :string, :limit => 50
+ end
+ end
+end
diff --git a/db/migrate/20090129194632_add_display_order_to_forums.rb b/db/migrate/20090129194632_add_display_order_to_forums.rb
new file mode 100644
index 00000000..3163e25a
--- /dev/null
+++ b/db/migrate/20090129194632_add_display_order_to_forums.rb
@@ -0,0 +1,13 @@
+class AddDisplayOrderToForums < ActiveRecord::Migration
+ def self.up
+ change_table :forums do |t|
+ t.integer :display_order, :default => 10, :null => true
+ end
+ end
+
+ def self.down
+ change_table :forums do |t|
+ t.remove :display_order
+ end
+ end
+end
\ No newline at end of file
diff --git a/db/migrate/20090204212649_add_dummy_to_user_topic_read.rb b/db/migrate/20090204212649_add_dummy_to_user_topic_read.rb
new file mode 100644
index 00000000..82f45166
--- /dev/null
+++ b/db/migrate/20090204212649_add_dummy_to_user_topic_read.rb
@@ -0,0 +1,13 @@
+class AddDummyToUserTopicRead < ActiveRecord::Migration
+ def self.up
+ change_table :user_topic_reads do |t|
+ t.integer :dummy, :default => 1, :null => false
+ end
+ end
+
+ def self.down
+ change_table :user_topic_reads do |t|
+ t.remove :dummy
+ end
+ end
+end
diff --git a/db/migrate/20090211175855_add_tracking_to_topics.rb b/db/migrate/20090211175855_add_tracking_to_topics.rb
new file mode 100644
index 00000000..546d5beb
--- /dev/null
+++ b/db/migrate/20090211175855_add_tracking_to_topics.rb
@@ -0,0 +1,29 @@
+require "migration_helpers"
+
+class AddTrackingToTopics < ActiveRecord::Migration
+ extend MigrationHelpers
+
+ def self.up
+ change_table :forums do |t|
+ t.boolean :tracked, :default => false, :null => false
+ end
+ change_table :topics do |t|
+ t.boolean :open, :default => true, :null => false
+ t.column :owner_id, :integer, :null => true, :references => :user
+ t.datetime :closed_at
+ end
+ add_foreign_key(:topics, :owner_id, :users)
+ end
+
+ def self.down
+ remove_foreign_key(:topics, :owner_id)
+ change_table :forums do |t|
+ t.remove :tracked
+ end
+ change_table :topics do |t|
+ t.remove :open
+ t.remove :owner_id
+ t.remove :closed_at
+ end
+ end
+end
diff --git a/db/migrate/20090213023402_add_downloads_to_attachment.rb b/db/migrate/20090213023402_add_downloads_to_attachment.rb
new file mode 100644
index 00000000..1d27459e
--- /dev/null
+++ b/db/migrate/20090213023402_add_downloads_to_attachment.rb
@@ -0,0 +1,13 @@
+class AddDownloadsToAttachment < ActiveRecord::Migration
+ def self.up
+ change_table :attachments do |t|
+ t.integer :downloads, :default => 0, :null => false
+ end
+ end
+
+ def self.down
+ change_table :attachments do |t|
+ t.remove :downloads
+ end
+ end
+end
diff --git a/db/migrate/20090213133600_add_alias_to_attachments.rb b/db/migrate/20090213133600_add_alias_to_attachments.rb
new file mode 100644
index 00000000..2e670f8e
--- /dev/null
+++ b/db/migrate/20090213133600_add_alias_to_attachments.rb
@@ -0,0 +1,15 @@
+class AddAliasToAttachments < ActiveRecord::Migration
+ def self.up
+ change_table :attachments do |t|
+ t.string :alias, :null => true, :limit => 40
+ end
+ add_index :attachments, :alias, :unique => true
+ end
+
+ def self.down
+ remove_index :attachments, :alias
+ change_table :attachments do |t|
+ t.remove :alias
+ end
+ end
+end
diff --git a/db/migrate/20090213194308_create_enterprise_types_attachments.rb b/db/migrate/20090213194308_create_enterprise_types_attachments.rb
new file mode 100644
index 00000000..b4a2f5bd
--- /dev/null
+++ b/db/migrate/20090213194308_create_enterprise_types_attachments.rb
@@ -0,0 +1,28 @@
+
+require "migration_helpers"
+
+class CreateEnterpriseTypesAttachments < ActiveRecord::Migration
+ extend MigrationHelpers
+
+ def self.up
+ create_table :attachments_enterprise_types, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8', :id => false do |t|
+ t.references :attachment, :null => false
+ t.references :enterprise_type, :null => false
+ t.column :lock_version, :integer, :default => 0
+ t.timestamps
+ end
+
+ add_foreign_key(:attachments_enterprise_types, :attachment_id, :attachments)
+ add_foreign_key(:attachments_enterprise_types, :enterprise_type_id, :lookup_codes)
+
+ add_index :attachments_enterprise_types, [:attachment_id, :enterprise_type_id],
+ :unique => true, :name => ':attachments_enterprise_types_u1'
+ end
+
+ def self.down
+ remove_foreign_key(:attachments_enterprise_types, :attachment_id)
+ remove_foreign_key(:attachments_enterprise_types, :enterprise_type_id)
+
+ drop_table :attachments_enterprise_types
+ end
+end
diff --git a/db/migrate/20090215012112_add_forum_type_to_forums.rb b/db/migrate/20090215012112_add_forum_type_to_forums.rb
new file mode 100644
index 00000000..855f5193
--- /dev/null
+++ b/db/migrate/20090215012112_add_forum_type_to_forums.rb
@@ -0,0 +1,30 @@
+class AddForumTypeToForums < ActiveRecord::Migration
+ def self.up
+ change_table :forums do |t|
+ t.string :forum_type, :default => 'forum', :null => false
+ end
+ Forum.reset_column_information
+
+ for forum in Forum.find(:all)
+ forum.forum_type = 'blog' if forum.restrict_topic_creation
+ forum.save!
+ end
+ change_table :forums do |t|
+ t.remove :restrict_topic_creation
+ end
+ end
+
+ def self.down
+ change_table :forums do |t|
+ t.boolean :restrict_topic_creation, :default => false, :null => false
+ end
+ Forum.reset_column_information
+ for forum in Forum.find(:all)
+ forum.restrict_topic_creation = true if forum.forum_type != 'forum'
+ forum.save!
+ end
+ change_table :forums do |t|
+ t.remove :forum_type
+ end
+ end
+end
diff --git a/db/migrate/20090216034325_update_rebuild_solr_index.rb b/db/migrate/20090216034325_update_rebuild_solr_index.rb
new file mode 100644
index 00000000..4ef05fff
--- /dev/null
+++ b/db/migrate/20090216034325_update_rebuild_solr_index.rb
@@ -0,0 +1,19 @@
+class UpdateRebuildSolrIndex < ActiveRecord::Migration
+ def self.up
+ job = RunAtPeriodicJob.find_by_job_and_last_run_at('Topic.rebuild_solr_index; TopicComment.rebuild_solr_index',
+ nil)
+ unless job.nil?
+ job.job = 'SearchUtils.rebuild_indexes'
+ job.save!
+ end
+ end
+
+ def self.down
+ job = RunAtPeriodicJob.find_by_job_and_last_run_at('SearchUtils.rebuild_indexes',
+ nil)
+ unless job.nil?
+ job.job = 'Topic.rebuild_solr_index; TopicComment.rebuild_solr_index'
+ job.save!
+ end
+ end
+end
diff --git a/db/migrate/20090226021246_add_attachments_groups.rb b/db/migrate/20090226021246_add_attachments_groups.rb
new file mode 100644
index 00000000..04374cce
--- /dev/null
+++ b/db/migrate/20090226021246_add_attachments_groups.rb
@@ -0,0 +1,26 @@
+require "migration_helpers"
+
+class AddAttachmentsGroups < ActiveRecord::Migration
+ extend MigrationHelpers
+
+ def self.up
+ create_table :attachments_groups, :id => false do |t|
+ t.references :attachment, :null => false
+ t.references :group, :null => false
+ t.column :lock_version, :integer, :default => 0
+ t.timestamps
+ end
+
+ add_foreign_key(:attachments_groups, :attachment_id, :attachments)
+ add_foreign_key(:attachments_groups, :group_id, :groups)
+
+ add_index :attachments_groups, [:attachment_id, :group_id], :unique => true
+ end
+
+ def self.down
+ remove_foreign_key(:attachments_groups, :attachment_id)
+ remove_foreign_key(:attachments_groups, :group_id)
+
+ drop_table :attachments_groups
+ end
+end
diff --git a/db/migrate/20090302214023_rename_topic_open.rb b/db/migrate/20090302214023_rename_topic_open.rb
new file mode 100644
index 00000000..69d72f8b
--- /dev/null
+++ b/db/migrate/20090302214023_rename_topic_open.rb
@@ -0,0 +1,9 @@
+class RenameTopicOpen < ActiveRecord::Migration
+ def self.up
+ rename_column "topics", "open", "open_status"
+ end
+
+ def self.down
+ rename_column "topics", "open_status", "open"
+ end
+end
diff --git a/db/migrate/20090306031024_attachment_edited_at.rb b/db/migrate/20090306031024_attachment_edited_at.rb
new file mode 100644
index 00000000..7395f0a2
--- /dev/null
+++ b/db/migrate/20090306031024_attachment_edited_at.rb
@@ -0,0 +1,22 @@
+class AttachmentEditedAt < ActiveRecord::Migration
+ def self.up
+ change_table :attachments do |t|
+ t.datetime :edited_at
+ end
+
+ Attachment.reset_column_information
+ for attachment in Attachment.find(:all)
+ attachment.update_attribute(:edited_at, attachment.updated_at)
+ end
+
+ change_table :attachments do |t|
+ t.change :edited_at, :datetime, :null => false
+ end
+ end
+
+ def self.down
+ change_table :attachments do |t|
+ t.remove :edited_at
+ end
+ end
+end
diff --git a/lib/search_utils.rb b/lib/search_utils.rb
new file mode 100644
index 00000000..bcb390ae
--- /dev/null
+++ b/lib/search_utils.rb
@@ -0,0 +1,13 @@
+# To change this template, choose Tools | Templates
+# and open the template in the editor.
+
+class SearchUtils
+ def self.rebuild_indexes
+ Topic.rebuild_solr_index
+ TopicComment.rebuild_solr_index
+ Idea.rebuild_solr_index
+ User.rebuild_solr_index
+ Enterprise.rebuild_solr_index
+ Attachment.rebuild_solr_index
+ end
+end
diff --git a/vendor/plugins/acts_as_solr/.gitignore b/vendor/plugins/acts_as_solr/.gitignore
new file mode 100644
index 00000000..d295b20c
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/.gitignore
@@ -0,0 +1,8 @@
+*.log
+*.log
+*_pid
+coverage/*
+coverage.data
+solr/solr/data/*
+.svn
+test/db/*.db
diff --git a/vendor/plugins/acts_as_solr/CHANGE_LOG b/vendor/plugins/acts_as_solr/CHANGE_LOG
new file mode 100644
index 00000000..66597a8e
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/CHANGE_LOG
@@ -0,0 +1,230 @@
+== CHANGE_LOG
+=== Development
+NEW:: A unit test suite based on Shoulda, not requiring a running Solr or a Rails environment. (Mathias Meyer)
+NEW:: Added the :offline option to the acts_as_solr method. (Mathias Meyer)
+NEW:: Added :lazy flag to find_by_solr, and with that support to load records lazily. (Mathias Meyer)
+FIX:: Added test: to solr.yml so you don't need to do this by hand. (Ken Harris)
+FIX:: Updated bundled Solr to 1.3. (Mathias Meyer)
+FIX:: Updated bundled solr-ruby to 0.0.6 which comes bundled with Solr 1.3. (Mathias Meyer)
+FIX:: Improved logging of the reindex rake task. (David Stevenson)
+FIX:: Added requirement for libxml-ruby > 0.7, if libxml-ruby is installed. (David Stevenson)
+FIX:: Ruby 1.9 compatibility fixes. (David Palm)
+FIX:: Fixed compatibility with Desert by renaming environment.rb to solr_environment.rb. (David Stevenson)
+FIX:: Moved the require of solr_environment only into tasks that need it. (David Stevenson)
+
+=== 06-18-2007: Version 0.9
+NEW:: Added the option :scores when doing a search. If set to true this will return the score as a 'solr_score' attribute or each one of the instances found
+ books = Book.find_by_solr 'ruby OR splinter', :scores => true
+ books.records.first.solr_score
+ => 1.21321397
+ books.records.last.solr_score
+ => 0.12321548
+
+NEW:: Major change on the way the results returned are accessed.
+ books = Book.find_by_solr 'ruby'
+ # the above will return a SearchResults class with 4 methods:
+ # docs|results|records: will return an array of records found
+ #
+ # books.records.is_a?(Array)
+ # => true
+ #
+ # total|num_found|total_hits: will return the total number of records found
+ #
+ # books.total
+ # => 2
+ #
+ # facets: will return the facets when doing a faceted search
+ #
+ # max_score|highest_score: returns the highest score found
+ #
+ # books.max_score
+ # => 1.3213213
+
+NEW:: Integrating acts_as_solr to use solr-ruby as the 'backend'. Integration based on the patch submitted by Erik Hatcher
+NEW:: Re-factoring rebuild_solr_index to allow adds to be done in batch; and if a finder block is given, it will be called to retrieve the items to index. (thanks Daniel E.)
+NEW:: Adding the option to specify the port Solr should start when using rake solr:start
+ rake solr:start RAILS_ENV=your_env PORT=XX
+
+NEW:: Adding deprecation warning for the :background configuration option. It will no longer be updated.
+NEW:: Adding support for models that use a primary key other than integer
+ class Posting < ActiveRecord::Base
+ set_primary_key 'guid' #string
+ #make sure you set the :primary_key_field => 'pk_s' if you wish to use a string field as the primary key
+ acts_as_solr({},{:primary_key_field => 'pk_s'})
+ end
+
+FIX:: Disabling of storing most fields. Storage isn't useful for acts_as_solr in any field other than the pk and id fields. It just takes up space and time. (thanks Daniel E.)
+FIX:: Re-factoring code submitted by Daniel E.
+NEW:: Adding an :auto_commit option that will only send the commit command to Solr if it is set to true
+ class Author < ActiveRecord::Base
+ acts_as_solr :auto_commit => false
+ end
+
+FIX:: Fixing bug on rake's test task
+FIX:: Making acts_as_solr's Post class compatible with Solr 1.2 (thanks Si)
+NEW:: Adding Solr 1.2
+FIX:: Removing Solr 1.1
+NEW:: Adding a conditional :if option to the acts_as_solr call. It behaves the same way ActiveRecord's :if argument option does.
+ class Electronic < ActiveRecord::Base
+ acts_as_solr :if => proc{|record| record.is_active?}
+ end
+
+NEW:: Adding fixtures to Solr index when using rake db:fixtures:load
+FIX:: Fixing boost warning messages
+FIX:: Fixing bug when adding a facet to a field that contains boost
+NEW:: Deprecating find_with_facet and combining functionality with find_by_solr
+NEW:: Adding the option to :exclude_fields when indexing a model
+ class User < ActiveRecord::Base
+ acts_as_solr :exclude_fields => [:password, :login, :credit_card_number]
+ end
+
+FIX:: Fixing branch bug on older ruby version
+NEW:: Adding boost support for fields and documents being indexed:
+ class Electronic < ActiveRecord::Base
+ # You can add boosting on a per-field basis or on the entire document
+ acts_as_solr :fields => [{:price => {:boost => 5.0}}], :boost => 5.0
+ end
+
+FIX:: Fixed the acts_as_solr limitation to only accept test|development|production environments.
+
+=== 05-16-2007: Version 0.8.5
+FIX:: There's no need to specify the :field_types anymore when doing a search in a model that specifies a field type for a field.
+FIX:: Better handling of nil values from indexed fields. Solr complained when indexing fields with field type and the field values being passed as nils.
+NEW:: Adding Solr sort (order by) option to the search query (thanks Kevin Hunt)
+FIX:: Applying patch suggested for increasing the Solr commit speed (thanks Mourad Hammiche)
+FIX:: Updated documentation
+
+=== 05-10-2007: Version 0.8
+NEW: New video tutorial
+NEW: Faceted search has been implemented and its possible to 'drill-down' on the facets
+NEW: New rake tasks you can use to start/stop the solr server in test, development and production environments: (thanks Matt Clark)
+ rake solr:start|stop RAILS_ENV=test|development|production (defaults to development if none given)
+
+NEW: Changes to the plugin's test framework and it now supports Sqlite as well (thanks Matt Clark)
+FIX: Patch applied (thanks Micah) that allows one to have multiple solr instances in the same servlet
+FIX: Patch applied (thanks Micah) that allows indexing of STIs
+FIX: Patch applied (thanks Gordon) that allows the plugin to use a table's primary key different than 'id'
+FIX: Returning empty array instead of empty strings when no records are found
+FIX: Problem with unit tests failing due to order of the tests and speed of the commits
+
+=== 02-16-2007: Version 0.7
+NEW: You can now specify the field types when indexing and searching if
+you'd like to preserve its original type:
+
+Indexing
+
+Each field passed can also be a hash with the value being a field type
+
+ class Electronic < ActiveRecord::Base
+ acts_as_solr :fields => [{:price => :range_float}, {:current_time => :date}]
+ def current_time
+ Time.now
+ end
+ end
+
+Searching
+ Electronic.find_by_solr "ipod AND price:[* TO 59.99]",
+ :field_types => [{:price => :range_float}]
+
+The field types accepted are:
+:float:: Index the field value as a float (ie.: 12.87)
+:integer:: Index the field value as an integer (ie.: 31)
+:boolean:: Index the field value as a boolean (ie.: true/false)
+:date:: Index the field value as a date (ie.: Wed Nov 15 23:13:03 PST 2006)
+:string:: Index the field value as a text string, not applying the same indexing filters as a regular text field
+:range_integer:: Index the field value for integer range queries (ie.:[5 TO 20])
+:range_float:: Index the field value for float range queries (ie.:[14.56 TO 19.99])
+
+Setting the field type preserves its original type when indexed
+
+FIX: Fixing sorting bug. Thanks for the catch Laurel
+
+FIX: Fixing small bug when installing the plugin
+
+NEW: Adding the :additional_fields option to the acts_as_solr method
+
+=== 02-05-2007: Version 0.6.5
+NEW:: Added multi-model search, which can be used to execute a search across multiple models:
+ Book.multi_solr_search "Napoleon OR Tom", :models => [Movie]
+
+====options:
+Accepts the same options as find_by_solr plus:
+models:: The additional models you'd like to include in the search
+results_format:: Specify the format of the results found
+ :objects :: Will return an array with the results being objects (default). Example:
+ Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :objects
+ :ids :: Will return an array with the ids of each entry found. Example:
+ Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :ids
+ => [{"id" => "Movie:1"},{"id" => Book:1}]
+ Where the value of each array is as Model:instance_id
+
+=== 02-03-2007: Version 0.6
+NEW:: Added basic faceted search functionality for indexing and searching:
+
+==== Indexing:
+
+ class Electronic < ActiveRecord::Base
+ acts_as_solr :facets => [:category, :manufacturer]
+ end
+
+==== Searching:
+
+ Electronic.find_with_facet "memory", :facets => {:fields =>[:category]}
+
+=== 01-15-2007: Version 0.5
+NEW:: Added model association indexing, which means you can include any :has_one, :has_many,
+:belongs_to and :has_and_belongs_to_many association to be indexed:
+
+ class Category < ActiveRecord::Base
+ has_many :books
+ acts_as_solr :include => [:books]
+ end
+
+ class Book < ActiveRecord::Base
+ belongs_to :category
+ acts_as_solr :include => [:category]
+ end
+
+=== 01-11-2007:
+NEW:: Added the acts_as_solr's plugin tests
+
+=== 11-07-2006: Version 0.4
+NEW:: Added :background option, which takes and integer value (in minutes) to wait before committing the changes to Solr. This depends on rail_cron being installed. By setting up the background job we prevent the users from having to wait for Solr records to be created, and we keep from updating the index over and over for quickly successive changes. (Rob Kaufman)
+
+=== 11-02-2006: Version 0.3
+NEW:: Added a method (Model.count_by_solr) that returns the total number of documents found based on query passed
+NEW:: Added configuration for production and development environments
+
+=== 10-21-2006: Version 0.2
+PLUGIN
+FIX:: Fixed bug when mixing search-by-field and 'free' search: Model.find_by_solr 'solr AND name:Thiago'
+FIX:: Fixed bug with multi-terms search: Book.find_by_solr 'anteater john'
+FIX:: Fixed bug when including more than one search field: Model.find_by_solr 'name:Thiago AND engine:Solr'
+FIX:: Fixed bug when rebuilding the index, it wasn't saving the data
+NEW:: Added the ability to index custom methods from a model as search fields
+NEW:: Added a search method (Model.find_id_by_solr) that will return only the id of the results
+
+SCHEMA.XML
+NEW:: Added a new field:
+NEW:: Added a default search field: default
+FIX:: Changed the defaultOperator to AND instead of OR
+
+=== 09-29-2006: Version 0.1
+PLUGIN
+NEW:: Included the option of having a Solr config file inside the rails env.
+NEW:: Added the ability of indexing only certain fields, if you chose to.
+NEW:: Added configuration options
+NEW:: Changed the way the search was done:
+ Old: You were forced the specify the field you wanted to look for
+ ('field:value') and you had to specify a default search field as
+ well, for when you didn't include the 'field' in the search term
+ New: The new search features include:
+ - You don't have to specify a default search field;
+ - You are not forced to include the field name in the search term,
+ unless you choose to search for a specific field ('name:Thiago');
+ - You can pass the starting row and the number of rows per page,
+ which is usefull for pagination
+NEW:: Included a method to rebuild the index files
+
+SCHEMA.XML
+NEW:: Created an optimized version of the config file to better work with this plugin
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/LICENSE b/vendor/plugins/acts_as_solr/LICENSE
new file mode 100644
index 00000000..e4a2ef54
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2006 Erik Hatcher, Thiago Jackiw
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/README.markdown b/vendor/plugins/acts_as_solr/README.markdown
new file mode 100644
index 00000000..18a7e434
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/README.markdown
@@ -0,0 +1,89 @@
+`acts_as_solr` Rails plugin
+======
+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.
+It was based on the first draft by Erik Hatcher.
+
+Current Release
+======
+The current stable release is v0.9 and was released on 06-18-2007.
+
+Changes
+======
+Please refer to the CHANGE_LOG
+
+Installation
+======
+
+For Rails >= 2.1:
+
+ script/plugin install git://github.com/mattmatt/acts_as_solr.git
+
+For Rails < 2.1:
+
+ cd vendor/plugins
+ git clone git://github.com/mattmatt/acts_as_solr.git
+ rm -rf acts_as_solr/.git
+
+Make sure you copy `vendor/plugins/acts_as_solr/config/solr.yml` to your Rails
+application's config directory, when you install via `git clone`.
+
+Requirements
+------
+* 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)
+* If you have libxml-ruby installed, make sure it's at least version 0.7
+
+Configuration
+======
+Basically everything is configured to work out of the box. You can use `rake solr:start` and `rake solr:stop`
+to start and stop the Solr web server (an embedded Jetty). If the default JVM options aren't suitable for
+your environment, you can configure them in solr.yml with the option `jvm_options`. There is a default
+set for the production environment to have some more memory available for the JVM than the defaults, but
+feel free to change them to your liking.
+
+Basic Usage
+======
+
+# Just include the line below to any of your ActiveRecord models:
+ acts_as_solr
+
+# Or if you want, you can specify only the fields that should be indexed:
+ acts_as_solr :fields => [:name, :author]
+
+# Then to find instances of your model, just do:
+ Model.find_by_solr(query) #query is a string representing your query
+
+# Please see ActsAsSolr::ActsMethods for a complete info
+
+
+
+
+`acts_as_solr` in your tests
+======
+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`
+
+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:
+
+
+
+([via](http://www.subelsky.com/2007/10/actsassolr-capistranhttpwwwbloggercomim.html#c1646308013209805416))
+
+Authors
+======
+Erik Hatcher: First draft
+Thiago Jackiw: Previous developer
+Luke Francl: Current developer
+Mathias Meyer: Current developer
+
+Release Information
+======
+Released under the MIT license.
+
+More info
+======
+The old [acts_as_solr homepage](http://acts-as-solr.railsfreaks.com) is no more. For more up-to-date information, check out the project page of the current mainline on [GitHub](http://github.com/mattmatt/acts_as_solr/wikis).
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/Rakefile b/vendor/plugins/acts_as_solr/Rakefile
new file mode 100644
index 00000000..25ecdd0a
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/Rakefile
@@ -0,0 +1,45 @@
+require 'rubygems'
+require 'rake'
+require 'rake/testtask'
+
+Dir["#{File.dirname(__FILE__)}/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
+
+desc "Default Task"
+task :default => [:test]
+
+desc "Runs the unit tests"
+task :test => "test:unit"
+
+namespace :test do
+ task :setup do
+ ENV['RAILS_ENV'] = "test"
+ require File.dirname(__FILE__) + '/config/solr_environment'
+ puts "Using " + DB
+ %x(mysql -u#{MYSQL_USER} < #{File.dirname(__FILE__) + "/test/fixtures/db_definitions/mysql.sql"}) if DB == 'mysql'
+
+ Rake::Task["test:migrate"].invoke
+ end
+
+ desc 'Measures test coverage using rcov'
+ task :rcov => :setup do
+ rm_f "coverage"
+ rm_f "coverage.data"
+ rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib"
+
+ system("#{rcov} --html #{Dir.glob('test/**/*_test.rb').join(' ')}")
+ system("open coverage/index.html") if PLATFORM['darwin']
+ end
+
+ desc 'Runs the functional tests, testing integration with Solr'
+ Rake::TestTask.new('functional' => :setup) do |t|
+ t.pattern = "test/functional/*_test.rb"
+ t.verbose = true
+ end
+
+ desc "Unit tests"
+ Rake::TestTask.new(:unit) do |t|
+ t.libs << 'test/unit'
+ t.pattern = "test/unit/*_shoulda.rb"
+ t.verbose = true
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/TESTING_THE_PLUGIN b/vendor/plugins/acts_as_solr/TESTING_THE_PLUGIN
new file mode 100644
index 00000000..06d5d763
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/TESTING_THE_PLUGIN
@@ -0,0 +1,25 @@
+acts_as_solr comes with a quick and fast unit test suite, and with a longer-running
+functional test suite, the latter testing the actual integration with Solr.
+
+The unit test suite is written using Shoulda, so make sure you have a recent version
+installed.
+
+Running `rake test` or just `rake` will run both test suites. Use `rake test:unit` to
+just run the unit test suite.
+
+== How to run functional tests for this plugin:
+To run the acts_as_solr's plugin tests run the following steps:
+
+- create a MySQL database called "actsassolr_test" (if you want to use MySQL)
+
+- 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
+
+- copy vendor/plugins/acts_as_solr/config/solr.yml to config/ (the Rails config folder)
+
+- rake solr:start RAILS_ENV=test
+
+- rake test:functional (Accepts the following arguments: DB=sqlite|mysql and MYSQL_USER=user)
+
+== Troubleshooting:
+If for some reason the tests don't run and you get MySQL errors, make sure you edit the MYSQL_USER entry under
+config/environment.rb. It's recommended to create or use a MySQL user with no password.
diff --git a/vendor/plugins/acts_as_solr/config/solr.yml b/vendor/plugins/acts_as_solr/config/solr.yml
new file mode 100644
index 00000000..89373734
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/config/solr.yml
@@ -0,0 +1,15 @@
+# Config file for the acts_as_solr plugin.
+#
+# If you change the host or port number here, make sure you update
+# them in your Solr config file
+
+development:
+ url: http://localhost:8982/solr
+
+production:
+ url: http://localhost:8983/solr
+ jvm_options: -server -d64 -Xmx1024M -Xms64M
+
+test:
+ url: http://localhost:8981/solr
+
diff --git a/vendor/plugins/acts_as_solr/config/solr_environment.rb b/vendor/plugins/acts_as_solr/config/solr_environment.rb
new file mode 100644
index 00000000..3d4aeb7e
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/config/solr_environment.rb
@@ -0,0 +1,22 @@
+ENV['RAILS_ENV'] = (ENV['RAILS_ENV'] || 'development').dup
+# RAILS_ROOT isn't defined yet, so figure it out.
+rails_root_dir = "#{File.dirname(File.expand_path(__FILE__))}/../../../../"
+SOLR_PATH = "#{File.dirname(File.expand_path(__FILE__))}/../solr" unless defined? SOLR_PATH
+
+SOLR_LOGS_PATH = "#{rails_root_dir}/log" unless defined? SOLR_LOGS_PATH
+SOLR_PIDS_PATH = "#{rails_root_dir}/tmp/pids" unless defined? SOLR_PIDS_PATH
+SOLR_DATA_PATH = "#{rails_root_dir}/solr/#{ENV['RAILS_ENV']}" unless defined? SOLR_DATA_PATH
+
+unless defined? SOLR_PORT
+ config = YAML::load_file(rails_root_dir+'/config/solr.yml')
+
+ SOLR_PORT = ENV['PORT'] || URI.parse(config[ENV['RAILS_ENV']]['url']).port
+end
+
+SOLR_JVM_OPTIONS = config[ENV['RAILS_ENV']]['jvm_options'] unless defined? SOLR_JVM_OPTIONS
+
+if ENV['RAILS_ENV'] == 'test'
+ DB = (ENV['DB'] ? ENV['DB'] : 'mysql') unless defined?(DB)
+ MYSQL_USER = (ENV['MYSQL_USER'].nil? ? 'root' : ENV['MYSQL_USER']) unless defined? MYSQL_USER
+ require File.join(File.dirname(File.expand_path(__FILE__)), '..', 'test', 'db', 'connections', DB, 'connection.rb')
+end
diff --git a/vendor/plugins/acts_as_solr/init.rb b/vendor/plugins/acts_as_solr/init.rb
new file mode 100644
index 00000000..fcbeb750
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/init.rb
@@ -0,0 +1,21 @@
+# Copyright (c) 2006 Erik Hatcher, Thiago Jackiw
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+require 'acts_as_solr'
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/install.rb b/vendor/plugins/acts_as_solr/install.rb
new file mode 100644
index 00000000..99ad8ffa
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/install.rb
@@ -0,0 +1,11 @@
+require 'fileutils'
+
+def install(file)
+ puts "Installing: #{file}"
+ target = File.join(File.dirname(__FILE__), '..', '..', '..', file)
+ FileUtils.cp File.join(File.dirname(__FILE__), file), target
+ dir_to_rename = File.dirname(__FILE__) + '/../trunk'
+ FileUtils.mv(dir_to_rename, File.dirname(__FILE__) + '/../acts_as_solr') if File.exists? dir_to_rename
+end
+
+install File.join( 'config', 'solr.yml' )
diff --git a/vendor/plugins/acts_as_solr/lib/acts_as_solr.rb b/vendor/plugins/acts_as_solr/lib/acts_as_solr.rb
new file mode 100644
index 00000000..b8d44a98
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/lib/acts_as_solr.rb
@@ -0,0 +1,59 @@
+# Copyright (c) 2006 Erik Hatcher, Thiago Jackiw
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+require 'active_record'
+require 'rexml/document'
+require 'net/http'
+require 'yaml'
+
+require File.dirname(__FILE__) + '/solr'
+require File.dirname(__FILE__) + '/acts_methods'
+require File.dirname(__FILE__) + '/class_methods'
+require File.dirname(__FILE__) + '/instance_methods'
+require File.dirname(__FILE__) + '/common_methods'
+require File.dirname(__FILE__) + '/deprecation'
+require File.dirname(__FILE__) + '/search_results'
+require File.dirname(__FILE__) + '/lazy_document'
+module ActsAsSolr
+
+ class Post
+ def self.execute(request)
+ begin
+ if File.exists?(RAILS_ROOT+'/config/solr.yml')
+ config = YAML::load_file(RAILS_ROOT+'/config/solr.yml')
+ url = config[RAILS_ENV]['url']
+ # for backwards compatibility
+ url ||= "http://#{config[RAILS_ENV]['host']}:#{config[RAILS_ENV]['port']}/#{config[RAILS_ENV]['servlet_path']}"
+ else
+ url = 'http://localhost:8982/solr'
+ end
+ connection = Solr::Connection.new(url)
+ return connection.send(request)
+ rescue
+ raise "Couldn't connect to the Solr server at #{url}. #{$!}"
+ false
+ end
+ end
+ end
+
+end
+
+# reopen ActiveRecord and include the acts_as_solr method
+ActiveRecord::Base.extend ActsAsSolr::ActsMethods
diff --git a/vendor/plugins/acts_as_solr/lib/acts_methods.rb b/vendor/plugins/acts_as_solr/lib/acts_methods.rb
new file mode 100644
index 00000000..0efa8c38
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/lib/acts_methods.rb
@@ -0,0 +1,222 @@
+module ActsAsSolr #:nodoc:
+
+ module ActsMethods
+
+ # declares a class as solr-searchable
+ #
+ # ==== options:
+ # fields:: This option can be used to specify only the fields you'd
+ # like to index. If not given, all the attributes from the
+ # class will be indexed. You can also use this option to
+ # include methods that should be indexed as fields
+ #
+ # class Movie < ActiveRecord::Base
+ # acts_as_solr :fields => [:name, :description, :current_time]
+ # def current_time
+ # Time.now.to_s
+ # end
+ # end
+ #
+ # Each field passed can also be a hash with the value being a field type
+ #
+ # class Electronic < ActiveRecord::Base
+ # acts_as_solr :fields => [{:price => :range_float}]
+ # def current_time
+ # Time.now
+ # end
+ # end
+ #
+ # The field types accepted are:
+ #
+ # :float:: Index the field value as a float (ie.: 12.87)
+ # :integer:: Index the field value as an integer (ie.: 31)
+ # :boolean:: Index the field value as a boolean (ie.: true/false)
+ # :date:: Index the field value as a date (ie.: Wed Nov 15 23:13:03 PST 2006)
+ # :string:: Index the field value as a text string, not applying the same indexing
+ # filters as a regular text field
+ # :range_integer:: Index the field value for integer range queries (ie.:[5 TO 20])
+ # :range_float:: Index the field value for float range queries (ie.:[14.56 TO 19.99])
+ #
+ # Setting the field type preserves its original type when indexed
+ #
+ # additional_fields:: This option takes fields to be include in the index
+ # in addition to those derived from the database. You
+ # can also use this option to include custom fields
+ # derived from methods you define. This option will be
+ # ignored if the :fields option is given. It also accepts
+ # the same field types as the option above
+ #
+ # class Movie < ActiveRecord::Base
+ # acts_as_solr :additional_fields => [:current_time]
+ # def current_time
+ # Time.now.to_s
+ # end
+ # end
+ #
+ # exclude_fields:: This option taks an array of fields that should be ignored from indexing:
+ #
+ # class User < ActiveRecord::Base
+ # acts_as_solr :exclude_fields => [:password, :login, :credit_card_number]
+ # end
+ #
+ # include:: This option can be used for association indexing, which
+ # means you can include any :has_one, :has_many, :belongs_to
+ # and :has_and_belongs_to_many association to be indexed:
+ #
+ # class Category < ActiveRecord::Base
+ # has_many :books
+ # acts_as_solr :include => [:books]
+ # end
+ #
+ # facets:: This option can be used to specify the fields you'd like to
+ # index as facet fields
+ #
+ # class Electronic < ActiveRecord::Base
+ # acts_as_solr :facets => [:category, :manufacturer]
+ # end
+ #
+ # boost:: You can pass a boost (float) value that will be used to boost the document and/or a field. To specify a more
+ # boost for the document, you can either pass a block or a symbol. The block will be called with the record
+ # as an argument, a symbol will result in the according method being called:
+ #
+ # class Electronic < ActiveRecord::Base
+ # acts_as_solr :fields => [{:price => {:boost => 5.0}}], :boost => 10.0
+ # end
+ #
+ # class Electronic < ActiveRecord::Base
+ # acts_as_solr :fields => [{:price => {:boost => 5.0}}], :boost => proc {|record| record.id + 120*37}
+ # end
+ #
+ # class Electronic < ActiveRecord::Base
+ # acts_as_solr :fields => [{:price => {:boost => :price_rating}}], :boost => 10.0
+ # end
+ #
+ # if:: Only indexes the record if the condition evaluated is true. The argument has to be
+ # either a symbol, string (to be eval'ed), proc/method, or class implementing a static
+ # validation method. It behaves the same way as ActiveRecord's :if option.
+ #
+ # class Electronic < ActiveRecord::Base
+ # acts_as_solr :if => proc{|record| record.is_active?}
+ # end
+ #
+ # offline:: Assumes that your using an outside mechanism to explicitly trigger indexing records, e.g. you only
+ # want to update your index through some asynchronous mechanism. Will accept either a boolean or a block
+ # that will be evaluated before actually contacting the index for saving or destroying a document. Defaults
+ # to false. It doesn't refer to the mechanism of an offline index in general, but just to get a centralized point
+ # where you can control indexing. Note: This is only enabled for saving records. acts_as_solr doesn't always like
+ # it, if you have a different number of results coming from the database and the index. This might be rectified in
+ # another patch to support lazy loading.
+ #
+ # class Electronic < ActiveRecord::Base
+ # acts_as_solr :offline => proc {|record| record.automatic_indexing_disabled?}
+ # end
+ #
+ # auto_commit:: The commit command will be sent to Solr only if its value is set to true:
+ #
+ # class Author < ActiveRecord::Base
+ # acts_as_solr :auto_commit => false
+ # end
+ #
+ def acts_as_solr(options={}, solr_options={})
+
+ extend ClassMethods
+ include InstanceMethods
+ include CommonMethods
+ include ParserMethods
+
+ cattr_accessor :configuration
+ cattr_accessor :solr_configuration
+
+ self.configuration = {
+ :fields => nil,
+ :additional_fields => nil,
+ :exclude_fields => [],
+ :auto_commit => true,
+ :include => nil,
+ :facets => nil,
+ :boost => nil,
+ :if => "true",
+ :offline => false
+ }
+ self.solr_configuration = {
+ :type_field => "type_s",
+ :primary_key_field => "pk_i",
+ :default_boost => 1.0
+ }
+
+ configuration.update(options) if options.is_a?(Hash)
+ solr_configuration.update(solr_options) if solr_options.is_a?(Hash)
+ Deprecation.validate_index(configuration)
+
+ configuration[:solr_fields] = {}
+
+ after_save :solr_save
+ after_destroy :solr_destroy
+
+ if configuration[:fields].respond_to?(:each)
+ process_fields(configuration[:fields])
+ else
+ process_fields(self.new.attributes.keys.map { |k| k.to_sym })
+ process_fields(configuration[:additional_fields])
+ end
+ end
+
+ private
+ def get_field_value(field)
+ field_name, options = determine_field_name_and_options(field)
+ configuration[:solr_fields][field_name] = options
+
+ define_method("#{field_name}_for_solr".to_sym) do
+ begin
+ value = self[field_name] || self.instance_variable_get("@#{field_name.to_s}".to_sym) || self.send(field_name.to_sym)
+ case options[:type]
+ # format dates properly; return nil for nil dates
+ when :date then value ? value.utc.strftime("%Y-%m-%dT%H:%M:%SZ") : nil
+ else value
+ end
+ rescue
+ value = ''
+ logger.debug "There was a problem getting the value for the field '#{field_name}': #{$!}"
+ end
+ end
+ end
+
+ def process_fields(raw_field)
+ if raw_field.respond_to?(:each)
+ raw_field.each do |field|
+ next if configuration[:exclude_fields].include?(field)
+ get_field_value(field)
+ end
+ end
+ end
+
+ def determine_field_name_and_options(field)
+ if field.is_a?(Hash)
+ name = field.keys.first
+ options = field.values.first
+ if options.is_a?(Hash)
+ [name, {:type => type_for_field(field)}.merge(options)]
+ else
+ [name, {:type => options}]
+ end
+ else
+ [field, {:type => type_for_field(field)}]
+ end
+ end
+
+ def type_for_field(field)
+ if configuration[:facets] && configuration[:facets].include?(field)
+ :facet
+ elsif column = columns_hash[field.to_s]
+ case column.type
+ when :string then :text
+ when :datetime then :date
+ when :time then :date
+ else column.type
+ end
+ else
+ :text
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/lib/class_methods.rb b/vendor/plugins/acts_as_solr/lib/class_methods.rb
new file mode 100644
index 00000000..287df9c4
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/lib/class_methods.rb
@@ -0,0 +1,173 @@
+require File.dirname(__FILE__) + '/common_methods'
+require File.dirname(__FILE__) + '/parser_methods'
+
+module ActsAsSolr #:nodoc:
+
+ module ClassMethods
+ include CommonMethods
+ include ParserMethods
+
+ # Finds instances of a model. Terms are ANDed by default, can be overwritten
+ # by using OR between terms
+ #
+ # Here's a sample (untested) code for your controller:
+ #
+ # def search
+ # results = Book.find_by_solr params[:query]
+ # end
+ #
+ # You can also search for specific fields by searching for 'field:value'
+ #
+ # ====options:
+ # offset:: - The first document to be retrieved (offset)
+ # limit:: - The number of rows per page
+ # order:: - Orders (sort by) the result set using a given criteria:
+ #
+ # Book.find_by_solr 'ruby', :order => 'description asc'
+ #
+ # field_types:: This option is deprecated and will be obsolete by version 1.0.
+ # There's no need to specify the :field_types anymore when doing a
+ # search in a model that specifies a field type for a field. The field
+ # types are automatically traced back when they're included.
+ #
+ # class Electronic < ActiveRecord::Base
+ # acts_as_solr :fields => [{:price => :range_float}]
+ # end
+ #
+ # facets:: This option argument accepts the following arguments:
+ # fields:: The fields to be included in the faceted search (Solr's facet.field)
+ # query:: The queries to be included in the faceted search (Solr's facet.query)
+ # zeros:: Display facets with count of zero. (true|false)
+ # sort:: Sorts the faceted resuls by highest to lowest count. (true|false)
+ # browse:: This is where the 'drill-down' of the facets work. Accepts an array of
+ # fields in the format "facet_field:term"
+ #
+ # Example:
+ #
+ # Electronic.find_by_solr "memory", :facets => {:zeros => false, :sort => true,
+ # :query => ["price:[* TO 200]",
+ # "price:[200 TO 500]",
+ # "price:[500 TO *]"],
+ # :fields => [:category, :manufacturer],
+ # :browse => ["category:Memory","manufacturer:Someone"]}
+ #
+ # scores:: If set to true this will return the score as a 'solr_score' attribute
+ # for each one of the instances found. Does not currently work with find_id_by_solr
+ #
+ # books = Book.find_by_solr 'ruby OR splinter', :scores => true
+ # books.records.first.solr_score
+ # => 1.21321397
+ # books.records.last.solr_score
+ # => 0.12321548
+ #
+ # lazy:: If set to true the search will return objects that will touch the database when you ask for one
+ # of their attributes for the first time. Useful when you're using fragment caching based solely on
+ # types and ids.
+ #
+ def find_by_solr(query, options={})
+ data = parse_query(query, options)
+ return parse_results(data, options) if data
+ end
+
+ # Finds instances of a model and returns an array with the ids:
+ # Book.find_id_by_solr "rails" => [1,4,7]
+ # The options accepted are the same as find_by_solr
+ #
+ def find_id_by_solr(query, options={})
+ data = parse_query(query, options)
+ return parse_results(data, {:format => :ids}) if data
+ end
+
+ # This method can be used to execute a search across multiple models:
+ # Book.multi_solr_search "Napoleon OR Tom", :models => [Movie]
+ #
+ # ====options:
+ # Accepts the same options as find_by_solr plus:
+ # models:: The additional models you'd like to include in the search
+ # results_format:: Specify the format of the results found
+ # :objects :: Will return an array with the results being objects (default). Example:
+ # Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :objects
+ # :ids :: Will return an array with the ids of each entry found. Example:
+ # Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :ids
+ # => [{"id" => "Movie:1"},{"id" => Book:1}]
+ # Where the value of each array is as Model:instance_id
+ #
+ def multi_solr_search(query, options = {})
+ models = "AND (#{solr_configuration[:type_field]}:#{self.name}"
+ options[:models].each{|m| models << " OR #{solr_configuration[:type_field]}:"+m.to_s} if options[:models].is_a?(Array)
+ options.update(:results_format => :objects) unless options[:results_format]
+ data = parse_query(query, options, models<<")")
+ result = []
+ if data.nil?
+ return SearchResults.new(:docs => [], :total => 0)
+ end
+
+ docs = data.hits
+ return SearchResults.new(:docs => [], :total => 0) if data.total_hits == 0
+ if options[:results_format] == :objects
+ docs.each{|doc|
+ k = doc.fetch('id').first.to_s.split(':')
+ result << k[0].constantize.find_by_id(k[1])
+ }
+ elsif options[:results_format] == :ids
+ docs.each{|doc| result << {"id"=>doc.values.pop.to_s}}
+ end
+
+ SearchResults.new :docs => result, :total => data.total_hits
+ end
+
+ # returns the total number of documents found in the query specified:
+ # Book.count_by_solr 'rails' => 3
+ #
+ def count_by_solr(query, options = {})
+ data = parse_query(query, options)
+ data.total_hits
+ end
+
+ # It's used to rebuild the Solr index for a specific model.
+ # Book.rebuild_solr_index
+ #
+ # If batch_size is greater than 0, adds will be done in batches.
+ # NOTE: If using sqlserver, be sure to use a finder with an explicit order.
+ # Non-edge versions of rails do not handle pagination correctly for sqlserver
+ # without an order clause.
+ #
+ # If a finder block is given, it will be called to retrieve the items to index.
+ # This can be very useful for things such as updating based on conditions or
+ # using eager loading for indexed associations.
+ def rebuild_solr_index(batch_size=0, &finder)
+ finder ||= lambda { |ar, options| ar.find(:all, options.merge({:order => self.primary_key})) }
+ start_time = Time.now
+
+ if batch_size > 0
+ items_processed = 0
+ limit = batch_size
+ offset = 0
+ begin
+ iteration_start = Time.now
+ items = finder.call(self, {:limit => limit, :offset => offset})
+ add_batch = items.collect { |content| content.to_solr_doc }
+
+ if items.size > 0
+ solr_add add_batch
+ solr_commit
+ end
+
+ items_processed += items.size
+ last_id = items.last.id if items.last
+ time_so_far = Time.now - start_time
+ iteration_time = Time.now - iteration_start
+ logger.info "#{Process.pid}: #{items_processed} items for #{self.name} have been batch added to index in #{'%.3f' % time_so_far}s at #{'%.3f' % (items_processed / time_so_far)} items/sec (#{'%.3f' % (items.size / iteration_time)} items/sec for the last batch). Last id: #{last_id}"
+ offset += items.size
+ end while items.nil? || items.size > 0
+ else
+ items = finder.call(self, {})
+ items.each { |content| content.solr_save }
+ items_processed = items.size
+ end
+ solr_optimize
+ logger.info items_processed > 0 ? "Index for #{self.name} has been rebuilt" : "Nothing to index for #{self.name}"
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/lib/common_methods.rb b/vendor/plugins/acts_as_solr/lib/common_methods.rb
new file mode 100644
index 00000000..1854a35e
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/lib/common_methods.rb
@@ -0,0 +1,89 @@
+module ActsAsSolr #:nodoc:
+
+ module CommonMethods
+
+ # Converts field types into Solr types
+ def get_solr_field_type(field_type)
+ if field_type.is_a?(Symbol)
+ case field_type
+ when :float
+ return "f"
+ when :integer
+ return "i"
+ when :boolean
+ return "b"
+ when :string
+ return "s"
+ when :date
+ return "d"
+ when :range_float
+ return "rf"
+ when :range_integer
+ return "ri"
+ when :facet
+ return "facet"
+ when :text
+ return "t"
+ else
+ raise "Unknown field_type symbol: #{field_type}"
+ end
+ elsif field_type.is_a?(String)
+ return field_type
+ else
+ raise "Unknown field_type class: #{field_type.class}: #{field_type}"
+ end
+ end
+
+ # Sets a default value when value being set is nil.
+ def set_value_if_nil(field_type)
+ case field_type
+ when "b", :boolean
+ return "false"
+ when "s", "t", "d", :date, :string, :text
+ return ""
+ when "f", "rf", :float, :range_float
+ return 0.00
+ when "i", "ri", :integer, :range_integer
+ return 0
+ else
+ return ""
+ end
+ end
+
+ # Sends an add command to Solr
+ def solr_add(add_xml)
+ ActsAsSolr::Post.execute(Solr::Request::AddDocument.new(add_xml))
+ end
+
+ # Sends the delete command to Solr
+ def solr_delete(solr_ids)
+ ActsAsSolr::Post.execute(Solr::Request::Delete.new(:id => solr_ids))
+ end
+
+ # Sends the commit command to Solr
+ def solr_commit
+ ActsAsSolr::Post.execute(Solr::Request::Commit.new)
+ end
+
+ # Optimizes the Solr index. Solr says:
+ #
+ # Optimizations can take nearly ten minutes to run.
+ # We are presuming optimizations should be run once following large
+ # batch-like updates to the collection and/or once a day.
+ #
+ # One of the solutions for this would be to create a cron job that
+ # runs every day at midnight and optmizes the index:
+ # 0 0 * * * /your_rails_dir/script/runner -e production "Model.solr_optimize"
+ #
+ def solr_optimize
+ ActsAsSolr::Post.execute(Solr::Request::Optimize.new)
+ end
+
+ # Returns the id for the given instance
+ def record_id(object)
+ eval "object.#{object.class.primary_key}"
+ end
+
+ end
+
+end
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/lib/deprecation.rb b/vendor/plugins/acts_as_solr/lib/deprecation.rb
new file mode 100644
index 00000000..94d2194c
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/lib/deprecation.rb
@@ -0,0 +1,61 @@
+module ActsAsSolr #:nodoc:
+
+ class Post
+ def initialize(body, mode = :search)
+ @body = body
+ @mode = mode
+ puts "The method ActsAsSolr::Post.new(body, mode).execute_post is depracated. " +
+ "Use ActsAsSolr::Post.execute(body, mode) instead!"
+ end
+
+ def execute_post
+ ActsAsSolr::Post.execute(@body, @mode)
+ end
+ end
+
+ module ClassMethods
+ def find_with_facet(query, options={})
+ Deprecation.plog "The method find_with_facet is deprecated. Use find_by_solr instead, passing the " +
+ "arguments the same way you used to do with find_with_facet."
+ find_by_solr(query, options)
+ end
+ end
+
+ class Deprecation
+ # Validates the options passed during query
+ def self.validate_query options={}
+ if options[:field_types]
+ plog "The option :field_types for searching is deprecated. " +
+ "The field types are automatically traced back when you specify a field type in your model."
+ end
+ if options[:sort_by]
+ plog "The option :sort_by is deprecated, use :order instead!"
+ options[:order] ||= options[:sort_by]
+ end
+ if options[:start]
+ plog "The option :start is deprecated, use :offset instead!"
+ options[:offset] ||= options[:start]
+ end
+ if options[:rows]
+ plog "The option :rows is deprecated, use :limit instead!"
+ options[:limit] ||= options[:rows]
+ end
+ end
+
+ # Validates the options passed during indexing
+ def self.validate_index options={}
+ if options[:background]
+ plog "The :background option is being deprecated. There are better and more efficient " +
+ "ways to handle delayed saving of your records."
+ end
+ end
+
+ # This will print the text to stdout and log the text
+ # if rails logger is available
+ def self.plog text
+ puts text
+ RAILS_DEFAULT_LOGGER.warn text if defined? RAILS_DEFAULT_LOGGER
+ end
+ end
+
+end
diff --git a/vendor/plugins/acts_as_solr/lib/instance_methods.rb b/vendor/plugins/acts_as_solr/lib/instance_methods.rb
new file mode 100644
index 00000000..3d1f84df
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/lib/instance_methods.rb
@@ -0,0 +1,148 @@
+module ActsAsSolr #:nodoc:
+
+ module InstanceMethods
+
+ # Solr id is : to be unique across all models
+ def solr_id
+ "#{self.class.name}:#{record_id(self)}"
+ end
+
+ # saves to the Solr index
+ def solr_save
+ return true if indexing_disabled?
+ if evaluate_condition(:if, self)
+ logger.debug "solr_save: #{self.class.name} : #{record_id(self)}"
+ solr_add to_solr_doc
+ solr_commit if configuration[:auto_commit]
+ true
+ else
+ solr_destroy
+ end
+ end
+
+ def indexing_disabled?
+ evaluate_condition(:offline, self) || !configuration[:if]
+ end
+
+ # remove from index
+ def solr_destroy
+ return true if indexing_disabled?
+ logger.debug "solr_destroy: #{self.class.name} : #{record_id(self)}"
+ solr_delete solr_id
+ solr_commit if configuration[:auto_commit]
+ true
+ end
+
+ # convert instance to Solr document
+ def to_solr_doc
+ logger.debug "to_solr_doc: creating doc for class: #{self.class.name}, id: #{record_id(self)}"
+ doc = Solr::Document.new
+ doc.boost = validate_boost(configuration[:boost]) if configuration[:boost]
+
+ doc << {:id => solr_id,
+ solr_configuration[:type_field] => self.class.name,
+ solr_configuration[:primary_key_field] => record_id(self).to_s}
+
+ # iterate through the fields and add them to the document,
+ configuration[:solr_fields].each do |field_name, options|
+ #field_type = configuration[:facets] && configuration[:facets].include?(field) ? :facet : :text
+
+ field_boost = options[:boost] || solr_configuration[:default_boost]
+ field_type = get_solr_field_type(options[:type])
+
+ value = self.send("#{field_name}_for_solr")
+ value = set_value_if_nil(field_type) if value.to_s == ""
+
+ # add the field to the document, but only if it's not the id field
+ # or the type field (from single table inheritance), since these
+ # fields have already been added above.
+ if field_name.to_s != self.class.primary_key and field_name.to_s != "type"
+ suffix = get_solr_field_type(field_type)
+ # This next line ensures that e.g. nil dates are excluded from the
+ # document, since they choke Solr. Also ignores e.g. empty strings,
+ # but these can't be searched for anyway:
+ # http://www.mail-archive.com/solr-dev@lucene.apache.org/msg05423.html
+ next if value.nil? || value.to_s.strip.empty?
+ [value].flatten.each do |v|
+ v = set_value_if_nil(suffix) if value.to_s == ""
+ field = Solr::Field.new("#{field_name}_#{suffix}" => ERB::Util.html_escape(v.to_s))
+ field.boost = validate_boost(field_boost)
+ doc << field
+ end
+ end
+ end
+
+ add_includes(doc) if configuration[:include]
+ doc
+ end
+
+ private
+ def add_includes(doc)
+ if configuration[:include].is_a?(Array)
+ configuration[:include].each do |association|
+ data = ""
+ klass = association.to_s.singularize
+ case self.class.reflect_on_association(association).macro
+ when :has_many, :has_and_belongs_to_many
+ records = self.send(association).to_a
+ unless records.empty?
+ records.each{|r| data << r.attributes.inject([]){|k,v| k << "#{v.first}=#{ERB::Util.html_escape(v.last)}"}.join(" ")}
+ doc["#{klass}_t"] = data
+ end
+ when :has_one, :belongs_to
+ record = self.send(association)
+ unless record.nil?
+ data = record.attributes.inject([]){|k,v| k << "#{v.first}=#{ERB::Util.html_escape(v.last)}"}.join(" ")
+ doc["#{klass}_t"] = data
+ end
+ end
+ end
+ end
+ end
+
+ def validate_boost(boost)
+ boost_value = case boost
+ when Float
+ return solr_configuration[:default_boost] if boost < 0
+ boost
+ when Proc
+ boost.call(self)
+ when Symbol
+ if self.respond_to?(boost)
+ self.send(boost)
+ end
+ end
+
+ boost_value || solr_configuration[:default_boost]
+ end
+
+ def condition_block?(condition)
+ condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)
+ end
+
+ def evaluate_condition(which_condition, field)
+ condition = configuration[which_condition]
+ case condition
+ when Symbol
+ field.send(condition)
+ when String
+ eval(condition, binding)
+ when FalseClass, NilClass
+ false
+ when TrueClass
+ true
+ else
+ if condition_block?(condition)
+ condition.call(field)
+ else
+ raise(
+ ArgumentError,
+ "The :#{which_condition} option has to be either a symbol, string (to be eval'ed), proc/method, true/false, or " +
+ "class implementing a static validation method"
+ )
+ end
+ end
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/lib/lazy_document.rb b/vendor/plugins/acts_as_solr/lib/lazy_document.rb
new file mode 100644
index 00000000..8cf89607
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/lib/lazy_document.rb
@@ -0,0 +1,18 @@
+module ActsAsSolr
+ class LazyDocument
+ attr_reader :id, :clazz
+
+ def initialize(id, clazz)
+ @id = id
+ @clazz = clazz
+ end
+
+ def method_missing(name, *args)
+ unless @__instance
+ @__instance = @clazz.find(@id)
+ end
+
+ @__instance.send(name, *args)
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/acts_as_solr/lib/parser_methods.rb b/vendor/plugins/acts_as_solr/lib/parser_methods.rb
new file mode 100644
index 00000000..3439866c
--- /dev/null
+++ b/vendor/plugins/acts_as_solr/lib/parser_methods.rb
@@ -0,0 +1,140 @@
+module ActsAsSolr #:nodoc:
+ module ParserMethods
+ protected
+
+ # Method used by mostly all the ClassMethods when doing a search
+ def parse_query(query=nil, options={}, models=nil)
+ valid_options = [:offset, :limit, :facets, :models, :results_format, :order, :scores, :operator, :include, :lazy]
+ query_options = {}
+
+ return nil if (query.nil? || query.strip == '')
+
+ raise "Invalid parameters: #{(options.keys - valid_options).join(',')}" unless (options.keys - valid_options).empty?
+ begin
+ Deprecation.validate_query(options)
+ query_options[:start] = options[:offset]
+ query_options[:rows] = options[:limit]
+ query_options[:operator] = options[:operator]
+
+ # first steps on the facet parameter processing
+ if options[:facets]
+ query_options[:facets] = {}
+ query_options[:facets][:limit] = -1 # TODO: make this configurable
+ query_options[:facets][:sort] = :count if options[:facets][:sort]
+ query_options[:facets][:mincount] = 0
+ query_options[:facets][:mincount] = 1 if options[:facets][:zeros] == false
+ query_options[:facets][:fields] = options[:facets][:fields].collect{|k| "#{k}_facet"} if options[:facets][:fields]
+ query_options[:filter_queries] = replace_types([*options[:facets][:browse]].collect{|k| "#{k.sub!(/ *: */,"_facet:")}"}) if options[:facets][:browse]
+ query_options[:facets][:queries] = replace_types(options[:facets][:query].collect{|k| "#{k.sub!(/ *: */,"_t:")}"}) if options[:facets][:query]
+ end
+
+ if models.nil?
+ # TODO: use a filter query for type, allowing Solr to cache it individually
+ models = "AND #{solr_type_condition}"
+ field_list = solr_configuration[:primary_key_field]
+ else
+ field_list = "id"
+ end
+
+ query_options[:field_list] = [field_list, 'score']
+ query = "(#{query.gsub(/ *: */,"_t:")}) #{models}"
+ order = options[:order].split(/\s*,\s*/).collect{|e| e.gsub(/\s+/,'_t ').gsub(/\bscore_t\b/, 'score') }.join(',') if options[:order]
+ query_options[:query] = replace_types([query])[0] # TODO adjust replace_types to work with String or Array
+
+ if options[:order]
+ # TODO: set the sort parameter instead of the old ;order. style.
+ query_options[:query] << ';' << replace_types([order], false)[0]
+ end
+
+ ActsAsSolr::Post.execute(Solr::Request::Standard.new(query_options))
+ rescue
+ raise "There was a problem executing your search: #{$!} in #{$!.backtrace.first}"
+ end
+ end
+
+ def solr_type_condition
+ subclasses.inject("(#{solr_configuration[:type_field]}:#{self.name}") do |condition, subclass|
+ condition << " OR #{solr_configuration[:type_field]}:#{subclass.name}"
+ end << ')'
+ end
+
+ # Parses the data returned from Solr
+ def parse_results(solr_data, options = {})
+ results = {
+ :docs => [],
+ :total => 0
+ }
+
+ configuration = {
+ :format => :objects
+ }
+ results.update(:facets => {'facet_fields' => []}) if options[:facets]
+ return SearchResults.new(results) if (solr_data.nil? || solr_data.total_hits == 0)
+
+ configuration.update(options) if options.is_a?(Hash)
+
+ ids = solr_data.hits.collect {|doc| doc["#{solr_configuration[:primary_key_field]}"]}.flatten
+
+ result = find_objects(ids, options, configuration)
+
+ add_scores(result, solr_data) if configuration[:format] == :objects && options[:scores]
+
+ results.update(:facets => solr_data.data['facet_counts']) if options[:facets]
+ results.update({:docs => result, :total => solr_data.total_hits, :max_score => solr_data.max_score, :query_time => solr_data.data['responseHeader']['QTime']})
+ SearchResults.new(results)
+ end
+
+
+ def find_objects(ids, options, configuration)
+ result = if configuration[:lazy] && configuration[:format] != :ids
+ ids.collect {|id| ActsAsSolr::LazyDocument.new(id, self)}
+ elsif configuration[:format] == :objects
+ conditions = [ "#{self.table_name}.#{primary_key} in (?)", ids ]
+ find_options = {:conditions => conditions}
+ find_options[:include] = options[:include] if options[:include]
+ result = reorder(self.find(:all, find_options), ids)
+ else
+ ids
+ end
+
+ result
+ end
+
+ # Reorders the instances keeping the order returned from Solr
+ def reorder(things, ids)
+ ordered_things = Array.new(things.size)
+ raise "Out of sync! Found #{ids.size} items in index, but only #{things.size} were found in database!" unless things.size == ids.size
+ things.each do |thing|
+ position = ids.index(thing.id)
+ ordered_things[position] = thing
+ end
+ ordered_things
+ end
+
+ # Replaces the field types based on the types (if any) specified
+ # on the acts_as_solr call
+ def replace_types(strings, include_colon=true)
+ suffix = include_colon ? ":" : ""
+ if configuration[:solr_fields]
+ configuration[:solr_fields].each do |name, options|
+ field = "#{name.to_s}_#{get_solr_field_type(options[:type])}#{suffix}"
+ strings.each_with_index {|s,i| strings[i] = s.gsub(/#{name.to_s}_t#{suffix}/,field) }
+ end
+ end
+ strings
+ end
+
+ # Adds the score to each one of the instances found
+ def add_scores(results, solr_data)
+ with_score = []
+ solr_data.hits.each do |doc|
+ with_score.push([doc["score"],
+ results.find {|record| record_id(record).to_s == doc["#{solr_configuration[:primary_key_field]}"].to_s }])
+ end
+ with_score.each do |score,object|
+ class <