undestroy 0.0.1 → 0.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/ARCH.md ADDED
@@ -0,0 +1,84 @@
1
+ # Undestroy Model Structure
2
+
3
+ This is the basic class structure of this gem. It was designed to be
4
+ modular and easy to tailor to your specific needs.
5
+
6
+ ### `Config`
7
+
8
+ Holds configuration information for Undestroy. An instance is created
9
+ globally and serves as defaults for each model using Undestroy. Each
10
+ model also creates its own instance of Config allowing any model to
11
+ override any of the globally configurable options.
12
+
13
+ To change global defaults use this configuration DSL:
14
+
15
+ ```ruby
16
+ Undestroy::Config.configure do |config|
17
+ config.abstract_class = ArchiveModel
18
+ config.fields = {
19
+ :deleted_at => proc { Time.now },
20
+ :deleted_by_id => proc { User.current.id if User.current }
21
+ }
22
+ end
23
+ ```
24
+
25
+ This changes the default abstract class from ActiveRecord::Base to a
26
+ model called ArchiveModel. This also sets the default fields to include
27
+ a deleted_by_id which automatically sets the current user as the deleter
28
+ of the record.
29
+
30
+ Possible configuration options are listed in the _Usage_ section above.
31
+
32
+ ### `Archive`
33
+
34
+ Map the source model's schema to the archive model's and initiate the
35
+ transfer through `Transfer`. When `run` is called the Transfer is
36
+ initialized with a primitive hash mapping the schema to the archive
37
+ table.
38
+
39
+ Initialized with:
40
+
41
+ * `:config`: Instance of Undestroy::Config for this model
42
+ * `:source`: Instance of the source model
43
+
44
+ ### `Restore`
45
+
46
+ Map the archive model's schema to the source model's and initiate the
47
+ transfer through `Transfer`
48
+
49
+ Initialized with:
50
+
51
+ * `:config`: Instance of Undestroy::Config for this model
52
+ * `:archive`: Instance of the archived model
53
+
54
+ ### `Transfer`
55
+
56
+ Handles the actual movement of data from one table to another. This
57
+ class simply uses the AR interface to create and delete the appropriate
58
+ records. This can be subclassed to provide enhanced performance or
59
+ customized behavior for your situation.
60
+
61
+ Initialized with:
62
+
63
+ * `:fields`: Hash of field names to values to be stored in this table
64
+ * `:klass`: Target AR model which will be created with these attributes
65
+
66
+ ### `Binding::ActiveRecord`
67
+
68
+ Binds the base functionality to ActiveRecord. It is initialized by the
69
+ parameters to the `undestroy` class method and contains the method that
70
+ is bound to the `before_destroy` callback that performs the archiving
71
+ functions. Any of the code that handles ActiveRecord specific logic
72
+ lives in here.
73
+
74
+ Initialized with: *Config options from above*
75
+
76
+ Attributes:
77
+
78
+ * `config`: Returns this model's config object
79
+ * `model`: The AR model this instnace was created for
80
+
81
+ Methods:
82
+
83
+ * `before_destroy`: Perform the archive process
84
+
data/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in undestroy.gemspec
4
3
  gemspec
5
4
 
6
5
  gem 'rake'
6
+ gem 'sqlite3'
7
+
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Travis Petticrew
1
+ Copyright (c) 2012 Travis Petticrew and Team Insight
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  Allow copying records to alternate table before destroying an
4
4
  ActiveRecord model for archiving purposes. Data will be mapped
5
5
  one-to-one to the archive table schema. Additional fields can also be
6
- configured for additional tracking information. Archive table schema
6
+ configured for additional tracking information. -Archive table schema
7
7
  will automatically be updated when the parent model's table is migrated
8
- through Rails.
8
+ through Rails.- (not yet)
9
9
 
10
10
  ## Installation
11
11
 
@@ -21,21 +21,34 @@ Or install it yourself as:
21
21
 
22
22
  $ gem install undestroy
23
23
 
24
+ You can also tell Undestroy to not extend ActiveRecord when required by
25
+ using this line in your Gemfile instead:
26
+
27
+ gem 'undestroy', :require => 'undestroy/without_binding'
28
+
29
+ If you do this you must call
30
+ `Undestroy::Binding::ActiveRecord.add(MyARSubclass)` where
31
+ `MYARSubclass` is the class you want Undestroy to extend instead.
32
+
24
33
  ## Usage
25
34
 
26
- To activate Undestroy on a model, simply call the `undestroyable` method
35
+ To activate Undestroy on a model, simply call the `undestroy` method
27
36
  on the class like so:
28
37
 
29
- class Person < ActiveRecord::Base
30
- undestroyable
31
- end
38
+ ```ruby
39
+ class Person < ActiveRecord::Base
40
+ undestroy
41
+ end
42
+ ```
32
43
 
33
- This method also can accept an options hash to further customize
44
+ This method can also accept an options hash to further customize
34
45
  Undestroy to your needs.
