jchupp-is_paranoid 0.0.1

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,19 @@
1
+ Copyright (c) 2009 Jeffrey Chupp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all 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,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,68 @@
1
+ h1. is_paranoid ( same as it ever was )
2
+
3
+ h3. and you may ask yourself, well, how did I get here?
4
+
5
+ Sometimes you want to delete something in ActiveRecord, but you realize you might need it later (for an undo feature, or just as a safety net, etc.). There are a plethora of plugins that accomplish this, the most famous of which is the venerable acts_as_paranoid which is great but not really actively developed any more. What's more, acts_as_paranoid was written for an older version of ActiveRecord and, with default_scope in 2.3, it is now possible to do the same thing with significantly less complexity. Thus, *is_paranoid*.
6
+
7
+ h3. and you may ask yourself, how do I work this?
8
+
9
+ You should read the specs, or the RDOC, or even the source itself (which is very readable), but for the lazy, here's the hand-holding:
10
+
11
+ You need ActiveRecord 2.3 and you need to properly install this gem. Then you need a model with a deleted_at timestamp column on its database table. If that column is null, the item isn't deleted. If it has a timestamp, it should count as deleted.
12
+
13
+ So let's assume we have a model Automobile that has a deleted_at column on the automobiles table.
14
+
15
+ If you're working with Rails, in your environment.rb, add the following to your initializer block.
16
+
17
+ <pre>
18
+ Rails::Initializer.run do |config|
19
+ # ...
20
+ config.gem "jchupp-is_paranoid", :lib => 'is_paranoid', :version => ">= 0.0.1"
21
+ end
22
+ </pre>
23
+
24
+ Then in your ActiveRecord model
25
+
26
+ <pre>
27
+ class Automobile < ActiveRecord::Base
28
+ is_paranoid
29
+ end
30
+ </pre>
31
+
32
+ Now our automobiles are now soft-deleteable.
33
+
34
+ <pre>
35
+ that_large_automobile = Automobile.create()
36
+ Automobile.count # => 1
37
+
38
+ that_large_automobile.destroy
39
+ Automobile.count # => 0
40
+ Automobile.count_with_deleted # => 1
41
+
42
+ # where is that large automobile?
43
+ that_large_automobile = Automobile.find_with_deleted(:all).first
44
+ that_large_automobile.restore
45
+ Automobile.count # => 1
46
+ </pre>
47
+
48
+ One thing to note, destroying is always undo-able, but deleting is not.
49
+
50
+ <pre>
51
+ Automobile.destroy_all
52
+ Automobile.count # => 0
53
+ Automobile.count_with_deleted # => 1
54
+
55
+ Automobile.delete_all
56
+ Automobile.count_with_deleted # => 0
57
+ # And you may tell yourself, "My god! What have I done?"
58
+ </pre>
59
+
60
+ h3. and you may ask yourself, where does that highway go to?
61
+
62
+ If you find any bugs, have any ideas of features you think are missing, or find things you're like to see work differently, feel free to send me a message or a pull request.
63
+
64
+ h3. Thanks
65
+
66
+ Thanks to Rick Olson for acts_as_paranoid which is obviously an inspiration in concept and execution, Ryan Bates for mentioning the idea of using default_scope for this on Ryan Daigle's "post introducing default_scope":defscope, and the Talking Heads for being the Talking Heads.
67
+
68
+ [defscope]http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "spec"
2
+ require "spec/rake/spectask"
3
+ require 'lib/is_paranoid.rb'
4
+
5
+ Spec::Rake::SpecTask.new do |t|
6
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
7
+ t.spec_files = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ task :install do
11
+ rm_rf "*.gem"
12
+ puts `gem build is_paranoid.gemspec`
13
+ puts `sudo gem install is_paranoid-0.0.1.gem`
14
+ end
@@ -0,0 +1,72 @@
1
+ require 'activerecord'
2
+
3
+ module IsParanoid
4
+ def self.included(base) # :nodoc:
5
+ base.extend SafetyNet
6
+ end
7
+
8
+ module SafetyNet
9
+ # Call this in your model to enable all the safety-net goodness
10
+ #
11
+ # Example:
12
+ #
13
+ # class Android < ActiveRecord::Base
14
+ # is_paranoid
15
+ # end
16
+ def is_paranoid
17
+ class_eval do
18
+ # This is the real magic. All calls made to this model will append
19
+ # the conditions deleted_at => nil. Exceptions require using
20
+ # exclusive_scope (see self.delete_all, self.count_with_deleted,
21
+ # and self.find_with_deleted )
22
+ default_scope :conditions => {:deleted_at => nil}
23
+
24
+ # Actually delete the model, bypassing the safety net. Because
25
+ # this method is called internally by Model.delete and on the
26
+ # delete method in each instance, we don't need to specify those
27
+ # methods separately
28
+ def self.delete_all conditions = nil
29
+ self.with_exclusive_scope do
30
+ super conditions
31
+ end
32
+ end
33
+
34
+ # Return a count that includes the soft-deleted models.
35
+ def self.count_with_deleted *args
36
+ self.with_exclusive_scope { count(*args) }
37
+ end
38
+
39
+ # Return instances of all models matching the query regardless
40
+ # of whether or not they have been soft-deleted.
41
+ def self.find_with_deleted *args
42
+ self.with_exclusive_scope { find(*args) }
43
+ end
44
+
45
+ # Mark the model deleted_at as now.
46
+ def destroy_without_callbacks
47
+ self.update_attribute(:deleted_at, Time.now.utc)
48
+ end
49
+
50
+ # Override the default destroy to allow us to flag deleted_at.
51
+ # This preserves the before_destroy and after_destroy callbacks.
52
+ # Because this is also called internally by Model.destroy_all and
53
+ # the destroy Model.destroy, we don't need to specify those methods
54
+ # separately.
55
+ def destroy
56
+ return false if callback(:before_destroy) == false
57
+ result = destroy_without_callbacks
58
+ callback(:after_destroy)
59
+ result
60
+ end
61
+
62
+ # Set deleted_at flag on a model to nil, effectively undoing the
63
+ # soft-deletion.
64
+ def restore
65
+ self.update_attribute(:deleted_at, nil)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ ActiveRecord::Base.send(:include, IsParanoid)
@@ -0,0 +1,76 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class Person < ActiveRecord::Base
4
+ has_many :androids, :foreign_key => :owner_id, :dependent => :destroy
5
+ end
6
+
7
+ class Android < ActiveRecord::Base
8
+ is_paranoid
9
+ end
10
+
11
+ describe Android do
12
+ before(:each) do
13
+ Android.delete_all
14
+ Person.delete_all
15
+
16
+ @luke = Person.create(:name => 'Luke Skywalker')
17
+ @r2d2 = Android.create(:name => 'R2D2', :owner_id => @luke.id)
18
+ @c3p0 = Android.create(:name => 'C3P0', :owner_id => @luke.id)
19
+ end
20
+
21
+ it "should delete normally" do
22
+ Android.count_with_deleted.should == 2
23
+ Android.delete_all
24
+ Android.count_with_deleted.should == 0
25
+ end
26
+
27
+ it "should handle Model.destroy_all properly" do
28
+ lambda{
29
+ Android.destroy_all("owner_id = #{@luke.id}")
30
+ }.should change(Android, :count).from(2).to(0)
31
+ Android.count_with_deleted.should == 2
32
+ end
33
+
34
+ it "should handle Model.destroy(id) properly" do
35
+ lambda{
36
+ Android.destroy(@r2d2.id)
37
+ }.should change(Android, :count).from(2).to(1)
38
+
39
+ Android.count_with_deleted.should == 2
40
+ end
41
+
42
+ it "should be not show up in the relationship to the owner once deleted" do
43
+ @luke.androids.size.should == 2
44
+ @r2d2.destroy
45
+ @luke.androids.size.should == 1
46
+ Android.count.should == 1
47
+ Android.first(:conditions => {:name => 'R2D2'}).should be_blank
48
+ end
49
+
50
+ it "should be able to find deleted items via find_with_deleted" do
51
+ @r2d2.destroy
52
+ Android.find(:first, :conditions => {:name => 'R2D2'}).should be_blank
53
+ Android.find_with_deleted(:first, :conditions => {:name => 'R2D2'}).should_not be_blank
54
+ end
55
+
56
+ it "should have a proper count inclusively and exclusively of deleted items" do
57
+ @r2d2.destroy
58
+ @c3p0.destroy
59
+ Android.count.should == 0
60
+ Android.count_with_deleted.should == 2
61
+ end
62
+
63
+ it "should mark deleted on dependent destroys" do
64
+ lambda{
65
+ @luke.destroy
66
+ }.should change(Android, :count).from(2).to(0)
67
+ Android.count_with_deleted.should == 2
68
+ end
69
+
70
+ it "should allow restoring" do
71
+ @r2d2.destroy
72
+ lambda{
73
+ @r2d2.restore
74
+ }.should change(Android, :count).from(1).to(2)
75
+ end
76
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ test:
2
+ :adapter: sqlite3
3
+ :dbfile: is_paranoid.db
data/spec/schema.rb ADDED
@@ -0,0 +1,15 @@
1
+ ActiveRecord::Schema.define(:version => 20090317164830) do
2
+ create_table "androids", :force => true do |t|
3
+ t.string "name"
4
+ t.integer "owner_id"
5
+ t.datetime "deleted_at"
6
+ t.datetime "created_at"
7
+ t.datetime "updated_at"
8
+ end
9
+
10
+ create_table "people", :force => true do |t|
11
+ t.string "name"
12
+ t.datetime "created_at"
13
+ t.datetime "updated_at"
14
+ end
15
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require "#{File.dirname(__FILE__)}/../lib/is_paranoid"
3
+ require 'activerecord'
4
+ require 'yaml'
5
+ require 'spec'
6
+
7
+ def connect(environment)
8
+ conf = YAML::load(File.open(File.dirname(__FILE__) + '/database.yml'))
9
+ ActiveRecord::Base.establish_connection(conf[environment])
10
+ end
11
+
12
+ # Open ActiveRecord connection
13
+ connect('test')
14
+ load(File.dirname(__FILE__) + "/schema.rb")
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jchupp-is_paranoid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jeffrey Chupp
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-20 00:00:00 -07: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.0
24
+ version:
25
+ description:
26
+ email: jeff@semanticart.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/is_paranoid.rb
35
+ - README.textile
36
+ - Rakefile
37
+ - MIT-LICENSE
38
+ - spec/android_spec.rb
39
+ - spec/database.yml
40
+ - spec/spec.opts
41
+ - spec/spec_helper.rb
42
+ - spec/schema.rb
43
+ has_rdoc: true
44
+ homepage: http://github.com/jchupp/is_paranoid/
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.2.0
66
+ signing_key:
67
+ specification_version: 2
68
+ summary: ActiveRecord 2.3 compatible gem "allowing you to hide and restore records without actually deleting them." Yes, like acts_as_paranoid, only with less code and less complexity.
69
+ test_files: []
70
+