dump_cleaner 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +25 -0
  4. data/CHANGELOG.md +5 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +295 -0
  7. data/Rakefile +8 -0
  8. data/doc/workflow_steps.md +1400 -0
  9. data/dump_cleaner.gemspec +38 -0
  10. data/exe/dump_cleaner +7 -0
  11. data/lib/dump_cleaner/cleaners/base_cleaner.rb +32 -0
  12. data/lib/dump_cleaner/cleaners/mysql_shell_dump_cleaner.rb +47 -0
  13. data/lib/dump_cleaner/cleaners/mysql_shell_dump_helpers.rb +11 -0
  14. data/lib/dump_cleaner/cleaners/mysql_shell_table_cleaner.rb +184 -0
  15. data/lib/dump_cleaner/cleanup/bytesize_helpers.rb +39 -0
  16. data/lib/dump_cleaner/cleanup/cleaning.rb +69 -0
  17. data/lib/dump_cleaner/cleanup/cleaning_steps/add_repetition_suffix.rb +23 -0
  18. data/lib/dump_cleaner/cleanup/cleaning_steps/base.rb +33 -0
  19. data/lib/dump_cleaner/cleanup/cleaning_steps/fill_up_with_string.rb +20 -0
  20. data/lib/dump_cleaner/cleanup/cleaning_steps/generate_random_string.rb +37 -0
  21. data/lib/dump_cleaner/cleanup/cleaning_steps/inspect_context.rb +16 -0
  22. data/lib/dump_cleaner/cleanup/cleaning_steps/randomize_email.rb +78 -0
  23. data/lib/dump_cleaner/cleanup/cleaning_steps/randomize_formatted_number.rb +63 -0
  24. data/lib/dump_cleaner/cleanup/cleaning_steps/randomize_number.rb +29 -0
  25. data/lib/dump_cleaner/cleanup/cleaning_steps/select_data_by_bytesize.rb +17 -0
  26. data/lib/dump_cleaner/cleanup/cleaning_steps/select_data_by_pattern.rb +20 -0
  27. data/lib/dump_cleaner/cleanup/cleaning_steps/take_sample.rb +28 -0
  28. data/lib/dump_cleaner/cleanup/data_source.rb +19 -0
  29. data/lib/dump_cleaner/cleanup/data_source_steps/base.rb +26 -0
  30. data/lib/dump_cleaner/cleanup/data_source_steps/group_by_bytesize.rb +37 -0
  31. data/lib/dump_cleaner/cleanup/data_source_steps/inspect_context.rb +16 -0
  32. data/lib/dump_cleaner/cleanup/data_source_steps/load_yaml_file.rb +24 -0
  33. data/lib/dump_cleaner/cleanup/data_source_steps/remove_accents.rb +29 -0
  34. data/lib/dump_cleaner/cleanup/inspection.rb +37 -0
  35. data/lib/dump_cleaner/cleanup/step_context.rb +46 -0
  36. data/lib/dump_cleaner/cleanup/uniqueness.rb +66 -0
  37. data/lib/dump_cleaner/cleanup/workflow.rb +38 -0
  38. data/lib/dump_cleaner/conditions.rb +42 -0
  39. data/lib/dump_cleaner/config.rb +109 -0
  40. data/lib/dump_cleaner/log.rb +42 -0
  41. data/lib/dump_cleaner/options.rb +46 -0
  42. data/lib/dump_cleaner/processor.rb +37 -0
  43. data/lib/dump_cleaner/version.rb +5 -0
  44. data/lib/dump_cleaner.rb +10 -0
  45. metadata +105 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fad879c857b2b0f2bb5c0eb130a91b49fef18dd822b16ab0467ef1af7f9293e9
