simple_rewriter 0.0.2

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 (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
@@ -0,0 +1,68 @@
1
+ module SimpleRewriter
2
+ class ClassResolver < BaseService
3
+ NotSupportedAttribute = Class.new(StandardError)
4
+
5
+ def initialize(value, guess, options = nil)
6
+ @value = value
7
+ @guess = guess
8
+ @attr_klass = value.class
9
+ @options = options
10
+ end
11
+
12
+ def call
13
+ desired_class
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :attr_klass, :options, :klass, :guess
19
+
20
+ def desired_class
21
+ return klass if detect_type? && try_class(detect_type)
22
+ return klass if default? && try_class(default_class)
23
+ return klass if try_class(attr_klass)
24
+ return SimpleRewriter::Readers::ActiveRecordReader if active_record?
25
+
26
+ raise_unsupported_error
27
+ end
28
+
29
+ def raise_unsupported_error
30
+ raise NotSupportedAttribute, "Attribute '#{attr_klass}' is not supported (#{string_to_constantize(attr_klass)})"
31
+ end
32
+
33
+ def try_class(scoped_klass)
34
+ return false unless result = safe_constantize(string_to_constantize(scoped_klass))
35
+ @klass = result
36
+ end
37
+
38
+ def string_to_constantize(scoped_klass)
39
+ guess % { klass: scoped_klass }
40
+ end
41
+
42
+ def default_class
43
+ options[:default].class
44
+ end
45
+
46
+ def default?
47
+ options && options[:default]
48
+ end
49
+
50
+ def detect_type?
51
+ options && options[:detect_type]
52
+ end
53
+
54
+ def detect_type
55
+ SimpleRewriter::TypeDetector.call(value)
56
+ end
57
+
58
+ def safe_constantize(target)
59
+ Object.const_get(target)
60
+ rescue StandardError
61
+ false
62
+ end
63
+
64
+ def active_record?
65
+ attr_klass.is_a?(ActiveRecord::Base) if defined?(ActiveRecord::Base)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,15 @@
1
+ class SimpleRewriter::Configuration
2
+ attr_accessor :save, :integer_range, :hash_key_length, :hash_value_length, :rand_num_max, :address,
3
+ :reader, :writer, :sample_pdf_url
4
+
5
+ def initialize
6
+ @reader = SimpleRewriter::Reader
7
+ @writer = SimpleRewriter::Writer
8
+ @save = false
9
+ @integer_range = 0..10
10
+ @hash_key_length = 5
11
+ @rand_num_max = 10
12
+ @address = nil
13
+ @sample_pdf_url = "http://www.africau.edu/images/default/sample.pdf"
14
+ end
15
+ end
@@ -0,0 +1,48 @@
1
+ class SimpleRewriter::Reader
2
+ INDEX_OF_ATTRIBUTE_NAME = 0
3
+
4
+ def self.call(record, attributes)
5
+ new(record, attributes).call { |data| yield(data) }
6
+ end
7
+
8
+ def initialize(record, attributes)
9
+ @record = record
10
+ @attributes = attributes
11
+ end
12
+
13
+ def call
14
+ attributes.each do |attribute_with_options|
15
+ @attribute_with_options = attribute_with_options
16
+
17
+ yield [attribute, attr_value, options]
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :record, :attributes, :attribute_with_options
24
+
25
+ def attr_value
26
+ reader.call(attribute)
27
+ end
28
+
29
+ def reader
30
+ @_reader ||= reader_class.new(record: record)
31
+ end
32
+
33
+ def attribute
34
+ attributes_resolver.call(attribute: attribute_with_options).attribute
35
+ end
36
+
37
+ def options
38
+ attributes_resolver.call(attribute: attribute_with_options).options
39
+ end
40
+
41
+ def attributes_resolver
42
+ SimpleRewriter::AttributeResolver
43
+ end
44
+
45
+ def reader_class
46
+ SimpleRewriter::ClassResolver.call(record, "SimpleRewriter::Readers::%{klass}Reader")
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ module SimpleRewriter
2
+ module Readers
3
+ class ActiveRecordReader < BaseReader
4
+ private
5
+
6
+ def read_attribute(attribute)
7
+ record.send(attribute)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module SimpleRewriter
2
+ module Readers
3
+ class BaseReader
4
+ def initialize(record:)
5
+ @record = record
6
+ end
7
+
8
+ def call(attribute)
9
+ read_attribute(attribute)
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :record
15
+
16
+ def read_attribute(_attribute)
17
+ raise NotImplementedError
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module SimpleRewriter
2
+ module Readers
3
+ class HashReader < BaseReader
4
+ private
5
+
6
+ def read_attribute(attribute)
7
+ record[attribute.to_s]
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module SimpleRewriter
2
+ module Readers
3
+ class OpenStructReader < ActiveRecordReader
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module SimpleRewriter::Readers
2
+ autoload :BaseReader, "simple_rewriter/readers/base_reader"
3
+ autoload :ActiveRecordReader, "simple_rewriter/readers/active_record_reader"
4
+ autoload :HashReader, "simple_rewriter/readers/hash_reader"
5
+ autoload :OpenStructReader, "simple_rewriter/readers/open_struct_reader"
6
+ end
@@ -0,0 +1,57 @@
1
+ require "ostruct"
2
+
3
+ module SimpleRewriter
4
+ class Rewriter < BaseService
5
+ def initialize(record:, attributes: [])
6
+ @record = record
7
+ @attributes = attributes
8
+ end
9
+
10
+ def call
11
+ rewrite_attributes
12
+ finalize
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :record, :attributes
18
+
19
+ def rewrite_attributes
20
+ configuration.reader.call(record, attributes) do |attribute, attr_value, options|
21
+ rewrite(attribute, attr_value, options)
22
+ rescue SimpleRewriter::ClassResolver::NotSupportedAttribute
23
+ next
24
+ end
25
+ end
26
+
27
+ def rewrite(attribute, attr_value, options = nil)
28
+ new_attr_value = new_value_for(attr_value, options)
29
+ configuration.writer.call(record, attribute, new_attr_value)
30
+ end
31
+
32
+ def new_value_for(value, options)
33
+ SimpleRewriter::Attribute.new(value, options).generate
34
+ end
35
+
36
+ def finalize
37
+ OpenStruct.new(
38
+ success?: success?,
39
+ record: record
40
+ )
41
+ end
42
+
43
+ def success?
44
+ return true unless saveable?
45
+ record.save
46
+ end
47
+
48
+ def saveable?
49
+ return false if record.is_a?(Hash)
50
+ configuration.save
51
+ end
52
+
53
+ def configuration
54
+ SimpleRewriter.configuration
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,2 @@
1
+ class SimpleRewriter::Rewriters::ActiveSupport::TimeWithZoneRewriter < SimpleRewriter::Rewriters::DateRewriter
2
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleRewriter::Rewriters::ActiveSupport
2
+ autoload :TimeWithZoneRewriter, "simple_rewriter/rewriters/active_support/time_with_zone_rewriter.rb"
3
+ end
@@ -0,0 +1,18 @@
1
+ class SimpleRewriter::Rewriters::ArrayRewriter < SimpleRewriter::Rewriters::Base
2
+ def generate
3
+ random_sized_array
4
+ end
5
+
6
+ private
7
+
8
+ def array
9
+ @_array ||= []
10
+ end
11
+
12
+ def random_sized_array
13
+ rand_num.times do
14
+ array << string_generator.generate
15
+ end
16
+ array
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ require "faker"
2
+
3
+ class SimpleRewriter::Rewriters::Base
4
+ include SimpleRewriter::Rewriters::Generators
5
+ include SimpleRewriter::Rewriters::OptionsResolver
6
+
7
+ REGISTERED_OPTIONS = %i(email address hex name first_name last_name phonenumber zip street city date
8
+ sample_pdf deep).freeze
9
+
10
+ def initialize(attr_value, options)
11
+ @attr_value = attr_value
12
+ @_options = options
13
+ end
14
+
15
+ def generate
16
+ collect_values_from_options
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :attr_value
22
+
23
+ def scoped_options
24
+ REGISTERED_OPTIONS
25
+ end
26
+
27
+ def passed_options
28
+ options.keys.flatten
29
+ end
30
+
31
+ REGISTERED_OPTIONS.each do |method_name|
32
+ define_method("#{method_name}?") { options.include?(method_name) }
33
+ end
34
+
35
+ def configuration
36
+ SimpleRewriter.configuration
37
+ end
38
+
39
+ def options
40
+ @_options ||= []
41
+ end
42
+ end
@@ -0,0 +1,11 @@
1
+ class SimpleRewriter::Rewriters::DateRewriter < SimpleRewriter::Rewriters::Base
2
+ def generate
3
+ random_date
4
+ end
5
+
6
+ private
7
+
8
+ def random_date
9
+ Time.at(rand * Time.now.to_i)
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ class SimpleRewriter::Rewriters::FloatRewriter < SimpleRewriter::Rewriters::Base
2
+ def generate
3
+ random_float
4
+ end
5
+
6
+ private
7
+
8
+ # rand generates pseudo random from 0-1
9
+ # rand(10) generates pseudo random from 0-10
10
+ # we use it to get something like 4.41445946155691
11
+ def random_float
12
+ rand * rand(10)
13
+ end
14
+ end
@@ -0,0 +1,69 @@
1
+ module SimpleRewriter::Rewriters::Generators
2
+ def random_sample_pdf
3
+ configuration.sample_pdf_url
4
+ end
5
+
6
+ def random_address
7
+ configuration.address || default_address
8
+ end
9
+
10
+ def default_address
11
+ {
12
+ street: random_street,
13
+ city: random_city,
14
+ zip: random_zip
15
+ }
16
+ end
17
+
18
+ def random_name
19
+ Faker::Name.name
20
+ end
21
+
22
+ def random_email
23
+ "#{random_hex(5)}-#{Faker::Internet.email}"
24
+ end
25
+
26
+ def random_first_name
27
+ Faker::Name.first_name
28
+ end
29
+
30
+ def random_last_name
31
+ Faker::Name.last_name
32
+ end
33
+
34
+ def random_phonenumber
35
+ Faker::PhoneNumber.phone_number
36
+ end
37
+
38
+ def random_hex(hex_length = nil)
39
+ SecureRandom.hex(hex_length)
40
+ end
41
+
42
+ def random_zip
43
+ Faker::Address.zip
44
+ end
45
+
46
+ def random_city
47
+ Faker::Address.city
48
+ end
49
+
50
+ def random_street
51
+ Faker::Address.street_address
52
+ end
53
+
54
+ def random_date
55
+ date_generator.generate
56
+ end
57
+
58
+ def rand_num
59
+ SecureRandom.random_number(configuration.rand_num_max)
60
+ end
61
+
62
+ def string_generator
63
+ @_string_generator ||= SimpleRewriter::Rewriters::StringRewriter.new(attr_value, options)
64
+ end
65
+
66
+ def date_generator
67
+ @_date_generator ||= SimpleRewriter::Rewriters::DateRewriter.new(attr_value, options)
68
+ end
69
+ end
@@ -0,0 +1,36 @@
1
+ class SimpleRewriter::Rewriters::HashRewriter < SimpleRewriter::Rewriters::Base
2
+ def generate
3
+ return super if any_option_passed?
4
+ random_sized_hash
5
+ end
6
+
7
+ private
8
+
9
+ def scoped_options
10
+ %i(address deep)
11
+ end
12
+
13
+ def random_sized_hash
14
+ {}.tap do |hash|
15
+ rand_num.times do |_i|
16
+ hash[hash_key] = hash_value
17
+ end
18
+ end
19
+ end
20
+
21
+ def hash_key
22
+ random_hex(configuration.hash_key_length)
23
+ end
24
+
25
+ def hash_value
26
+ string_generator.generate
27
+ end
28
+
29
+ def resolve_options_with(values)
30
+ values[0]
31
+ end
32
+
33
+ def random_deep
34
+ SimpleRewriter.call(record: attr_value, attributes: options[:deep_attributes_map]).record
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ require "securerandom"
2
+
3
+ class SimpleRewriter::Rewriters::IntegerRewriter < SimpleRewriter::Rewriters::Base
4
+ def generate
5
+ SecureRandom.random_number(configuration.integer_range)
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ module SimpleRewriter::Rewriters::OptionsResolver
2
+ def any_option_passed?
3
+ scoped_options.any? { |option| send(:"#{option}?") }
4
+ end
5
+
6
+ def collect_values_from_options
7
+ values = filtered_options.each_with_object([]) do |option, arr|
8
+ arr << send("random_#{option}") if send(:"#{option}?")
9
+ end
10
+
11
+ resolve_options_with(values)
12
+ end
13
+
14
+ def filtered_options
15
+ passed_options.select { |option| scoped_options.include?(option) }
16
+ end
17
+
18
+ def resolve_options_with(values)
19
+ values.join(" ")
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ require "faker"
2
+
3
+ class SimpleRewriter::Rewriters::StringRewriter < SimpleRewriter::Rewriters::Base
4
+ def generate
5
+ return super if any_option_passed?
6
+ random_text
7
+ end
8
+
9
+ private
10
+
11
+ def scoped_options
12
+ REGISTERED_OPTIONS - [:deep]
13
+ end
14
+
15
+ def random_text
16
+ "#{random_hex(5)}-#{Faker::Quote.famous_last_words}"
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module SimpleRewriter::Rewriters
2
+ autoload :Base, "simple_rewriter/rewriters/base"
3
+ autoload :ArrayRewriter, "simple_rewriter/rewriters/array_rewriter"
4
+ autoload :DateRewriter, "simple_rewriter/rewriters/date_rewriter"
5
+ autoload :FloatRewriter, "simple_rewriter/rewriters/float_rewriter"
6
+ autoload :HashRewriter, "simple_rewriter/rewriters/hash_rewriter"
7
+ autoload :IntegerRewriter, "simple_rewriter/rewriters/integer_rewriter"
8
+ autoload :StringRewriter, "simple_rewriter/rewriters/string_rewriter"
9
+ autoload :ActiveSupport, "simple_rewriter/rewriters/active_support"
10
+ autoload :Generators, "simple_rewriter/rewriters/generators"
11
+ autoload :OptionsResolver, "simple_rewriter/rewriters/options_resolver"
12
+ end
@@ -0,0 +1,41 @@
1
+ module SimpleRewriter
2
+ class TypeDetector < BaseService
3
+ def initialize(attr_value)
4
+ @attr_value = attr_value
5
+ end
6
+
7
+ def call
8
+ detect_type
9
+ end
10
+
11
+ private
12
+
13
+ attr_reader :attr_value
14
+
15
+ def detect_type
16
+ return Integer if integer?
17
+ return Date if date?
18
+ return Float if float?
19
+ end
20
+
21
+ def integer?
22
+ return attr_value.match?(/\A[-+]?[0-9]+\z/) if attr_value.respond_to?(:match?)
23
+ false
24
+ end
25
+
26
+ def date?
27
+ Date.parse(attr_value) if string?
28
+ rescue ArgumentError
29
+ false
30
+ end
31
+
32
+ def float?
33
+ return attr_value.match?(/[0-9]+[.]{1}[0-9]+/) if attr_value.respond_to?(:match?)
34
+ false
35
+ end
36
+
37
+ def string?
38
+ attr_value.is_a?(String)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleRewriter
2
+ VERSION = "0.0.2".freeze
3
+ end
@@ -0,0 +1,30 @@
1
+ module SimpleRewriter
2
+ class Writer < BaseService
3
+ def initialize(record, attribute, new_attr_value)
4
+ @record = record
5
+ @attribute = attribute
6
+ @new_attr_value = new_attr_value
7
+ end
8
+
9
+ def call
10
+ write_attribute
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :record, :attribute, :new_attr_value
16
+
17
+ def write_attribute
18
+ return write_hash_attribute if record.is_a?(Hash)
19
+ write_by_method
20
+ end
21
+
22
+ def write_hash_attribute
23
+ record[attribute.to_s] = new_attr_value
24
+ end
25
+
26
+ def write_by_method
27
+ record.send("#{attribute}=", new_attr_value)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ require "simple_rewriter/version"
2
+
3
+ module SimpleRewriter
4
+ autoload :Rewriter, "simple_rewriter/rewriter"
5
+ autoload :Rewriters, "simple_rewriter/rewriters"
6
+
7
+ autoload :Reader, "simple_rewriter/reader"
8
+ autoload :Readers, "simple_rewriter/readers"
9
+ autoload :Attribute, "simple_rewriter/attribute"
10
+ autoload :Writer, "simple_rewriter/writer"
11
+
12
+ autoload :Configuration, "simple_rewriter/configuration"
13
+ autoload :TypeDetector, "simple_rewriter/type_detector"
14
+ autoload :ClassResolver, "simple_rewriter/class_resolver"
15
+ autoload :AttributeResolver, "simple_rewriter/attribute_resolver"
16
+ autoload :BaseService, "simple_rewriter/base_service"
17
+
18
+ class << self
19
+ def call(record:, attributes:)
20
+ SimpleRewriter::Rewriter.call(record: record, attributes: attributes)
21
+ end
22
+
23
+ def configure
24
+ yield(configuration)
25
+ end
26
+
27
+ def configuration
28
+ @_configuration ||= SimpleRewriter::Configuration.new
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path("lib/simple_rewriter/version", __dir__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Adam Grzybowski", "Piotr Misiurek", "Michał Szymański"]
5
+ gem.email = ["adam@ragnarson.com", "piotr@ragnarson.com", "michal.t.szymanski@gmail.com"]
6
+ gem.description = "Simple rewriter for rails based apps"
7
+ gem.summary = "Simple rewriter for rails based apps"
8
+ gem.license = "MIT"
9
+ gem.homepage = "https://github.com/Verivox/simple_rewriter"
10
+
11
+ gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "simple_rewriter"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = SimpleRewriter::VERSION
17
+
18
+ gem.add_runtime_dependency("faker", "~> 1.9.3")
19
+
20
+ gem.add_development_dependency("pry", "~> 0.12.2")
21
+ gem.add_development_dependency("ragnarson-stylecheck", "~> 0.7.0")
22
+ gem.add_development_dependency("rake", "~> 12.3.2")
23
+ gem.add_development_dependency("rspec", "~> 3.8.0")
24
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ describe SimpleRewriter::Attribute do
4
+ subject { described_class.new(value, options).generate }
5
+
6
+ context "when value is unsupported" do
7
+ let(:value) { nil }
8
+ let(:options) { nil }
9
+
10
+ it "raises error" do
11
+ expect do
12
+ subject
13
+ end.to raise_error(SimpleRewriter::ClassResolver::NotSupportedAttribute)
14
+ end
15
+ end
16
+ end