├── spec ├── spec.opts ├── fixtures │ ├── frameworks │ │ ├── rails2 │ │ │ ├── init.rb │ │ │ ├── database.yml │ │ │ ├── application_controller.rb │ │ │ └── routes.rb │ │ ├── rails3 │ │ │ ├── database.yml │ │ │ ├── Gemfile │ │ │ ├── application_controller.rb │ │ │ └── routes.rb │ │ └── sinatra │ │ │ └── application.rb │ ├── config │ │ ├── acts_as_archive.yml │ │ └── database.yml.example │ ├── models │ │ ├── has_many.rb │ │ ├── has_one.rb │ │ ├── belongs_to.rb │ │ ├── has_one_through.rb │ │ ├── has_one_through_through.rb │ │ ├── has_many_through_through.rb │ │ ├── has_many_through.rb │ │ └── record.rb │ ├── gemsets.yml │ ├── gemspec.yml │ ├── db │ │ └── migrate │ │ │ ├── 001_belongs_tos.rb │ │ │ ├── 003_has_ones.rb │ │ │ ├── 002_records.rb │ │ │ ├── 006_has_many_throughs.rb │ │ │ ├── 004_has_manies.rb │ │ │ ├── 008_has_one_throughs.rb │ │ │ ├── 007_has_one_through_throughs.rb │ │ │ └── 005_has_many_through_throughs.rb │ ├── frameworks.yml │ └── helpers │ │ └── spec_helper.rb ├── Rakefile ├── run ├── spec_helper.rb ├── acts_as_archive_spec.rb └── acts_as_archive │ └── gems_spec.rb ├── init.rb ├── lib ├── acts_as_archive │ ├── adapters │ │ ├── rails2.rb │ │ ├── rails3.rb │ │ └── sinatra.rb │ └── gems.rb └── acts_as_archive.rb ├── rails └── init.rb ├── bin └── acts_as_archive ├── .gitignore ├── config ├── gemsets.yml ├── externals.yml └── gemspec.yml ├── LICENSE ├── acts_as_archive.gemspec ├── Rakefile └── README.md /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --color -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/rails/init" -------------------------------------------------------------------------------- /lib/acts_as_archive/adapters/rails2.rb: -------------------------------------------------------------------------------- 1 | ActsAsArchive.load_from_yaml(Rails.root) -------------------------------------------------------------------------------- /spec/fixtures/frameworks/rails2/init.rb: -------------------------------------------------------------------------------- 1 | require "#{$root}/lib/acts_as_archive" -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../lib/acts_as_archive') -------------------------------------------------------------------------------- /bin/acts_as_archive: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | puts `script/runner "ActsAsArchive.update #{ARGV.join ', '}"` -------------------------------------------------------------------------------- /spec/fixtures/config/acts_as_archive.yml: -------------------------------------------------------------------------------- 1 | Record: 2 | - class: Record::Archive 3 | table: archived_records -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.gem 3 | coverage 4 | pkg 5 | spec/fixtures/config/database.yml 6 | spec/fixtures/builds 7 | spec/fixtures/log 8 | tmp 9 | vendor -------------------------------------------------------------------------------- /spec/fixtures/models/has_many.rb: -------------------------------------------------------------------------------- 1 | class HasMany < ActiveRecord::Base 2 | 3 | belongs_to :record, :dependent => :delete 4 | 5 | acts_as_archive 6 | end -------------------------------------------------------------------------------- /spec/fixtures/models/has_one.rb: -------------------------------------------------------------------------------- 1 | class HasOne < ActiveRecord::Base 2 | 3 | belongs_to :record, :dependent => :delete 4 | 5 | acts_as_archive 6 | end -------------------------------------------------------------------------------- /spec/fixtures/config/database.yml.example: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql 3 | database: acts_as_archive 4 | username: root 5 | password: 6 | host: localhost 7 | -------------------------------------------------------------------------------- /spec/fixtures/models/belongs_to.rb: -------------------------------------------------------------------------------- 1 | class BelongsTo < ActiveRecord::Base 2 | 3 | has_many :records, :dependent => :delete_all 4 | 5 | acts_as_archive 6 | end -------------------------------------------------------------------------------- /spec/fixtures/gemsets.yml: -------------------------------------------------------------------------------- 1 | name: 2 | rake: =0.8.7 3 | default: 4 | mysql: =2.8.1 5 | rspec: =1.3.1 6 | rspec2: 7 | mysql2: =0.2.6 8 | rspec: =2.3.0 9 | solo: null -------------------------------------------------------------------------------- /spec/fixtures/models/has_one_through.rb: -------------------------------------------------------------------------------- 1 | class HasOneThrough < ActiveRecord::Base 2 | 3 | belongs_to :has_one_through_through, :dependent => :delete 4 | 5 | acts_as_archive 6 | end -------------------------------------------------------------------------------- /spec/fixtures/models/has_one_through_through.rb: -------------------------------------------------------------------------------- 1 | class HasOneThroughThrough < ActiveRecord::Base 2 | 3 | belongs_to :record, :dependent => :delete 4 | has_one :has_one_through, :dependent => :delete 5 | 6 | acts_as_archive 7 | end -------------------------------------------------------------------------------- /spec/fixtures/models/has_many_through_through.rb: -------------------------------------------------------------------------------- 1 | class HasManyThroughThrough < ActiveRecord::Base 2 | 3 | belongs_to :record, :dependent => :delete 4 | belongs_to :has_many_through, :dependent => :delete 5 | 6 | acts_as_archive 7 | end -------------------------------------------------------------------------------- /config/gemsets.yml: -------------------------------------------------------------------------------- 1 | acts_as_archive: 2 | rake: ">=0.8.7" 3 | rspec: "~>1.0" 4 | default: 5 | active_wrapper-solo: "=0.4.4" 6 | also_migrate: "=0.3.5" 7 | externals: "=1.0.2" 8 | framework_fixture: "=0.1.3" 9 | mover: "=0.3.6" 10 | rack-test: "=0.5.6" -------------------------------------------------------------------------------- /lib/acts_as_archive/adapters/rails3.rb: -------------------------------------------------------------------------------- 1 | if Rails.root.nil? 2 | class ActsAsArchiveRailtie < Rails::Railtie 3 | initializer "acts_as_archive" do 4 | ActsAsArchive.load_from_yaml(Rails.root) 5 | end 6 | end 7 | else 8 | ActsAsArchive.load_from_yaml(Rails.root) 9 | end -------------------------------------------------------------------------------- /spec/fixtures/models/has_many_through.rb: -------------------------------------------------------------------------------- 1 | class HasManyThrough < ActiveRecord::Base 2 | 3 | has_many :has_many_through_throughs, :dependent => :delete_all 4 | has_many :records, :dependent => :delete_all, :through => :has_many_through_throughs 5 | 6 | acts_as_archive 7 | end -------------------------------------------------------------------------------- /lib/acts_as_archive/adapters/sinatra.rb: -------------------------------------------------------------------------------- 1 | class ActsAsArchive 2 | module Adapters 3 | module Sinatra 4 | 5 | def self.included(klass) 6 | if klass.root 7 | ActsAsArchive.load_from_yaml(klass.root) 8 | end 9 | end 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /spec/Rakefile: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../lib/acts_as_archive/gems') 2 | ActsAsArchive::Gems.activate 'active_wrapper-solo' 3 | 4 | require 'active_wrapper/tasks' 5 | 6 | ActiveWrapper::Tasks.new( 7 | :base => File.dirname(__FILE__) + "/fixtures", 8 | :env => ENV['ENV'] 9 | ) -------------------------------------------------------------------------------- /spec/fixtures/frameworks/rails2/database.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql 3 | database: acts_as_archive 4 | username: root 5 | password: 6 | host: localhost 7 | development: 8 | adapter: mysql 9 | database: acts_as_archive 10 | username: root 11 | password: 12 | host: localhost 13 | -------------------------------------------------------------------------------- /spec/fixtures/frameworks/rails3/database.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql2 3 | database: acts_as_archive 4 | username: root 5 | password: 6 | host: localhost 7 | development: 8 | adapter: mysql2 9 | database: acts_as_archive 10 | username: root 11 | password: 12 | host: localhost 13 | -------------------------------------------------------------------------------- /spec/fixtures/gemspec.yml: -------------------------------------------------------------------------------- 1 | name: name 2 | version: 0.1.0 3 | authors: 4 | - Author 5 | email: email@email.com 6 | homepage: http://github.com/author/name 7 | summary: Summary 8 | description: Description 9 | dependencies: 10 | - rake 11 | - default: 12 | - mysql 13 | - rspec2: 14 | - mysql2 15 | development_dependencies: null -------------------------------------------------------------------------------- /config/externals.yml: -------------------------------------------------------------------------------- 1 | active_wrapper-solo: 2 | repo: git@github.com:winton/active_wrapper.git 3 | path: vendor 4 | also_migrate: 5 | repo: git@github.com:winton/also_migrate.git 6 | path: vendor 7 | framework_fixture: 8 | repo: git@github.com:winton/framework_fixture.git 9 | path: vendor 10 | mover: 11 | repo: git@github.com:winton/mover.git 12 | path: vendor -------------------------------------------------------------------------------- /spec/fixtures/db/migrate/001_belongs_tos.rb: -------------------------------------------------------------------------------- 1 | class BelongsTos < ActiveRecord::Migration 2 | def self.up 3 | create_table :belongs_tos do |t| 4 | t.string :string, :default => 'string' 5 | t.integer :integer, :default => '1' 6 | t.datetime :restored_at 7 | t.timestamps 8 | end 9 | end 10 | 11 | def self.down 12 | drop_table :belongs_tos 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/fixtures/db/migrate/003_has_ones.rb: -------------------------------------------------------------------------------- 1 | class HasOnes < ActiveRecord::Migration 2 | def self.up 3 | create_table :has_ones do |t| 4 | t.string :string, :default => 'string' 5 | t.integer :integer, :default => '1' 6 | t.integer :record_id 7 | t.datetime :restored_at 8 | t.timestamps 9 | end 10 | end 11 | 12 | def self.down 13 | drop_table :has_ones 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/fixtures/db/migrate/002_records.rb: -------------------------------------------------------------------------------- 1 | class Records < ActiveRecord::Migration 2 | def self.up 3 | create_table :records do |t| 4 | t.string :string, :default => 'string' 5 | t.integer :integer, :default => '1' 6 | t.integer :belongs_to_id 7 | t.datetime :restored_at 8 | t.timestamps 9 | end 10 | end 11 | 12 | def self.down 13 | drop_table :records 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/fixtures/db/migrate/006_has_many_throughs.rb: -------------------------------------------------------------------------------- 1 | class HasManyThroughs < ActiveRecord::Migration 2 | def self.up 3 | create_table :has_many_throughs do |t| 4 | t.string :string, :default => 'string' 5 | t.integer :integer, :default => '1' 6 | t.datetime :restored_at 7 | t.timestamps 8 | end 9 | end 10 | 11 | def self.down 12 | drop_table :has_many_throughs 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/fixtures/db/migrate/004_has_manies.rb: -------------------------------------------------------------------------------- 1 | class HasManies < ActiveRecord::Migration 2 | def self.up 3 | create_table :has_manies do |t| 4 | t.string :string, :default => 'string' 5 | t.integer :integer, :default => '1' 6 | t.integer :record_id 7 | t.datetime :restored_at 8 | t.timestamps 9 | end 10 | end 11 | 12 | def self.down 13 | drop_table :has_manies 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/fixtures/db/migrate/008_has_one_throughs.rb: -------------------------------------------------------------------------------- 1 | class HasOneThroughs < ActiveRecord::Migration 2 | def self.up 3 | create_table :has_one_throughs do |t| 4 | t.string :string, :default => 'string' 5 | t.integer :integer, :default => '1' 6 | t.integer :has_one_through_through_id 7 | t.datetime :restored_at 8 | t.timestamps 9 | end 10 | end 11 | 12 | def self.down 13 | drop_table :has_one_throughs 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config/gemspec.yml: -------------------------------------------------------------------------------- 1 | name: acts_as_archive 2 | version: 0.4.1 3 | authors: 4 | - Winton Welsh 5 | email: mail@wintoni.us 6 | homepage: http://github.com/winton/acts_as_archive 7 | summary: Don't delete your records, move them to a different table 8 | description: Don't delete your records, move them to a different table. Like acts_as_paranoid, but doesn't mess with your SQL queries. 9 | dependencies: 10 | - also_migrate 11 | - mover 12 | development_dependencies: null -------------------------------------------------------------------------------- /spec/fixtures/db/migrate/007_has_one_through_throughs.rb: -------------------------------------------------------------------------------- 1 | class HasOneThroughThroughs < ActiveRecord::Migration 2 | def self.up 3 | create_table :has_one_through_throughs do |t| 4 | t.string :string, :default => 'string' 5 | t.integer :integer, :default => '1' 6 | t.integer :record_id 7 | t.datetime :restored_at 8 | t.timestamps 9 | end 10 | end 11 | 12 | def self.down 13 | drop_table :has_one_through_throughs 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/fixtures/db/migrate/005_has_many_through_throughs.rb: -------------------------------------------------------------------------------- 1 | class HasManyThroughThroughs < ActiveRecord::Migration 2 | def self.up 3 | create_table :has_many_through_throughs do |t| 4 | t.string :string, :default => 'string' 5 | t.integer :integer, :default => '1' 6 | t.integer :has_many_through_id 7 | t.integer :record_id 8 | t.datetime :restored_at 9 | t.timestamps 10 | end 11 | end 12 | 13 | def self.down 14 | drop_table :has_many_through_throughs 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | system "spec -f n -c spec" 4 | 5 | puts "\n" 6 | 7 | ENV['ACTIVERECORD'] = '2' 8 | system "spec -f n -c spec/acts_as_archive_spec.rb" 9 | 10 | ENV.delete 'ACTIVERECORD' 11 | 12 | puts "\n" 13 | 14 | ENV['RAILS'] = '2' 15 | system "spec -f n -c spec/acts_as_archive_spec.rb" 16 | 17 | puts "\n" 18 | 19 | ENV['RAILS'] = '3' 20 | system "spec -f n -c spec/acts_as_archive_spec.rb" 21 | 22 | ENV.delete 'RAILS' 23 | 24 | puts "\n" 25 | 26 | ENV['SINATRA'] = '1' 27 | system "spec -f n -c spec/acts_as_archive_spec.rb" -------------------------------------------------------------------------------- /spec/fixtures/models/record.rb: -------------------------------------------------------------------------------- 1 | class Record < ActiveRecord::Base 2 | 3 | belongs_to :belongs_to, :dependent => :delete 4 | 5 | has_one :has_one, :dependent => :destroy 6 | 7 | has_many :has_manies, :dependent => :delete_all 8 | 9 | has_many :has_many_through_throughs, :dependent => :destroy 10 | has_many :has_many_throughs, :dependent => :destroy, :through => :has_many_through_throughs 11 | 12 | has_one :has_one_through_through, :dependent => :destroy 13 | has_one :has_one_through, :dependent => :destroy, :through => :has_one_through_through 14 | end -------------------------------------------------------------------------------- /spec/fixtures/frameworks/rails3/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'active_wrapper-solo' 4 | gem 'mysql2' 5 | gem 'rails', '3.0.3' 6 | gem 'rspec' 7 | 8 | gem 'also_migrate', :path => "../../../../vendor/also_migrate" 9 | gem 'mover', :path => "../../../../vendor/mover" 10 | gem 'acts_as_archive', :path => "../../../../" 11 | 12 | # Bundle edge Rails instead: 13 | # gem 'rails', :git => 'git://github.com/rails/rails.git' 14 | 15 | 16 | # Use unicorn as the web server 17 | # gem 'unicorn' 18 | 19 | # Deploy with Capistrano 20 | # gem 'capistrano' 21 | 22 | # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) 23 | # gem 'ruby-debug' 24 | # gem 'ruby-debug19' 25 | 26 | # Bundle the extra gems: 27 | # gem 'bj' 28 | # gem 'nokogiri' 29 | # gem 'sqlite3-ruby', :require => 'sqlite3' 30 | # gem 'aws-s3', :require => 'aws/s3' 31 | 32 | # Bundle gems for the local environment. Make sure to 33 | # put test-only gems in this group so their generators 34 | # and rake tasks are available in development mode: 35 | # group :development, :test do 36 | # gem 'webrat' 37 | # end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | 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, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /spec/fixtures/frameworks.yml: -------------------------------------------------------------------------------- 1 | rails: 2 | <3: 3 | config: &c 4 | - config/acts_as_archive.yml 5 | frameworks/rails2: 6 | - app/controllers/application_controller.rb 7 | - config/database.yml 8 | - config/routes.rb 9 | - vendor/plugins/plugin/rails/init.rb 10 | helpers: &h 11 | - app/helpers/spec_helper.rb 12 | models: &m 13 | - app/models/belongs_to.rb 14 | - app/models/has_many.rb 15 | - app/models/has_many_through.rb 16 | - app/models/has_many_through_through.rb 17 | - app/models/has_one.rb 18 | - app/models/has_one_through.rb 19 | - app/models/has_one_through_through.rb 20 | - app/models/record.rb 21 | <4: 22 | config: *c 23 | frameworks/rails3: 24 | - app/controllers/application_controller.rb 25 | - config/database.yml 26 | - config/routes.rb 27 | - Gemfile 28 | helpers: *h 29 | models: *m 30 | sinatra: 31 | <1: 32 | config: *c 33 | frameworks/sinatra: &s 34 | - application.rb 35 | helpers: *h 36 | models: *m 37 | <2: 38 | config: *c 39 | frameworks/sinatra: *s 40 | helpers: *h 41 | models: *m -------------------------------------------------------------------------------- /acts_as_archive.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | root = File.expand_path('../', __FILE__) 3 | lib = "#{root}/lib" 4 | $:.unshift lib unless $:.include?(lib) 5 | 6 | require 'acts_as_archive/gems' 7 | ActsAsArchive::Gems.gemset ||= ENV['GEMSET'] || :default 8 | 9 | Gem::Specification.new do |s| 10 | ActsAsArchive::Gems.gemspec.hash.each do |key, value| 11 | if key == 'name' && ActsAsArchive::Gems.gemset != :default 12 | s.name = "#{value}-#{ActsAsArchive::Gems.gemset}" 13 | elsif key == 'summary' && ActsAsArchive::Gems.gemset == :solo 14 | s.summary = value + " (no dependencies)" 15 | elsif !%w(dependencies development_dependencies).include?(key) 16 | s.send "#{key}=", value 17 | end 18 | end 19 | 20 | ActsAsArchive::Gems.dependencies.each do |g| 21 | s.add_dependency g.to_s, ActsAsArchive::Gems.versions[g] 22 | end 23 | 24 | ActsAsArchive::Gems.development_dependencies.each do |g| 25 | s.add_development_dependency g.to_s, ActsAsArchive::Gems.versions[g] 26 | end 27 | 28 | s.executables = `cd #{root} && git ls-files -- {bin}/*`.split("\n").collect { |f| File.basename(f) } 29 | s.files = `cd #{root} && git ls-files`.split("\n") 30 | s.require_paths = %w(lib) 31 | s.test_files = `cd #{root} && git ls-files -- {features,test,spec}/*`.split("\n") 32 | end -------------------------------------------------------------------------------- /spec/fixtures/frameworks/rails3/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | include SpecHelper 5 | 6 | def pulse 7 | render :text => '1' 8 | end 9 | 10 | def should_have_valid_schema_action 11 | before_each false, true 12 | should_have_valid_schema 13 | render :text => '1' 14 | end 15 | 16 | def should_create_records_action 17 | before_each false, true 18 | should_create_records 19 | render :text => '1' 20 | end 21 | 22 | def should_migrate_record_and_preserve_deleted_at_action 23 | before_each false, true 24 | should_migrate_record_and_preserve_deleted_at 25 | render :text => '1' 26 | end 27 | 28 | def should_emulate_delete_all_action 29 | before_each false, true 30 | should_emulate_delete_all 31 | render :text => '1' 32 | end 33 | 34 | def should_move_records_back_to_original_tables_action 35 | before_each false, true 36 | should_move_records_back_to_original_tables(params[:type]) 37 | render :text => '1' 38 | end 39 | 40 | def should_move_records_to_archive_tables_action 41 | before_each false, true 42 | should_move_records_to_archive_tables(params[:type]) 43 | render :text => '1' 44 | end 45 | 46 | def should_delete_records_without_archiving_action 47 | before_each false, true 48 | should_delete_records_without_archiving(params[:type]) 49 | render :text => '1' 50 | end 51 | end -------------------------------------------------------------------------------- /spec/fixtures/frameworks/sinatra/application.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require "#{$root}/lib/acts_as_archive" 3 | 4 | class Application < Sinatra::Base 5 | 6 | set :app_file, __FILE__ 7 | set :dump_errors, true 8 | set :raise_errors, true 9 | set :show_exceptions, false 10 | 11 | include ActsAsArchive::Adapters::Sinatra 12 | include SpecHelper 13 | 14 | get '/pulse' do 15 | '1' 16 | end 17 | 18 | get "/should_have_valid_schema_action" do 19 | before_each false, true 20 | should_have_valid_schema 21 | '1' 22 | end 23 | 24 | get "/should_create_records_action" do 25 | before_each false, true 26 | should_create_records 27 | '1' 28 | end 29 | 30 | get "/should_migrate_record_and_preserve_deleted_at_action" do 31 | before_each false, true 32 | should_migrate_record_and_preserve_deleted_at 33 | '1' 34 | end 35 | 36 | get "/should_emulate_delete_all_action" do 37 | before_each false, true 38 | should_emulate_delete_all 39 | '1' 40 | end 41 | 42 | get "/should_move_records_back_to_original_tables_action" do 43 | before_each false, true 44 | should_move_records_back_to_original_tables(params[:type]) 45 | '1' 46 | end 47 | 48 | get "/should_move_records_to_archive_tables_action" do 49 | before_each false, true 50 | should_move_records_to_archive_tables(params[:type]) 51 | '1' 52 | end 53 | 54 | get "/should_delete_records_without_archiving_action" do 55 | before_each false, true 56 | should_delete_records_without_archiving(params[:type]) 57 | '1' 58 | end 59 | end -------------------------------------------------------------------------------- /spec/fixtures/frameworks/rails2/application_controller.rb: -------------------------------------------------------------------------------- 1 | # Filters added to this controller apply to all controllers in the application. 2 | # Likewise, all the methods added will be available for all controllers. 3 | 4 | class ApplicationController < ActionController::Base 5 | helper :all # include all helpers, all the time 6 | protect_from_forgery # See ActionController::RequestForgeryProtection for details 7 | 8 | # Scrub sensitive parameters from your log 9 | # filter_parameter_logging :password 10 | 11 | include SpecHelper 12 | 13 | def pulse 14 | render :text => '1' 15 | end 16 | 17 | def should_have_valid_schema_action 18 | before_each false, true 19 | should_have_valid_schema 20 | render :text => '1' 21 | end 22 | 23 | def should_create_records_action 24 | before_each false, true 25 | should_create_records 26 | render :text => '1' 27 | end 28 | 29 | def should_migrate_record_and_preserve_deleted_at_action 30 | before_each false, true 31 | should_migrate_record_and_preserve_deleted_at 32 | render :text => '1' 33 | end 34 | 35 | def should_emulate_delete_all_action 36 | before_each false, true 37 | should_emulate_delete_all 38 | render :text => '1' 39 | end 40 | 41 | def should_move_records_back_to_original_tables_action 42 | before_each false, true 43 | should_move_records_back_to_original_tables(params[:type]) 44 | render :text => '1' 45 | end 46 | 47 | def should_move_records_to_archive_tables_action 48 | before_each false, true 49 | should_move_records_to_archive_tables(params[:type]) 50 | render :text => '1' 51 | end 52 | 53 | def should_delete_records_without_archiving_action 54 | before_each false, true 55 | should_delete_records_without_archiving(params[:type]) 56 | render :text => '1' 57 | end 58 | end -------------------------------------------------------------------------------- /spec/fixtures/frameworks/rails3/routes.rb: -------------------------------------------------------------------------------- 1 | Rails3::Application.routes.draw do 2 | # The priority is based upon order of creation: 3 | # first created -> highest priority. 4 | 5 | # Sample of regular route: 6 | # match 'products/:id' => 'catalog#view' 7 | # Keep in mind you can assign values other than :controller and :action 8 | 9 | # Sample of named route: 10 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 11 | # This route can be invoked with purchase_url(:id => product.id) 12 | 13 | # Sample resource route (maps HTTP verbs to controller actions automatically): 14 | # resources :products 15 | 16 | # Sample resource route with options: 17 | # resources :products do 18 | # member do 19 | # get 'short' 20 | # post 'toggle' 21 | # end 22 | # 23 | # collection do 24 | # get 'sold' 25 | # end 26 | # end 27 | 28 | # Sample resource route with sub-resources: 29 | # resources :products do 30 | # resources :comments, :sales 31 | # resource :seller 32 | # end 33 | 34 | # Sample resource route with more complex sub-resources 35 | # resources :products do 36 | # resources :comments 37 | # resources :sales do 38 | # get 'recent', :on => :collection 39 | # end 40 | # end 41 | 42 | # Sample resource route within a namespace: 43 | # namespace :admin do 44 | # # Directs /admin/products/* to Admin::ProductsController 45 | # # (app/controllers/admin/products_controller.rb) 46 | # resources :products 47 | # end 48 | 49 | # You can have the root of your site routed with "root" 50 | # just remember to delete public/index.html. 51 | # root :to => "welcome#index" 52 | 53 | # See how all your routes lay out with "rake routes" 54 | 55 | # This is a legacy wild controller route that's not recommended for RESTful applications. 56 | # Note: This route will make all actions in every controller accessible via GET requests. 57 | # match ':controller(/:action(/:id(.:format)))' 58 | 59 | match ':action', :controller => 'application' 60 | end -------------------------------------------------------------------------------- /spec/fixtures/frameworks/rails2/routes.rb: -------------------------------------------------------------------------------- 1 | ActionController::Routing::Routes.draw do |map| 2 | # The priority is based upon order of creation: first created -> highest priority. 3 | 4 | # Sample of regular route: 5 | # map.connect 'products/:id', :controller => 'catalog', :action => 'view' 6 | # Keep in mind you can assign values other than :controller and :action 7 | 8 | # Sample of named route: 9 | # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' 10 | # This route can be invoked with purchase_url(:id => product.id) 11 | 12 | # Sample resource route (maps HTTP verbs to controller actions automatically): 13 | # map.resources :products 14 | 15 | # Sample resource route with options: 16 | # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } 17 | 18 | # Sample resource route with sub-resources: 19 | # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller 20 | 21 | # Sample resource route with more complex sub-resources 22 | # map.resources :products do |products| 23 | # products.resources :comments 24 | # products.resources :sales, :collection => { :recent => :get } 25 | # end 26 | 27 | # Sample resource route within a namespace: 28 | # map.namespace :admin do |admin| 29 | # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) 30 | # admin.resources :products 31 | # end 32 | 33 | # You can have the root of your site routed with map.root -- just remember to delete public/index.html. 34 | # map.root :controller => "welcome" 35 | 36 | # See how all your routes lay out with "rake routes" 37 | 38 | # Install the default routes as the lowest priority. 39 | # Note: These default routes make all actions in every controller accessible via GET requests. You should 40 | # consider removing or commenting them out if you're using named routes and resources. 41 | 42 | # map.connect ':controller/:action/:id' 43 | # map.connect ':controller/:action/:id.:format' 44 | 45 | map.connect ':action', :controller => 'application' 46 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | 3 | $root = File.expand_path('../../', __FILE__) 4 | require "#{$root}/lib/acts_as_archive/gems" 5 | 6 | ActsAsArchive::Gems.activate :framework_fixture 7 | require 'framework_fixture' 8 | 9 | if FrameworkFixture.framework == 'rails' 10 | ENV['RAILS_ENV'] = 'test' 11 | FrameworkFixture.generate File.dirname(__FILE__) + '/fixtures' 12 | end 13 | 14 | ActsAsArchive::Gems.activate %w(active_wrapper-solo rack-test rspec) 15 | require 'active_wrapper/gems' 16 | 17 | # Framework specs 18 | if FrameworkFixture.framework 19 | require 'rack/test' 20 | 21 | if FrameworkFixture.rails == '<3' 22 | ActiveWrapper::Gems.gemset = :ar2 23 | elsif FrameworkFixture.sinatra 24 | ActiveWrapper::Gems.activate %w(activesupport) 25 | require 'active_support/dependencies' 26 | ActiveSupport::Dependencies.autoload_paths << "#{$root}/spec/fixtures/builds/sinatra#{ENV['SINATRA']}/app/models" 27 | ActiveSupport::Dependencies.autoload_paths << "#{$root}/spec/fixtures/builds/sinatra#{ENV['SINATRA']}/app/helpers" 28 | end 29 | 30 | require 'active_wrapper' 31 | 32 | # Normal specs 33 | else 34 | if ENV['ACTIVERECORD'] == '2' 35 | ActiveWrapper::Gems.gemset = :ar2 36 | else 37 | ActiveWrapper::Gems.activate %w(activesupport) 38 | require 'active_support/dependencies' 39 | end 40 | 41 | require 'active_wrapper' 42 | 43 | require "#{$root}/lib/acts_as_archive" 44 | 45 | ActiveSupport::Dependencies.autoload_paths << "#{$root}/spec/fixtures/models" 46 | ActiveSupport::Dependencies.autoload_paths << "#{$root}/spec/fixtures/helpers" 47 | 48 | include SpecHelper 49 | end 50 | 51 | $db, $log, $mail = ActiveWrapper.setup( 52 | :adapter => ActiveWrapper::Gems.gemset == :ar2 ? 'mysql' : 'mysql2', 53 | :base => "#{$root}/spec/fixtures", 54 | :env => 'test' 55 | ) 56 | $db.establish_connection 57 | 58 | unless FrameworkFixture.framework 59 | ActsAsArchive.load_from_yaml("#{$root}/spec/fixtures") 60 | end 61 | 62 | if FrameworkFixture.framework == 'sinatra' 63 | FrameworkFixture.generate File.dirname(__FILE__) + '/fixtures' 64 | end 65 | 66 | Spec::Runner.configure do |config| 67 | end 68 | 69 | def before_each(migrate=true, setup=true) 70 | if migrate 71 | [ 8, 0, 8 ].each { |v| $db.migrate(v) } 72 | Record.reset_column_information 73 | end 74 | if setup 75 | @record, @lengths, @zero_lengths = setup_records 76 | end 77 | end -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/lib/acts_as_archive/gems' 2 | 3 | ActsAsArchive::Gems.activate %w(rake rspec) 4 | 5 | require 'rake' 6 | require 'spec/rake/spectask' 7 | 8 | def gemspec 9 | @gemspec ||= begin 10 | file = File.expand_path('../acts_as_archive.gemspec', __FILE__) 11 | eval(File.read(file), binding, file) 12 | end 13 | end 14 | 15 | if defined?(Spec::Rake::SpecTask) 16 | desc "Run specs" 17 | Spec::Rake::SpecTask.new do |t| 18 | t.spec_files = FileList['spec/**/*_spec.rb'] 19 | t.spec_opts = %w(-fs --color) 20 | t.warning = true 21 | end 22 | task :spec 23 | task :default => :spec 24 | end 25 | 26 | desc "Build gem(s)" 27 | task :gem do 28 | old_gemset = ENV['GEMSET'] 29 | root = File.expand_path('../', __FILE__) 30 | pkg = "#{root}/pkg" 31 | system "rm -Rf #{pkg}" 32 | ActsAsArchive::Gems.gemset_names.each do |gemset| 33 | ENV['GEMSET'] = gemset.to_s 34 | system "cd #{root} && gem build acts_as_archive.gemspec" 35 | system "mkdir -p #{pkg} && mv *.gem pkg" 36 | end 37 | ENV['GEMSET'] = old_gemset 38 | end 39 | 40 | namespace :gem do 41 | desc "Install gem(s)" 42 | task :install do 43 | Rake::Task['gem'].invoke 44 | Dir["#{File.dirname(__FILE__)}/pkg/*.gem"].each do |pkg| 45 | system "gem install #{pkg} --no-ri --no-rdoc" 46 | end 47 | end 48 | 49 | desc "Push gem(s)" 50 | task :push do 51 | Rake::Task['gem'].invoke 52 | Dir["#{File.dirname(__FILE__)}/pkg/*.gem"].each do |pkg| 53 | system "gem push #{pkg}" 54 | end 55 | end 56 | end 57 | 58 | namespace :gems do 59 | desc "Install gem dependencies (DEV=0 DOCS=0 GEMSPEC=default SUDO=0)" 60 | task :install do 61 | dev = ENV['DEV'] == '1' 62 | docs = ENV['DOCS'] == '1' ? '' : '--no-ri --no-rdoc' 63 | gemset = ENV['GEMSET'] 64 | sudo = ENV['SUDO'] == '1' ? 'sudo' : '' 65 | 66 | ActsAsArchive::Gems.gemset = gemset if gemset 67 | 68 | if dev 69 | gems = ActsAsArchive::Gems.gemspec.development_dependencies 70 | else 71 | gems = ActsAsArchive::Gems.gemspec.dependencies 72 | end 73 | 74 | gems.each do |name| 75 | name = name.to_s 76 | version = ActsAsArchive::Gems.versions[name] 77 | if Gem.source_index.find_name(name, version).empty? 78 | version = version ? "-v #{version}" : '' 79 | system "#{sudo} gem install #{name} #{version} #{docs}" 80 | else 81 | puts "already installed: #{name} #{version}" 82 | end 83 | end 84 | end 85 | end 86 | 87 | desc "Validate the gemspec" 88 | task :gemspec do 89 | gemspec.validate 90 | end -------------------------------------------------------------------------------- /spec/acts_as_archive_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | unless FrameworkFixture.framework 4 | describe ActsAsArchive do 5 | 6 | before(:each) do 7 | before_each 8 | end 9 | 10 | it "should have valid schema" do 11 | should_have_valid_schema 12 | end 13 | 14 | it "should create records" do 15 | should_create_records 16 | end 17 | 18 | describe :migrate_from_acts_as_paranoid do 19 | it "should migrate record and preserve deleted_at" do 20 | should_migrate_record_and_preserve_deleted_at 21 | end 22 | end 23 | 24 | describe :restore_all do 25 | it "should emulate delete_all" do 26 | should_emulate_delete_all 27 | end 28 | end 29 | 30 | %w(delete delete_all destroy destroy_all).each do |type| 31 | describe type do 32 | it "should move records to archive tables" do 33 | should_move_records_to_archive_tables(type) 34 | end 35 | 36 | it "should move records back to original tables" do 37 | should_move_records_back_to_original_tables(type) 38 | end 39 | end 40 | 41 | describe "#{type}!" do 42 | it "should delete records without archiving" do 43 | should_delete_records_without_archiving(type) 44 | end 45 | end 46 | end 47 | end 48 | end 49 | 50 | if FrameworkFixture.framework 51 | describe "#{FrameworkFixture.framework} #{FrameworkFixture.exact_version}" do 52 | 53 | include Rack::Test::Methods 54 | 55 | def app 56 | FrameworkFixture.app.call 57 | end 58 | 59 | before(:each) do 60 | before_each true, false 61 | end 62 | 63 | it "should have a pulse" do 64 | get "/pulse" 65 | last_response.body.should == '1' 66 | end 67 | 68 | it "should have valid schema" do 69 | get "/should_have_valid_schema_action" 70 | last_response.body.should == '1' 71 | end 72 | 73 | it "should create records" do 74 | get "/should_create_records_action" 75 | last_response.body.should == '1' 76 | end 77 | 78 | describe :migrate_from_acts_as_paranoid do 79 | it "should migrate record and preserve deleted_at" do 80 | get "/should_migrate_record_and_preserve_deleted_at_action" 81 | last_response.body.should == '1' 82 | end 83 | end 84 | 85 | describe :restore_all do 86 | it "should emulate delete_all" do 87 | get "/should_emulate_delete_all_action" 88 | last_response.body.should == '1' 89 | end 90 | end 91 | 92 | %w(delete delete_all destroy destroy_all).each do |type| 93 | describe type do 94 | it "should move records to archive tables" do 95 | get "/should_move_records_to_archive_tables_action", :type => type 96 | last_response.body.should == '1' 97 | end 98 | 99 | it "should move records back to original tables" do 100 | get "/should_move_records_back_to_original_tables_action", :type => type 101 | last_response.body.should == '1' 102 | end 103 | end 104 | 105 | describe "#{type}!" do 106 | it "should delete records without archiving" do 107 | get "/should_delete_records_without_archiving_action", :type => type 108 | last_response.body.should == '1' 109 | end 110 | end 111 | end 112 | end 113 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ActsAsArchive 2 | ============= 3 | 4 | **This project is no longer maintained. If you would like to become the new owner, please email [Winton Welsh](mailto:mail@wintoni.us).** 5 | 6 | Don't delete your records, move them to a different table. 7 | 8 | Like acts\_as\_paranoid, but doesn't mess with your SQL queries. 9 | 10 | Install 11 | ------- 12 | 13 |
 14 | gem install acts_as_archive
 15 | 
