deleted_at 0.3.0 → 0.4.0rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +77 -15
- data/.travis.yml +36 -12
- data/Gemfile +16 -1
- data/README.md +6 -1
- data/Rakefile +3 -3
- data/deleted_at.gemspec +14 -17
- data/gemfiles/activerecord-4.0.Gemfile +3 -0
- data/gemfiles/activerecord-4.1.Gemfile +3 -0
- data/gemfiles/activerecord-4.2.Gemfile +3 -0
- data/gemfiles/activerecord-5.0.Gemfile +3 -0
- data/gemfiles/activerecord-5.1.Gemfile +3 -0
- data/lib/deleted_at.rb +23 -2
- data/lib/deleted_at/active_record/base.rb +46 -105
- data/lib/deleted_at/active_record/connection_adapters/abstract/schema_definition.rb +17 -0
- data/lib/deleted_at/active_record/relation.rb +10 -57
- data/lib/deleted_at/version.rb +1 -1
- data/lib/deleted_at/views.rb +24 -20
- data/spec/deleted_at/active_record/base_spec.rb +21 -0
- data/spec/deleted_at/active_record/relation_spec.rb +166 -0
- data/spec/deleted_at/views_spec.rb +76 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/rails/app/models/animals/dog.rb +5 -0
- data/spec/support/rails/app/models/comment.rb +6 -0
- data/spec/support/rails/app/models/post.rb +7 -0
- data/spec/support/rails/app/models/user.rb +7 -0
- data/spec/support/rails/config/database.yml +4 -0
- data/spec/support/rails/config/routes.rb +3 -0
- data/spec/support/rails/db/schema.rb +27 -0
- data/spec/support/rails/log/.gitignore +1 -0
- data/spec/support/rails/public/favicon.ico +0 -0
- metadata +57 -60
- data/bin/console +0 -10
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c164168eb496804835ab5f48676d72a12fb549dccb649b0249355403db9b5b7e
|
4
|
+
data.tar.gz: 6e684b4ef6a4146f40b6a0684b5aa18df975894df8da62ec62907d34bece3ec9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f16daa013ef9ba2c06a42dea4ed2cd1d3e4008d419daa987aa01224408c1a11dd2d38ad93c5685119acfc28b46db4de240ff66ab93a629a7feec2dc632ddf905
|
7
|
+
data.tar.gz: 916c6f5c3e8cf4ebabcb628b23e4015fb16f3c83eb57ef5c393bdfafdb16973149dbedafcf3cfba593bd68f7da6f6dd39200c527ce3c9555fec88d303081bcd2
|
data/.gitignore
CHANGED
@@ -1,16 +1,78 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
/
|
9
|
-
/
|
10
|
-
|
11
|
-
|
1
|
+
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile ~/.gitignore_global
|
6
|
+
|
7
|
+
# Database config and secrets
|
8
|
+
/config/database.yml
|
9
|
+
/config/secrets.yml
|
10
|
+
|
11
|
+
# Ignore bundler config
|
12
|
+
/.bundle
|
13
|
+
|
14
|
+
# Ignore client credentials
|
15
|
+
/config/client_api_credentials.yml
|
16
|
+
|
17
|
+
# Ignore the default SQLite database.
|
18
|
+
/db/*.sqlite3
|
19
|
+
|
20
|
+
# Ignore all logfiles and tempfiles.
|
21
|
+
/log/*.log
|
22
|
+
/tmp
|
23
|
+
/db/structure.sql
|
24
|
+
/doc/app/*
|
25
|
+
/vendor/cldr/*
|
26
|
+
/public/uploads/*
|
27
|
+
/public/photo/*
|
28
|
+
/public/test/*
|
29
|
+
/test/assets/*
|
30
|
+
/spec/assets/*
|
31
|
+
/public/assets/**
|
32
|
+
.powenv
|
33
|
+
.rvmrc
|
34
|
+
.env
|
35
|
+
.ruby-version
|
36
|
+
|
37
|
+
# Compiled source #
|
38
|
+
###################
|
39
|
+
/build/
|
40
|
+
*.com
|
41
|
+
*.class
|
42
|
+
*.dll
|
43
|
+
*.exe
|
12
44
|
*.o
|
13
|
-
*.
|
14
|
-
|
15
|
-
|
16
|
-
|
45
|
+
*.so
|
46
|
+
|
47
|
+
# Packages #
|
48
|
+
############
|
49
|
+
# it's better to unpack these files and commit the raw source
|
50
|
+
# git has its own built in compression methods
|
51
|
+
*.7z
|
52
|
+
*.dmg
|
53
|
+
*.gz
|
54
|
+
*.iso
|
55
|
+
*.jar
|
56
|
+
*.rar
|
57
|
+
*.tar
|
58
|
+
*.zip
|
59
|
+
|
60
|
+
# Logs and databases #
|
61
|
+
######################
|
62
|
+
*.log
|
63
|
+
*.sql
|
64
|
+
*.sqlite
|
65
|
+
|
66
|
+
# OS generated files #
|
67
|
+
######################
|
68
|
+
.DS_Store
|
69
|
+
.DS_Store?
|
70
|
+
._*
|
71
|
+
.Spotlight-V100
|
72
|
+
.Trashes
|
73
|
+
Icon?
|
74
|
+
ehthumbs.db
|
75
|
+
Thumbs.db
|
76
|
+
/Gemfile.lock
|
77
|
+
/coverage
|
78
|
+
/.editorconfig
|
data/.travis.yml
CHANGED
@@ -1,19 +1,43 @@
|
|
1
1
|
language: ruby
|
2
2
|
sudo: false
|
3
|
-
|
4
|
-
- 2.0
|
5
|
-
- 2.1
|
6
|
-
- 2.2
|
7
|
-
- 2.3.1
|
8
|
-
- 2.4.0
|
3
|
+
|
9
4
|
cache: bundler
|
10
5
|
script:
|
11
6
|
- bundle exec rspec
|
12
|
-
|
13
|
-
|
7
|
+
|
8
|
+
after_success:
|
9
|
+
- bundle exec codeclimate-test-reporter
|
10
|
+
|
14
11
|
addons:
|
15
|
-
postgresql: "9.
|
12
|
+
postgresql: "9.3"
|
13
|
+
|
14
|
+
rvm:
|
15
|
+
- 2.4
|
16
|
+
- 2.3
|
17
|
+
- 2.2
|
18
|
+
- 2.1
|
19
|
+
- 2.0
|
20
|
+
|
21
|
+
gemfile:
|
22
|
+
- gemfiles/activerecord-5.1.Gemfile
|
23
|
+
- gemfiles/activerecord-5.0.Gemfile
|
24
|
+
- gemfiles/activerecord-4.2.Gemfile
|
25
|
+
- gemfiles/activerecord-4.1.Gemfile
|
26
|
+
- gemfiles/activerecord-4.0.Gemfile
|
27
|
+
|
28
|
+
matrix:
|
29
|
+
exclude:
|
30
|
+
- rvm: 2.0
|
31
|
+
gemfile: gemfiles/activerecord-5.0.Gemfile
|
32
|
+
- rvm: 2.0
|
33
|
+
gemfile: gemfiles/activerecord-5.1.Gemfile
|
34
|
+
|
35
|
+
- rvm: 2.1
|
36
|
+
gemfile: gemfiles/activerecord-5.0.Gemfile
|
37
|
+
- rvm: 2.1
|
38
|
+
gemfile: gemfiles/activerecord-5.1.Gemfile
|
16
39
|
|
17
|
-
|
18
|
-
|
19
|
-
-
|
40
|
+
- rvm: 2.4
|
41
|
+
gemfile: gemfiles/activerecord-4.0.Gemfile
|
42
|
+
- rvm: 2.4
|
43
|
+
gemfile: gemfiles/activerecord-4.1.Gemfile
|
data/Gemfile
CHANGED
@@ -1,4 +1,19 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in deleted_at.gemspec
|
4
3
|
gemspec
|
4
|
+
|
5
|
+
group :test do
|
6
|
+
|
7
|
+
# Generates coverage stats of specs
|
8
|
+
gem 'simplecov'
|
9
|
+
|
10
|
+
# Publishes coverage to codeclimate
|
11
|
+
gem 'codeclimate-test-reporter'
|
12
|
+
|
13
|
+
gem 'rspec'
|
14
|
+
|
15
|
+
gem 'database_cleaner'
|
16
|
+
|
17
|
+
gem 'combustion'
|
18
|
+
|
19
|
+
end
|
data/README.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
|
1
|
+
[![Version ](https://img.shields.io/gem/v/deleted_at.svg?maxAge=2592000)](https://rubygems.org/gems/deleted_at)
|
2
|
+
[![Build Status ](https://travis-ci.org/TwilightCoders/deleted_at.svg)](https://travis-ci.org/TwilightCoders/deleted_at)
|
3
|
+
[![Code Climate ](https://api.codeclimate.com/v1/badges/762cdcd63990efa768b0/maintainability)](https://codeclimate.com/github/TwilightCoders/deleted_at)
|
4
|
+
[![Test Coverage](https://codeclimate.com/github/TwilightCoders/deleted_at/badges/coverage.svg)](https://codeclimate.com/github/TwilightCoders/deleted_at/coverage)
|
5
|
+
|
6
|
+
# DeletedAt
|
2
7
|
|
3
8
|
Deleting data is never good. A common solution is to use `default_scope`, but conventional wisdom (and for good reason) deams this a bad practice. So how do we achieve the same effect with minimal intervention. What we're looking for is the cliche "clean" solution.
|
4
9
|
|
data/Rakefile
CHANGED
data/deleted_at.gemspec
CHANGED
@@ -22,24 +22,21 @@ Gem::Specification.new do |spec|
|
|
22
22
|
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
23
23
|
end
|
24
24
|
|
25
|
-
spec.files = `git ls-files -z`.split("\x0")
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
spec.
|
30
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
-
spec.require_paths = ["lib"]
|
25
|
+
spec.files = `git ls-files -z`.split("\x0")
|
26
|
+
spec.bindir = 'bin'
|
27
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
28
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
29
|
+
spec.require_paths = ['lib', 'spec']
|
32
30
|
|
33
|
-
rails_versions = ['>= 4
|
34
|
-
spec.required_ruby_version = '>= 2.0
|
31
|
+
rails_versions = ['>= 4', '< 6']
|
32
|
+
spec.required_ruby_version = '>= 2.0'
|
35
33
|
|
36
34
|
spec.add_runtime_dependency 'pg', '~> 0'
|
37
|
-
spec.add_runtime_dependency
|
38
|
-
|
39
|
-
spec.add_development_dependency
|
40
|
-
spec.add_development_dependency
|
41
|
-
spec.add_development_dependency
|
42
|
-
spec.add_development_dependency
|
43
|
-
|
44
|
-
spec.add_development_dependency "codeclimate-test-reporter", "~> 1.0"
|
35
|
+
spec.add_runtime_dependency 'activerecord', rails_versions
|
36
|
+
|
37
|
+
spec.add_development_dependency 'pry-byebug', '~> 3'
|
38
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
39
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
40
|
+
spec.add_development_dependency 'combustion', '~> 0.7'
|
41
|
+
|
45
42
|
end
|
data/lib/deleted_at.rb
CHANGED
@@ -2,17 +2,32 @@ require "deleted_at/version"
|
|
2
2
|
require 'deleted_at/views'
|
3
3
|
require 'deleted_at/active_record/base'
|
4
4
|
require 'deleted_at/active_record/relation'
|
5
|
+
require 'deleted_at/active_record/connection_adapters/abstract/schema_definition'
|
5
6
|
|
6
7
|
require 'deleted_at/railtie' if defined?(Rails::Railtie)
|
7
8
|
|
8
9
|
module DeletedAt
|
9
10
|
|
11
|
+
class << self
|
12
|
+
attr_writer :logger
|
13
|
+
|
14
|
+
def logger
|
15
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
16
|
+
log.progname = self.name
|
17
|
+
log.level = Logger::INFO
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
10
22
|
def self.load
|
11
|
-
::ActiveRecord::Relation.send :
|
23
|
+
::ActiveRecord::Relation.send :prepend, DeletedAt::ActiveRecord::Relation
|
12
24
|
::ActiveRecord::Base.send :include, DeletedAt::ActiveRecord::Base
|
25
|
+
::ActiveRecord::ConnectionAdapters::TableDefinition.send :prepend, DeletedAt::ActiveRecord::ConnectionAdapters::TableDefinition
|
13
26
|
end
|
14
27
|
|
15
28
|
def self.install(model)
|
29
|
+
return false unless model.has_deleted_at_column?
|
30
|
+
|
16
31
|
DeletedAt::Views.install_present_view(model)
|
17
32
|
DeletedAt::Views.install_deleted_view(model)
|
18
33
|
|
@@ -22,11 +37,17 @@ module DeletedAt
|
|
22
37
|
end
|
23
38
|
|
24
39
|
def self.uninstall(model)
|
40
|
+
return false unless model.has_deleted_at_column?
|
41
|
+
|
25
42
|
DeletedAt::Views.uninstall_deleted_view(model)
|
26
43
|
DeletedAt::Views.uninstall_present_view(model)
|
27
44
|
|
28
45
|
# We've removed the database views, now remove the class extensions
|
29
|
-
|
46
|
+
DeletedAt::ActiveRecord::Base.remove_class_views(model)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.testify(value)
|
50
|
+
value == true || value == 't' || value == 1 || value == '1'
|
30
51
|
end
|
31
52
|
|
32
53
|
private
|
@@ -7,125 +7,61 @@ module DeletedAt
|
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
9
|
included do
|
10
|
-
class_attribute :
|
11
|
-
|
12
|
-
|
13
|
-
class << self
|
14
|
-
[:archive_with_deleted_at?, :archive_with_deleted_by?].each do |sym|
|
15
|
-
define_method(sym) do
|
16
|
-
false
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
10
|
+
class_attribute :archive_with_deleted_at
|
11
|
+
class_attribute :deleted_at_column
|
20
12
|
|
13
|
+
self.archive_with_deleted_at = false
|
21
14
|
end
|
22
15
|
|
23
16
|
module ClassMethods
|
24
17
|
|
25
18
|
def with_deleted_at(options={})
|
26
19
|
|
27
|
-
|
20
|
+
DeletedAt::ActiveRecord::Base.parse_options(self, options)
|
28
21
|
|
29
|
-
|
30
|
-
|
31
|
-
unless ::DeletedAt::Views.all_table_exists?(self) && ::DeletedAt::Views.deleted_view_exists?(self)
|
32
|
-
return warn("You're trying to use `with_deleted_at` on #{name} but you have not installed the views, yet.")
|
33
|
-
end
|
34
|
-
|
35
|
-
unless columns.map(&:name).include?(deleted_at_column)
|
36
|
-
return warn("Missing `#{deleted_at_column}` in `#{name}` when trying to employ `deleted_at`")
|
37
|
-
end
|
38
|
-
|
39
|
-
[:archive_with_deleted_at?, :archive_with_deleted_by?].each do |sym|
|
40
|
-
class_eval <<-BBB
|
41
|
-
def self.#{sym}
|
42
|
-
true
|
43
|
-
end
|
44
|
-
BBB
|
45
|
-
end
|
22
|
+
return DeletedAt.logger.warn("You're trying to use `with_deleted_at` on #{name} but you have not installed the views, yet.") unless
|
23
|
+
has_deleted_at_views?
|
46
24
|
|
25
|
+
return DeletedAt.logger.warn("Missing `#{deleted_at_column}` in `#{name}` when trying to employ `deleted_at`") unless
|
26
|
+
has_deleted_at_column?
|
47
27
|
|
48
28
|
# We are confident at this point that the tables and views have been setup.
|
49
29
|
# We need to do a bit of wizardy by setting the table name to the actual table
|
50
30
|
# (at this point: model/all), such that the model has all the information
|
51
|
-
# regarding its structure and intended behavior.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
primary_key = self.primary_key
|
56
|
-
self.table_name = self.original_table_name
|
57
|
-
|
58
|
-
setup_class_views
|
59
|
-
with_deleted_by
|
60
|
-
end
|
31
|
+
# regarding its structure and intended behavior. (e.g. setting primary key)
|
32
|
+
DeletedAt::Views.while_spoofing_table_name(self, ::DeletedAt::Views.all_table(self)) do
|
33
|
+
reset_primary_key
|
34
|
+
end
|
61
35
|
|
62
|
-
|
63
|
-
self.send(:remove_const, :All) if self.const_defined?(:All)
|
64
|
-
self.send(:remove_const, :Deleted) if self.const_defined?(:Deleted)
|
36
|
+
DeletedAt::ActiveRecord::Base.setup_class_views(self)
|
65
37
|
end
|
66
38
|
|
67
|
-
private
|
68
39
|
|
69
|
-
def parse_options(options)
|
70
|
-
self.deleted_at_column = (options.try(:[], :deleted_at).try(:[], :column) || :deleted_at).to_s
|
71
|
-
self.deleted_by_column = (options.try(:[], :deleted_by).try(:[], :column) || :deleted_by).to_s
|
72
|
-
self.deleted_by_class = (options.try(:[], :deleted_by).try(:[], :class) || User)
|
73
|
-
self.deleted_by_primary_key = (options.try(:[], :deleted_by).try(:[], :primary_key) || deleted_by_class.try(:primary_key)).to_s
|
74
|
-
end
|
75
40
|
|
76
|
-
def
|
77
|
-
|
41
|
+
def has_deleted_at_column?
|
42
|
+
columns.map(&:name).include?(deleted_at_column)
|
78
43
|
end
|
79
44
|
|
80
|
-
def
|
81
|
-
|
82
|
-
self.deleted_by_class = self.deleted_by_class.const_get(:All) if deleted_by_class_is_delete_at(self.deleted_by_class)
|
83
|
-
|
84
|
-
unless reflect_on_association(:destroyer)
|
85
|
-
class_eval do
|
86
|
-
belongs_to :destroyer, foreign_key: deleted_by_column, primary_key: deleted_by_primary_key, class_name: deleted_by_class.name
|
87
|
-
end
|
88
|
-
end
|
45
|
+
def has_deleted_at_views?
|
46
|
+
::DeletedAt::Views.all_table_exists?(self) && ::DeletedAt::Views.deleted_view_exists?(self)
|
89
47
|
end
|
90
48
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
49
|
+
def deleted_at_attributes
|
50
|
+
attributes = {
|
51
|
+
deleted_at_column => Time.now.utc
|
52
|
+
}
|
95
53
|
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
54
|
|
100
|
-
|
101
|
-
self.const_set(:All, Class.new(self) do |klass|
|
102
|
-
class_eval <<-AAA
|
103
|
-
self.table_name = '#{::DeletedAt::Views.all_table(klass)}'
|
104
|
-
AAA
|
105
|
-
end)
|
106
|
-
|
107
|
-
self.const_set(:Deleted, Class.new(self) do |klass|
|
108
|
-
class_eval <<-AAA
|
109
|
-
self.table_name = '#{::DeletedAt::Views.deleted_view(klass)}'
|
110
|
-
AAA
|
111
|
-
end)
|
55
|
+
attributes
|
112
56
|
end
|
113
57
|
|
114
|
-
end
|
115
|
-
|
116
|
-
[:archive_with_deleted_at?, :archive_with_deleted_by?].each do |sym|
|
117
|
-
class_eval <<-BBB
|
118
|
-
def #{sym}
|
119
|
-
self.class.#{sym}
|
120
|
-
end
|
121
|
-
BBB
|
122
|
-
end
|
58
|
+
end # End ClassMethods
|
123
59
|
|
124
60
|
def destroy
|
125
|
-
if archive_with_deleted_at?
|
61
|
+
if self.archive_with_deleted_at?
|
126
62
|
with_transaction_returning_status do
|
127
63
|
run_callbacks :destroy do
|
128
|
-
update_columns(deleted_at_attributes)
|
64
|
+
update_columns(self.class.deleted_at_attributes)
|
129
65
|
self
|
130
66
|
end
|
131
67
|
end
|
@@ -134,28 +70,33 @@ module DeletedAt
|
|
134
70
|
end
|
135
71
|
end
|
136
72
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
deleted_at_column => Time.now.utc
|
142
|
-
}
|
143
|
-
|
144
|
-
# attributes.merge({
|
145
|
-
# deleted_by_column => DeletedAt::who_by
|
146
|
-
# }) if by_who?
|
147
|
-
|
148
|
-
attributes
|
73
|
+
def self.remove_class_views(model)
|
74
|
+
model.archive_with_deleted_at = false
|
75
|
+
model.send(:remove_const, :All) if model.const_defined?(:All)
|
76
|
+
model.send(:remove_const, :Deleted) if model.const_defined?(:Deleted)
|
149
77
|
end
|
150
78
|
|
151
|
-
def
|
152
|
-
|
79
|
+
def self.parse_options(model, options)
|
80
|
+
model.deleted_at_column = (options.try(:[], :deleted_at).try(:[], :column) || :deleted_at).to_s
|
153
81
|
end
|
154
82
|
|
155
|
-
|
156
|
-
|
83
|
+
|
84
|
+
def self.setup_class_views(model)
|
85
|
+
model.archive_with_deleted_at = true
|
86
|
+
model.const_set(:All, Class.new(model) do |klass|
|
87
|
+
class_eval <<-AAA
|
88
|
+
self.table_name = '#{::DeletedAt::Views.all_table(klass)}'
|
89
|
+
AAA
|
90
|
+
end)
|
91
|
+
|
92
|
+
model.const_set(:Deleted, Class.new(model) do |klass|
|
93
|
+
class_eval <<-AAA
|
94
|
+
self.table_name = '#{::DeletedAt::Views.deleted_view(klass)}'
|
95
|
+
AAA
|
96
|
+
end)
|
157
97
|
end
|
158
98
|
|
159
99
|
end
|
100
|
+
|
160
101
|
end
|
161
102
|
end
|