historical_society 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ db
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.2@historical_society --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in historical_society.gemspec
4
+ gemspec
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2011 New Leaders Ventures, LLC., Robert Bousquet
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,43 @@
1
+ HistoricalSociety -- Preserve the history of your data, starting with soft delete.
2
+ ====================================
3
+
4
+ ## DESCRIPTION
5
+
6
+ The historical society gem currently provides basic soft delete functionality on an ActiveRecord instance through the use
7
+ of a "deleted_at" timestamp column and a default_scope that excludes the soft deleted records.
8
+
9
+ _Note: This currently provides a default_scope that excludes soft deleted records. It also overrides
10
+ the normal #destroy and #delete instance methods so they just set the deleted_at timestamp without
11
+ actually deleting the records from the db._
12
+
13
+ ## INSTALLATION
14
+
15
+ $ gem install historical_society
16
+
17
+ ## USAGE
18
+
19
+ In your model:
20
+
21
+ class User < ActiveRecord::Base
22
+ include HistoricalSociety
23
+ end
24
+
25
+ In your queries:
26
+
27
+ User.all # does not include deleted records
28
+ User.unscoped.all # includes deleted records
29
+
30
+ User.find(1) # raises ActiveRecord::RecordNotFound if user has been soft deleted
31
+ User.unscoped.find(1) # finds user record regardless of soft deletion
32
+
33
+ In relationships:
34
+
35
+ @account.users # finds only users within the account scope that have not been soft deleted
36
+ @account.users.unscoped # CAREFUL! THIS REMOVES THE ACCOUNT SCOPE TOO!
37
+
38
+ # THIS IS PROBABLY WHAT YOU WANT
39
+ # Removes the default scope on user, but then adds the account scope through an explicit where method.
40
+
41
+ User.unscoped.all.where(:account_id => @account.id)
42
+
43
+
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ # If you want to make this the default task
7
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "historical_society/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "historical_society"
7
+ s.version = HistoricalSociety::VERSION
8
+ s.authors = ["Robert Bousquet"]
9
+ s.email = ["rbousquet@newleaders.com"]
10
+ s.homepage = "https://github.com/bousquet/historical_society"
11
+ s.summary = "Preserve the history of a db record"
12
+ s.description = "Currently offers soft deletion, and a default scope that excludes 'deleted' records"
13
+
14
+ s.rubyforge_project = "historical_society"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "rake"
24
+ s.add_development_dependency "sqlite3"
25
+ s.add_runtime_dependency "activerecord", "~> 3.1.0"
26
+ s.add_runtime_dependency "activesupport", "~> 3.1.0"
27
+ end
@@ -0,0 +1,36 @@
1
+ require "historical_society/version"
2
+
3
+ module HistoricalSociety
4
+ extend ActiveSupport::Concern
5
+ # Model must include a deleted_at datetime column, default NULL
6
+ included do
7
+ default_scope lambda {
8
+ field = [self.table_name, "deleted_at"].map{|str|"`#{str}`"}.join(".")
9
+ where(["#{field} IS NULL OR (#{field} IS NOT NULL AND #{field} > ?)", historical_time])
10
+ }
11
+ end
12
+
13
+ module ClassMethods
14
+ def historical_time
15
+ (Time.zone || Time).now
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+
21
+ def destroy
22
+ set_deleted_at
23
+ end
24
+
25
+ def delete
26
+ set_deleted_at
27
+ end
28
+
29
+ private
30
+
31
+ def set_deleted_at
32
+ update_attribute(:deleted_at, self.class.historical_time)
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module HistoricalSociety
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'active_record'
5
+ require 'active_support'
6
+
7
+ require 'historical_society'
8
+
9
+ RSpec.configure do |config|
10
+
11
+ root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
12
+
13
+ ActiveRecord::Base.establish_connection(
14
+ :adapter => "sqlite3",
15
+ :database => "#{root}/db/historical_society.db"
16
+ )
17
+
18
+ ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'users'")
19
+ ActiveRecord::Base.connection.create_table(:users) do |t|
20
+ t.string :name
21
+ t.datetime :deleted_at
22
+ end
23
+
24
+ class User < ActiveRecord::Base
25
+ include HistoricalSociety
26
+ end
27
+
28
+ config.before(:each) do
29
+ ActiveRecord::Base.connection.increment_open_transactions
30
+ ActiveRecord::Base.connection.begin_db_transaction
31
+ end
32
+
33
+ config.after(:each) do
34
+ ActiveRecord::Base.connection.rollback_db_transaction
35
+ ActiveRecord::Base.connection.decrement_open_transactions
36
+ end
37
+
38
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ describe User do
3
+
4
+ describe "User queries with paranoia" do
5
+ let!(:user) { User.create(:name => "Bob") }
6
+ let!(:deleted_user) { User.create(:name => "Steve", :deleted_at => 5.minutes.ago) }
7
+
8
+ it "should be in default scope" do
9
+ User.all.should include(user)
10
+ end
11
+
12
+ it "should not include undeleted user" do
13
+ User.all.should_not include(deleted_user)
14
+ end
15
+
16
+ it "should have deleted users in unscoped queries" do
17
+ User.unscoped.all.should include(user)
18
+ User.unscoped.all.should include(deleted_user)
19
+ end
20
+ end
21
+
22
+ describe "User deletion with paranoia" do
23
+ let!(:user) { User.create(:name => "Bob") }
24
+
25
+ before :each do
26
+ user.destroy
27
+ end
28
+
29
+ it "should have deleted_at time" do
30
+ user.deleted_at.is_a?(Time).should be_true
31
+ end
32
+
33
+ it "should not be findable by default" do
34
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
35
+ end
36
+
37
+ it "should be findable via unscoped" do
38
+ expect { User.unscoped.find(user.id) }.to_not raise_error(ActiveRecord::RecordNotFound)
39
+ end
40
+ end
41
+
42
+ describe "User with another default_scope" do
43
+ before :each do
44
+ class User < ActiveRecord::Base
45
+ include HistoricalSociety
46
+ default_scope order(:name)
47
+ end
48
+ end
49
+
50
+ it "should combine default scopes" do
51
+ User.scoped.to_sql.should include("ORDER BY name")
52
+ User.scoped.to_sql.should include("`users`.`deleted_at` IS NOT NULL")
53
+ end
54
+ end
55
+
56
+
57
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: historical_society
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Robert Bousquet
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-12 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70115113351260 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70115113351260
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70115113350340 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70115113350340
36
+ - !ruby/object:Gem::Dependency
37
+ name: sqlite3
38
+ requirement: &70115113349880 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70115113349880
47
+ - !ruby/object:Gem::Dependency
48
+ name: activerecord
49
+ requirement: &70115113349320 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 3.1.0
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70115113349320
58
+ - !ruby/object:Gem::Dependency
59
+ name: activesupport
60
+ requirement: &70115113348560 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 3.1.0
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70115113348560
69
+ description: Currently offers soft deletion, and a default scope that excludes 'deleted'
70
+ records
71
+ email:
72
+ - rbousquet@newleaders.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - .rspec
79
+ - .rvmrc
80
+ - Gemfile
81
+ - MIT-LICENSE
82
+ - README.md
83
+ - Rakefile
84
+ - historical_society.gemspec
85
+ - lib/historical_society.rb
86
+ - lib/historical_society/version.rb
87
+ - spec/spec_helper.rb
88
+ - spec/user_spec.rb
89
+ homepage: https://github.com/bousquet/historical_society
90
+ licenses: []
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project: historical_society
109
+ rubygems_version: 1.8.6
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Preserve the history of a db record
113
+ test_files:
114
+ - spec/spec_helper.rb
115
+ - spec/user_spec.rb