undestroy 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+