simple_rewriter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +57 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +25 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +70 -0
  7. data/LICENSE.md +8 -0
  8. data/README.md +115 -0
  9. data/Rakefile +4 -0
  10. data/bin/pry +29 -0
  11. data/bin/rake +29 -0
  12. data/bin/rspec +29 -0
  13. data/bin/rubocop +29 -0
  14. data/examples/DB_ANONYMIZER.md +57 -0
  15. data/examples/db_anonymizer/mappings.rb +13 -0
  16. data/examples/db_anonymizer.rb +52 -0
  17. data/lib/simple_rewriter/attribute.rb +24 -0
  18. data/lib/simple_rewriter/attribute_resolver.rb +51 -0
  19. data/lib/simple_rewriter/base_service.rb +5 -0
  20. data/lib/simple_rewriter/class_resolver.rb +68 -0
  21. data/lib/simple_rewriter/configuration.rb +15 -0
  22. data/lib/simple_rewriter/reader.rb +48 -0
  23. data/lib/simple_rewriter/readers/active_record_reader.rb +11 -0
  24. data/lib/simple_rewriter/readers/base_reader.rb +21 -0
  25. data/lib/simple_rewriter/readers/hash_reader.rb +11 -0
  26. data/lib/simple_rewriter/readers/open_struct_reader.rb +6 -0
  27. data/lib/simple_rewriter/readers.rb +6 -0
  28. data/lib/simple_rewriter/rewriter.rb +57 -0
  29. data/lib/simple_rewriter/rewriters/active_support/time_with_zone_rewriter.rb +2 -0
  30. data/lib/simple_rewriter/rewriters/active_support.rb +3 -0
  31. data/lib/simple_rewriter/rewriters/array_rewriter.rb +18 -0
  32. data/lib/simple_rewriter/rewriters/base.rb +42 -0
  33. data/lib/simple_rewriter/rewriters/date_rewriter.rb +11 -0
  34. data/lib/simple_rewriter/rewriters/float_rewriter.rb +14 -0
  35. data/lib/simple_rewriter/rewriters/generators.rb +69 -0
  36. data/lib/simple_rewriter/rewriters/hash_rewriter.rb +36 -0
  37. data/lib/simple_rewriter/rewriters/integer_rewriter.rb +7 -0
  38. data/lib/simple_rewriter/rewriters/options_resolver.rb +21 -0
  39. data/lib/simple_rewriter/rewriters/string_rewriter.rb +18 -0
  40. data/lib/simple_rewriter/rewriters.rb +12 -0
  41. data/lib/simple_rewriter/type_detector.rb +41 -0
  42. data/lib/simple_rewriter/version.rb +3 -0
  43. data/lib/simple_rewriter/writer.rb +30 -0
  44. data/lib/simple_rewriter.rb +31 -0
  45. data/simple_rewriter.gemspec +24 -0
  46. data/spec/simple_rewriter/attribute_spec.rb +16 -0
  47. data/spec/simple_rewriter/rewriters/hash_rewriter_spec.rb +25 -0
  48. data/spec/simple_rewriter/rewriters/string_rewriter_spec.rb +62 -0
  49. data/spec/simple_rewriter/type_detector_spec.rb +23 -0
  50. data/spec/simple_rewriter_spec.rb +102 -0
  51. data/spec/spec_helper.rb +104 -0
  52. data/spec/support/simple_rewriter/base_checkers.rb +12 -0
  53. metadata +180 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a52b7ed209e661abcd1b72ab09401597cda25e6e34ea3af81d91302de7daeb63
