anony 1.0.0

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: eee3daa49f033d44737b37aee49c2281430cc23a4ea770c4c0891684ed47fbf8
4
+ data.tar.gz: 2304bfd91b8f503e7c562db4bed2994da418e78180ed11f629630c4122142982
5
+ SHA512:
6
+ metadata.gz: 7fca58237bbd4a8e22217ac9547475f4f13483532a05ac6fe1a02a3e93bd047f965734b0eca08fb5690fde36ac7b3d1b94129bcffa822c31ed5fc78b93be04ea
7
+ data.tar.gz: 2eed4ea29fe1c960bff89b2fa41e63a8338c69069d1dea353a675f4a2e6b1e6e8cb62d4a1be7c7b4d8a445b933870eb24f3dfae920bfbef83b5a9daeccc3a640
@@ -0,0 +1,86 @@
1
+ version: 2
2
+
3
+ references:
4
+ steps: &steps
5
+ - checkout
6
+
7
+ - type: cache-restore
8
+ key: anony-bundler-{{ checksum "anony.gemspec" }}
9
+
10
+ - run: gem install bundler -v 2.1.4
11
+ - run: bundle config set path 'vendor/bundle'
12
+ - run: bundle install
13
+
14
+ - type: cache-save
15
+ key: anony-bundler-{{ checksum "anony.gemspec" }}
16
+ paths:
17
+ - vendor/bundle
18
+
19
+ - type: shell
20
+ command: |
21
+ bundle exec rspec --profile 10 \
22
+ --format RspecJunitFormatter \
23
+ --out /tmp/test-results/rspec.xml \
24
+ --format progress \
25
+ spec
26
+
27
+ - type: store_test_results
28
+ path: /tmp/test-results
29
+
30
+ - run: bundle exec rubocop --parallel --extra-details --display-style-guide
31
+
32
+ jobs:
33
+ ruby24-rails52:
34
+ docker:
35
+ - image: ruby:2.4
36
+ environment:
37
+ - RAILS_VERSION=5.2
38
+ steps: *steps
39
+ ruby25-rails52:
40
+ docker:
41
+ - image: ruby:2.5
42
+ environment:
43
+ - RAILS_VERSION=5.2
44
+ steps: *steps
45
+ ruby25-rails60:
46
+ docker:
47
+ - image: ruby:2.5
48
+ environment:
49
+ - RAILS_VERSION=6.0
50
+ steps: *steps
51
+ ruby26-rails52:
52
+ docker:
53
+ - image: ruby:2.6
54
+ environment:
55
+ - RAILS_VERSION=5.2
56
+ steps: *steps
57
+ ruby26-rails60:
58
+ docker:
59
+ - image: ruby:2.6
60
+ environment:
61
+ - RAILS_VERSION=6.0
62
+ steps: *steps
63
+ ruby27-rails52:
64
+ docker:
65
+ - image: ruby:2.7
66
+ environment:
67
+ - RAILS_VERSION=5.2
68
+ steps: *steps
69
+ ruby27-rails60:
70
+ docker:
71
+ - image: ruby:2.7
72
+ environment:
73
+ - RAILS_VERSION=6.0
74
+ steps: *steps
75
+
76
+ workflows:
77
+ version: 2
78
+ tests:
79
+ jobs:
80
+ - ruby24-rails52
81
+ - ruby25-rails52
82
+ - ruby25-rails60
83
+ - ruby26-rails52
84
+ - ruby26-rails60
85
+ - ruby27-rails52
86
+ - ruby27-rails60
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ # Libraries shouldn't check in Gemfile.lock
14
+ Gemfile.lock
@@ -0,0 +1,9 @@
1
+ inherit_from: .rubocop_todo.yml
2
+ inherit_gem:
3
+ gc_ruboconfig: rubocop.yml
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.4
7
+
8
+ Layout/LineLength:
9
+ Max: 100
@@ -0,0 +1,28 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2019-11-11 12:18:14 +0000 using RuboCop version 0.76.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ RSpec/LeakyConstantDeclaration:
10
+ Exclude:
11
+ - 'spec/anony/anonymisable_spec.rb'
12
+ - 'spec/anony/strategies/overwrite_spec.rb'
13
+
14
+ RSpec/ExampleLength:
15
+ Exclude:
16
+ - 'spec/anony/anonymisable_spec.rb'
17
+
18
+ RSpec/NestedGroups:
19
+ Exclude:
20
+ - 'spec/anony/anonymisable_spec.rb'
21
+
22
+ RSpec/FilePath:
23
+ Exclude:
24
+ - 'spec/anony/cops/define_deletion_strategy_spec.rb'
25
+
26
+ RSpec/DescribeClass:
27
+ Exclude:
28
+ - 'spec/anony/rspec_shared_examples_spec.rb'
@@ -0,0 +1 @@
1
+ 2.6.5
@@ -0,0 +1,64 @@
1
+ # v1.0.0
2
+
3
+ * Create a result object when calling `anonymise!` [#44](https://github.com/gocardless/anony/pull/44)
4
+
5
+ # v0.8.0
6
+
7
+ * Improve the documentation [#45](https://github.com/gocardless/anony/pull/45)
8
+ * Rename fields strategy to overwrite [#46](https://github.com/gocardless/anony/pull/46)
9
+
10
+ # v0.7.3
11
+
12
+ * Allow customising the model superclass for the `DefineDeletionStrategy` cop [#36](https://github.com/gocardless/anony/pull/36)
13
+
14
+ # v0.7.2
15
+
16
+ * Add ability to prevent anonymisation with `skip_if` [#25](https://github.com/gocardless/anony/pull/25)
17
+
18
+ # v0.7.1
19
+
20
+ * Fix breakage when applying a strategy multiple times [#35](https://github.com/gocardless/anony/pull/35)
21
+
22
+ # v0.7.0
23
+
24
+ * **BREAKING** Switch to nesting field-level configuration in a `fields` block
25
+ [#32](https://github.com/gocardless/anony/pull/32). This should just be a case of
26
+ switching `anonymise { ... }` to `anonymise { fields { ... } }` in most cases, but for
27
+ more details please check the README.
28
+ * **BREAKING** `Anony::Strategies.register` was renamed to `Anony::FieldLevelStrategies.register`.
29
+
30
+ # v0.6.0
31
+
32
+ * Use ActiveRecord::Persistence#current_time_from_proper_timezone [#34](https://github.com/gocardless/anony/pull/34)
33
+
34
+ # v0.5.0
35
+
36
+ * Make `valid_anonymisation?` a class method [#24](https://github.com/gocardless/anony/pull/24)
37
+ * Allow dynamic registration of Anony::Strategies [#23](https://github.com/gocardless/anony/pull/23)
38
+ * Only apply anonymisation strategies to columns that are defined [#28](https://github.com/gocardless/anony/pull/28)
39
+
40
+ # v0.4.0
41
+
42
+ * Allow using a constant value as a strategy [#19](https://github.com/gocardless/anony/pull/19)
43
+
44
+ # v0.3.1
45
+
46
+ * Fix `anonymised_at` column [#13](https://github.com/gocardless/anony/pull/13)
47
+
48
+ # v0.3.0
49
+
50
+ * Support `anonymised_at` column [#9](https://github.com/gocardless/anony/pull/9)
51
+
52
+ # v0.2.1
53
+
54
+ * Fix relative require in DefineDeletionStrategy cop [#8](https://github.com/gocardless/anony/pull/8)
55
+
56
+ # v0.2
57
+
58
+ * Improve the README [#5](https://github.com/gocardless/anony/pulls/5)
59
+ * Use Rubocop for testing code style [#6](https://github.com/gocardless/anony/pulls/6)
60
+ * Add an [RSpec helper](https://github.com/gocardless/anony/blob/v0.2/README.md#testing) for testing [#7](https://github.com/gocardless/anony/pulls/7)
61
+
62
+ # v0.1
63
+
64
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in anony.gemspec
8
+ gemspec
9
+
10
+ gem "activerecord", "~> #{ENV['RAILS_VERSION']}" if ENV["RAILS_VERSION"]
11
+ gem "activesupport", "~> #{ENV['RAILS_VERSION']}" if ENV["RAILS_VERSION"]
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2019 GoCardless
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,451 @@
1
+ # Anony
2
+
3
+ Anony is a small library that defines how ActiveRecord models should be anonymised for
4
+ deletion purposes.
5
+
6
+ ```ruby
7
+ class User < ActiveRecord::Base
8
+ include Anony::Anonymisable
9
+
10
+ anonymise do
11
+ overwrite do
12
+ hex :first_name
13
+ end
14
+ end
15
+ end
16
+ ```
17
+ ```ruby
18
+ irb(main):001:0> user = User.find(1)
19
+ => #<User id="1" first_name="Alice">
20
+
21
+ irb(main):002:0> user.anonymise!
22
+ => #<Anony::Result status="overwritten" fields=[:first_name] error=nil>
23
+ ```
24
+
25
+ For our policy on compatibility with Ruby and Rails versions, see [COMPATIBILITY.md](docs/COMPATIBILITY.md).
26
+
27
+ ## Installation & configuration
28
+
29
+ This library is distributed as a Ruby gem, and we recommend adding it your Gemfile:
30
+
31
+ ```ruby
32
+ gem "anony"
33
+ ```
34
+
35
+ The library injects itself using a mixin. To add this to a model class, you should include
36
+ `Anony::Anonymisable`:
37
+
38
+ ```ruby
39
+ class User < ActiveRecord::Base
40
+ include Anony::Anonymisable
41
+ # ...
42
+ end
43
+ ```
44
+
45
+ Alternatively, if you have a Rails application, you might wish to expose this behaviour
46
+ for all of your models: in which case, you can instead add it to `ApplicationRecord` once:
47
+
48
+ ```ruby
49
+ # app/models/application_record.rb
50
+ class ApplicationRecord < ActiveRecord::Base
51
+ include Anony::Anonymisable
52
+ end
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ There are two primary ways to use this library: to either overwrite existing fields on a
58
+ record, or to destroy the record altogether.
59
+
60
+ First, you should establish an `anonymise` block in your model class:
61
+
62
+ ```ruby
63
+ class Employee < ActiveRecord::Base
64
+ include Anony::Anonymisable
65
+
66
+ anonymise do
67
+ end
68
+ end
69
+ ```
70
+
71
+ If you want to overwrite certain fields on the model, you should use the `overwrite`
72
+ DSL. There are many different ways (known as "strategies") to overwrite your fields (see
73
+ [Field strategies](#field-strategies) below). For now, let's use the `hex` & `nilable` strategies, which
74
+ overwrites fields using `SecureRandom.hex` or sets them to `nil`:
75
+
76
+ ```ruby
77
+ anonymise do
78
+ overwrite do
79
+ hex :field_name
80
+ nilable :nullable_field
81
+ end
82
+ end
83
+ ```
84
+
85
+ Alternative, you may wish to simply destroy the record altogether when we call
86
+ `#anonymise!` (this is useful if you're anonymising a collection of different models
87
+ together, only some of which need to be destroyed). This can be configured liked so:
88
+
89
+ ```ruby
90
+ anonymise do
91
+ destroy
92
+ end
93
+ ```
94
+
95
+ Please note that both the `overwrite` and `destroy` strategies cannot be used simultaneously.
96
+
97
+ Now, given a model instance, we can use the `#anonymise!` method to apply our strategies:
98
+
99
+ ```ruby
100
+ irb(main):001:0> model = Model.find(1)
101
+ => #<Model id="1" field_name="Previous value" nullable_field="Previous">
102
+
103
+ irb(main):002:0> model.anonymise!
104
+ => #<Anony::Result status="overwritten" fields=[:field_name, :nullable_field] error=nil>
105
+ ```
106
+
107
+ Or, if you were using the `destroy` strategy:
108
+
109
+ ```ruby
110
+ irb(main):002:0> model.anonymise!
111
+ => #<Anony::Result status="destroyed" fields=nil error=nil>
112
+ ```
113
+
114
+ ### Result object
115
+
116
+ When a model is anonymised, an `Anony::Result` is returned. This allows the library to detail the changes is made and the strategy it used. The result object also contains the errors that may have been raised within Anony, allowing you to handle them elegantly without using the exceptions for flow control.
117
+
118
+ The result object has 3 attributes:
119
+
120
+ * `status` - If the model was `destroyed`, `overwritten`, `skipped` or the operation `failed`
121
+ * `fields` - In the event the model was `overwritten`, the fields that were updated (excludes timestamps)
122
+ * `error` - In the event the anonymisation `failed`, then the associated error. Note only rescues the following errors: `ActiveRecord::RecordNotSaved`, `ActiveRecord::RecordNotDestroyed`. Anything else is thrown.
123
+
124
+ For convenience, the result object can also be queried with `destroyed?`, `overwritten?`, `skipped?` and `failed?`, so that it can be directly interrogated or used in a `switch case` with the `status` property.
125
+
126
+ ### Field strategies
127
+
128
+ This library ships with a number of built-in strategies:
129
+
130
+ * **nilable** overwrites the field with `nil`
131
+ * **hex** overwrites the field with random hexadecimal characters
132
+ * **email** overwrites the field with an email
133
+ * **phone_number** overwrites the field with a dummy phone number
134
+ * **current_datetime** overwrites the field with `Time.zone.now` (using [ActiveSupport's TimeWithZone](https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-i-now))
135
+
136
+ ### Custom strategies
137
+
138
+ You can override the default strategies, or add your own ones to make them available
139
+ everywhere, using the `Anony::FieldLevelStrategies.register(name, &block)` method somewhere after
140
+ your application boots (e.g. in a Rails initializer):
141
+
142
+ ```ruby
143
+ Anony::FieldLevelStrategies.register(:reverse) do |original|
144
+ original.reverse
145
+ end
146
+
147
+ class Employee < ApplicationRecord
148
+ include Anony::Anonymisable
149
+
150
+ anonymise do
151
+ overwrite do
152
+ reverse :first_name
153
+ end
154
+ end
155
+ end
156
+ ```
157
+
158
+ > One strategy you might want to override is `:email`, if your application has a more
159
+ > specific replacement. For example, at GoCardless we use an email on the
160
+ > `@gocardless.com` domain so we can ensure any emails accidentally sent to this address
161
+ > would be quickly identified and fixed. `:phone_number` is another strategy that you
162
+ > might wish to replace (depending on your primary location).
163
+
164
+ You can also use strategies on a case-by-case basis, by honouring the
165
+ `.call(existing_value)` signature:
166
+
167
+ ```ruby
168
+ module OverwriteUUID
169
+ def self.call(_existing_value)
170
+ SecureRandom.uuid
171
+ end
172
+ end
173
+ ```
174
+
175
+ ```ruby
176
+ require "overwrite_uuid"
177
+
178
+ class Manager < ApplicationRecord
179
+ include Anony::Anonymisable
180
+
181
+ anonymise do
182
+ overwrite do
183
+ with_strategy OverwriteUUID, :id
184
+ end
185
+ end
186
+ end
187
+ ```
188
+
189
+ If your strategy doesn't respond to `.call`, then it will be used as a constant value
190
+ whenever the field is anonymised.
191
+
192
+ ```ruby
193
+ class Manager < ApplicationRecord
194
+ include Anony::Anonymisable
195
+
196
+ anonymise do
197
+ overwrite do
198
+ with_strategy 123, :id
199
+ end
200
+ end
201
+ end
202
+ ```
203
+
204
+ ```
205
+ irb(main):001:0> manager = Manager.first
206
+ => #<Manager id=42>
207
+
208
+ irb(main):002:0> manager.anonymise!
209
+ => #<Anony::Result status="overwritten" fields=[:id] error=nil>
210
+
211
+ irb(main):003:0> manager
212
+ => #<Manager id=123>
213
+ ```
214
+
215
+ You can also use a block, which is executed in the context of the model so it can
216
+ access local properties & methods. Blocks take the existing value of the column as the
217
+ only argument:
218
+
219
+ ```ruby
220
+ class Manager < ApplicationRecord
221
+ include Anony::Anonymisable
222
+
223
+ anonymise do
224
+ overwrite do
225
+ with_strategy(:first_name) { |name| Digest::SHA2.hexdigest(name) }
226
+ with_strategy(:last_name) { "previous-name-of-#{id}" }
227
+ end
228
+ end
229
+ end
230
+ ```
231
+
232
+ ```
233
+ irb(main):001:0> manager = Manager.first
234
+ => #<Manager id=42>
235
+
236
+ irb(main):002:0> manager.anonymise!
237
+ => #<Anony::Result status="overwritten" fields=[:first_name, :last_name] error=nil>
238
+
239
+ irb(main):003:0> manager
240
+ => #<Manager first_name="e9ab2800-d4b9-4227-94a7-7f81118d8a8a" last_name="previous-name-of-42">
241
+ ```
242
+
243
+ ### Identifying anonymised records
244
+
245
+ If your model has an `anonymised_at` column, Anony will automatically set that value
246
+ when calling `#anonymise!` (similar to how Rails will modify the `updated_at` timestamp).
247
+ This means you could automatically filter out anonymised records without matching on the
248
+ anonymised values.
249
+
250
+ Here is an example of adding this column with new tables:
251
+
252
+ ```ruby
253
+ class AddEmployees < ActiveRecord::Migration[6.0]
254
+ def change
255
+ create_table(:employees) do |t|
256
+ # ... the rest of your columns
257
+ t.column :anonymised_at, :datetime, null: true
258
+ end
259
+ end
260
+ end
261
+ ```
262
+
263
+ Here is an example of adding this column to an existing table:
264
+
265
+ ```ruby
266
+ class AddAnonymisedAtToEmployees < ActiveRecord::Migration[6.0]
267
+ def change
268
+ add_column(:employees, :anonymised_at, :datetime, null: true)
269
+ end
270
+ end
271
+ ```
272
+
273
+ Records can then be filtered out like so:
274
+
275
+ ```ruby
276
+ class Employees < ApplicationRecord
277
+ scope :without_anonymised, -> { where(anonymised_at: nil) }
278
+ end
279
+ ```
280
+
281
+ ### Preventing anonymisation
282
+
283
+ You might have a need to preserve model data in some (or all) circumstances. Anony exposes
284
+ the `skip_if` DSL for expressing this preference, which runs the given block before
285
+ attempting any strategy.
286
+
287
+ * If the block returns _truthy_, anonymisation is skipped.
288
+ * If the block returns _falsey_, anonymisation continues.
289
+
290
+ ```ruby
291
+ class Manager
292
+ def should_not_be_anonymised?
293
+ id == 1 # The first manager must be kept
294
+ end
295
+
296
+ anonymise do
297
+ skip_if { should_not_be_anonymised? }
298
+ end
299
+ end
300
+ ```
301
+
302
+ The result object will indicate the model was skipped:
303
+
304
+ ```
305
+ irb(main):001:0> manager = Manager.find(1)
306
+ => #<Manager id=1>
307
+
308
+ irb(main):002:0> manager.anonymise!
309
+ => #<Anony::Result status="skipped" fields=[] error=nil>
310
+ ```
311
+
312
+ ## Incomplete field strategies
313
+
314
+ One of the goals of this library is to ensure that your field strategies are _complete_,
315
+ i.e. that the anonymisation behaviour of the model is always correct, even when database
316
+ columns are added/removed or the contents of those columns changes.
317
+
318
+ As such, Anony will validate your model configuration when you try to anonymise the
319
+ model (unfortunately this cannot be safely done at boot as the database might not be
320
+ available). If your configuration is incomplete, calling `#anonymise!` will raise a
321
+ `FieldsException` and will not return an `Anony:Result` object. This is perceived
322
+ to a critical error as anony cannot safely anonymise the model.
323
+
324
+ ```
325
+ irb(main):001:0> manager = Manager.find(1)
326
+ => #<Manager id=1>
327
+
328
+ irb(main):002:0> manager.anonymise!
329
+ Anony::FieldException (Invalid anonymisation strategy for field(s) [:username])
330
+ ```
331
+
332
+ We recommend adding a test for each model that you anonymise (see [Testing](#testing)
333
+ below).
334
+
335
+ ### Adding new columns
336
+
337
+ Anony will fail if you try to anonymise a model without specifying a
338
+ strategy for all of the columns (to ensure that anonymisation rules aren't missed over
339
+ time). However, it's fine to define a strategy for a column
340
+ that hasn't yet been added.
341
+
342
+ This means that, in order to add a new column, you should:
343
+
344
+ 1. Define a strategy for the new column (e.g. `nilable :new_column`)
345
+ 2. Add the column in a database migration.
346
+
347
+ > At GoCardless we do zero-downtime deploys so we would deploy the first change before
348
+ > then deploying the migration.
349
+
350
+ ### Excluding common Rails columns
351
+
352
+ Rails applications typically have an `id`, `created_at` and `updated_at` column on all new
353
+ tables by default. To avoid anonymising these fields (and thus prevent a
354
+ `FieldsException`), they can be globally ignored:
355
+
356
+ ```ruby
357
+ # config/initializers/anony.rb
358
+
359
+ Anony::Config.ignore_fields(:id, :created_at, :updated_at)
360
+ ```
361
+
362
+ By default, `Config.ignore_fields` is an empty array and all fields are considered
363
+ anonymisable.
364
+
365
+ ## Testing
366
+
367
+ This library ships with a set of useful RSpec examples for your specs. Just require them
368
+ somewhere before running your spec:
369
+
370
+ ```ruby
371
+ require "anony/rspec_shared_examples"
372
+ ```
373
+
374
+ ```ruby
375
+ # spec/models/employee_spec.rb
376
+
377
+ RSpec.describe Employee do
378
+ # We use FactoryBot at GoCardless, but
379
+ # however you setup a model instance is fine
380
+ subject { FactoryBot.build(:employee) }
381
+
382
+ # If you just anonymise fields normally
383
+ it_behaves_like "overwritten anonymisable model"
384
+
385
+ # Or, if your anonymised model should be skipped
386
+ it_behaves_like "skipped anonymisable model"
387
+
388
+ # Or, if you anonymise by destroying the record
389
+ it_behaves_like "destroyed anonymisable model"
390
+ end
391
+ ```
392
+
393
+ You can also override the subject _inside_ the shared example if it helps (e.g. if you
394
+ need to persist the record before anonymising it):
395
+
396
+ ```ruby
397
+ RSpec.describe Employee do
398
+ it_behaves_like "anonymisable model with destruction" do
399
+ subject { FactoryBot.create(:employee) }
400
+ end
401
+ end
402
+ ```
403
+
404
+ If you're not using RSpec, or want more control over the tests, Anony also exposes an
405
+ instance method called `#valid_anonymisation?`. A simple spec would be:
406
+
407
+ ```ruby
408
+ RSpec.describe Employee do
409
+ subject { described_class.new }
410
+
411
+ it { is_expected.to be_valid_anonymisation }
412
+ end
413
+ ```
414
+
415
+ ## Integration with Rubocop
416
+
417
+ At GoCardless, we use Rubocop heavily to ensure consistency in our applications. This
418
+ library includes some Rubocop cops, which can be used by adding `anony/cops` to the
419
+ `require` list in your `.rubocop.yml`:
420
+
421
+ ```yml
422
+ require:
423
+ - anony/cops
424
+ ```
425
+
426
+ ### `Lint/DefineDeletionStrategy`
427
+
428
+ This cop ensures that all models in your application have defined an `anonymise` block.
429
+ The output looks like this:
430
+
431
+ ```
432
+ app/models/employee.rb:7:1: W: Lint/DefineDeletionStrategy:
433
+ Define .anonymise for Employee, see https://github.com/gocardless/anony/blob/master/README.md for details:
434
+ class Employee < ApplicationRecord ...
435
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
436
+ ```
437
+
438
+ If your models do not inherit from `ApplicationRecord`, you can specify their superclass
439
+ in your `.rubocop.yml`:
440
+
441
+ ```yml
442
+ Lint/DefineDeletionStrategy:
443
+ ModelSuperclass: Acme::Record
444
+ ```
445
+
446
+ ## License & Contributing
447
+
448
+ * Anony is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
449
+ * Bug reports and pull requests are welcome on GitHub at https://github.com/gocardless/anony.
450
+
451
+ GoCardless ♥ open source. If you do too, come [join us](https://gocardless.com/about/jobs).