-
Notifications
You must be signed in to change notification settings - Fork 1
Custom Patterns and Name Patterns
The 89 built-ins can't know your org's internal ID formats or your customers'
names. Two APIs close that gap: add_pattern (any regex) and name_pattern (a
generated regex for a person's name across all its written variations).
Register at boot, or at runtime from any thread (registration is thread-safe):
# String (POSIX ERE) or Regexp — both accepted
DataRedactor.add_pattern(name: "employee_id", regex: "EMP-[0-9]{6}")
DataRedactor.add_pattern(name: "ticket_ref", regex: /TICKET-[A-Z]{2}[0-9]{4}/, boundary: true)
# :custom by default; pass any built-in tag to group differently
DataRedactor.add_pattern(name: "internal_key", regex: "INT-[A-Z]{3}", tag: :credentials)
DataRedactor.redact(text) # all patterns incl. custom
DataRedactor.redact(text, only: [:custom]) # only your patterns
DataRedactor.redact(text, only: [:custom, :credentials]) # mix
DataRedactor.custom_patterns # => [{name:, source:, tag:, boundary:}, ...]
DataRedactor.remove_pattern("employee_id")
DataRedactor.clear_custom_patterns! # mostly for test suitesCustom patterns use the same POSIX ERE engine as the built-ins. Not
supported — and rejected at registration with DataRedactor::InvalidPatternError,
never at redaction time:
| Don't use | Use instead |
|---|---|
\d |
[0-9] |
\s |
[[:space:]] |
\w |
[[:alnum:]_] |
\b, lookahead/behind, non-greedy, named groups |
— (unsupported) |
Wraps the pattern with (^|[^0-9A-Za-z])(PATTERN)([^0-9A-Za-z]|$) so it only
fires when the token isn't embedded in a longer alphanumeric string. Incompatible
with patterns that contain capture groups. Use it for generic digit/alphanumeric
formats (the same rule the built-ins follow — see
Pattern Catalogue and
C Engine Internals).
Personal names can't ship as built-ins, but the regex to match a name across its
written variations is the same every time. name_pattern generates that regex,
ready for add_pattern:
DataRedactor.add_pattern(
name: "person_mario_rossi",
regex: DataRedactor.name_pattern("Mario", "Rossi"),
tag: :contact
)
DataRedactor.redact("ticket from Mario Rossi about ...")
# => "ticket from [REDACTED] about ..."One generated pattern matches all of these:
| Variation | Matches |
|---|---|
| Case |
Mario Rossi, mario rossi, MARIO ROSSI
|
| Order |
Mario Rossi, Rossi Mario, Rossi, Mario, Rossi,Mario
|
| Initials |
M. Rossi, M Rossi, Mario R., M.R., MR
|
| Diacritics |
name_pattern("Jose", "Munoz") also matches José Muñoz (and vice versa) |
| Separators | spaces/hyphens interchangeable — Anne-Marie, Anne Marie, AnneMarie; each half alone (Anne Berg, Marie Berg); multi-word parts like "Van der Berg" tolerate any separator |
Add a middle name — both the no-middle and with-middle forms match:
DataRedactor.name_pattern("Mario", "Rossi", middle: "Luigi")
# matches "Mario Rossi" AND "Mario Luigi Rossi" AND "Rossi Mario Luigi"The returned string is already boundary-wrapped — it won't fire inside a
longer word (Mario won't match inside Mariolino). So register it with the
default boundary: false. Passing boundary: true would double-wrap and be
rejected because of the baked-in capture groups.
-
examples/custom_pattern.rb—add_patternend to end. -
examples/llm_payload.rb— redacting text before an LLM call.
- Pattern Catalogue — the built-ins and their tags.
- ⭐ RubyLLM Integration — teach it your names/IDs, then scrub every prompt automatically.
Guides
Reference
Elsewhere