Module: ConcernsOnRails::Models::Encryptable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/concerns_on_rails/models/encryptable.rb
Overview
Transparent per-field encryption for sensitive columns (SSN, DOB, cards).
Reads and writes stay plaintext; the DB column holds an authenticated
AES-256-GCM envelope. Encryption is implemented as a custom
ActiveModel::Type on the declared column, so it is invisible to the rest
of the stack — sibling concerns that read self[:field] (Maskable,
Normalizable) compose for free, and dirty tracking compares plaintext.
ConcernsOnRails.configure_encryption { |c| c.key = ENV["ENCRYPTION_KEY"] }
class Patient < ApplicationRecord
include ConcernsOnRails::Models::Encryptable
encryptable :ssn, :notes # transparent string encryption
encryptable :dob, type: :date # decrypts back to a Date
end
p = Patient.create!(ssn: "123-45-6789", dob: Date.new(1990, 1, 1))
p.ssn # => "123-45-6789"
p.reload.dob # => Wed, 01 Jan 1990
p.ssn_ciphertext # => "AQEA..." (Base64 envelope; no plaintext at rest)
p.ssn_encrypted? # => true
type: casts the decrypted value (reuses the Storable caster set:
:string default, :integer, :float, :decimal, :boolean, :date, :datetime).
key: overrides the gem-level key per field (a String or a Proc).
Notes:
* The declared column must be `text` (or binary): it stores an opaque
Base64 envelope, not the logical type. `nil` stays `nil` (never an
encrypted blank).
* Non-deterministic by design (random IV) — the same plaintext yields
different ciphertext every write, so encrypted columns are NOT
queryable/searchable (`where(:ssn)` matches ciphertext). Deterministic,
queryable fields and multi-key rotation are planned follow-ups; the
envelope already reserves the bytes for them.
* Never `update_column`/`update_columns` an encrypted field — those
bypass the type and write raw plaintext to the column.
* Auditing an encrypted field would persist its plaintext to the audit
column, so declaring a field with BOTH `encryptable` and `auditable_by`
raises. Maskable masks the decrypted value; Normalizable normalizes the
plaintext before it is encrypted (order-independent).
Defined Under Namespace
Modules: ClassMethods Classes: EncryptedType
Constant Summary collapse
- LABEL =
"ConcernsOnRails::Models::Encryptable".freeze
- VALID_TYPES =
%i[string integer float decimal boolean date datetime].freeze
- CASTERS =
Reusable ActiveModel casters. :decimal and :datetime round-trip through Strings and are handled explicitly (mirrors Models::Storable).
{ string: ActiveModel::Type::String.new, integer: ActiveModel::Type::Integer.new, float: ActiveModel::Type::Float.new, boolean: ActiveModel::Type::Boolean.new, date: ActiveModel::Type::Date.new, datetime: ActiveModel::Type::DateTime.new }.freeze