wipe_out 1.0.0

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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +50 -0
  3. data/.gitignore +7 -0
  4. data/.markdownlint.json +7 -0
  5. data/.rspec +2 -0
  6. data/.yardopts +6 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE +19 -0
  10. data/README.md +58 -0
  11. data/bin/rake +29 -0
  12. data/bin/rspec +29 -0
  13. data/bin/standardrb +29 -0
  14. data/bin/yard +29 -0
  15. data/bin/yardoc +29 -0
  16. data/bin/yri +29 -0
  17. data/docs/development.md +57 -0
  18. data/docs/getting_started.md +350 -0
  19. data/docs/releasing.md +14 -0
  20. data/docs/yard_plugin.rb +12 -0
  21. data/lib/wipe_out.rb +65 -0
  22. data/lib/wipe_out/attribute_strategies/const_value.rb +13 -0
  23. data/lib/wipe_out/attribute_strategies/nullify.rb +5 -0
  24. data/lib/wipe_out/attribute_strategies/randomize.rb +13 -0
  25. data/lib/wipe_out/callback.rb +25 -0
  26. data/lib/wipe_out/callbacks_observer.rb +23 -0
  27. data/lib/wipe_out/config.rb +30 -0
  28. data/lib/wipe_out/execute.rb +31 -0
  29. data/lib/wipe_out/execution/context.rb +34 -0
  30. data/lib/wipe_out/execution/execute_plan.rb +53 -0
  31. data/lib/wipe_out/plans/built_plan.rb +35 -0
  32. data/lib/wipe_out/plans/dsl.rb +117 -0
  33. data/lib/wipe_out/plans/plan.rb +63 -0
  34. data/lib/wipe_out/plans/union.rb +19 -0
  35. data/lib/wipe_out/plugin.rb +31 -0
  36. data/lib/wipe_out/plugins/logger.rb +42 -0
  37. data/lib/wipe_out/validate.rb +48 -0
  38. data/lib/wipe_out/validators/attributes.rb +49 -0
  39. data/lib/wipe_out/validators/base.rb +13 -0
  40. data/lib/wipe_out/validators/defined_relations.rb +26 -0
  41. data/lib/wipe_out/validators/relations_plans.rb +26 -0
  42. data/lib/wipe_out/version.rb +3 -0
  43. data/wipe_out.gemspec +47 -0
  44. metadata +274 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: eafa15a844d625664e5cd81d01e921514a84f34a2d488efedd950a5fadc77adb
