acts_as_archive 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/.gitignore +9 -0
  2. data/LICENSE +18 -0
  3. data/README.md +112 -0
  4. data/Rakefile +90 -0
  5. data/acts_as_archive.gemspec +32 -0
  6. data/bin/acts_as_archive +2 -0
  7. data/config/externals.yml +12 -0
  8. data/config/gemsets.yml +10 -0
  9. data/config/gemspec.yml +17 -0
  10. data/init.rb +1 -0
  11. data/lib/acts_as_archive.rb +192 -0
  12. data/lib/acts_as_archive/gems.rb +154 -0
  13. data/rails/init.rb +1 -0
  14. data/spec/Rakefile +9 -0
  15. data/spec/acts_as_archive/gems_spec.rb +249 -0
  16. data/spec/acts_as_archive_spec.rb +113 -0
  17. data/spec/fixtures/config/database.yml.example +6 -0
  18. data/spec/fixtures/db/migrate/001_belongs_tos.rb +14 -0
  19. data/spec/fixtures/db/migrate/002_records.rb +15 -0
  20. data/spec/fixtures/db/migrate/003_has_ones.rb +15 -0
  21. data/spec/fixtures/db/migrate/004_has_manies.rb +15 -0
  22. data/spec/fixtures/db/migrate/005_has_many_through_throughs.rb +16 -0
  23. data/spec/fixtures/db/migrate/006_has_many_throughs.rb +14 -0
  24. data/spec/fixtures/db/migrate/007_has_one_through_throughs.rb +15 -0
  25. data/spec/fixtures/db/migrate/008_has_one_throughs.rb +15 -0
  26. data/spec/fixtures/frameworks.yml +36 -0
  27. data/spec/fixtures/frameworks/rails2/application_controller.rb +58 -0
  28. data/spec/fixtures/frameworks/rails2/database.yml +12 -0
  29. data/spec/fixtures/frameworks/rails2/init.rb +1 -0
  30. data/spec/fixtures/frameworks/rails2/routes.rb +46 -0
  31. data/spec/fixtures/frameworks/rails3/Gemfile +34 -0
  32. data/spec/fixtures/frameworks/rails3/application_controller.rb +51 -0
  33. data/spec/fixtures/frameworks/rails3/database.yml +12 -0
  34. data/spec/fixtures/frameworks/rails3/routes.rb +60 -0
  35. data/spec/fixtures/frameworks/sinatra/application.rb +58 -0
  36. data/spec/fixtures/gemsets.yml +9 -0
  37. data/spec/fixtures/gemspec.yml +15 -0
  38. data/spec/fixtures/helpers/spec_helper.rb +210 -0
  39. data/spec/fixtures/models/belongs_to.rb +6 -0
  40. data/spec/fixtures/models/has_many.rb +6 -0
  41. data/spec/fixtures/models/has_many_through.rb +7 -0
  42. data/spec/fixtures/models/has_many_through_through.rb +7 -0
  43. data/spec/fixtures/models/has_one.rb +6 -0
  44. data/spec/fixtures/models/has_one_through.rb +6 -0
  45. data/spec/fixtures/models/has_one_through_through.rb +7 -0
  46. data/spec/fixtures/models/record.rb +16 -0
  47. data/spec/run +27 -0
  48. data/spec/spec.opts +1 -0
  49. data/spec/spec_helper.rb +73 -0
  50. metadata +96 -13
