archivist 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2010 Tyler Pickett
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,33 @@
1
+ README.md
2
+ =================
3
+
4
+ This gem is intended as a direct replacement for acts\_as\_archive (AAA)
5
+ in Rails 3 apps with most of the same functionality and wrapping AAA's
6
+ methods in aliases to maintain compatibilty for some time. Thanks to
7
+ [Winton Welsh](https://github.com/winton "Winton on github") for his
8
+ original work on AAA, it is good solution to a problem that makes
9
+ maintaining audit records a breeze.
10
+
11
+ More Later
12
+
13
+ TODO
14
+ -----------------
15
+
16
+ v1.0
17
+
18
+ * <del>License</del>
19
+ * <del>Base Module</del>
20
+ * <del> Inserting Subclass (SomeModel::Archive) </del>
21
+ * <del> Archive method </del>
22
+ * <del> Intercept destroy/deletes </del>
23
+ * <del>Restore archive</del>
24
+ * <del> Build archive table for existing models </del>
25
+ * <del>Migrations Module</del>
26
+ * <del>rewrite method_missing to act on the archived table</del>
27
+
28
+ Future
29
+
30
+ * give subclass Archive its parent's methods
31
+ * give Archive relations
32
+ * give Archive scopes
33
+ * make archive\_all method chain-able with scopes
data/lib/archivist.rb ADDED
@@ -0,0 +1,24 @@
1
+ #require dependancies eventhough starting a rails app will already have them in place
2
+ require 'rubygems'
3
+ gem 'activerecord','~>3.0.1' #enforce rails 3+
4
+ require 'active_record'
5
+
6
+ #require the rest of Archivist's files
7
+ files = File.join(File.dirname(__FILE__),'**','*.rb')
8
+ Dir.glob(files).each do |file|
9
+ require file
10
+ end
11
+
12
+ ActiveRecord::Base.send(:include, Archivist::Base)
13
+ ActiveRecord::Migration.send(:include,Archivist::Migration)
14
+ module Archivist
15
+ def self.update(*models)
16
+ models.each do |klass|
17
+ if klass.respond_to?(:has_archive) && klass.has_archive?
18
+ klass.create_archive_table
19
+ klass.create_archive_indexes
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,129 @@
1
+ # Require sub-module's files
2
+ mods = File.join(File.dirname(__FILE__),"base","**","*.rb")
3
+ Dir.glob(mods).each do |mod|
4
+ require mod
5
+ end
6
+
7
+ module Archivist
8
+ module Base
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+ end
12
+
13
+ module ClassMethods
14
+ def has_archive(options={})
15
+ class_eval <<-EOF
16
+ alias_method :delete!, :delete
17
+
18
+ class << self
19
+ alias_method :delete_all!, :delete_all
20
+ end
21
+
22
+ def self.archive_indexes
23
+ #{Array(options[:indexes]).collect{|i| i.to_s}.inspect}
24
+ end
25
+
26
+ def self.has_archive?
27
+ true
28
+ end
29
+
30
+ def self.acts_as_archive?
31
+ warn "DEPRECATION WARNING: #acts_as_archive is provided for compatibility with AAA and will be removed soon, please use has_archive?"
32
+ has_archive?
33
+ end
34
+ class Archive < ActiveRecord::Base
35
+ self.record_timestamps = false
36
+ self.table_name = "archived_#{self.table_name}"
37
+ end
38
+ EOF
39
+ include InstanceMethods
40
+ extend ClassExtensions
41
+ include DB
42
+ end
43
+
44
+ def acts_as_archive(options={})
45
+ has_archive(options)
46
+ end
47
+ end
48
+
49
+ module InstanceMethods #these defs can't happen untill after we've aliased their respective originals
50
+ def delete
51
+ self.class.copy_to_archive({:id=>self.id}) unless new_record?
52
+ @destroyed = true
53
+ freeze
54
+ end
55
+
56
+ def destroy
57
+ _run_destroy_callbacks do
58
+ self.delete
59
+ end
60
+ end
61
+
62
+ def destroy!
63
+ transaction do
64
+ _run_destroy_callbacks do
65
+ self.delete!
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ module ClassExtensions #these can't get included in the class def untill after all aliases are done
72
+ def copy_to_archive(conditions,delete=true)
73
+ where = sanitize_sql(conditions)
74
+ found = self.where(where)
75
+
76
+ found.each do |m|
77
+ self.transaction do # I would hate for something to happen in the middle of all of this
78
+ attrs = m.attributes.merge(:deleted_at=>DateTime.now)
79
+
80
+ if self::Archive.where(:id=>m.id).empty? #create a new one if necessary, else update
81
+ archived = self::Archive.new
82
+ archived.id = m.id
83
+ archived.attributes = attrs.reject{|k,v| k==:id}
84
+ archived.save
85
+ else
86
+ self::Archive.where(:id=>m.id).first.update_attributes(attrs)
87
+ end
88
+ m.destroy! if delete
89
+ end
90
+ end
91
+ end
92
+
93
+ def copy_from_archive(conditions,delete=true)
94
+ where = sanitize_sql(conditions)
95
+ where = where.gsub("#{table_name}","archived_#{table_name}") unless where.nil? || where =~ /archived/
96
+ unless where == ""
97
+ found = self::Archive.where(where)
98
+ else
99
+ found = self::Archive.all
100
+ end
101
+
102
+ found.each do |m|
103
+ self.transaction do
104
+ attrs = m.attributes.reject{|k,v| k=="deleted_at"}
105
+
106
+ if self.where(:id=>m.id).empty?
107
+ new_m = self.create(attrs)
108
+ connection.execute(%Q{UPDATE #{table_name}
109
+ SET #{self.primary_key} = #{m.id}
110
+ WHERE #{self.primary_key} = #{new_m.id}
111
+ })
112
+ else
113
+ self.where(:id=>m.id).first.update_attributes(attrs)
114
+ end
115
+ m.destroy if delete
116
+ end
117
+ end
118
+ end
119
+
120
+ def delete_all(conditions=nil)
121
+ copy_to_archive(conditions)
122
+ end
123
+
124
+ def restore_all(conditions=nil)
125
+ copy_from_archive(conditions)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,53 @@
1
+ module Archivist
2
+ module Base
3
+ module DB
4
+
5
+ def self.included(base)
6
+ base.send(:extend, ClassMethods)
7
+ base.send(:include, InstanceMethods)
8
+
9
+ if base.connection.class.to_s.include?("Mysql")
10
+ base.send(:extend, MySQL)
11
+ elsif base.connection.class.to_s.include?("Postgresql")
12
+ base.send(:extend, PostgreSQL)
13
+ else
14
+ raise "DB type not supported by Archivist!"
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def archive_table_exists?
20
+ connection.table_exists?("archived_#{table_name}")
21
+ end
22
+
23
+ def create_archive_table
24
+ if table_exists? && !archive_table_exists?
25
+ cols = self.content_columns
26
+ connection.create_table("archived_#{table_name}")
27
+ cols.each do |c|
28
+ connection.add_column("archived_#{table_name}",c.name,c.type)
29
+ end
30
+ connection.add_column("archived_#{table_name}",:deleted_at,:datetime)
31
+ end
32
+ end
33
+ end
34
+
35
+ module InstanceMethods
36
+ end
37
+
38
+ module MySQL
39
+ private
40
+ def archive_table_indexed_columns
41
+ @indexes ||= connection.select_all("SHOW INDEX FROM archived_#{table_name}").collect{|r| r["Column_name"]}
42
+ end
43
+ end
44
+
45
+ module PostgreSQL
46
+ private
47
+ def archived_table_indexed_columns
48
+ @indexes ||= connection.indexes("archived_#{table_name}").map{|i| i.column_names}
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,36 @@
1
+ module Archivist
2
+ module Migration
3
+ def self.included(base)
4
+ base.send(:extend, ClassMethods)
5
+ base.class_eval do
6
+ class << self
7
+ unless method_defined?(:method_missing_without_archive)
8
+ alias_method(:method_missing_without_archive,:method_missing)
9
+ alias_method(:method_missing, :method_missing_with_archive)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def method_missing_with_archive(method, *arguments, &block)
17
+ method_missing_without_archive(method, *arguments, &block)
18
+ allowed = [:add_column,:add_timestamps,:change_column,
19
+ :change_column_default,:change_table,:drop_table,
20
+ :remove_column, :remove_columns, :remove_timestamps,
21
+ :rename_column,:rename_table]
22
+ return if arguments.include?(:deleted_at) || arguments.include?('deleted_at')
23
+
24
+ if !arguments.empty? && allowed.include?(method)
25
+ args = Marshal.load(Marshal.dump(arguments))
26
+ args[0] = "archived_#{ActiveRecord::Migrator.proper_table_name(args[0])}"
27
+ args[1] = "archived_#{args[1].to_s}" if method == :rename_table
28
+
29
+ if ActiveRecord::Base.connection.table_exists?(args[0])
30
+ ActiveRecord::Base.connection.send(method, *args, &block)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: archivist
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 2
10
+ version: 1.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Tyler Pickett
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-11 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 5
30
+ segments:
31
+ - 3
32
+ - 0
33
+ - 1
34
+ version: 3.0.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: thoughtbot-shoulda
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ description: |-
52
+ This is a functional replacement for acts_as_archive in
53
+ rails 3 applications, the only functionality that is not
54
+ duplicated is the migration from acts_as_paranoid
55
+ email:
56
+ - t.pickett66@gmail.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files: []
62
+
63
+ files:
64
+ - lib/archivist/base/db.rb
65
+ - lib/archivist/base.rb
66
+ - lib/archivist/migration.rb
67
+ - lib/archivist.rb
68
+ - MIT-LICENSE
69
+ - README.md
70
+ has_rdoc: true
71
+ homepage: http://github.com/tpickett66/archivist
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options: []
76
+
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 3
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.3.7
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: A rails 3 model archiving system based on acts_as_archive
104
+ test_files: []
105
+