4
+ data.tar.gz: 92ebfff20053d46b9e2629e1c00dc3097073874083bc0d46bb077e43d1418346
5
+ SHA512:
6
+ metadata.gz: 2177346bc816df8cd6a270abd66e8c660db21297e4fead9500c912792f2bb4f76fd12666ae7029091b4a3c71a430974087992d9615c155d6811f6165e03b3b27
7
+ data.tar.gz: dc02b071110c0c5963f44a132de42def2ee24e317b4623eb0106eee64739160c93e1350065083b023e5e5ab3a7812819febc864ca07474eb00206de0fbc36448
@@ -0,0 +1,50 @@
1
+ name: CI Suite
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: 3.0
16
+ bundler-cache: true
17
+ - run: bin/standardrb --format progress --no-fix
18
+ docs:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v2
22
+ - uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: 3.0
25
+ bundler-cache: true
26
+ - name: NODE SETUP ACTION
27
+ uses: actions/setup-node@v2
28
+ with:
29
+ node-version: '16.x'
30
+ - run: yarn global add markdownlint-cli@0.27.1
31
+ - run: export PATH="${PATH}:$(yarn global bin)"
32
+ - run: markdownlint --config .markdownlint.json -i .yarn -i vendor .
33
+ - run: bin/yard
34
+ test:
35
+ strategy:
36
+ fail-fast: false
37
+ matrix:
38
+ include:
39
+ - gemfile: Gemfile
40
+ ruby: 3.0
41
+ runs-on: ubuntu-latest
42
+ steps:
43
+ - run: echo BUNDLE_GEMFILE=${{ matrix.gemfile }} > $GITHUB_ENV
44
+ - uses: actions/checkout@v2
45
+ - uses: ruby/setup-ruby@v1
46
+ with:
47
+ ruby-version: ${{ matrix.ruby }}
48
+ bundler-cache: true
49
+ bundler: ${{ matrix.bundler || 'default' }}
50
+ - run: bin/rspec
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ .rubocop-xunit.xml
4
+ spec/internal/log/*
5
+ coverage
6
+ doc
7
+ .yardoc/
@@ -0,0 +1,7 @@
1
+ {
2
+ "MD006": false,
3
+ "MD013": {
4
+ "line_length": 120
5
+ },
6
+ "default": true
7
+ }
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --no-private
2
+ --markup=markdown
3
+ --readme=README.md
4
+ --title='WipeOut Documentation'
5
+ --load ./docs/yard_plugin.rb
6
+ 'lib/**/*.rb' - '*.md' 'docs/*.md' LICENSE
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## v1.0.0
4
+
5
+ Initial release, see docs:
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://www.rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2021 Spa Worldwide Ltd
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.md ADDED
@@ -0,0 +1,58 @@
1
+ # WipeOut
2
+
3
+ Library for removing and clearing data in Rails ActiveRecord models.
4
+
5
+ ## Installation
6
+
7
+ 1. Add WipeOut to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem "wipe_out", "~> 1.0"
11
+ ```
12
+
13
+ Check newest release at [here](https://rubygems.org/gems/wipe_out).
14
+
15
+ ## Usage
16
+
17
+ Quick example:
18
+
19
+ Given the following model:
20
+
21
+ ```ruby
22
+ # == Schema Info
23
+ #
24
+ # Table name: users
25
+ #
26
+ # id :integer(11) not null, primary key
27
+ # name :varchar(11) not null
28
+ # orders_count :integer(11) not null
29
+ class User < ActiveRecord::Base
30
+ end
31
+
32
+ ```
33
+
34
+ We can define custom wipe out plan:
35
+
36
+ ```ruby
37
+ UserWipeOutPlan = WipeOut.build_plan do
38
+ wipe_out :name
39
+ ignore :orders_count
40
+ end
41
+ ```
42
+
43
+ and execute it:
44
+
45
+ ```ruby
46
+ User.last.then { |user| UserWipeOutPlan.execute(user) }
47
+ ```
48
+
49
+ It will overwrite data inside `name` but leave, `orders_count` untouched.
50
+
51
+ There is also support for relations and making sure that policies are defined
52
+ for any added columns.
53
+
54
+ Read more in [getting started](./docs/getting_started.md) doc.
55
+
56
+ ## Contributing && Development
57
+
58
+ See [development.md](./docs/development.md)
data/bin/rake ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
data/bin/rspec ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/standardrb ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'standardrb' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("standard", "standardrb")
data/bin/yard ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'yard' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("yard", "yard")
data/bin/yardoc ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'yardoc' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("yard", "yardoc")
data/bin/yri ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'yri' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("yard", "yri")
@@ -0,0 +1,57 @@
1
+ # Development
2
+
3
+ So, you want to hack on GraphQL Ruby! Here are some tips for getting started.
4
+
5
+ * [Setup](#setup) your development environment
6
+ * [Run tests](#run-tests)
7
+ * [Debug](#debug)
8
+ * [Coding guidelines](#coding-guidelines)
9
+ * [Releasing](#releasing)
10
+
11
+ ## Setup
12
+
13
+ Follow the steps below to setup wipe_out locally:
14
+
15
+ * Make sure you're running on Ruby 3.0.0 or newer
16
+ * sqlite is installed (required for tests)
17
+
18
+ ```bash
19
+ git clone https://github.com/GlobalAppTesting/wipe_out
20
+ cd wipe_out
21
+ bundle install
22
+ ```
23
+
24
+ ## Run tests
25
+
26
+ ```bash
27
+ ./bin/rspec
28
+ ```
29
+
30
+ ## Debug
31
+
32
+ By default `pry` is included so feel free to run tests and put `binding.pry`
33
+ wherever you like.
34
+
35
+ ## Coding guidelines
36
+
37
+ * Please make sure to run `./bin/standardrb --fix`
38
+ * Markdown files are linted too via [markdownlint](https://github.com/DavidAnson/markdownlint)
39
+ * Each change should be covered by tests and add CHANGELOG info
40
+
41
+ ## Releasing
42
+
43
+ Releasing will be done manually for now.
44
+
45
+ 1. Bump version in `lib/wipe_out/version.rb`
46
+ 1. Ensure CHANGELOG.md is matching new version and has details about published changes.
47
+ Make sure that breaking changes contain update instructions.
48
+ 1. Commit all changes `git commit -m "Release: vX.Y.Z"`
49
+ 1. Tag commit `git tag vX.Y.Z`
50
+ 1. Push changes and tag
51
+
52
+ ```bash
53
+ git push origin master
54
+ git push origin vX.Y.Z
55
+ ```
56
+
57
+ 1. (TODO) Publish release on [Rubygems](https://rubygems.org/)
@@ -0,0 +1,350 @@
1
+ # Getting started
2
+
3
+ ## Usage
4
+
5
+ Removal strategy definition is called _Plan_. _Plan_ is created using DSL.
6
+ Plan can be validated and executed.
7
+
8
+ _Plan_ defines a list of attributes and relations to clear.
9
+ Relations work as nested _Plans_ and can be nested infinitely.
10
+
11
+ Given schema:
12
+
13
+ ```ruby
14
+ create_table "users" do |t|
15
+ t.string "first_name"
16
+ t.string "last_name"
17
+ t.string "reset_password_token"
18
+ t.string "access_tokens"
19
+ t.datetime "confirmed_at"
20
+ t.integer "sign_in_count"
21
+ end
22
+
23
+ create_table "comments" do |t|
24
+ t.integer "user_id"
25
+ t.string "value"
26
+ end
27
+
28
+ create_table "resource_files" do |t|
29
+ t.integer "comment_id"
30
+ end
31
+
32
+ create_table "dashboards" do |t|
33
+ t.integer "user_id"
34
+ t.string "order"
35
+ end
36
+ ```
37
+
38
+ and models
39
+
40
+ ```ruby
41
+ class User < ActiveRecord::Base
42
+ has_many :comments
43
+ has_one :dashboard
44
+ end
45
+
46
+ class Comment < ActiveRecord::Base
47
+ has_many :resource_files
48
+ end
49
+
50
+ class ResourceFile < ActiveRecord::Base; end
51
+ class Dashboard < ActiveRecord::Base; end
52
+ ```
53
+
54
+ Example Plan:
55
+
56
+ ```ruby
57
+ UserWipeOutPlan = WipeOut.build_plan do
58
+ # Set nil value by default
59
+ wipe_out :first_name, :last_name
60
+ # Custom strategy
61
+ wipe_out :sign_in_count, strategy: WipeOut::AttributeStrategies::ConstValue.new(0)
62
+ # Inline custom strategy
63
+ wipe_out :reset_password_token do
64
+ "random-value-#{SecureRandom.hex}"
65
+ end
66
+
67
+ # has_many relation
68
+ relation :comments do
69
+ # Behaves like nested Plan.
70
+ wipe_out :value, strategy: WipeOut::AttributeStrategies::Randomize.new
71
+
72
+ relation :resource_files do
73
+ on_execute ->(execution) { execution.record.destroy! }
74
+ ignore_all
75
+ end
76
+ end
77
+
78
+ # has_one relation
79
+ relation :dashboard do
80
+ wipe_out :order
81
+ ignore :name
82
+ end
83
+
84
+ # Ignore is used to mark both ignored relations
85
+ # and ignored attributes
86
+ ignore :access_tokens, :confirmed_at
87
+ end
88
+ ```
89
+
90
+ After executing on a record
91
+
92
+ ```ruby
93
+ record = User.last
94
+ UserWipeOutPlan.execute(record)
95
+ ```
96
+
97
+ User's:
98
+
99
+ * `first_name` and `last_name` attributes are set to `nil` value
100
+ * `sign_in_count` is set to `0`
101
+ * `reset_password_token` is randomized
102
+ * `access_tokens` and `confirmed_at` attributes are not changed
103
+
104
+ User's Comments:
105
+
106
+ * `value` is randomized
107
+ * Comment's resource_files are all destroyed
108
+
109
+ User's dashboard:
110
+
111
+ * `order` attribute is set to `nil`
112
+ * `name` attribute is ignored
113
+
114
+ ## Validation
115
+
116
+ _Plans_ can be validated against DB schema:
117
+
118
+ ```ruby
119
+ UserWipeOutPlan.validate(User)
120
+ UserWipeOutPlan.validate(User).errors
121
+ UserWipeOutPlan.validate(User).valid?
122
+ ```
123
+
124
+ Method performs validation. It will contain errors if plan is invalid.
125
+
126
+ When new attribute is added to schema or a new relation is added to a model
127
+ that is used in _Plan_ then validation will fail.
128
+
129
+ ### Ignoring
130
+
131
+ Every relation or attribute which is not part of removal plan
132
+ has to be marked as ignored with `ignore`.
133
+
134
+ `through` and `belongs_to` relations are ignored automatically and they don't have to be ignored manually.
135
+
136
+ By default these attributes are ignored:
137
+
138
+ * `id`
139
+ * `created_at`
140
+ * `updated_at`
141
+ * `archived_at`
142
+
143
+ Given schema:
144
+
145
+ ```ruby
146
+ create_table "users" do |t|
147
+ t.string "first_name"
148
+ t.string "last_name"
149
+ t.integer "company_id"
150
+ t.datetime "created_at"
151
+ t.datetime "updated_at"
152
+ end
153
+ ```
154
+
155
+ and class:
156
+
157
+ ```ruby
158
+ class User < ActiveRecord::Base
159
+ belongs_to :company
160
+ has_many :comments
161
+ has_one :dashboard
162
+ has_many :resource_files, through: :comments
163
+ end
164
+ ```
165
+
166
+ a _Plan_ to handle removing of this object has to provide strategy or ignore:
167
+
168
+ * attributes:
169
+
170
+ * `first_name`
171
+ * `last_name`
172
+ * `company_id`
173
+
174
+ * relations:
175
+
176
+ * `comments`
177
+ * `dashboard`
178
+
179
+ _Plan_ can skip providing strategy for:
180
+
181
+ * attributes `id`, `created_at`, `updated_at` - ignored by default
182
+ * relation `company` - `belongs_to` relation
183
+ * relation `resource_files` - through relation
184
+
185
+ ### Reusing _Plans_
186
+
187
+ #### Extracting
188
+
189
+ Nested plans can be extracted as independent object. An exemplary plan can be rewritten to:
190
+
191
+ ```ruby
192
+ CommentsWipeOutPlan = WipeOut.build_plan do
193
+ wipe_out :value, strategy: WipeOut::AttributeStrategies::Randomize.new
194
+
195
+ relation :resource_files do
196
+ on_execute ->(execution) { execution.record.destroy! }
197
+ ignore_all
198
+ end
199
+ end
200
+
201
+ DashboardWipeOutPlan = WipeOut.build_plan do
202
+ relation :dashboard do
203
+ wipe_out :order
204
+ ignore :name
205
+ end
206
+ end
207
+
208
+ UserWipeOutPlan = WipeOut.build_plan do
209
+ # …
210
+ relation :comments, CommentsWipeOutPlan
211
+ relation :dashboard, DashboardWipeOutPlan
212
+ # …
213
+ end
214
+ ```
215
+
216
+ #### Including
217
+
218
+ _Plan_ can be included to other existing _Plan_. When _Plan_ is included then
219
+ its strategy is copied and extends current definition.
220
+
221
+ E.g.
222
+
223
+ ```ruby
224
+ HasAttachmentsPlan = WipeOut.build_plan do
225
+ relation(:images) do
226
+ on_execute ->(execution) { execution.record.destroy! }
227
+ ignore_all
228
+ end
229
+ relation(:videos) do
230
+ on_execute ->(execution) { execution.record.destroy! }
231
+ ignore_all
232
+ end
233
+ wipe_out :attachments_count
234
+ end
235
+
236
+ WipeOut.build_plan do
237
+ include_plan HasAttachmentsPlan
238
+ relation(:comments) do
239
+ wipe_out :content
240
+ include_plan HasAttachmentsPlan
241
+ end
242
+ end
243
+ ```
244
+
245
+ is exactly the same as:
246
+
247
+ ```ruby
248
+ WipeOut.build_plan do
249
+ wipe_out :attachments_count
250
+
251
+ relation(:images) do
252
+ on_execute ->(execution) { execution.record.destroy! }
253
+ ignore_all
254
+ end
255
+ relation(:videos) do
256
+ on_execute ->(execution) { execution.record.destroy! }
257
+ ignore_all
258
+ end
259
+
260
+ relation(:comments) do
261
+ wipe_out :content, :attachments_count
262
+
263
+ relation(:images) do
264
+ on_execute ->(execution) { execution.record.destroy! }
265
+ ignore_all
266
+ end
267
+ relation(:videos) do
268
+ on_execute ->(execution) { execution.record.destroy! }
269
+ ignore_all
270
+ end
271
+ end
272
+ end
273
+ ```
274
+
275
+ ### Dynamic plan selection
276
+
277
+ In some cases relation needs to have multiple _Plan_ depending on the record's state.
278
+ The list of _Plans_ have to be known upfront to provide static validation.
279
+ During _Plan_ execution callback is called determine which _Plan_ from the defined list
280
+ to use for a given record.
281
+
282
+ ```ruby
283
+ UserPlan = WipeOut.build_plan do
284
+ normal_plan = WipeOut.build_plan do
285
+ on_execute ->(execution) { execution.record.destroy! }
286
+ end
287
+ vip_plan = WipeOut.build_plan do
288
+ ignore … # do not remove all data yet
289
+ end
290
+
291
+ relation(:resource_files, plans: [vip_plan, normal_plan] do |resource_file|
292
+ resource_file.user.vip? ? vip_plan : normal_plan
293
+ end
294
+ end
295
+ ```
296
+
297
+ ### Plugins
298
+
299
+ Plugins are used to define behaviours which are not supported by the
300
+ core library.
301
+
302
+ Plugins usage can be defined by including them in a plan block.
303
+
304
+ Currently the only hooks available are:
305
+
306
+ * `before(:plan) { |execution| ... }` - called before plan execution, already in transaction
307
+ * `after(:plan) { |execution| ... }` - called after plan execution, still in transaction, last place to rollback
308
+ * `before(:execution) { |execution| ... }` - called before record is wiped out
309
+ * `after(:execution) { |execution| ... }` - called after record is wiped out
310
+
311
+ When _Plan_ with plugins is nested inside other _Plan_ (see "Reusing plans")
312
+ then its plugins are ignored.
313
+
314
+ E.g. in scenario:
315
+
316
+ ```ruby
317
+ XPlan = WipeOut.build_plan do
318
+ plugin PluginX
319
+ wipe_out …
320
+ end
321
+
322
+ YPlan = WipeOut.build_plan do
323
+ relation :x, XPlan
324
+ end
325
+ ```
326
+
327
+ plugin defined in `XPlan` is completely ignored and not used.
328
+
329
+ ## Configuration
330
+
331
+ WipeOut global settings can can be configured:
332
+
333
+ ```ruby
334
+ WipeOut.config do |config|
335
+ config.ignored_attributes << :user_id # defaults: [:id, :updated_at, :created_at, :archived_at]
336
+ end
337
+ ```
338
+
339
+ _Plans_ can override global config:
340
+
341
+ ```ruby
342
+ WipeOut.build_plan do
343
+ configure do |config|
344
+ config.ignored_attributes += [:some, :attributes]
345
+ end
346
+ end
347
+ ```
348
+
349
+ Similarly to Plugins, when _Plan_ with config override is nested inside other _Plan_
350
+ (see "Reusing plans") then its custom configuration is ignored.