deactivatable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +21 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/lib/deactivatable.rb +118 -0
- data/test/deactivatable_dependency.rb +5 -0
- data/test/deactivatable_item.rb +7 -0
- data/test/deactivatable_test.rb +94 -0
- data/test/test_helper.rb +28 -0
- metadata +88 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Greg Fitzgerald
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
= deactivatable
|
2
|
+
|
3
|
+
Deactivatable provides methods and a default_scope to allow ActiveRecord objects to be deactivated instead of deleted.
|
4
|
+
This is useful if an object needs to be removed from general use, but it's data needs to be retained.
|
5
|
+
Additionally, Deactivatable provides the ability to specify dependencies which also need to be deactivated.
|
6
|
+
Deactivation is determined by populating a deactivated_at field with the date time at which deactivation happened.
|
7
|
+
|
8
|
+
== Note on Patches/Pull Requests
|
9
|
+
|
10
|
+
* Fork the project.
|
11
|
+
* Make your feature addition or bug fix.
|
12
|
+
* Add tests for it. This is important so I don't break it in a
|
13
|
+
future version unintentionally.
|
14
|
+
* Commit, do not mess with rakefile, version, or history.
|
15
|
+
(if you want to have your own version, that is fine but
|
16
|
+
bump version in a commit by itself I can ignore when I pull)
|
17
|
+
* Send me a pull request. Bonus points for topic branches.
|
18
|
+
|
19
|
+
== Copyright
|
20
|
+
|
21
|
+
Copyright (c) 2009 Greg Fitzgerald. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "deactivatable"
|
8
|
+
gem.summary = %Q{Adds methods and scopes to ActiveRecord objects to allow deactivation instead of deletion.}
|
9
|
+
gem.description = %Q{Deactivatable provides methods and a default_scope to allow ActiveRecord objects to be deactivated instead of deleted.
|
10
|
+
This is useful if an object needs to be removed from general use, but it's data needs to be retained.
|
11
|
+
Additionally, Deactivatable provides the ability to specify dependencies which also need to be deactivated.
|
12
|
+
Deactivation is determined by populating a deactivated_at field with the date time at which deactivation happened.
|
13
|
+
}
|
14
|
+
gem.email = "greg_fitz@yahoo.com"
|
15
|
+
gem.homepage = "http://github.com/gregfitz23/deactivatable"
|
16
|
+
gem.authors = ["Greg Fitzgerald"]
|
17
|
+
gem.add_dependency "activerecord", ">= 2.3"
|
18
|
+
gem.add_development_dependency "thoughtbot-shoulda"
|
19
|
+
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
Rake::TestTask.new(:test) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = 'test/**/*_test.rb'
|
30
|
+
test.verbose = true
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
require 'rcov/rcovtask'
|
35
|
+
Rcov::RcovTask.new do |test|
|
36
|
+
test.libs << 'test'
|
37
|
+
test.pattern = 'test/**/*_test.rb'
|
38
|
+
test.verbose = true
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
task :rcov do
|
42
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
task :test => :check_dependencies
|
47
|
+
|
48
|
+
task :default => :test
|
49
|
+
|
50
|
+
require 'rake/rdoctask'
|
51
|
+
Rake::RDocTask.new do |rdoc|
|
52
|
+
if File.exist?('VERSION')
|
53
|
+
version = File.read('VERSION')
|
54
|
+
else
|
55
|
+
version = ""
|
56
|
+
end
|
57
|
+
|
58
|
+
rdoc.rdoc_dir = 'rdoc'
|
59
|
+
rdoc.title = "deactivatable #{version}"
|
60
|
+
rdoc.rdoc_files.include('README*')
|
61
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
62
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Acts
|
3
|
+
module Deactivatable
|
4
|
+
|
5
|
+
def self.append_features(base) #:nodoc:
|
6
|
+
super
|
7
|
+
base.extend(Definition)
|
8
|
+
end
|
9
|
+
|
10
|
+
module Definition
|
11
|
+
# Define the calling class as being deactivatable.
|
12
|
+
# A call to this will set the default scope of the object to look for deactivated_at = nil.
|
13
|
+
# Options
|
14
|
+
# *:dependencies* => A list of symbols specifying any associations that are also deactivatable. (This associations must separately be defined with acts_as_deactivatable).
|
15
|
+
#
|
16
|
+
def acts_as_deactivatable(options={})
|
17
|
+
extend ActiveRecord::Acts::Deactivatable::ClassMethods
|
18
|
+
include ActiveRecord::Acts::Deactivatable::InstanceMethods
|
19
|
+
|
20
|
+
default_scope :conditions => {:deactivated_at => nil}
|
21
|
+
|
22
|
+
@deactivatable_options = options
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
|
29
|
+
def deactivatable_options
|
30
|
+
@deactivatable_options || {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def deactivated_dependencies
|
34
|
+
@deactivated_dependencies ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
# Yields to a block, executing that block after removing the deactivated_at scope.
|
38
|
+
#
|
39
|
+
def with_deactivated_objects_scope
|
40
|
+
with_exclusive_scope do
|
41
|
+
with_scope(:find => {:conditions => "`#{self.table_name}`.`deactivated_at` IS NOT NULL"}) do
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module InstanceMethods
|
49
|
+
|
50
|
+
# Deactivate this object, and any associated objects as specified at definition time.
|
51
|
+
#
|
52
|
+
def deactivate!
|
53
|
+
with_transaction do
|
54
|
+
self.deactivated_at = Time.now
|
55
|
+
deactivate_dependencies
|
56
|
+
self.save!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Activate this object, and any associated objects as specified at definition time.
|
61
|
+
#
|
62
|
+
def activate!
|
63
|
+
with_transaction do
|
64
|
+
self.deactivated_at = nil
|
65
|
+
activate_dependencies
|
66
|
+
self.save!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def deactivated?
|
71
|
+
deactivated_at?
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
# Iterate the list of associated objects that need to be deactivated, and deactivate each of them.
|
76
|
+
#
|
77
|
+
def deactivate_dependencies
|
78
|
+
traverse_dependencies(:deactivate!)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Iterate the list of associated objects that need to be activated, and activate each of them.
|
82
|
+
#
|
83
|
+
def activate_dependencies
|
84
|
+
traverse_dependencies(:activate!)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Traverse the list of dependencies, executing *method* on each of them.
|
88
|
+
#
|
89
|
+
def traverse_dependencies(method)
|
90
|
+
if dependencies = self.class.deactivatable_options[:dependencies]
|
91
|
+
dependencies.each { |dependency_name| execute_on_dependency(dependency_name, method) }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Find the dependency indicated by *dependency_name* and execute *method* on it.
|
96
|
+
# Execution must be wrapped in the dependency's with_deactivated_objects_scope for activate! to work.
|
97
|
+
#
|
98
|
+
def execute_on_dependency(dependency_name, method)
|
99
|
+
self.class.reflections[dependency_name].klass.send(:with_exclusive_scope) do
|
100
|
+
dependency = self.__send__(dependency_name)
|
101
|
+
dependency.respond_to?(:map) ? dependency.map(&method) : dependency.__send__(method)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def with_transaction
|
106
|
+
self.class.transaction do
|
107
|
+
yield
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
ActiveRecord::Base.class_eval do
|
117
|
+
include ActiveRecord::Acts::Deactivatable
|
118
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class DeactivatableTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "An inactive item, @item" do
|
6
|
+
setup do
|
7
|
+
@inactive_item = DeactivatableItem.new
|
8
|
+
@inactive_item.deactivated_at = Time.now
|
9
|
+
@inactive_item.save!
|
10
|
+
end
|
11
|
+
|
12
|
+
should "not be returned on find" do
|
13
|
+
assert !DeactivatableItem.exists?(@inactive_item.id)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "be findable in using the deactivated_objects_scope" do
|
17
|
+
assert DeactivatableItem.with_deactivated_objects_scope { DeactivatableItem.exists?(@inactive_item.id) }
|
18
|
+
end
|
19
|
+
|
20
|
+
should "return true on deactivated?" do
|
21
|
+
assert @inactive_item.deactivated?
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when activated" do
|
25
|
+
setup do
|
26
|
+
@inactive_item.activate!
|
27
|
+
end
|
28
|
+
|
29
|
+
should "be findable" do
|
30
|
+
assert DeactivatableItem.exists?(@inactive_item.id)
|
31
|
+
end
|
32
|
+
end #when reactivated
|
33
|
+
end #An inactive item, @inactive_item
|
34
|
+
|
35
|
+
context "An active item, @item" do
|
36
|
+
setup do
|
37
|
+
@item = DeactivatableItem.create!
|
38
|
+
end
|
39
|
+
|
40
|
+
should "have a null deactivated_at" do
|
41
|
+
assert_nil @item.deactivated_at
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when deactivated" do
|
45
|
+
setup do
|
46
|
+
@item.deactivate!
|
47
|
+
end
|
48
|
+
|
49
|
+
should "set deactivated_at" do
|
50
|
+
assert_not_nil @item.deactivated_at
|
51
|
+
end
|
52
|
+
|
53
|
+
should "not be findable" do
|
54
|
+
assert !DeactivatableItem.exists?(@item.id)
|
55
|
+
end
|
56
|
+
end #when deactivated
|
57
|
+
|
58
|
+
context "with dependencies, @dependencies" do
|
59
|
+
setup do
|
60
|
+
@dependencies = (0..5).map { DeactivatableDependency.new }
|
61
|
+
@item.deactivatable_dependencies = @dependencies
|
62
|
+
end
|
63
|
+
|
64
|
+
context "on a call to deactivate!" do
|
65
|
+
setup do
|
66
|
+
@item.deactivate!
|
67
|
+
end
|
68
|
+
|
69
|
+
should "render dependency unfindable" do
|
70
|
+
@dependencies.each { |dependency| assert !DeactivatableDependency.exists?(dependency.id) }
|
71
|
+
end
|
72
|
+
|
73
|
+
should "set deactivate on dependency post" do
|
74
|
+
DeactivatableDependency.send(:with_exclusive_scope) do
|
75
|
+
@dependencies.map(&:reload)
|
76
|
+
@dependencies.each {|dependency| assert_not_nil(dependency.deactivated_at) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when reactivated" do
|
81
|
+
setup do
|
82
|
+
@item.activate!
|
83
|
+
end
|
84
|
+
|
85
|
+
should "reactivate all dependencies" do
|
86
|
+
@dependencies.each { |dependency| assert(DeactivatableDependency.exists?(dependency.id)) }
|
87
|
+
end
|
88
|
+
end #when reactivated
|
89
|
+
|
90
|
+
end #on a call to deactivate!
|
91
|
+
end #with dependencies, @dependencies
|
92
|
+
end #An active item, @item
|
93
|
+
|
94
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
require 'activerecord'
|
8
|
+
require 'deactivatable'
|
9
|
+
require 'deactivatable_dependency'
|
10
|
+
require 'deactivatable_item'
|
11
|
+
|
12
|
+
class Test::Unit::TestCase
|
13
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
14
|
+
|
15
|
+
ActiveRecord::Schema.define(:version => 1) do
|
16
|
+
create_table :deactivatable_items do |t|
|
17
|
+
t.datetime :deactivated_at
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveRecord::Schema.define(:version => 1) do
|
22
|
+
create_table :deactivatable_dependencies do |t|
|
23
|
+
t.datetime :deactivated_at
|
24
|
+
t.belongs_to :deactivatable_item
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deactivatable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Greg Fitzgerald
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-06 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activerecord
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "2.3"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thoughtbot-shoulda
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: "Deactivatable provides methods and a default_scope to allow ActiveRecord objects to be deactivated instead of deleted.\n This is useful if an object needs to be removed from general use, but it's data needs to be retained.\n Additionally, Deactivatable provides the ability to specify dependencies which also need to be deactivated.\n Deactivation is determined by populating a deactivated_at field with the date time at which deactivation happened.\n "
|
36
|
+
email: greg_fitz@yahoo.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- lib/deactivatable.rb
|
52
|
+
- test/deactivatable_dependency.rb
|
53
|
+
- test/deactivatable_item.rb
|
54
|
+
- test/deactivatable_test.rb
|
55
|
+
- test/test_helper.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://github.com/gregfitz23/deactivatable
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options:
|
62
|
+
- --charset=UTF-8
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.5
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Adds methods and scopes to ActiveRecord objects to allow deactivation instead of deletion.
|
84
|
+
test_files:
|
85
|
+
- test/deactivatable_dependency.rb
|
86
|
+
- test/deactivatable_item.rb
|
87
|
+
- test/deactivatable_test.rb
|
88
|
+
- test/test_helper.rb
|