Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 5b11b050a24828d128e4d22a644bb0e50cde3f3b @janxious janxious committed Dec 7, 2009
@@ -0,0 +1,20 @@
+Copyright (c) 2009 [name of plugin creator]
+
+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.
13 README
@@ -0,0 +1,13 @@
+ActsAsArchival
+==============
+
+Introduction goes here.
+
+
+Example
+=======
+
+Example goes here.
+
+
+Copyright (c) 2009 [name of plugin creator], released under the MIT license
@@ -0,0 +1,23 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the acts_as_archival plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.libs << 'test'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the acts_as_archival plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'ActsAsArchival'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
@@ -0,0 +1,3 @@
+# Include hook code here
+ActiveRecord::Base.send :include, ExpectedBehavior::ActsAsArchivalActiveRecordMethods
+ActiveRecord::Base.send :include, ExpectedBehavior::ActsAsArchival
@@ -0,0 +1 @@
+# Install hook code here
@@ -0,0 +1,114 @@
+module ExpectedBehavior
+ module ActsAsArchival
+ require 'digest/md5'
+
+ ARCHIVED_CONDITIONS = 'archived_at IS NOT NULL AND archive_number IS NOT NULL'
+ UNARCHIVED_CONDITIONS = { :archived_at => nil, :archive_number => nil }
+
+ MissingArchivalColumnError = Class.new(ActiveRecord::ActiveRecordError) unless defined?(MissingArchivalColumnError) == 'constant' && MissingArchivalColumnError.class == Class
+
+
+ def self.included(base)
+ base.extend ActMethods
+ end
+
+ module ActMethods
+ def acts_as_archival
+ unless included_modules.include? InstanceMethods
+ extend ClassMethods
+ include InstanceMethods
+
+ before_save :raise_if_not_archival
+
+ named_scope :archived, :conditions => ARCHIVED_CONDITIONS
+ named_scope :unarchived, :conditions => UNARCHIVED_CONDITIONS
+ named_scope :archived_from_archive_number, lambda { |head_archive_number| {:conditions => ['archived_at IS NOT NULL AND archive_number = ?', head_archive_number] } }
+
+ define_callbacks :before_archive, :after_archive
+ define_callbacks :before_unarchive, :after_unarchive
+ end
+ end
+ end
+
+ module InstanceMethods
+ def raise_if_not_archival
+ missing_columns = []
+ missing_columns << "archive_number" unless self.respond_to?(:archive_number)
+ missing_columns << "archived_at" unless self.respond_to?(:archived_at)
+ raise MissingArchivalColumnError.new("Add '#{missing_columns.join "', '"}' column(s) to '#{self.class.name}' to make it archival") unless missing_columns.blank?
+ end
+
+ def archived?
+ self.archived_at? && self.archive_number
+ end
+
+ def archive(head_archive_number=nil)
+ self.class.transaction do
+ begin
+ run_callbacks :before_archive
+ unless self.archived?
+ head_archive_number ||= Digest::MD5.hexdigest("#{self.class.name}#{self.id}")
+ self.update_attributes!({:archived_at => DateTime.now, :archive_number => head_archive_number})
+ self.archive_associations(head_archive_number)
+ end
+ run_callbacks :after_archive
+ rescue
+ raise ActiveRecord::Rollback
+ end
+ end
+ self
+ end
+
+ def unarchive(head_archive_number=nil)
+ self.class.transaction do
+ begin
+ run_callbacks :before_unarchive
+ if self.archived?
+ head_archive_number ||= self.archive_number
+ self.unarchive_associations(head_archive_number)
+ self.update_attributes!({:archived_at => nil, :archive_number => nil})
+ end
+ run_callbacks :after_unarchive
+ rescue
+ raise ActiveRecord::Rollback
+ end
+ end
+ self
+ end
+
+ def archive_associations(head_archive_number)
+ act_only_on_dependent_destroy_associations = Proc.new {|association| association.options[:dependent] == :destroy}
+ act_on_all_archival_associations(head_archive_number, :archive => true, :association_options => act_only_on_dependent_destroy_associations)
+ end
+
+ def unarchive_associations(head_archive_number)
+ act_on_all_archival_associations(head_archive_number, :unarchive => true)
+ end
+
+ # associations_options => lambda.new {|association| association.options[:dependent] == :destroy}
+ def act_on_all_archival_associations(head_archive_number, options={})
+ return if options.length == 0
+ options[:association_options] ||= Proc.new { true }
+ self.class.reflect_on_all_associations.each do |association|
+ if association.klass.is_archival? && association.macro.to_s =~ /^has/ && options[:association_options].call(association)
+ act_on_a_related_archival(association.klass, association.primary_key_name, id, head_archive_number, options)
+ end
+ end
+ end
+
+ def act_on_a_related_archival(klass, key_name, id, head_archive_number, options={})
+ # puts "[klass => #{klass.name}, key_name => #{key_name}, :id => #{id}, :head_archive_number => #{head_archive_number}, options => #{options.inspect}]"
+ return if options.length == 0 || (!options[:archive] && !options[:unarchive])
+ if options[:archive]
+ klass.unarchived.find(:all, :conditions => ["#{key_name} = ?", id]).each do |related_record|
+ related_record.archive(head_archive_number)
+ end
+ else
+ klass.archived.find(:all, :conditions => ["#{key_name} = ? AND archive_number = ?", id, head_archive_number]).each do |related_record|
+ related_record.unarchive(head_archive_number)
+ end
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,20 @@
+module ExpectedBehavior
+ module ActsAsArchivalActiveRecordMethods
+ def self.included(base)
+ base.extend ARClassMethods
+ base.send :include, ARInstanceMethods
+ end
+
+ module ARClassMethods
+ def is_archival?
+ self.included_modules.map(&:to_s).include?("ExpectedBehavior::ActsAsArchival::InstanceMethods")
+ end
+ end
+
+ module ARInstanceMethods
+ def is_archival?
+ self.class.is_archival?
+ end
+ end
+ end
+end
@@ -0,0 +1,4 @@
+# desc "Explaining what the task does"
+# task :acts_as_archival do
+# # Task goes here
+# end
Oops, something went wrong.

0 comments on commit 5b11b05

Please sign in to comment.