35
46
 
36
- * `:table_name`: use this table for archiving
37
- * `:class_name`: use this AR model for archiving
38
- * `:connection`: use this connection for archiving
47
+ * `:table_name`: use this table for archiving (Defaults to the
48
+ source class's table_name prefixed with "archive_").
49
+ * `:abstract_class`: use this as the base class for the target_class
50
+ specify an alternate for custom extensions / DB connections (defaults
51
+ to ActiveRecord::Base)
39
52
  * `:fields`: Specify a hash of fields to values for additional fields
40
53
  you would like to include on the archive table -- lambdas will be
41
54
  called with the instance being destroyed and returned value will be
@@ -43,44 +56,41 @@ Undestroy to your needs.
43
56
  * `:migrate`: Should Undestroy migrate the archive table together with
44
57
  this model's table (default: true)
45
58
 
46
- $ person = Person.find(1)
47
- $ person.destroy
48
- # => Inserts person data into archive_people table
49
- # => Deletes person data from people table
50
-
51
- ## Stucture
52
-
53
- This is the basic class structure of this gem. It was designed to be
54
- modular and easy to tailor to your specific needs.
55
-
56
- ### `Archive`
57
-
58
- Map the source model's schema to the archive model's and initiate the
59
- transfer through `Transfer`. When `run` is called the Transfer is
60
- initialized with a primitive hash mapping the schema to the archive
61
- table.
62
-
63
- Initialized with:
64
-
65
- * `:config`: Instance of Undestroy::Config for this model
66
- * `:source`: Instance of the source model
67
-
68
- ### `Restore`
69
-
70
- Map the archive model's schema to the source model's and initiate the
71
- transfer through `Transfer`
72
-
73
- Initialized with:
74
-
75
- * `:config`: Instance of Undestroy::Config for this model
76
- * `:archive`: Instance of the archived model
77
-
78
- ### `Transfer`
79
-
80
- Handles the actual movement of data from one table to another. This
81
- class simply uses the AR interface to create and delete the appropriate
82
- records. This can be subclassed to provide enhanced performance or
83
- customized behavior for your situation.
59
+ Internal Options (for advanced users):
60
+
61
+ * `:source_class`: the AR model of the originating data. Set
62
+ automatically to class `undestroy` method is called on.
63
+ * `:target_class`: use this AR model for archiving. Set automatically
64
+ to dynamically generated class based on `archive_*` options.
65
+ * `internals`: internal classes to use for archival process. Possible
66
+ keys are `:archive`, `:transfer` and `:restore`. Defaults to
67
+ corresponding internal classes. Customize to your heart's content.
68
+
69
+ ```
70
+ $ person = Person.find(1)
71
+ $ person.destroy
72
+ # => Inserts person data into archive_people table
73
+ # => Deletes person data from people table
74
+ ```
75
+
76
+ ## Configuring
77
+
78
+ You can specify custom global configurations for Undestroy through a
79
+ configuration block in your application initializer:
80
+
81
+ ```ruby
82
+ Undestroy::Config.configure do |config|
83
+ config.abstract_class = ArchiveModelBase
84
+ config.fields = {
85
+ :deleted_at => proc { Time.now },
86
+ :deleted_by_id => proc { User.current.id if User.current }
87
+ }
88
+ end
89
+ ```
90
+
91
+ Options set in this block will be the default for all models with
92
+ undestroy activated. They can be overriden with options passed to the
93
+ `undestroy` method
84
94
 
85
95
  ## Contributing
86
96
 
@@ -1,5 +1,4 @@
1
- require "undestroy/version"
1
+ # Default behavior is to setup binding to ORM
2
+ # If you want a non-intrusive require use 'undestroy/without_binding'
3
+ require 'undestroy/with_binding'
2
4
 
3
- module Undestroy
4
- # Your code goes here...
5
- end
@@ -0,0 +1,36 @@
1
+
2
+ class Undestroy::Archive
3
+ attr_accessor :source, :config, :transfer
4
+
5
+ def initialize(args={})
6
+ validate_arguments(args)
7
+
8
+ self.source = args[:source]
9
+ self.config = args[:config]
10
+ self.transfer = args[:transfer]
11
+ end
12
+
13
+ def transfer
14
+ @transfer ||= self.config.internals[:transfer].new(
15
+ :klass => self.config.target_class,
16
+ :fields => archive_fields
17
+ )
18
+ end
19
+
20
+ def run
21
+ transfer.run
22
+ end
23
+
24
+ protected
25
+
26
+ def archive_fields
27
+ self.config.primitive_fields(self.source).merge(self.source.attributes)
28
+ end
29
+
30
+ def validate_arguments(args)
31
+ unless (args.keys & [:source, :config]).size == 2
32
+ raise ArgumentError, ":source and :config are required keys"
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,8 @@
1
+ module Undestroy::Binding
2
+ autoload :ActiveRecord, 'undestroy/binding/active_record'
3
+
4
+ def self.bind
5
+ Undestroy::Binding::ActiveRecord.add(::ActiveRecord::Base)
6
+ end
7
+ end
8
+
@@ -0,0 +1,63 @@
1
+ require 'active_record'
2
+
3
+ class Undestroy::Binding::ActiveRecord
4
+ attr_accessor :config, :model
5
+
6
+ def initialize(model, options={})
7
+ ensure_is_ar! model
8
+
9
+ self.model = model
10
+ self.config = Undestroy::Config.config.merge(options)
11
+
12
+ set_defaults
13
+ end
14
+
15
+ def before_destroy(instance)
16
+ config.internals[:archive].new(:config => config, :source => instance).run
17
+ end
18
+
19
+ protected
20
+
21
+ def set_defaults
22
+ self.config.source_class = self.model
23
+ self.config.table_name ||= table_prefix + self.model.table_name if self.model.respond_to?(:table_name)
24
+ self.config.target_class ||= create_target_class
25
+ ensure_is_ar! self.config.target_class
26
+ end
27
+
28
+ # Builds a dynamic AR class representing the archival table
29
+ def create_target_class
30
+ Class.new(self.config.abstract_class || ActiveRecord::Base).tap do |target_class|
31
+ target_class.table_name = self.config.table_name
32
+ end
33
+ end
34
+
35
+ def table_prefix
36
+ "archive_"
37
+ end
38
+
39
+ def ensure_is_ar!(klass)
40
+ raise ArgumentError, "#{klass.inspect} must be an ActiveRecord model" unless is_ar?(klass)
41
+ end
42
+
43
+ def is_ar?(klass)
44
+ klass.is_a?(Class) && klass.ancestors.include?(ActiveRecord::Base)
45
+ end
46
+
47
+ # Add binding to the given class if it doesn't already have it
48
+ def self.add(klass=ActiveRecord::Base)
49
+ klass.class_eval do
50
+ class_attribute :undestroy_model_binding, :instance_writer => false
51
+
52
+ def self.undestroy(options={})
53
+ before_destroy do
54
+ self.undestroy_model_binding.before_destroy(self) if undestroy_model_binding
55
+ end unless self.undestroy_model_binding
56
+ self.undestroy_model_binding = Undestroy::Binding::ActiveRecord.new(self, options)
57
+ end
58
+
59
+ end unless klass.respond_to?(:undestroy_model_binding)
60
+ end
61
+
62
+ end
63
+
@@ -0,0 +1,54 @@
1
+ class Undestroy::Config
2
+ OPTIONS = [
3
+ :table_name, :abstract_class, :fields, :migrate,
4
+ :source_class, :target_class, :internals
5
+ ]
6
+ attr_accessor *OPTIONS
7
+
8
+ def initialize(options={})
9
+ self.migrate = true
10
+ self.fields = {
11
+ :deleted_at => proc { Time.now }
12
+ }
13
+ self.internals = {
14
+ :archive => Undestroy::Archive,
15
+ :transfer => Undestroy::Transfer,
16
+ }
17
+
18
+ options.each do |key, value|
19
+ self[key] = value
20
+ end
21
+ end
22
+
23
+ def [](key)
24
+ self.send(key) if OPTIONS.include?(key)
25
+ end
26
+
27
+ def []=(key, value)
28
+ self.send("#{key}=", value) if OPTIONS.include?(key)
29
+ end
30
+
31
+ def to_hash
32
+ OPTIONS.inject({}) { |hash, key| hash.merge(key => self[key]) }
33
+ end
34
+
35
+ def merge(object)
36
+ self.class.new(self.to_hash.merge(object.to_hash))
37
+ end
38
+
39
+ def primitive_fields(object)
40
+ self.fields.inject({}) do |hash, (key, val)|
41
+ hash.merge(key => val.is_a?(Proc) ? val.call(object) : val)
42
+ end
43
+ end
44
+
45
+ def self.configure
46
+ yield(config) if block_given?
47
+ end
48
+
49
+ def self.config
50
+ @config ||= self.new
51
+ end
52
+
53
+ end
54
+
@@ -0,0 +1,22 @@
1
+
2
+ class Undestroy::Transfer
3
+ attr_accessor :target
4
+
5
+ def initialize(args={})
6
+ raise ArgumentError, ":klass option required" unless args[:klass]
7
+ args[:fields] ||= {}
8
+
9
+ self.target = args[:klass].new
10
+
11
+ # Set instance values directly to avoid AR's filtering of protected fields
12
+ args[:fields].each do |field, value|
13
+ self.target[field] = value
14
+ end
15
+ end
16
+
17
+ def run
18
+ self.target.save
19
+ end
20
+
21
+ end
22
+
@@ -1,3 +1,4 @@
1
1
  module Undestroy
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
4
+
@@ -0,0 +1,5 @@
1
+ require 'undestroy/without_binding'
2
+
3
+ # Bind Undestroy to ORM
4
+ Undestroy::Binding.bind
5
+