4
+ data.tar.gz: 0f61df165fe4a75b5a680da636dae30072f3f29068b7ba5df19037b49f3e9a42
5
+ SHA512:
6
+ metadata.gz: 682070d30384de78cb82ef0b56e3b1caf03ccb8e49109f9fe887a3b8831adaf04ed7183c186e6ba6697d7cd21e41bb5a0a7516abe53920d9d912f85d44196240
7
+ data.tar.gz: 8f5200373fa9e75467137586c4b4d2c7e8454ec800afa42e1997e1ceed1e1612bdc4bcd0db628a955d1baeef3e6450f8cefae8ecb0c873b8596f32685f4a48b7
@@ -0,0 +1,57 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ # specify the version you desire here
10
+ - image: circleci/ruby:2.5.3-node-browsers
11
+
12
+ working_directory: ~/repo
13
+
14
+ steps:
15
+ - checkout
16
+
17
+ # Download and cache dependencies
18
+ - restore_cache:
19
+ keys:
20
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}
21
+ # fallback to using the latest cache if no exact match is found
22
+ - v1-dependencies-
23
+
24
+ - run:
25
+ name: install dependencies
26
+ command: |
27
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
28
+
29
+ - save_cache:
30
+ paths:
31
+ - ./vendor/bundle
32
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
33
+
34
+ # run tests!
35
+ - run:
36
+ name: linter
37
+ command: |
38
+ rubocop --parallel
39
+
40
+ - run:
41
+ name: run tests
42
+ command: |
43
+ mkdir /tmp/test-results
44
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
45
+ circleci tests split --split-by=timings)"
46
+
47
+ bin/rspec \
48
+ --format progress \
49
+ --out /tmp/test-results/rspec.xml \
50
+ $TEST_FILES
51
+
52
+ # collect reports
53
+ - store_test_results:
54
+ path: /tmp/test-results
55
+ - store_artifacts:
56
+ path: /tmp/test-results
57
+ destination: test-results
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,25 @@
1
+ inherit_gem:
2
+ ragnarson-stylecheck: config/rubocop.yml
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 2.5
6
+
7
+ Metrics/BlockLength:
8
+ Exclude:
9
+ - spec/**/*
10
+ Rails:
11
+ Enabled: false
12
+ Style/Documentation:
13
+ Enabled: false
14
+ Style/FrozenStringLiteralComment:
15
+ Enabled: false
16
+ Naming/MemoizedInstanceVariableName:
17
+ EnforcedStyleForLeadingUnderscores: required
18
+ Metrics/MethodLength:
19
+ Max: 15
20
+ Metrics/ClassLength:
21
+ Max: 150
22
+ Metrics/ModuleLength:
23
+ Max: 180
24
+ Metrics/CyclomaticComplexity:
25
+ Max: 7
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby '2.5.3'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,70 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ simple_rewriter (0.0.1)
5
+ faker (~> 1.9.3)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.0)
11
+ coderay (1.1.2)
12
+ concurrent-ruby (1.1.5)
13
+ diff-lcs (1.3)
14
+ faker (1.9.3)
15
+ i18n (>= 0.7)
16
+ i18n (1.6.0)
17
+ concurrent-ruby (~> 1.0)
18
+ jaro_winkler (1.5.2)
19
+ method_source (0.9.2)
20
+ parallel (1.14.0)
21
+ parser (2.6.0.0)
22
+ ast (~> 2.4.0)
23
+ powerpack (0.1.2)
24
+ pry (0.12.2)
25
+ coderay (~> 1.1.0)
26
+ method_source (~> 0.9.0)
27
+ ragnarson-stylecheck (0.7.0)
28
+ rake (>= 10.0)
29
+ rubocop (~> 0.58.2)
30
+ rainbow (3.0.0)
31
+ rake (12.3.2)
32
+ rspec (3.8.0)
33
+ rspec-core (~> 3.8.0)
34
+ rspec-expectations (~> 3.8.0)
35
+ rspec-mocks (~> 3.8.0)
36
+ rspec-core (3.8.0)
37
+ rspec-support (~> 3.8.0)
38
+ rspec-expectations (3.8.2)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.8.0)
41
+ rspec-mocks (3.8.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.8.0)
44
+ rspec-support (3.8.0)
45
+ rubocop (0.58.2)
46
+ jaro_winkler (~> 1.5.1)
47
+ parallel (~> 1.10)
48
+ parser (>= 2.5, != 2.5.1.1)
49
+ powerpack (~> 0.1)
50
+ rainbow (>= 2.2.2, < 4.0)
51
+ ruby-progressbar (~> 1.7)
52
+ unicode-display_width (~> 1.0, >= 1.0.1)
53
+ ruby-progressbar (1.10.0)
54
+ unicode-display_width (1.5.0)
55
+
56
+ PLATFORMS
57
+ ruby
58
+
59
+ DEPENDENCIES
60
+ pry (~> 0.12.2)
61
+ ragnarson-stylecheck (~> 0.7.0)
62
+ rake (~> 12.3.2)
63
+ rspec (~> 3.8.0)
64
+ simple_rewriter!
65
+
66
+ RUBY VERSION
67
+ ruby 2.5.3p105
68
+
69
+ BUNDLED WITH
70
+ 1.16.6
data/LICENSE.md ADDED
@@ -0,0 +1,8 @@
1
+ Copyright 2019 Verivox GmbH
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+
2
+ [![Gem Version](https://badge.fury.io/rb/simple_rewriter.svg)](https://badge.fury.io/rb/simple_rewriter)
3
+ [![CircleCI](https://circleci.com/gh/Verivox/simple_rewriter.svg?style=svg)](https://circleci.com/gh/Verivox/simple_rewriter)
4
+
5
+ ## Simple Rewriter
6
+ As usually staging and production databases are different from each other, so testing new features on staging database can be painful because of lack of data, and diferrences of data.
7
+
8
+ Simple Rewriter enables ability to anonymize whole database with simply mappings, you can use dump production data to have production-like data on staging database without worrying about personal data.
9
+
10
+ ### Instalation
11
+ ```
12
+ gem install simple_rewriter
13
+ ```
14
+ Or in gemfile
15
+
16
+ ```ruby
17
+ gem "simple_rewriter", "~> 0.0.1"
18
+ ```
19
+
20
+ ### Sample usage:
21
+ ```ruby
22
+ SimpleRewriter.call(
23
+ record: user,
24
+ attributes: [
25
+ :created_at,
26
+ { token: :hex },
27
+ { email_field: :email },
28
+ { address_field: :address },
29
+ { hash_field: [:deep, :detect_type] },
30
+ { remote_file_urls: { sample_pdf: true, default: [] } # carrierwave support
31
+ ]
32
+ )
33
+ ```
34
+
35
+ | Options | Expected Output |
36
+ |-----------------|-----------------|
37
+ | :email | eac94f-example@domain.com |
38
+ | :address | {:street=>\"137 Fahey Street\", :city=>\"New Terryville\", :zip=>\"30752\"} |
39
+ | :hex | 6fe6a2485164f0 |
40
+ | :name | Evie Upton |
41
+ | :first_name | Evie |
42
+ | :last_name | Upton |
43
+ | :phonenumber | 333-333-3333 |
44
+ | :zip | 58517 |
45
+ | :street | 282 Kevin Brook |
46
+ | :city | Imogeneborough |
47
+ | :date | 1972-01-02 01:23:21 +0100 |
48
+ | :sample_pdf | http://www.africau.edu/images/default/sample.pdf |
49
+ | :deep | nil _Only used to trigger rewriter to go deeper into record_ |
50
+ | :deep_attributes_map | nil _Used to map deeper attributes of record_ |
51
+
52
+ Options can be joined for e.g. passing `[:first_name, :last_name]` will give the same as `[:name]`
53
+
54
+ ### Configuration
55
+
56
+ Prevent rewriter from saving record
57
+ ```ruby
58
+
59
+ SimpleRewriter.configure do |config|
60
+ config.save = false
61
+ end
62
+ ```
63
+
64
+ Custom seeds
65
+ ```ruby
66
+ SimpleRewriter.configure do |config|
67
+ config.integer_range = 0..10
68
+ config.hash_key_length = 5
69
+ config.rand_num_max = 10
70
+ config.address = nil # override default address generator as hash with values
71
+ end
72
+ ```
73
+
74
+ Custom readers/writers
75
+ ```ruby
76
+ SimpleRewriter.configure do |config|
77
+ config.reader = SimpleRewriter::Reader
78
+ config.writer = SimpleRewriter::Writer
79
+ end
80
+ ```
81
+
82
+ Sample Pdf url
83
+ ```ruby
84
+ SimpleRewriter.configure do |config|
85
+ config.sample_pdf_url = "http://www.africau.edu/images/default/sample.pdf"
86
+ end
87
+ ```
88
+
89
+ ### Examples
90
+ Real [anonymizer](/examples/DB_ANONYMIZER.md) configured with sample mappings for batch updates.
91
+
92
+ ### Contributing
93
+ We use PORO to keep dependieces clear and simple.
94
+
95
+ 1. Fork it
96
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
97
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
98
+ 4. Push to the branch (`git push origin my-new-feature`)
99
+ 5. Create new Pull Request
100
+
101
+ Running tests
102
+ ```ruby
103
+ bundle install
104
+ bin/rspec
105
+ ```
106
+
107
+ ### Authors
108
+
109
+ > [jcapt](https://github.com/jcapt) &nbsp;&middot;&nbsp;
110
+ > [PiotrMisiurek](https://github.com/PiotrMisiurek) &nbsp;&middot;&nbsp;
111
+ > [michalsz](https://github.com/michalsz)
112
+
113
+ ---
114
+
115
+ Licensed under MIT by the Verivox GmbH
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ task :console do
2
+ exec 'bin/pry -r simple_rewriter -I ./lib'
3
+ end
4
+ task c: :console
data/bin/pry 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 'pry' 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("pry", "pry")
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/rubocop 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 'rubocop' 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("rubocop", "rubocop")
@@ -0,0 +1,57 @@
1
+ ### Db Anonymizer
2
+
3
+ ```ruby
4
+ class DbAnonymizer
5
+ include Mappings
6
+
7
+ ProductionRestrictedError = Class.new(StandardError)
8
+
9
+ def initialize(bulk_import: true, mappings: CLASSES_WITH_ATTRIBUTES)
10
+ raise_production_error if Rails.env.production?
11
+
12
+ @bulk_import = bulk_import
13
+ @mappings = mappings
14
+ end
15
+
16
+ def call
17
+ rewrite_attributes
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :bulk_import, :mappings
23
+
24
+ def rewrite_attributes
25
+ each_batch do |klass, objects, attributes, valid_objects|
26
+ objects.each do |object|
27
+ result = SimpleRewriter.call(record: object, attributes: attributes)
28
+ valid_objects << result.record if result.success?
29
+ end
30
+
31
+ klass.bulk_import(valid_objects, on_duplicate_key_update: :all, validate: false) if bulk_import
32
+ end
33
+ end
34
+
35
+ def each_batch
36
+ klass_with_config do |klass, attributes|
37
+ klass.find_in_batches(batch_size: 100) do |objects|
38
+ yield(klass, objects, attributes, [])
39
+ end
40
+ end
41
+ end
42
+
43
+ def klass_with_config
44
+ mappings.each do |klass, attribute|
45
+ yield(klass.to_s.safe_constantize, attribute)
46
+ end
47
+ end
48
+
49
+ def raise_production_error
50
+ raise ProductionRestrictedError, "User force_production option to run it instead"
51
+ end
52
+ end
53
+ ```
54
+
55
+ 1. `klass_with_config` iterates over mappings and yields record class, and attribute to rewrite
56
+ 2. `each_batch` yields 100 records to rewrite on one batch
57
+ 3. `rewrite_attributes` calls `SimpleRewriter` with options and does import on 100 records
@@ -0,0 +1,13 @@
1
+ module DbAnonymizer::Mappings
2
+ CLASSES_WITH_ATTRIBUTES = {
3
+ User: [
4
+ :name,
5
+ :place,
6
+ :birthdate,
7
+ { email: :email },
8
+ { token: :hex },
9
+ { jsonb_field: %i(deep detect_type) },
10
+ { remote_attachments_urls: { sample_pdf: true, default: [] } }
11
+ ]
12
+ }.freeze
13
+ end
@@ -0,0 +1,52 @@
1
+ require "activerecord-import"
2
+ require "activerecord-import/active_record/adapters/postgresql_adapter"
3
+
4
+ class DbAnonymizer
5
+ include Mappings
6
+
7
+ ProductionRestrictedError = Class.new(StandardError)
8
+
9
+ def initialize(bulk_import: true, mappings: CLASSES_WITH_ATTRIBUTES)
10
+ raise_production_error if Rails.env.production?
11
+
12
+ @bulk_import = bulk_import
13
+ @mappings = mappings
14
+ end
15
+
16
+ def call
17
+ rewrite_attributes
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :bulk_import, :mappings
23
+
24
+ def rewrite_attributes
25
+ each_batch do |klass, objects, attributes, valid_objects|
26
+ objects.each do |object|
27
+ result = SimpleRewriter.call(record: object, attributes: attributes)
28
+ valid_objects << result.record if result.success?
29
+ end
30
+
31
+ klass.bulk_import(valid_objects, on_duplicate_key_update: :all, validate: false) if bulk_import
32
+ end
33
+ end
34
+
35
+ def each_batch
36
+ klass_with_config do |klass, attributes|
37
+ klass.find_in_batches(batch_size: 100) do |objects|
38
+ yield(klass, objects, attributes, [])
39
+ end
40
+ end
41
+ end
42
+
43
+ def klass_with_config
44
+ mappings.each do |klass, attribute|
45
+ yield(klass.to_s.safe_constantize, attribute)
46
+ end
47
+ end
48
+
49
+ def raise_production_error
50
+ raise ProductionRestrictedError, "User force_production option to run it instead"
51
+ end
52
+ end
@@ -0,0 +1,24 @@
1
+ require "forwardable"
2
+
3
+ class SimpleRewriter::Attribute
4
+ extend Forwardable
5
+
6
+ def initialize(attr_value, options)
7
+ @attr_value = attr_value
8
+ @options = options
9
+ end
10
+
11
+ def_delegator :generator, :generate
12
+
13
+ private
14
+
15
+ attr_reader :attr_value, :options, :generator_klass
16
+
17
+ def generator
18
+ @_generator ||= generator_class.new(attr_value, options)
19
+ end
20
+
21
+ def generator_class
22
+ SimpleRewriter::ClassResolver.call(attr_value, "SimpleRewriter::Rewriters::%{klass}Rewriter", options)
23
+ end
24
+ end
@@ -0,0 +1,51 @@
1
+ module SimpleRewriter
2
+ class AttributeResolver < BaseService
3
+ def initialize(attribute:)
4
+ @attribute = attribute
5
+ end
6
+
7
+ def call
8
+ finalize
9
+ end
10
+
11
+ private
12
+
13
+ INDEX_OF_ATTRIBUTE_NAME = 0
14
+ private_constant :INDEX_OF_ATTRIBUTE_NAME
15
+
16
+ attr_reader :attribute
17
+
18
+ def options
19
+ return extract_options if attribute.is_a?(Hash)
20
+ end
21
+
22
+ def extract_options
23
+ options = attribute.values
24
+
25
+ return options[0] if options[0].is_a?(Hash)
26
+ return convert_array_options(options.flatten) if options.is_a?(Array)
27
+
28
+ options.flatten
29
+ end
30
+
31
+ def convert_array_options(array)
32
+ array.each_with_object({}) { |option, obj| converted_option(option, obj) }
33
+ end
34
+
35
+ def converted_option(option, obj)
36
+ return obj[option.keys[0]] = option.values[0] if option.is_a?(Hash)
37
+
38
+ obj[option] = true
39
+ end
40
+
41
+ def extracted_attribute
42
+ return attribute.keys[INDEX_OF_ATTRIBUTE_NAME] if attribute.is_a?(Hash)
43
+
44
+ attribute
45
+ end
46
+
47
+ def finalize
48
+ OpenStruct.new(attribute: extracted_attribute, options: options)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ class SimpleRewriter::BaseService
2
+ def self.call(*args)
3
+ new(*args).call
4
+ end
5
+ end