archivist 1.0.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.
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
+