data/.gitignore ADDED
@@ -0,0 +1,9 @@
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
data/LICENSE ADDED
@@ -0,0 +1,18 @@
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.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ ActsAsArchive
2
+ =============
3
+
4
+ Don't delete your records, move them to a different table.
5
+
6
+ Like <code>acts\_as\_paranoid</code>, but doesn't mess with your SQL queries.
7
+
8
+ Install
9
+ -------
10
+
11
+ <pre>
12
+ gem install acts_as_archive
13
+ </pre>
14
+
15
+ ### Rails 2
16
+
17
+ #### config/environment.rb
18
+
19
+ <pre>
20
+ config.gem 'acts_as_archive'
21
+ </pre>
22
+
23
+ ### Rails 3
24
+
25
+ #### Gemfile
26
+
27
+ <pre>
28
+ gem 'acts_as_archive'
29
+ </pre>
30
+
31
+ ### Sinatra
32
+
33
+ <pre>
34
+ require 'acts_as_archive'
35
+ </pre>
36
+
37
+ Add to models
38
+ -------------
39
+
40
+ Add <code>acts\_as\_archive</code> to your models:
41
+
42
+ <pre>
43
+ class Article &lt; ActiveRecord::Base
44
+ acts_as_archive
45
+ end
46
+ </pre>
47
+
48
+ Migrate
49
+ -------
50
+
51
+ Next time you run <code>rake db:migrate</code>, your archive tables will be created automatically.
52
+
53
+ That's it!
54
+ ----------
55
+
56
+ Use <code>destroy</code>, <code>destroy\_all</code>, <code>delete</code>, and <code>delete_all</code> like you normally would.
57
+
58
+ Records move into the archive table instead of being destroyed.
59
+
60
+ What if my schema changes?
61
+ --------------------------
62
+
63
+ New migrations are automatically applied to the archive table.
64
+
65
+ No action is necessary on your part.
66
+
67
+ Query the archive
68
+ -----------------
69
+
70
+ Add <code>::Archive</code> to your ActiveRecord class:
71
+
72
+ <pre>
73
+ Article::Archive.first
74
+ </pre>
75
+
76
+ Delete records without archiving
77
+ --------------------------------
78
+
79
+ Use any of the destroy methods, but add a bang (!):
80
+
81
+ <pre>
82
+ Article::Archive.find(:first).destroy!
83
+ Article.delete_all!([ "id in (?)", [ 1, 2, 3 ] ])
84
+ </pre>
85
+
86
+ Restore from the archive
87
+ ------------------------
88
+
89
+ Use any of the destroy/delete methods on the archived record to move it back to its original table:
90
+
91
+ <pre>
92
+ Article::Archive.first.destroy
93
+ Article::Archive.delete_all([ "id in (?)", [ 1, 2, 3 ] ])
94
+ </pre>
95
+
96
+ Magic columns
97
+ -------------
98
+
99
+ You will find an extra <code>deleted_at</code> datetime column on the archive table.
100
+
101
+ You may manually add a <code>restored_at</code> datetime column to the origin table if you wish to store restoration time as well.
102
+
103
+ Migrate from acts\_as\_paranoid
104
+ -------------------------------
105
+
106
+ Add this line to a migration, or run it via <code>script/console</code>:
107
+
108
+ <pre>
109
+ Article.migrate_from_acts_as_paranoid
110
+ </pre>
111
+
112
+ This copies all records with non-null <code>deleted_at</code> values to the archive.
data/Rakefile ADDED
@@ -0,0 +1,90 @@
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
@@ -0,0 +1,32 @@
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ puts `script/runner "ActsAsArchive.update #{ARGV.join ', '}"`
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,10 @@
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.2
7
+ externals: =1.0.2
8
+ framework_fixture: =0.1.3
9
+ mover: =0.3.6
10
+ rack-test: =0.5.6
@@ -0,0 +1,17 @@
1
+ name: acts_as_archive
2
+ version: 0.3.2
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:
13
+ - active_wrapper-solo
14
+ - externals
15
+ - framework_fixture
16
+ - rake
17
+ - rspec
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,192 @@
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
+
8
+ $:.unshift File.dirname(__FILE__)
9
+
10
+ class ActsAsArchive
11
+ class <<self
12
+
13
+ attr_accessor :configuration, :disabled
14
+
15
+ def deprecate(msg)
16
+ if defined?(::ActiveSupport::Deprecation)
17
+ ::ActiveSupport::Deprecation.warn msg
18
+ else
19
+ $stdout.puts msg
20
+ end
21
+ end
22
+
23
+ def disable(&block)
24
+ @mutex ||= Mutex.new
25
+ @mutex.synchronize do
26
+ self.disabled = true
27
+ block.call
28
+ end
29
+ ensure
30
+ self.disabled = false
31
+ end
32
+
33
+ def find(from)
34
+ from = [ from ] unless from.is_a?(::Array)
35
+ (@configuration || []).select do |hash|
36
+ if from[0].is_a?(::String)
37
+ from.include?(hash[:from].table_name)
38
+ else
39
+ from.include?(hash[:from])
40
+ end
41
+ end
42
+ end
43
+
44
+ def move(config, where, merge_options={})
45
+ config[:to].each do |to|
46
+ options = config[:options].dup.merge(merge_options)
47
+ if options[:conditions]
48
+ options[:conditions] += " AND #{where}"
49
+ elsif where
50
+ options[:conditions] = where
51
+ end
52
+ config[:from].move_to(to, options)
53
+ end
54
+ end
55
+
56
+ def update(*args)
57
+ deprecate "ActsAsArchive.update is deprecated and no longer necessary."
58
+ end
59
+ end
60
+
61
+ module Base
62
+ def self.included(base)
63
+ unless base.included_modules.include?(InstanceMethods)
64
+ base.extend ClassMethods
65
+ base.send :include, InstanceMethods
66
+ end
67
+ end
68
+
69
+ module ClassMethods
70
+ def acts_as_archive(*args)
71
+ return unless ActsAsArchive.find(self).empty?
72
+
73
+ ActsAsArchive.configuration ||= []
74
+ ActsAsArchive.configuration << (config = { :from => self })
75
+
76
+ options = args.last.is_a?(::Hash) ? args.pop : {}
77
+ options[:copy] = true
78
+
79
+ if options[:archive]
80
+ options[:magic] = 'restored_at'
81
+ else
82
+ options[:magic] = 'deleted_at' if options[:magic].nil?
83
+ options[:add] = [[ options[:magic], :datetime ]]
84
+ options[:ignore] = options[:magic]
85
+ options[:subtract] = 'restored_at'
86
+ options[:timestamps] = false if options[:timestamps].nil?
87
+
88
+ if args.empty?
89
+ class_eval <<-EVAL
90
+ class Archive < ActiveRecord::Base
91
+ set_table_name "archived_#{self.table_name}"
92
+ end
93
+ EVAL
94
+ args << self::Archive
95
+ end
96
+
97
+ args.each do |klass|
98
+ klass.class_eval <<-EVAL
99
+ record_timestamps = #{options[:timestamps].inspect}
100
+ acts_as_archive(#{self}, :archive => true)
101
+ EVAL
102
+ self.reflect_on_all_associations.each do |association|
103
+ if !ActsAsArchive.find(association.klass).empty? && association.options[:dependent]
104
+ opts = association.options.dup
105
+ opts[:class_name] = "::#{association.class_name}::Archive"
106
+ opts[:foreign_key] = association.primary_key_name
107
+ klass.send association.macro, association.name, opts
108
+ end
109
+ end
110
+ unless options[:migrate] == false
111
+ self.also_migrate klass.table_name, options
112
+ end
113
+ end
114
+ end
115
+
116
+ config[:to] = args
117
+ config[:options] = options
118
+ end
119
+
120
+ def delete_all!(*args)
121
+ ActsAsArchive.disable { self.delete_all(*args) }
122
+ end
123
+
124
+ def destroy_all!(*args)
125
+ ActsAsArchive.disable { self.destroy_all(*args) }
126
+ end
127
+
128
+ def migrate_from_acts_as_paranoid
129
+ time = Benchmark.measure do
130
+ ActsAsArchive.find(self).each do |config|
131
+ config = config.dup
132
+ config[:options][:copy] = false
133
+ ActsAsArchive.move(
134
+ config,
135
+ "`#{config[:options][:magic]}` IS NOT NULL",
136
+ :migrate => true
137
+ )
138
+ end
139
+ end
140
+ $stdout.puts "-- #{self}.migrate_from_acts_as_paranoid"
141
+ $stdout.puts " -> #{"%.4fs" % time.real}"
142
+ end
143
+
144
+ def restore_all(*args)
145
+ ActsAsArchive.deprecate "#{self}.restore_all is deprecated, please use #{self}.delete_all."
146
+ self.delete_all *args
147
+ end
148
+ end
149
+
150
+ module InstanceMethods
151
+ def delete!(*args)
152
+ ActsAsArchive.disable { self.delete(*args) }
153
+ end
154
+
155
+ def destroy!(*args)
156
+ ActsAsArchive.disable { self.destroy(*args) }
157
+ end
158
+ end
159
+ end
160
+
161
+ module DatabaseStatements
162
+ def self.included(base)
163
+ unless base.included_modules.include?(InstanceMethods)
164
+ base.send :include, InstanceMethods
165
+ base.class_eval do
166
+ unless method_defined?(:delete_sql_without_archive)
167
+ alias_method :delete_sql_without_archive, :delete_sql
168
+ alias_method :delete_sql, :delete_sql_with_archive
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ module InstanceMethods
175
+ def delete_sql_with_archive(sql, name = nil)
176
+ unless ActsAsArchive.disabled
177
+ from, where = /DELETE FROM (.+)/i.match(sql)[1].split(/\s+WHERE\s+/i, 2)
178
+ from = from.strip.gsub(/`/, '').split(/\s*,\s*/)
179
+
180
+ ActsAsArchive.find(from).each do |config|
181
+ ActsAsArchive.move(config, where)
182
+ end
183
+ end
184
+
185
+ delete_sql_without_archive(sql, name)
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ ::ActiveRecord::Base.send(:include, ::ActsAsArchive::Base)
192
+ ::ActiveRecord::ConnectionAdapters::DatabaseStatements.send(:include, ::ActsAsArchive::DatabaseStatements)