ruby
35 lines · 9 steps
How Rails encrypts sensitive payment fields
A PaymentMethod model uses Active Record Encryption to store card and bank data while keeping it queryable and maskable.
Explained by
highlit
1class PaymentMethod < ApplicationRecord
2 belongs_to :account
3
4 encrypts :card_number, deterministic: false
5 encrypts :routing_number, deterministic: true, downcase: false
6 encrypts :account_holder_name
7
8 blind_index :routing_number
9
10 validates :card_number, presence: true, format: { with: /\A\d{13,19}\z/ }
11 validates :routing_number, presence: true
12
13 before_validation :normalize_card_number
14
15 scope :for_routing, ->(number) { where(routing_number: number) }
16
17 def masked_card_number
18 "**** **** **** #{card_number.last(4)}"
19 end
20
21 def brand
22 case card_number
23 when /\A4/ then :visa
24 when /\A5[1-5]/ then :mastercard
25 when /\A3[47]/ then :amex
26 else :unknown
27 end
28 end
29
30 private
31
32 def normalize_card_number
33 self.card_number = card_number.to_s.gsub(/[\s-]/, "").presence
34 end
35end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Deterministic encryption trades some secrecy for the ability to query on a column, so choose it only when you need lookups.
- 2A blind index lets you search encrypted data without exposing plaintext, filling the gap left by non-deterministic encryption.
- 3Decryption is transparent in Ruby, so model methods can format and inspect sensitive values as if they were plain columns.
Related explainers
ruby
class Order class InvalidTransition < StandardError; end TRANSITIONS = {
A state machine for order transitions in Ruby
state-machine
data-driven
error-handling
Intermediate
8 steps
php
<?php namespace App\Http\Controllers;
Streaming a filtered CSV export in Laravel
streaming
csv-export
query-builder
Intermediate
9 steps
ruby
class CreateOrderItems < ActiveRecord::Migration[7.1] def change create_table :order_items do |t| t.references :order, null: false, foreign_key: { on_delete: :cascade }
Enforcing order-item integrity in Rails
migrations
foreign-keys
validations
Intermediate
7 steps
ruby
class Registration < ApplicationRecord belongs_to :event validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
Validating registrations in Rails
validations
i18n
error-handling
Intermediate
8 steps
ruby
class ApacheLogParser LINE_PATTERN = /\A(?<ip>\S+)\s\S+\s\S+\s\[(?<time>[^\]]+)\]\s"(?<method>[A-Z]+)\s(?<path>\S+)\s(?<protocol>[^"]+)"\s(?<status>\d{3})\s(?<bytes>\d+|-)/ TIME_FORMAT = "%d/%b/%Y:%H:%M:%S %z"
Parsing Apache logs with named captures
regex
named-captures
parsing
Intermediate
6 steps
ruby
require "csv" def parse_transaction_row(line) fields = CSV.parse_line(line, headers: false, skip_blanks: true)
Parsing one CSV transaction row in Ruby
parsing
type-coercion
error-handling
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-rails-encrypts-sensitive-payment-fields-explained-ruby-105c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.