16 | 17 | ### Rails 2 18 | 19 | #### config/environment.rb 20 | 21 |
 22 | config.gem 'acts_as_archive'
 23 | 
24 | 25 | ### Rails 3 26 | 27 | #### Gemfile 28 | 29 |
 30 | gem 'acts_as_archive'
 31 | 
32 | 33 | ### Sinatra 34 | 35 |
 36 | require 'acts_as_archive'
 37 | 
 38 | class Application < Sinatra::Base
 39 |   include ActsAsArchive::Adapters::Sinatra
 40 | end
 41 | 
42 | 43 | config/acts\_as\_archive.yml 44 | ---------------------------- 45 | 46 | Create config/acts\_as\_archive.yml to define the archive class and archive table for each of your models: 47 | 48 |
 49 | Article:
 50 |   - class: Article::Archive
 51 |     table: archived_articles
 52 | 
53 | 54 | It is expected that neither the archive class or archive table exist yet. ActsAsArchive will create these automatically. 55 | 56 | Migrate 57 | ------- 58 | 59 | Run rake db:migrate. Your archive table is created automatically. 60 | 61 | That's it! 62 | ---------- 63 | 64 | Use destroy, destroy\_all, delete, and delete_all like you normally would. 65 | 66 | Records move into the archive table instead of being destroyed. 67 | 68 | Automatically archive relationships 69 | ----------------------------------- 70 | 71 | If your model's relationship has the :dependent option, and the relationship also uses acts\_as\_archive, that relationship will archive automatically. 72 | 73 | What if my schema changes? 74 | -------------------------- 75 | 76 | New migrations are automatically applied to the archive table. 77 | 78 | No action is necessary on your part. 79 | 80 | Query the archive 81 | ----------------- 82 | 83 | Use the archive class you specified in the configuration: 84 | 85 |
 86 | Article::Archive.first
 87 | 
