Skip to content

Instantly share code, notes, and snippets.

@TALlama
Created May 17, 2024 16:03
Show Gist options
  • Save TALlama/90c29869bc6da0da3e7d4bc4fa73ac32 to your computer and use it in GitHub Desktop.
Save TALlama/90c29869bc6da0da3e7d4bc4fa73ac32 to your computer and use it in GitHub Desktop.
This class will use your existing FactoryBot factories to rewrite the data already in the db, keeping the associations and structure but replacing the content.
class Anonymizer
include ActiveSupport::Benchmarkable
attr_reader :factory_names, :callbacks
def initialize(factory_names = nil, callbacks = {})
raise ArgumentError.new("You must be in development to use the anonymizer") unless Rails.env.development?
require Rails.root.join("spec/factories") unless FactoryBot.factories.count > 0
@factory_names = [*factory_names].compact.map(&:to_sym)
@callbacks = default_callbacks.merge(callbacks).with_indifferent_access
end
def factories
@factories ||= FactoryBot.factories.select { |f|
factory_names.empty? || factory_names.include?(f.name)
}.reject { |f|
forbidden_factory_names.include? f.name.to_s
}.select { |f|
f.send(:parent).is_a? FactoryBot::NullFactory
}
end
def logger
Rails.logger
end
def valid?
factories.map do |f|
invalid = logger.silence { f.build_class.all.reject { |obj| obj.valid? } }
next if invalid.empty?
puts [invalid.count, f.build_class.name, "record".pluralize(invalid.count), "invalid:"].join(" ")
puts invalid.map { |o| " #%d: %s" % [o.id, o.errors.full_messages.join("; ")] }
[f.build_class, invalid]
end.compact.to_h
end
def call
puts "Anonymizing!"
benchmark "\nAnonymized!" do
User.transaction do
factories.map do |f|
anonymize_all(f)
end
end
end
end
def forbidden_factory_names # TODO customize this for your env
%w[
add_class_names_here
]
end
def default_callbacks # TODO customize this for your env
{
before_someclass: ->(instance, attrs) { attrs.delete(:attr_you_want_to_keep) },
}
end
private
def callback(f, name, *args, &block)
[
callbacks["#{name}_#{f.try(:name)}"],
callbacks[name]
].compact.map { |c| c.call(*args, &block) }
end
def dry_run?
callback(nil, :dry_run).any?
end
def anonymize_all(f)
logger.info "Anonymizing with #{f.name}"
benchmark "\nAnonymized with #{f.name}" do
logger.silence do
f.build_class.all.each do |obj|
anonymize(f, obj)
end
end
end
end
def anonymize(f, obj)
attrs = FactoryBot.attributes_for(f.name)
callback(f, :before, obj, attrs)
if attrs.present?
if dry_run?
obj.attributes = attrs
p obj, obj.errors.full_messages.join("; ") unless obj.valid?
else
obj.update! attrs
end
print "."
else
print "S"
end
callback(f, :after, obj)
obj
rescue ActiveRecord::RecordInvalid => e
p obj, obj.errors.full_messages.join("; ") unless obj.valid?
raise e
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment