deleted_at 0.3.0 → 0.4.0rc1
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.
- 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
|
+
[](https://rubygems.org/gems/deleted_at)
|
2
|
+
[](https://travis-ci.org/TwilightCoders/deleted_at)
|
3
|
+
[](https://codeclimate.com/github/TwilightCoders/deleted_at)
|
4
|
+
[](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
|