88 | 89 | Delete records without archiving 90 | -------------------------------- 91 | 92 | Use any of the destroy methods, but add a bang (!): 93 | 94 |
 95 | Article::Archive.first.destroy!
 96 | Article.delete_all!([ "id in (?)", [ 1, 2, 3 ] ])
 97 | 
98 | 99 | Restore from the archive 100 | ------------------------ 101 | 102 | Use any of the destroy/delete methods on the archived record to move it back to its original table: 103 | 104 |
105 | Article::Archive.first.destroy
106 | Article::Archive.delete_all([ "id in (?)", [ 1, 2, 3 ] ])
107 | 
108 | 109 | Any relationships that were automatically archived will be restored as well. 110 | 111 | Magic columns 112 | ------------- 113 | 114 | You will find an extra deleted_at datetime column on the archive table. 115 | 116 | You may manually add a restored_at datetime column to the origin table if you wish to store restoration time as well. 117 | 118 | Migrate from acts\_as\_paranoid 119 | ------------------------------- 120 | 121 | Add this line to a migration, or run it via script/console: 122 | 123 |
124 | Article.migrate_from_acts_as_paranoid
125 | 
126 | 127 | This copies all records with non-null deleted_at values to the archive. 128 | 129 | Running specs 130 | ------------- 131 | 132 | There is a [wiki entry](https://github.com/winton/acts_as_archive/wiki/Running-Specs) that describes the development setup in-depth. 133 | -------------------------------------------------------------------------------- /lib/acts_as_archive/gems.rb: -------------------------------------------------------------------------------- 1 | unless defined?(ActsAsArchive::Gems) 2 | 3 | require 'yaml' 4 | 5 | class ActsAsArchive 6 | module Gems 7 | class < "#{File.expand_path('../../../', __FILE__)}/config/gemspec.yml", 27 | :warn => true 28 | ) 29 | 30 | def activate(*gems) 31 | begin 32 | require 'rubygems' unless defined?(::Gem) 33 | rescue LoadError 34 | puts "rubygems library could not be required" if @config.warn 35 | end 36 | 37 | self.gemset ||= gemset_from_loaded_specs 38 | 39 | gems.flatten.collect(&:to_sym).each do |name| 40 | version = @versions[name] 41 | vendor = File.expand_path("../../../vendor/#{name}/lib", __FILE__) 42 | if File.exists?(vendor) 43 | $:.unshift vendor 44 | elsif defined?(gem) 45 | gem name.to_s, version 46 | else 47 | puts "#{name} #{"(#{version})" if version} failed to activate" if @config.warn 48 | end 49 | end 50 | end 51 | 52 | def dependencies 53 | dependency_filter(@gemspec.dependencies, @gemset) 54 | end 55 | 56 | def development_dependencies 57 | dependency_filter(@gemspec.development_dependencies, @gemset) 58 | end 59 | 60 | def gemset=(gemset) 61 | if gemset 62 | @gemset = gemset.to_sym 63 | 64 | @gemsets = @config.gemsets.reverse.collect { |config| 65 | if config.is_a?(::String) 66 | YAML::load(File.read(config)) rescue {} 67 | elsif config.is_a?(::Hash) 68 | config 69 | end 70 | }.inject({}) do |hash, config| 71 | deep_merge(hash, symbolize_keys(config)) 72 | end 73 | 74 | @versions = (@gemsets[gemspec.name.to_sym] || {}).inject({}) do |hash, (key, value)| 75 | if !value.is_a?(::Hash) && value 76 | hash[key] = value 77 | elsif key == @gemset 78 | (value || {}).each { |k, v| hash[k] = v } 79 | end 80 | hash 81 | end 82 | else 83 | @gemset = nil 84 | @gemsets = nil 85 | @versions = nil 86 | end 87 | end 88 | 89 | def gemset_names 90 | ( 91 | [ :default ] + 92 | @gemsets[gemspec.name.to_sym].inject([]) { |array, (key, value)| 93 | array.push(key) if value.is_a?(::Hash) || value.nil? 94 | array 95 | } 96 | ).uniq 97 | end 98 | 99 | def gemspec(reload=false) 100 | if @gemspec && !reload 101 | @gemspec 102 | else 103 | data = YAML::load(File.read(@config.gemspec)) rescue {} 104 | @gemspec = SimpleStruct.new(data) 105 | end 106 | end 107 | 108 | private 109 | 110 | def deep_merge(first, second) 111 | merger = lambda do |key, v1, v2| 112 | Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 113 | end 114 | first.merge(second, &merger) 115 | end 116 | 117 | def dependency_filter(dependencies, match) 118 | (dependencies || []).inject([]) { |array, value| 119 | if value.is_a?(::Hash) 120 | array += value[match.to_s] if value[match.to_s] 121 | else 122 | array << value 123 | end 124 | array 125 | }.uniq.collect(&:to_sym) 126 | end 127 | 128 | def gemset_from_loaded_specs 129 | if defined?(Gem) 130 | Gem.loaded_specs.each do |name, spec| 131 | if name == gemspec.name 132 | return :default 133 | elsif name[0..gemspec.name.length] == "#{gemspec.name}-" 134 | return name[gemspec.name.length+1..-1].to_sym 135 | end 136 | end 137 | :default 138 | else 139 | :none 140 | end 141 | end 142 | 143 | def symbolize_keys(hash) 144 | return {} unless hash.is_a?(::Hash) 145 | hash.inject({}) do |options, (key, value)| 146 | value = symbolize_keys(value) if value.is_a?(::Hash) 147 | options[(key.to_sym rescue key) || key] = value 148 | options 149 | end 150 | end 151 | end 152 | end 153 | end 154 | end -------------------------------------------------------------------------------- /spec/fixtures/helpers/spec_helper.rb: -------------------------------------------------------------------------------- 1 | module SpecHelper 2 | 3 | def all_records 4 | [ 5 | { 6 | :record => Record.all, 7 | :belongs_to => BelongsTo.all, 8 | :has_one => HasOne.all, 9 | :has_many => HasMany.all, 10 | :has_many_through => HasManyThrough.all, 11 | :has_many_through_through => HasManyThroughThrough.all, 12 | :has_one_through => HasOneThrough.all, 13 | :has_one_through_through => HasOneThroughThrough.all 14 | }, 15 | { 16 | :record => Record::Archive.all, 17 | :belongs_to => BelongsTo::Archive.all, 18 | :has_one => HasOne::Archive.all, 19 | :has_many => HasMany::Archive.all, 20 | :has_many_through => HasManyThrough::Archive.all, 21 | :has_many_through_through => HasManyThroughThrough::Archive.all, 22 | :has_one_through => HasOneThrough::Archive.all, 23 | :has_one_through_through => HasOneThroughThrough::Archive.all 24 | } 25 | ] 26 | end 27 | 28 | def before_each(migrate=true, setup=true) 29 | if migrate 30 | [ 8, 0, 8 ].each { |v| $db.migrate(v) } 31 | Record.reset_column_information 32 | end 33 | if setup 34 | @record, @lengths, @zero_lengths = setup_records 35 | end 36 | end 37 | 38 | def setup_records 39 | record = Record.create :belongs_to_id => BelongsTo.create.id 40 | 41 | HasOne.create :record_id => record.id 42 | 43 | HasMany.create :record_id => record.id 44 | HasMany.create :record_id => record.id 45 | 46 | Record.first.has_many_throughs.create 47 | Record.first.has_many_throughs.create 48 | 49 | Record.first.create_has_one_through_through.create_has_one_through 50 | 51 | lengths = { 52 | :record => 1, 53 | :belongs_to => 1, 54 | :has_one => 1, 55 | :has_many => 2, 56 | :has_many_through => 2, 57 | :has_many_through_through => 2, 58 | :has_one_through => 1, 59 | :has_one_through_through => 1 60 | } 61 | 62 | zero_lengths = lengths.inject({}) do |hash, (key, value)| 63 | hash[key] = 0 64 | hash 65 | end 66 | 67 | [ record, lengths, zero_lengths ] 68 | end 69 | 70 | def should_create_records 71 | original, archive = all_records 72 | verify_lengths original, @lengths 73 | verify_attributes original 74 | end 75 | 76 | def should_delete_records_without_archiving(type) 77 | type = "#{type}!" 78 | case type 79 | when 'delete!', 'destroy!' 80 | @record.send type 81 | when 'delete_all!', 'destroy_all!' 82 | Record.send type 83 | end 84 | 85 | Record.count.should == 0 86 | Record::Archive.count.should == 0 87 | end 88 | 89 | def should_emulate_delete_all 90 | id = @record.id 91 | @record.destroy 92 | Record.count.should == 0 93 | Record::Archive.count.should == 1 94 | Record::Archive.restore_all [ "id = ?", id ] 95 | Record.count.should == 1 96 | end 97 | 98 | def should_have_valid_schema 99 | [ 100 | BelongsTo, Record, HasOne, HasMany, HasManyThroughThrough, 101 | HasManyThrough, HasOneThroughThrough, HasOneThrough 102 | ].each do |klass| 103 | cols = [ 'string', 'integer', 'restored_at', 'created_at', 'updated_at' ] 104 | archive_cols = [ 'string', 'integer', 'created_at', 'updated_at', 'deleted_at' ] 105 | (klass.column_names & cols).should == cols 106 | (klass::Archive.column_names & archive_cols).should == archive_cols 107 | end 108 | end 109 | 110 | def should_migrate_record_and_preserve_deleted_at 111 | Record.connection.execute <<-SQL 112 | ALTER TABLE records ADD deleted_at DATETIME 113 | SQL 114 | Record.reset_column_information 115 | 116 | time = Time.now.utc - 24 * 60 * 60 117 | first = Record.first 118 | first.update_attribute :deleted_at, time 119 | Record.create 120 | 121 | Record.migrate_from_acts_as_paranoid 122 | Record::Archive.first.deleted_at.to_s.should == time.to_s 123 | Record::Archive.first.id.should == first.id 124 | Record::Archive.count.should == 1 125 | Record.count.should == 1 126 | end 127 | 128 | def should_move_records_back_to_original_tables(type) 129 | case type 130 | when 'delete', 'destroy' 131 | @record.send type 132 | Record::Archive.first.send type 133 | when 'delete_all', 'destroy_all' 134 | Record.send type 135 | Record::Archive.send type 136 | end 137 | 138 | original, archive = all_records 139 | 140 | verify_lengths original, @lengths 141 | verify_lengths archive, @zero_lengths 142 | 143 | verify_attributes original 144 | 145 | case type 146 | when 'delete', 'delete_all' then 147 | original[:record].first.restored_at.is_a?(::Time).should == true 148 | when 'destroy', 'destroy_all' then 149 | original.values.each do |records| 150 | records.each do |record| 151 | record.restored_at.is_a?(::Time).should == true 152 | end 153 | end 154 | end 155 | end 156 | 157 | def should_move_records_to_archive_tables(type) 158 | case type 159 | when 'delete', 'destroy' 160 | @record.send type 161 | when 'delete_all', 'destroy_all' 162 | Record.send type 163 | end 164 | 165 | original, archive = all_records 166 | 167 | case type 168 | when 'delete', 'delete_all' 169 | archive[:record].length.should == 1 170 | original[:record].length.should == 0 171 | 172 | verify_lengths archive, @zero_lengths, :exclude => [ :record ] 173 | verify_lengths original, @lengths, :exclude => [ :record ] 174 | 175 | verify_attributes archive, :only => [ :record ] 176 | 177 | when 'destroy', 'destroy_all' 178 | verify_lengths archive, @lengths 179 | verify_lengths original, @zero_lengths 180 | 181 | verify_attributes archive 182 | end 183 | end 184 | 185 | def verify_attributes(records, options={}) 186 | options[:exclude] ||= [] 187 | records.each do |key, value| 188 | if !options[:exclude].include?(key) && (!options[:only] || options[:only].include?(key)) 189 | value.each do |record| 190 | record[:string].should == 'string' 191 | record[:integer].should == 1 192 | record[:created_at].is_a?(::Time).should == true 193 | record[:updated_at].is_a?(::Time).should == true 194 | if record.respond_to?(:deleted_at) 195 | record[:deleted_at].is_a?(::Time).should == true 196 | end 197 | end 198 | end 199 | end 200 | end 201 | 202 | def verify_lengths(records, lengths, options={}) 203 | options[:exclude] ||= [] 204 | records.each do |key, value| 205 | if !options[:exclude].include?(key) && (!options[:only] || options[:only].include?(key)) 206 | value.length.should == lengths[key] 207 | end 208 | end 209 | end 210 | end -------------------------------------------------------------------------------- /lib/acts_as_archive.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/acts_as_archive/gems' 2 | 3 | ActsAsArchive::Gems.activate %w(also_migrate mover) 4 | 5 | require 'also_migrate' 6 | require 'mover' 7 | require 'yaml' 8 | 9 | $:.unshift File.dirname(__FILE__) 10 | 11 | class ActsAsArchive 12 | class < self }) 92 | 93 | options[:copy] = true 94 | 95 | if options[:archive] 96 | options[:magic] = 'restored_at' 97 | klass = options[:class] 98 | else 99 | options[:magic] = 'deleted_at' if options[:magic].nil? 100 | options[:add] = [[ options[:magic], :datetime ]] 101 | options[:ignore] = options[:magic] 102 | options[:subtract] = 'restored_at' 103 | options[:timestamps] = false if options[:timestamps].nil? 104 | 105 | unless options[:class] 106 | options[:class] = "#{self}::Archive" 107 | end 108 | 109 | unless options[:table] 110 | options[:table] = "archived_#{self.table_name}" 111 | end 112 | 113 | klass = eval(options[:class]) rescue nil 114 | 115 | if klass 116 | klass.send :set_table_name, options[:table] 117 | else 118 | eval <<-EVAL 119 | class ::#{options[:class]} < ActiveRecord::Base 120 | set_table_name "#{options[:table]}" 121 | end 122 | EVAL 123 | klass = eval("::#{options[:class]}") 124 | end 125 | 126 | klass.record_timestamps = options[:timestamps].inspect 127 | klass.acts_as_archive(:class => self, :archive => true) 128 | 129 | self.reflect_on_all_associations.each do |association| 130 | if !ActsAsArchive.find(association.klass).empty? && association.options[:dependent] 131 | opts = association.options.dup 132 | opts[:class_name] = "::#{association.class_name}::Archive" 133 | opts[:foreign_key] = association.primary_key_name 134 | klass.send association.macro, association.name, opts 135 | end 136 | end 137 | 138 | unless options[:migrate] == false 139 | AlsoMigrate.configuration ||= [] 140 | AlsoMigrate.configuration << options.merge( 141 | :source => self.table_name, 142 | :destination => klass.table_name 143 | ) 144 | end 145 | end 146 | 147 | config[:to] = klass 148 | config[:options] = options 149 | end 150 | 151 | def delete_all!(*args) 152 | ActsAsArchive.disable { self.delete_all(*args) } 153 | end 154 | 155 | def destroy_all!(*args) 156 | ActsAsArchive.disable { self.destroy_all(*args) } 157 | end 158 | 159 | def migrate_from_acts_as_paranoid 160 | time = Benchmark.measure do 161 | ActsAsArchive.find(self).each do |config| 162 | config = config.dup 163 | config[:options][:copy] = false 164 | ActsAsArchive.move( 165 | config, 166 | "`#{config[:options][:magic]}` IS NOT NULL", 167 | :migrate => true 168 | ) 169 | end 170 | end 171 | $stdout.puts "-- #{self}.migrate_from_acts_as_paranoid" 172 | $stdout.puts " -> #{"%.4fs" % time.real}" 173 | end 174 | 175 | def restore_all(*args) 176 | ActsAsArchive.deprecate "#{self}.restore_all is deprecated, please use #{self}.delete_all." 177 | self.delete_all *args 178 | end 179 | end 180 | 181 | module InstanceMethods 182 | def delete!(*args) 183 | ActsAsArchive.disable { self.delete(*args) } 184 | end 185 | 186 | def destroy!(*args) 187 | ActsAsArchive.disable { self.destroy(*args) } 188 | end 189 | end 190 | end 191 | 192 | module DatabaseStatements 193 | def self.included(base) 194 | unless base.included_modules.include?(InstanceMethods) 195 | base.send :include, InstanceMethods 196 | base.class_eval do 197 | unless method_defined?(:delete_sql_without_archive) 198 | alias_method :delete_sql_without_archive, :delete_sql 199 | alias_method :delete_sql, :delete_sql_with_archive 200 | end 201 | end 202 | end 203 | end 204 | 205 | module InstanceMethods 206 | def delete_sql_with_archive(sql, name = nil) 207 | @mutex ||= Mutex.new 208 | @mutex.synchronize do 209 | unless ActsAsArchive.disabled 210 | from, where = /DELETE FROM (.+)/i.match(sql)[1].split(/\s+WHERE\s+/i, 2) 211 | from = from.strip.gsub(/[`"]/, '').split(/\s*,\s*/) 212 | 213 | ActsAsArchive.find(from).each do |config| 214 | ActsAsArchive.move(config, where) 215 | end 216 | end 217 | end 218 | 219 | delete_sql_without_archive(sql, name) 220 | end 221 | end 222 | end 223 | end 224 | 225 | ::ActiveRecord::Base.send(:include, ::ActsAsArchive::Base) 226 | ::ActiveRecord::ConnectionAdapters::DatabaseStatements.send(:include, ::ActsAsArchive::DatabaseStatements) 227 | 228 | require "acts_as_archive/adapters/rails#{Rails.version[0..0]}" if defined?(Rails) 229 | require "acts_as_archive/adapters/sinatra" if defined?(Sinatra) 230 | -------------------------------------------------------------------------------- /spec/acts_as_archive/gems_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ActsAsArchive::Gems do 4 | 5 | before(:each) do 6 | @old_config = ActsAsArchive::Gems.config 7 | 8 | ActsAsArchive::Gems.config.gemspec = "#{$root}/spec/fixtures/gemspec.yml" 9 | ActsAsArchive::Gems.config.gemsets = [ 10 | "#{$root}/spec/fixtures/gemsets.yml" 11 | ] 12 | ActsAsArchive::Gems.config.warn = true 13 | 14 | ActsAsArchive::Gems.gemspec true 15 | ActsAsArchive::Gems.gemset = nil 16 | end 17 | 18 | after(:each) do 19 | ActsAsArchive::Gems.config = @old_config 20 | end 21 | 22 | describe :activate do 23 | it "should activate gems" do 24 | ActsAsArchive::Gems.stub!(:gem) 25 | ActsAsArchive::Gems.should_receive(:gem).with('rspec', '=1.3.1') 26 | ActsAsArchive::Gems.should_receive(:gem).with('rake', '=0.8.7') 27 | ActsAsArchive::Gems.activate :rspec, 'rake' 28 | end 29 | end 30 | 31 | describe :gemset= do 32 | before(:each) do 33 | ActsAsArchive::Gems.config.gemsets = [ 34 | { 35 | :name => { 36 | :rake => '>0.8.6', 37 | :default => { 38 | :externals => '=1.0.2' 39 | } 40 | } 41 | }, 42 | "#{$root}/spec/fixtures/gemsets.yml" 43 | ] 44 | end 45 | 46 | describe :default do 47 | before(:each) do 48 | ActsAsArchive::Gems.gemset = :default 49 | end 50 | 51 | it "should set @gemset" do 52 | ActsAsArchive::Gems.gemset.should == :default 53 | end 54 | 55 | it "should set @gemsets" do 56 | ActsAsArchive::Gems.gemsets.should == { 57 | :name => { 58 | :rake => ">0.8.6", 59 | :default => { 60 | :externals => '=1.0.2', 61 | :mysql => "=2.8.1", 62 | :rspec => "=1.3.1" 63 | }, 64 | :rspec2 => { 65 | :mysql2 => "=0.2.6", 66 | :rspec => "=2.3.0" 67 | }, 68 | :solo => nil 69 | } 70 | } 71 | end 72 | 73 | it "should set Gems.versions" do 74 | ActsAsArchive::Gems.versions.should == { 75 | :externals => "=1.0.2", 76 | :mysql => "=2.8.1", 77 | :rake => ">0.8.6", 78 | :rspec => "=1.3.1" 79 | } 80 | end 81 | 82 | it "should return proper values for Gems.dependencies" do 83 | ActsAsArchive::Gems.dependencies.should == [ :rake, :mysql ] 84 | ActsAsArchive::Gems.development_dependencies.should == [] 85 | end 86 | 87 | it "should return proper values for Gems.gemset_names" do 88 | ActsAsArchive::Gems.gemset_names.should == [ :default, :rspec2, :solo ] 89 | end 90 | end 91 | 92 | describe :rspec2 do 93 | before(:each) do 94 | ActsAsArchive::Gems.gemset = "rspec2" 95 | end 96 | 97 | it "should set @gemset" do 98 | ActsAsArchive::Gems.gemset.should == :rspec2 99 | end 100 | 101 | it "should set @gemsets" do 102 | ActsAsArchive::Gems.gemsets.should == { 103 | :name => { 104 | :rake => ">0.8.6", 105 | :default => { 106 | :externals => '=1.0.2', 107 | :mysql => "=2.8.1", 108 | :rspec => "=1.3.1" 109 | }, 110 | :rspec2 => { 111 | :mysql2=>"=0.2.6", 112 | :rspec => "=2.3.0" 113 | }, 114 | :solo => nil 115 | } 116 | } 117 | end 118 | 119 | it "should set Gems.versions" do 120 | ActsAsArchive::Gems.versions.should == { 121 | :mysql2 => "=0.2.6", 122 | :rake => ">0.8.6", 123 | :rspec => "=2.3.0" 124 | } 125 | end 126 | 127 | it "should return proper values for Gems.dependencies" do 128 | ActsAsArchive::Gems.dependencies.should == [ :rake, :mysql2 ] 129 | ActsAsArchive::Gems.development_dependencies.should == [] 130 | end 131 | 132 | it "should return proper values for Gems.gemset_names" do 133 | ActsAsArchive::Gems.gemset_names.should == [ :default, :rspec2, :solo ] 134 | end 135 | end 136 | 137 | describe :solo do 138 | before(:each) do 139 | ActsAsArchive::Gems.gemset = :solo 140 | end 141 | 142 | it "should set @gemset" do 143 | ActsAsArchive::Gems.gemset.should == :solo 144 | end 145 | 146 | it "should set @gemsets" do 147 | ActsAsArchive::Gems.gemsets.should == { 148 | :name => { 149 | :rake => ">0.8.6", 150 | :default => { 151 | :externals => '=1.0.2', 152 | :mysql => "=2.8.1", 153 | :rspec => "=1.3.1" 154 | }, 155 | :rspec2 => { 156 | :mysql2=>"=0.2.6", 157 | :rspec => "=2.3.0" 158 | }, 159 | :solo => nil 160 | } 161 | } 162 | end 163 | 164 | it "should set Gems.versions" do 165 | ActsAsArchive::Gems.versions.should == {:rake=>">0.8.6"} 166 | end 167 | 168 | it "should return proper values for Gems.dependencies" do 169 | ActsAsArchive::Gems.dependencies.should == [:rake] 170 | ActsAsArchive::Gems.development_dependencies.should == [] 171 | end 172 | 173 | it "should return proper values for Gems.gemset_names" do 174 | ActsAsArchive::Gems.gemset_names.should == [ :default, :rspec2, :solo ] 175 | end 176 | end 177 | 178 | describe :nil do 179 | before(:each) do 180 | ActsAsArchive::Gems.gemset = nil 181 | end 182 | 183 | it "should set everything to nil" do 184 | ActsAsArchive::Gems.gemset.should == nil 185 | ActsAsArchive::Gems.gemsets.should == nil 186 | ActsAsArchive::Gems.versions.should == nil 187 | end 188 | end 189 | end 190 | 191 | describe :gemset_from_loaded_specs do 192 | before(:each) do 193 | Gem.stub!(:loaded_specs) 194 | end 195 | 196 | it "should return the correct gemset for name gem" do 197 | Gem.should_receive(:loaded_specs).and_return({ "name" => nil }) 198 | ActsAsArchive::Gems.send(:gemset_from_loaded_specs).should == :default 199 | end 200 | 201 | it "should return the correct gemset for name-rspec gem" do 202 | Gem.should_receive(:loaded_specs).and_return({ "name-rspec2" => nil }) 203 | ActsAsArchive::Gems.send(:gemset_from_loaded_specs).should == :rspec2 204 | end 205 | end 206 | 207 | describe :reload_gemspec do 208 | it "should populate @gemspec" do 209 | ActsAsArchive::Gems.gemspec.hash.should == { 210 | "name" => "name", 211 | "version" => "0.1.0", 212 | "authors" => ["Author"], 213 | "email" => "email@email.com", 214 | "homepage" => "http://github.com/author/name", 215 | "summary" => "Summary", 216 | "description" => "Description", 217 | "dependencies" => [ 218 | "rake", 219 | { "default" => [ "mysql" ] }, 220 | { "rspec2" => [ "mysql2" ] } 221 | ], 222 | "development_dependencies" => nil 223 | } 224 | end 225 | 226 | it "should create methods from keys of @gemspec" do 227 | ActsAsArchive::Gems.gemspec.name.should == "name" 228 | ActsAsArchive::Gems.gemspec.version.should == "0.1.0" 229 | ActsAsArchive::Gems.gemspec.authors.should == ["Author"] 230 | ActsAsArchive::Gems.gemspec.email.should == "email@email.com" 231 | ActsAsArchive::Gems.gemspec.homepage.should == "http://github.com/author/name" 232 | ActsAsArchive::Gems.gemspec.summary.should == "Summary" 233 | ActsAsArchive::Gems.gemspec.description.should == "Description" 234 | ActsAsArchive::Gems.gemspec.dependencies.should == [ 235 | "rake", 236 | { "default" => ["mysql"] }, 237 | { "rspec2" => [ "mysql2" ] } 238 | ] 239 | ActsAsArchive::Gems.gemspec.development_dependencies.should == nil 240 | end 241 | 242 | it "should produce a valid gemspec" do 243 | ActsAsArchive::Gems.gemset = :default 244 | gemspec = File.expand_path("../../../acts_as_archive.gemspec", __FILE__) 245 | gemspec = eval(File.read(gemspec), binding, gemspec) 246 | gemspec.validate.should == true 247 | end 248 | end 249 | end 250 | --------------------------------------------------------------------------------