= attr_masker :source-highlighter: pygments :pygments-style: native :pygments-linenums-mode: inline image:https://img.shields.io/gem/v/attr_masker.svg["Gem Version", link="https://rubygems.org/gems/attr_masker"] image:https://img.shields.io/travis/riboseinc/attr_masker/master.svg["Build Status", link="https://travis-ci.org/riboseinc/attr_masker"] Mask ActiveRecord data with ease! == Introduction This gem is intended to mask sensitive data so that production database dumps can be used in staging or test environments. It works with Active Record 4+ and modern Rubies. == Getting started === Installation Add attr_masker to your gemfile: [source,ruby] ---- gem "attr_masker", github: "riboseinc/attr_masker" ---- Then install the gem: [source,sh] ---- bundle install ---- === Basic usage In your models, define attributes which should be masked: [source,ruby] ---- class User attr_masker :email, :first_name, :last_name end ---- Then, when you want to mask the data, run the `db:mask` Rake task in some Rails environment other than production, for example: [source,sh] ---- bundle exec rake db:mask RAILS_ENV=staging ---- WARNING: Data are destructively overwritten. Run `rake db:mask` with care! === Masking records selectively You can use `:if` and `:unless` options to prevent some records from being altered. [source,ruby] ---- # evaluates given proc for each record, and the record is passed as a proc's # argument attr_masker :email :unless => ->(record) { ! record.tester_user? } # calls #tester_user? method on each record attr_masker :first_name, :if => :tester_user? ---- The ActiveRecord's `::default_scope` method has no effect on masking. All table records are updated, provided that :if and :unless filters allow that. For example, if you're using a Paranoia[https://github.com/rubysherpas/paranoia] gem to soft-delete your data, records marked as deleted will be masked as well. === Using custom maskers By default, data is maksed with `AttrMasker::Maskers::SIMPLE` masker which always returns `"(redacted)"` string. But anything what responds to `#call` can be used instead: a lambda, `Method` instance, and more. You can specify it by setting the `:masker` option. For instance, you may want to use https://github.com/ffaker/ffaker[ffaker] or https://github.com/skalee/well_read_faker[Well Read Faker] to generate random replacement values: [source,ruby] ---- require "ffaker" attr_masker :first_name, :masker => ->(_hash) { FFaker::Name.first_name } ---- A hash is passed as an argument, which includes some information about the record being masked and the attribute value. It can be used to further customize masker's behaviour. === Built-in maskers Attr Masker comes with several built-in maskers. `AttrMasker::Maskers::SIMPLE`:: + Simply replaces any value with the `"(redacted)"`. Only useful for columns containing textual data. + This is a default masker. It is used when `:masker` option is unspecified. + Example: + [source,ruby] ---- attr_masker :first_name attr_masker :last_name, :masker => AttrMasker::Maskers::SIMPLE ---- + Would set both `first_name` and `last_name` attributes to `"(redacted)"`. `AttrMasker::Maskers::Replacing`:: + Replaces characters with some masker string (single asterisk by default). Can be initialized with options. + [options="header"] |=============================================================================== |Name|Default|Description |`replacement`|`"*"`|Replacement string, can be empty. |`alphanum_only`|`false`|When true, only alphanumeric charaters are replaced. |=============================================================================== + Example: + [source,ruby] ---- rm = AttrMasker::Maskers::Replacing.new(character: "X", alphanum_only: true) attr_masker :phone, :masker => rm ---- + Would mask "123-456-7890" as "XXX-XXX-XXXX". == Roadmap & TODOs - documentation - spec tests - Make the `Rails.env` (in which `db:mask` could be run) configurable ** maybe by passing `ENV` vars - more masking options! ** default scrambling algorithms? ** structured text preserving algorithms *** _e.g._, keeping an HTML snippet valid HTML, but with masked inner text ** structured *Object* preserving algorithms *** _i.e._ generalization of the above HTML scenario - I18n of the default `"(redacted)"` phrase - … == Acknowledgements https://github.com/attr-encrypted/attr_encrypted[attr_encrypted] for the initial code structure