4
+ data.tar.gz: 710ce63c414421cb7be41d7775bb8be22bcc5570ff208a1fa0701e2e36627b0c
5
+ SHA512:
6
+ metadata.gz: 8388f418065647c421e97f6c85f5a6c89fca228e3f544117fe6b5f687bf6f6aaaa986ea5c35ce4578c690f335a0f4245317f5374b7b017b6138e0f58c5ee8575
7
+ data.tar.gz: d8f449349ac08020eee4fffeaef9a33191703eab1d3048417741fa687be1a34126e88ab18678386e6251b054826518fbecf7f6b0b31771dd7193897aa20cddf3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,25 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Layout/LineLength:
5
+ Max: 120
6
+ IgnoreCopDirectives: true
7
+ AllowedPatterns:
8
+ - ' # +'
9
+
10
+ Metrics/AbcSize:
11
+ Enabled: false
12
+
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - spec/**/*.rb
16
+
17
+ Metrics/MethodLength:
18
+ CountComments: false
19
+ Max: 30
20
+
21
+ Style/Documentation:
22
+ Enabled: false
23
+
24
+ Style/StringLiterals:
25
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.5.0] - 2024-06-13
4
+
5
+ - Initial public release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 NejRemeslnici
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.
data/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # DumpCleaner
2
+
3
+ DumpCleaner is a tool that can randomize or anonymize your database dumps. Currently, it works with the [MySQL Shell Dump](https://dev.mysql.com/doc/mysql-shell/8.4/en/mysql-shell-utilities-dump-instance-schema.html) format (other formats may be added later).
4
+
5
+ _Even though we use DumpCleaner in our production setup, this project still beta quality and may experience breaking changes._
6
+
7
+ ## Why?
8
+
9
+ The main purpose of this tool is to provide a **safe way to work with production data during development**. Often, production databases can easily fit into the developers’ computers and if they don’t, the database tools usually provide a way to [dump a _subset_](https://dev.to/nejremeslnici/mysql-shell-the-best-tool-for-your-logical-backups-44fk#partial-imports-of-the-logical-dumps) of the data (and leave the audit logs behind, for example).
10
+
11
+ We believe that working with production data has several benefits over developing against a near-empty and/or a completely made-up data set:
12
+
13
+ - The volume of data in various tables reflects the production volume. This helps uncover slow queries, missing indices, or unoptimized data retrieval patterns much earlier in the development loop.
14
+ - This also provides better constraints for the UX design. There is a difference between building a screen with a list having 10 test records vs. 10 thousand production records.
15
+ - The data that developers work with is realistic. [Faker](https://github.com/faker-ruby/faker) is nice but it can never reach the breadth and variety of real data made by real people using your app.
16
+ - Developers don’t have to access the production database (they don’t even have to have the privileges to access it) to test their hypotheses about the data or learn the common patterns or edge cases.
17
+
18
+ That said, having an exact production data copy at developers’ machines is insecure and could lead to personal data leaks and violations of GDPR or similar legislation. Moreover, developers usually do not—or should not—need to work with real individual data records, they rather need **a realistic-enough approximation** of the data. That’s where the DumpCleaner’s main feature, a high-fidelity data anonymization / randomization, should come in handy.
19
+
20
+ ### The goals of this project
21
+
22
+ - DumpCleaner works with **database _dumps_** rather than databases themselves. Doing that, it fits nicely into the process of cloning the production data to the developer machines.
23
+ - It produces **high-fidelity data** during the randomization / anonymization, for example it allows to replace:
24
+ - an individual’s phone number with a random number from the same phone carrier,
25
+ - a gmail.com email address with a different random mailbox at gmail.com,
26
+ - a user’s geographic location with a random location within a few miles from the original one,
27
+ - someone’s name with a random name taken from a dictionary of names you specify,
28
+ - someone’s IP address with a random IP address having the same prefix (same or similar subnet),
29
+ - and so on…
30
+ - It works **deterministically**, i.e. multiple runs over the same source data result in the same cleaned data.
31
+ - It can generate **unique data** across a given table column if needed.
32
+ - It can **ignore certain columns and/or records** in the dump based on a set of conditions to e.g. skip randomizing contact information of internal admin users.
33
+ - It obeys the inherent limits of the given dump format, if any (for example, it takes great care to keep the length and byte size of the updated data the same as original so as not to corrupt the MySQL Shell dump chunk index files).
34
+
35
+ All in all, DumpCleaner is just a „more specialized and configurable `awk`“, i.e. a text replacement tool.
36
+
37
+ #### Non-goals and limitations
38
+
39
+ - This is not an effort to fully anonymize all production personal data according to GDPR rules. In simple cases DumpCleaner might achieve that but in general it is probably not performant and flexible enough for such task.
40
+ - The quality of the data randomization often relies heavily on the quality of source dictionaries. There is only a small effort for the tool to be able to _fake_ high fidelity data ”out of nothing“, there are [other tools](https://github.com/faker-ruby/faker) for that. If you need the resulting data to be more specific, you can usually prepare a more specific dictionary for your domain.
41
+ - Speed: while this tool can process millions of records in a few minutes, there are currently no speed optimizations applied or planned. This is probably not a good tool for live anonymization of bigger amounts of data. It is rather meant to be run as a background task somewhere on your server during the night, just after it dumps out the database backups.
42
+ - The cleaning process is currently stateless in the sense that one cleaned-up record field knows nothing about other cleaned-up fields in the same record.
43
+ - Currently, DumpCleaner works with the [MySQL Shell Dump](https://dev.mysql.com/doc/mysql-shell/8.4/en/mysql-shell-utilities-dump-instance-schema.html) format under default settings (mainly because [we use it ourselves](https://dev.to/nejremeslnici/mysql-shell-the-best-tool-for-your-logical-backups-44fk)) but other formats may be added later.
44
+
45
+ ## Installation
46
+
47
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
48
+
49
+ Install the gem and add to the application's Gemfile by executing:
50
+
51
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
52
+
53
+ If bundler is not being used to manage dependencies, install the gem by executing:
54
+
55
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
56
+
57
+ ## Usage
58
+
59
+ The gem provides a `dump_cleaner` executable that must be called with the following arguments:
60
+
61
+ ```sh
62
+ $ dump_cleaner -f <source_dump_path> -t <destination_dump_path> [-c <config_file>]
63
+ ```
64
+
65
+ where:
66
+ - `-f` / `--from=` sets the path to the source (original, non-anonymized) data dump; for MySQL Shell this is the directory with the dump created by the MySQL Shell dump utility
67
+ - `-t` / `--to=` sets the path to the destination (anonymized) data dump; for MySQL Shell this is the directory with the dump which will be created or overwritten by DumpCleaner
68
+ - `-c` / `--config=` sets the path to the [configuration file](#configuration) (default: `config/dump_cleaner.yml`); the configuration is documented below
69
+
70
+ ### A basic example
71
+
72
+ The repository includes a [sample MySQL Shell dump](https://github.com/NejRemeslnici/dump-cleaner/tree/main/spec/support/data/mysql_shell_dump) that has been produced by [running](https://dev.mysql.com/doc/mysql-shell/8.4/en/mysql-shell-utilities-dump-instance-schema.html#mysql-shell-utilities-dump-opt-run) the MySQL Shell dump utility against a `db` database:
73
+
74
+ ```
75
+ MySQLShell JS> util.dumpSchemas(["db"], "mysql_shell_dump");
76
+ ```
77
+
78
+ The dump contains a `users` table with the following sample contents:
79
+
80
+ ```sh
81
+ $ zstdcat spec/support/data/mysql_shell_dump/db@users@@0.tsv.zst
82
+
83
+ # id name email phone_number
84
+ 1 Johnson johnson@gmail.com +420774678763
85
+ 2 Smith amiright@example.com +420733653796
86
+ 3 Williams anette.williams@example.com N/A
87
+ ```
88
+
89
+ Now, after running DumpCleaner with the following options including a [certain config file](https://github.com/NejRemeslnici/dump-cleaner/blob/main/spec/support/data/mysql_shell_dump_cleaner.yml):
90
+
91
+ ```sh
92
+ $ dump_cleaner -f mysql_shell_dump -t mysql_shell_anonymized_dump \
93
+ -c mysql_shell_dump_cleaner.yml
94
+ ```
95
+
96
+ a destination dump directory gets created with a copy of the source dump but with the data in the `users` table randomized, in this case in the following way:
97
+
98
+ ```sh
99
+ $ zstdcat spec/support/data/mysql_shell_anonymized_dump/db@users@@0.tsv.zst
100
+
101
+ # id name email phone_number
102
+ 1 Jackson variety@gmail.com +420774443735
103
+ 2 Allen contains@present.com +420733637921
104
+ 3 Harrison should.visitors@program.com N/A
105
+ ```
106
+
107
+ There are a few things to note here:
108
+ - The names or mail boxes are replaced by random words from the dictionary specified in the config file.
109
+ - The replacements did not change the size of the data (actually it keeps the byte size, too).
110
+ - The well-known email domains as well as phone number carrier prefixes have been kept but other parts of the data randomized.
111
+ - Some values were ignored (`N/A`) as specified in the config file.
112
+
113
+ If DumpCleaner was run once again over the same source data and using the same config, it would produce exactly the same randomized data in the output.
114
+
115
+ _Read on if you are interested in more details about how DumpCleaner works, otherwise you can safely skip to the [Configuration](#configuration) section._
116
+
117
+ ### How does DumpCleaner work?
118
+
119
+ DumpCleaner first reads the [config file](#configuration). From the configuration, it finds the tables and columns that need to be sanitized by the cleaning process. It parses the dump data for each table, extracts the fields from each record and runs the following workflows for each to-be-cleaned field:
120
+
121
+ - A **”data source“ workflow** that grabs the data for the given data type that will be needed for the cleaning workflow that comes next. This data is then cached.
122
+ - A **”cleaning“ workflow** usually further extracts the relevant part from the somewhat generic source data based on the individual field value and then, more importantly, ”cleans“ the field value by randomizing or anonymizing it somehow.
123
+ - Optionally, a **”failure“ workflow** which serves as the last resort when the previous steps fail for some reason (return a `nil` value). This workflow usually replaces the field value with a random one.
124
+
125
+ After all configured table columns have been cleaned, the tool copies the remaining data from the original dump so that the destination dump is complete and ready for re-import.
126
+
127
+ The overall process is summarized in the diagram below, too:
128
+
129
+ ```mermaid
130
+ flowchart LR
131
+ A(start) --> AA[read\nconfig]
132
+ AA --> B{{each\ntable}}
133
+ B --> BB{{each\nrecord}}
134
+ BB --> C{{each\nfield}}
135
+ C -->D[run the\ndata source steps]
136
+ D -->E[run the\ncleaning steps]
137
+ E -->F{failed?}
138
+ F -->|yes|G[run the\nfailure steps]
139
+ G --> H
140
+ F -->|no|H{result\nunique?}
141
+ H -->|yes or\nirrelevant|L{more\ndata to\nclean?}
142
+ H --> |no but wanted| E
143
+ L -.-> |yes| C
144
+ L -.-> |yes| BB
145
+ L -.-> |yes| B
146
+ L --> |no| M[copy\nremaining\n data]
147
+ M --> Z(end)
148
+ ```
149
+
150
+ ### Unique values
151
+
152
+ A particular column in a table may be configured to require unique randomized data. In that case, the cleaning process is repeated until it produces a unique randomized value, or until max retries limit is reached (currently 1000).
153
+
154
+ The cleaning workflow steps usually just add a numeric suffix to the randomized value, without increasing its length (and byte size). For example, if the sanitized value is `something`, its unique variant may become `somethin1` or even `somethi99`. Some cleaning steps, on the other hand, allow repeatedly taking a random value from the dictionary instead of adding a suffix.
155
+
156
+ When max retries is reached, DumpCleaner prints an error and switches to the failure workflow for further processing.
157
+
158
+ ### Randomization is deterministic
159
+
160
+ To achieve a deterministic randomness when cleaning the data, each random number generation is seeded with a [value](https://github.com/NejRemeslnici/dump-cleaner/blob/main/lib/dump_cleaner/cleanup/cleaning_steps/base.rb#L21) reflecting **the identity of the current record** (usually it’s primary key value) and the field original value. This guarantees that the same source data in the same record will be always cleaned up to the same randomized data.
161
+
162
+ There are some practical limits to this consistency though:
163
+
164
+ - if the randomization works with a dictionary and that dictionary is updated, the cleaned data will change, too, and
165
+ - if [uniqueness](#unique-values) of a column is requested, the randomization process is retried for that field until a unique value is found; this makes the randomization rely on the values of the previously encountered conflicting values and if _any_ of them changes in the source data, the final cleaned value changes, too.
166
+
167
+ NOTE: The fact that the [RNG](https://en.wikipedia.org/wiki/Random_number_generation) seed is also dependent on the primary key has one more potentially undesired consequence: the same original value will be cleaned to different values in records with different primary keys, thus adding an artificial variance to the data. We will look into this issue in a future release.
168
+
169
+ ## Configuration
170
+
171
+ A basic DumpCleaner configuration file might look like this:
172
+
173
+ ```yaml
174
+ dump_cleaner:
175
+ log_level: info
176
+
177
+ dump:
178
+ format: mysql_shell
179
+
180
+ cleanup_tables:
181
+ - db: db
182
+ table: users
183
+ columns:
184
+ - name: name
185
+ cleanup_type: last_name
186
+ - name: e_mail
187
+ cleanup_type: email
188
+ unique: true
189
+ - name: phone_number
190
+ cleanup_type: phone_number_intl
191
+ record_context_columns:
192
+ - id
193
+ - admin_flag
194
+ keep_same_record_conditions:
195
+ - column: admin_flag
196
+ condition: non_zero
197
+ # id_column: user_id (not useful in this table)
198
+
199
+ cleanup_types:
200
+ last_name:
201
+ data_source:
202
+ - step: LoadYamlFile
203
+ params:
204
+ file: spec/support/data/dict/last_names.yml
205
+ - step: GroupByBytesize
206
+ cleaning:
207
+ - step: SelectDataByBytesize
208
+ - step: TakeSample
209
+ failure:
210
+ - step: FillUpWithString
211
+
212
+ email:
213
+ data_source:
214
+ ...
215
+ cleaning:
216
+ - step: RandomizeEmail
217
+ failure:
218
+ - step: FillUpWithString
219
+
220
+ phone_number_intl:
221
+ cleaning:
222
+ - step: RandomizeFormattedNumber
223
+ params:
224
+ # +420123456789
225
+ format: (?<front>\+(?:\d{6}))(?<x>\d{6})
226
+ failure:
227
+ - step: FillUpWithString
228
+ keep_same_conditions:
229
+ - condition: eq
230
+ value: "N/A"
231
+ ```
232
+
233
+ The individual config options are as follows:
234
+
235
+ ### `dump_cleaner`
236
+
237
+ This allows setting the log level using the `log_level` property. The DumpCleaner log output is printed to `STDOUT` and the default log level is `INFO`.
238
+
239
+ ### `dump`
240
+
241
+ This setting currently only defines the format of the data dump using the `format` property. The only recognized format now is `mysql_shell`.
242
+
243
+ ### `cleanup_tables`
244
+
245
+ This is where things get more interesting. The `cleanup_tables` key specifies which tables (via the `db` and `table` properties) and their columns (via the `name` property nested inside the `columns` array) should be cleaned and what `cleanup_type` each column is, i.e. which variant of the cleanup process will be used for it. Optionally, a column may also include a `unique` property: when set to `true` the randomized values in this column will be guaranteed to be unique across the table.
246
+
247
+ Optionally, an `id_column` key may be given that determines the foreign key which is responsible for determining the identity of the table records (see the [Randomization](#randomization-is-deterministic) section above). For example a table that [belongs_to](https://guides.rubyonrails.org/association_basics.html#the-belongs-to-association) the `users` table might have the `id_column` set to `user_id` and this would ensure that the values in this table would be randomized the same as the corresponding values in the `users` table, keeping consistency across the associated tables. This property defaults to `"id"`.
248
+
249
+ Optionally, the `keep_same_conditions` key may also hold [conditions](#keep_same_conditions) for ignoring the cleanup of a record from the table. When the conditions evaluate to a truthy value for the record, none of its fields gets cleaned. This is useful if you want to keep some records (say admin users) in the original state.
250
+
251
+ The optional `record_context_columns` property may define a list of columns the source values of which should be available for the cleaning workflows. This is currently used when evaluating the `keep_same_conditions`. (This could probably be refactored out as it unnecessarily duplicates the configuration a bit.)
252
+
253
+ ### `cleanup_types`
254
+
255
+ The core of the sanitization process lies here. Under this key the relevant steps for the `data_source`, `cleaning` or `failure` workflows are specified, each with optional `params`. In general, the output of one step makes the input of the following step. It is considered an error if a `cleaning` step returns a `nil` value and that’s when the processing switches to the `failure` workflow.
256
+
257
+ **See the [Workflow steps page](doc/workflow_steps.md) for the individual steps documentation.**
258
+
259
+ Optionally, under the `keep_same_conditions` property, [conditions](#keep_same_conditions) for ignoring the cleanup of the given cleanup type may be given. If they evaluate to true for the currently processed field value, it’s cleanup is skipped and the original value is returned.
260
+
261
+ Finally, the optional `ignore_keep_same_record_conditions` property may be set to true to indicate that current field value should be always cleaned, even if the `keep_same_conditions` were used for the whole record at the `cleanup_table` level.
262
+
263
+ ### `keep_same_conditions`
264
+
265
+ The `keep_same_conditions` property may define a list of conditions that will prevent cleaning up the given field or record. Each condition is a hash that consists of the following properties:
266
+
267
+ - `column` - defines the column in the table that the condition should take the field’s value from (this is useful only when using the `keep_same_conditions` under the [`cleanup_tables`](#cleanup_tables) configuration key whereas in the `cleanup_types` context the column is implied),
268
+ - `condition` - specifies the operator or function that the condition should evaluate; currently supported values here are:
269
+ - `eq` - tests with `==`
270
+ - `ne` - tests with `!=`
271
+ - `start_with` - tests strings with the [`start_with?` method](https://ruby-doc.org/3.3.2/String.html#method-i-start_with-3F)
272
+ - `end_with` - tests strings with the [`end_with?` method](https://ruby-doc.org/3.3.2/String.html#method-i-end_with-3F)
273
+ - `non_zero` - converts the value to an integer and tests if it is different from zero,
274
+ - `value` - the value to evaluate the condition against (some operators may not use a value, such as `non_zero`).
275
+
276
+ If multiple conditions are specified, they are logically OR-ed, i.e. if _any_ of the conditions yields true, the whole statement yields true and the record or field cleaning is skipped.
277
+
278
+ ## Ideas for future development
279
+
280
+ - The issue with random seeds being dependent on the primary key (and thus artificially increasing data variance): this behavior should probably be optional.
281
+ - The `RandomizeFormattedNumber` step could be generalized to `RandomizeFormattedString`, allowing to replace any matching part of the string with not only numbers, but alphanumeric etc. as well. The `RandomizeEmail` could then be rewritten using this new step.
282
+
283
+ ## Development
284
+
285
+ 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.
286
+
287
+ To install this gem onto your local machine, run `bundle exec rake install`.
288
+
289
+ ## Contributing
290
+
291
+ Bug reports and pull requests are welcome [on GitHub](https://github.com/NejRemeslnici/dump-cleaner/issues).
292
+
293
+ ## License
294
+
295
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec