ar_soft_delete 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e58284abe54c1cf091ef6c7d9a64c27c0c620f4c1428e9370caa58a9e88c5b6e
4
+ data.tar.gz: 12a8f3b5d7d48652adc6d8bb661bc58a297d90eedb632c0d5a27514c3fa7fe31
5
+ SHA512:
6
+ metadata.gz: 9d230ddf0015381f9b52dcf48be2884c0bf62f5facd3e146547a0cfde6edfe38a98909a2598385e68fddf25661c0b94ef9df6a559de77f9c4de112c47c092f95
7
+ data.tar.gz: 3e6f45ae18bd79522691fb4992d99efffdea755837aee251409f7da57dd8f9ac27ab02cac041dcb9ef1c9151c6696d51252d32e1c904fcc7a5c55765c94a38c5
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ notes_and_todos
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.5.8
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in soft_delete.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
8
+ gem "activerecord"
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ soft_delete (0.1.0)
5
+ activerecord (>= 4.2, < 7)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (6.0.3.2)
11
+ activesupport (= 6.0.3.2)
12
+ activerecord (6.0.3.2)
13
+ activemodel (= 6.0.3.2)
14
+ activesupport (= 6.0.3.2)
15
+ activesupport (6.0.3.2)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
+ i18n (>= 0.7, < 2)
18
+ minitest (~> 5.1)
19
+ tzinfo (~> 1.1)
20
+ zeitwerk (~> 2.2, >= 2.2.2)
21
+ concurrent-ruby (1.1.6)
22
+ database_cleaner (1.8.5)
23
+ diff-lcs (1.4.4)
24
+ i18n (1.8.5)
25
+ concurrent-ruby (~> 1.0)
26
+ minitest (5.14.1)
27
+ rake (12.3.3)
28
+ rspec (3.9.0)
29
+ rspec-core (~> 3.9.0)
30
+ rspec-expectations (~> 3.9.0)
31
+ rspec-mocks (~> 3.9.0)
32
+ rspec-core (3.9.2)
33
+ rspec-support (~> 3.9.3)
34
+ rspec-expectations (3.9.2)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.9.0)
37
+ rspec-mocks (3.9.1)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.9.0)
40
+ rspec-support (3.9.3)
41
+ sqlite3 (1.4.2)
42
+ thread_safe (0.3.6)
43
+ tzinfo (1.2.7)
44
+ thread_safe (~> 0.1)
45
+ with_model (2.1.4)
46
+ activerecord (>= 5.2, < 6.1)
47
+ zeitwerk (2.4.0)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ activerecord
54
+ bundler
55
+ database_cleaner (~> 1.5)
56
+ rake (~> 12.0)
57
+ rspec (~> 3.0)
58
+ soft_delete!
59
+ sqlite3
60
+ with_model (~> 2.0)
61
+
62
+ BUNDLED WITH
63
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,115 @@
1
+ # SoftDelete
2
+
3
+ (Yet another) Soft delete active_record models.
4
+
5
+ Why another soft delete? Soft deleting is a relatively common pattern which lets you hide records instead of deleting them. Some gems are heavy handed and override normal deleting. Most of them make a lot of decisions or assumptions about how you will be soft deleting. This gem takes an open approach and lets you decide how little or how much your project will be using the soft delete pattern. It can be configured on a per-model level to use whichever features are appropriate at the time. This makes it especially easy to introduce soft delete into existing projects.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'ar_soft_delete'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install ar_soft_delete
22
+
23
+ ## Usage
24
+
25
+ SoftDelete works by setting `deleted_at` to Time.now. Make sure your model has a datetime `deleted_at` column. And include the module into your active_record class:
26
+
27
+ ```ruby
28
+ class Author < ApplicationRecord
29
+ include SoftDelete::SoftDeletable
30
+
31
+ ...
32
+ end
33
+ ```
34
+
35
+ `myModel.soft_delete` returns `true|false`.
36
+
37
+ `myModel.soft_delete!` raises `ActiveRecord::RecordInvalid` on failure.
38
+
39
+ You can also pass an optional `validate: false` argument to ignore validations on save. This can be useful if you want to soft delete a record that would normally not save because it is invalid.
40
+
41
+ Exa:
42
+
43
+ ```ruby
44
+ foo.valid?
45
+ > false
46
+ foo.soft_delete(validate: false)
47
+ > true
48
+ ```
49
+
50
+ ## Dependency Behavior
51
+
52
+ In general, SoftDelete considers "soft deleting" and "normal deleting" to be two separate things. Under this philosophy, SoftDelete's default behavior is to do as little as possible (just set the `deleted_at` column). There are no callbacks fired off, associations are not updated, etc.
53
+
54
+ However, sometimes you want to handle soft deletes as if they are real deletes. To that end, SoftDelete allows you to set a dependency behavior
55
+ `include SoftDelete::SoftDeletable.dependent(:ignore|:default|:soft_delete)`
56
+ * `:ignore`: The default. Do nothing with associated records. Useful if you want to soft delete specific records with no other side effects. Example use case:
57
+ I have identified bad data. I want to be able to delete the data, see the ramifications and potentially quickly restore the data if the delete was a mistake.
58
+ * `:default`: Fire off the same action that is described by the active_record dsl in the model. Exa (`has_many :enemies, default: :destroy`) would destroy the enemies when the model is soft deleted. This is useful if you are incrementally adding soft delete to certain models and want the rest of the behavior to remain the same.
59
+ * `:soft_delete`: overrides the `:destroy` association option to invoke a `soft_delete` on the associated records. This comes the closest to automatically replacing normal deletes with soft deletes. It runs before|around|after destroy hooks when it soft deletes.
60
+
61
+ Exa:
62
+
63
+ ```ruby
64
+ class Author < ApplicationRecord
65
+ include SoftDelete::SoftDeletable.dependent(:soft_delete)
66
+
67
+ has_many :notes, dependent: :destroy
68
+ ...
69
+ end
70
+ ```
71
+
72
+ ## Default Scope
73
+
74
+ By default, SoftDelete uses a default_scope. Do you feel strongly that a default scope is not for you? SoftDelete can be included without a default scope:
75
+ `include SoftDelete::SoftDeletable.not_scoped`
76
+
77
+ This will skip adding a default scope to the model and instead will add an `active` scope that you can use to filter the records.
78
+
79
+ ## SoftDelete Restorable
80
+
81
+ You can also include the `SoftDelete::Restorable` module to include a `deleted` scope which overrides the default `deleted_at` scope if it exists. It also mixes in `restore_soft_delete` and `restore_soft_delete!`. They both can take an optional `validate` param to restore an otherwise invalid record.
82
+
83
+ exa:
84
+ ```ruby
85
+ note = Note.deleted.find_by(id: 2)
86
+ note.valid?
87
+ > false
88
+ note.restore_soft_delete(validate: false)
89
+ > true
90
+ ```
91
+
92
+ ## Caveats
93
+
94
+ SoftDelete uses a class var to hold the dependency behavior. This has implications if you subclass a model that includes SoftDelete. All subclasses share the same class variable and therefore would share the same soft delete dependency behavior. Changing it in a subclass changes it for the ancestors as well as any children.
95
+
96
+ SoftDelete does not currently support updating cache counters when a record is soft deleted.
97
+
98
+ ## Roadmap
99
+
100
+ * before|after soft_delete hooks.
101
+
102
+ ## Development
103
+
104
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
105
+
106
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
107
+
108
+ ## Contributing
109
+
110
+ Bug reports and pull requests are welcome on GitHub at https://github.com/swelltrain/soft_delete.
111
+
112
+
113
+ ## License
114
+
115
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "soft_delete"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require 'soft_delete/version'
5
+ require 'soft_delete/soft_deletable'
6
+ require 'soft_delete/restorable'
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoftDelete
4
+ module Restorable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :deleted, -> { unscope(where: :deleted_at).where.not(deleted_at: nil) }
9
+ end
10
+
11
+ def restore_soft_delete(validate: true)
12
+ restore_soft_delete!(validate: validate)
13
+ rescue ActiveRecord::RecordInvalid
14
+ false
15
+ end
16
+
17
+ def restore_soft_delete!(validate: true)
18
+ self.deleted_at = nil
19
+ save!(validate: validate)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoftDelete
4
+ # TODO: wrap in a transaction
5
+ module SoftDeletable
6
+ extend ActiveSupport::Concern
7
+ @@soft_delete_dependency_behavior = nil
8
+ @@include_default_scope = true
9
+
10
+ included do
11
+ if @@include_default_scope
12
+ default_scope { where(deleted_at: nil) }
13
+ else
14
+ scope :active, -> { where(deleted_at: nil) }
15
+ end
16
+ end
17
+
18
+ def self.not_scoped
19
+ @@include_default_scope = false
20
+
21
+ self
22
+ end
23
+
24
+ # descibes how soft delete should handle dependencies
25
+ # ignore
26
+ # ignore dependencies and do nothing. this is the default behavior
27
+ # default
28
+ # fire off the same action that is described by ar dsl
29
+ # soft_delete
30
+ # if dsl is :destroy, then override with a soft_delete
31
+ def self.dependent(behavior = :ignore)
32
+ raise ArgumentError unless %i[ignore default soft_delete].include? behavior
33
+
34
+ @@soft_delete_dependency_behavior = behavior
35
+ self
36
+ end
37
+
38
+ def soft_delete(validate: true)
39
+ soft_delete!(validate: validate)
40
+ rescue ActiveRecord::RecordInvalid
41
+ false
42
+ end
43
+
44
+ def soft_delete!(validate: true)
45
+ ActiveRecord::Base.transaction do
46
+ handle_soft_delete_dependency_behavior
47
+ run_callbacks(:destroy) do
48
+ self.deleted_at = Time.now
49
+ save!(validate: validate)
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def handle_soft_delete_dependency_behavior
57
+ return unless @@soft_delete_dependency_behavior.present?
58
+
59
+ case @@soft_delete_dependency_behavior
60
+ when :soft_delete
61
+ handle_overridden_soft_delete_dependencies
62
+ when :default
63
+ handle_normal_dependencies
64
+ end
65
+ end
66
+
67
+ def handle_normal_dependencies
68
+ soft_delete_dependent_associations.each(&:handle_dependency)
69
+ end
70
+
71
+ def handle_overridden_soft_delete_dependencies
72
+ soft_delete_dependent_associations.each do |assn|
73
+ next unless assn.options[:dependent] == :destroy
74
+
75
+ # TODO: pass in validate
76
+ assn.load_target.each(&:soft_delete!)
77
+
78
+ # see:
79
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/collection_association.rb#L174
80
+ assn.reset
81
+ assn.loaded!
82
+ end
83
+ handle_normal_dependencies
84
+ end
85
+
86
+ def soft_delete_dependent_associations
87
+ @soft_delete_dependent_associations ||=
88
+ _reflections.select { |_k, v| v.options[:dependent].present? }.map { |_k, v| association(v.name) }
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoftDelete
4
+ VERSION = '0.1.1'
5
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'lib/soft_delete/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "ar_soft_delete"
5
+ spec.version = SoftDelete::VERSION
6
+ spec.authors = ["Stephen Philp"]
7
+ spec.email = ["swelltrain@gmail.com"]
8
+
9
+ spec.summary = %q(Soft delete active_record models.)
10
+ spec.description = %q(This gem takes an open approach and lets you decide
11
+ how little or how much your project will be using the soft delete pattern.
12
+ It can be configured on a per-model level to use whichever features
13
+ are appropriate at the time. This makes it especially easy to introduce
14
+ soft delete into existing projects.)
15
+ # spec.homepage = "TODO: Put your gem's website or public repo URL here."
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
18
+
19
+ spec.metadata["source_code_uri"] = "https://github.com/swelltrain/soft_delete"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency 'activerecord', '>= 4.2', '<7'
31
+ spec.add_development_dependency 'bundler'
32
+ spec.add_development_dependency 'database_cleaner', '~> 1.5'
33
+ spec.add_development_dependency 'rake', '>= 12.3.3'
34
+ spec.add_development_dependency 'rspec', '~> 3.5.0'
35
+ spec.add_development_dependency 'sqlite3'
36
+ spec.add_development_dependency 'with_model', '~> 2.0'
37
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ar_soft_delete
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Philp
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7'
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: database_cleaner
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.5'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 12.3.3
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 12.3.3
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 3.5.0
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 3.5.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: sqlite3
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: with_model
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.0'
117
+ description: |-
118
+ This gem takes an open approach and lets you decide
119
+ how little or how much your project will be using the soft delete pattern.
120
+ It can be configured on a per-model level to use whichever features
121
+ are appropriate at the time. This makes it especially easy to introduce
122
+ soft delete into existing projects.
123
+ email:
124
+ - swelltrain@gmail.com
125
+ executables: []
126
+ extensions: []
127
+ extra_rdoc_files: []
128
+ files:
129
+ - ".gitignore"
130
+ - ".rspec"
131
+ - ".travis.yml"
132
+ - Gemfile
133
+ - Gemfile.lock
134
+ - LICENSE.txt
135
+ - README.md
136
+ - Rakefile
137
+ - bin/console
138
+ - bin/setup
139
+ - lib/soft_delete.rb
140
+ - lib/soft_delete/restorable.rb
141
+ - lib/soft_delete/soft_deletable.rb
142
+ - lib/soft_delete/version.rb
143
+ - soft_delete.gemspec
144
+ homepage:
145
+ licenses:
146
+ - MIT
147
+ metadata:
148
+ source_code_uri: https://github.com/swelltrain/soft_delete
149
+ post_install_message:
150
+ rdoc_options: []
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: 2.3.0
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubyforge_project:
165
+ rubygems_version: 2.7.6.2
166
+ signing_key:
167
+ specification_version: 4
168
+ summary: Soft delete active_record models.
169
+ test_files: []