Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 8 additions & 18 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
appraise "rails-6" do
gem "activerecord", "~> 6.1.4"
gem "activesupport", "~> 6.1.4"
gem "mutex_m", "~> 0.3.0"
gem "base64"
appraise "rails-7.1" do
gem "activerecord", "~> 7.1.0"
gem "activesupport", "~> 7.1.0"
gem "bigdecimal"
gem "sqlite3", "~> 1.4"

# Fixes uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger (NameError)
gem "concurrent-ruby", "< 1.3.5"
gem "sqlite3", "~> 2.6.0"
end

appraise "rails-7" do
gem "activerecord", "~> 7.0.1"
gem "activesupport", "~> 7.0.1"
gem "mutex_m", "~> 0.3.0"
gem "base64"
appraise "rails-7.2" do
gem "activerecord", "~> 7.2.0"
gem "activesupport", "~> 7.2.0"
gem "bigdecimal"
gem "sqlite3", "~> 1.4"

# Fixes uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger (NameError)
gem "concurrent-ruby", "< 1.3.5"
gem "sqlite3", "~> 2.6.0"
end

appraise "rails-8" do
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# 2.0
* **BREAKING CHANGE:** Minimum Rails version is now 7.1 (dropped Rails 6.x and 7.0)
* **BREAKING CHANGE:** Minimum Ruby version is now 3.2
* Added support for Rails 7.1 and 7.2 in test matrix
* Reimplemented internals on top of Rails 7.1's `ActiveRecord::Base.normalizes`. The `sanitizes` DSL is unchanged, but normalization now runs at **attribute assignment time** instead of `before_save`. This produces a few subtle behavior changes:
* **Read-after-write returns the normalized value.** `person.name = " john "; person.name` now returns `"john"` immediately, not `" john "`. Validations and any pre-save reads see normalized values.
* **Legacy un-normalized rows are no longer rewritten on every save.** Previously the `before_save` callback rewrote every declared column on each save, opportunistically cleaning up old data. To migrate legacy rows now, reassign the attribute or call `record.normalize_attribute(:col)` before saving.
* **`where` and `find_by` hash conditions now normalize their input.** `User.where(email: " X@Y.com ")` will match a row stored as `"x@y.com"`. Raw-SQL conditions (`where("email = ?", ...)`) still bypass normalization.
* Apps upgrading from Rails < 7.1 should set `config.active_record.marshalling_format_version = 7.1` to avoid `TypeError` when marshalling records with normalized attributes.

# 1.2
* Official support for Rails 8
* Official support for Ruby 3.4
Expand Down
50 changes: 50 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What This Is

The `sanitization` gem is a Ruby gem that adds automatic string sanitization to ActiveRecord models via a `before_save` callback. It can strip whitespace, collapse multiple spaces, convert empty strings to nil, and apply case transformations.

## Commands

```bash
# Run tests (default rake task)
rake spec

# Run tests with RSpec directly
bundle exec rspec

# Run a single test file or example
bundle exec rspec spec/sanitization_spec.rb
bundle exec rspec spec/sanitization_spec.rb:42

# Run tests across all supported Rails versions (6, 7, 8)
bundle exec appraisal rspec

# Run tests for a specific Rails version
bundle exec appraisal rails-8 rspec

# Install appraisal gemfiles after changing Appraisals file
bundle exec appraisal install
```

## Architecture

The gem has three core files:

- **`lib/sanitization/configuration.rb`** - `Sanitization::Configuration` class with global defaults (strip, collapse, case, nullify, include_text_type). `simple_defaults!` is a shortcut that enables strip + collapse + nullify.
- **`lib/sanitization/active_record_extension.rb`** - The core logic. `ClassMethods.sanitizes()` introspects table columns, filters by type (string, optionally text), validates options, and stores per-column config in `@sanitization__store`. Registers a `before_save` callback that applies transformations in order: strip -> collapse -> nullify -> case.
- **`lib/sanitization.rb`** - Entry point that includes `ActiveRecordExtension` into `ActiveRecord::Base` via `class_eval`.

## Testing

Tests use RSpec with an in-memory SQLite database. The `spec_helper.rb` creates a `people` table with string, text, date, and integer columns. A custom `String#leetcase` method is defined for testing custom case transformations.

CI runs on Ruby 3.2, 3.3, 3.4 against Rails 6.1, 7.0, and 8.0 via Appraisal.

Note: ActiveSupport 7+ changed `titlecase` behavior (it strips leading spaces), which affects test expectations between Rails versions.

## Changelog Workflow

Track changes in the `## Unreleased` section of `CHANGELOG.md` as they are made. When a new version is released, rename the `## Unreleased` heading to the version number (e.g., `# 1.3`) and add a fresh `## Unreleased` section above it.
32 changes: 17 additions & 15 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
PATH
remote: .
specs:
sanitization (1.2.0)
activerecord
activesupport
sanitization (2.0.0)
activerecord (>= 7.1)
activesupport (>= 7.1)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -43,7 +43,10 @@ GEM
i18n (1.14.7)
concurrent-ruby (~> 1.0)
logger (1.7.0)
minitest (5.25.5)
minitest (6.0.5)
drb (~> 2.0)
prism (~> 1.5)
prism (1.9.0)
rake (13.2.1)
rspec (3.13.0)
rspec-core (~> 3.13.0)
Expand All @@ -59,16 +62,16 @@ GEM
rspec-support (~> 3.13.0)
rspec-support (3.13.2)
securerandom (0.4.1)
sqlite3 (2.6.0-aarch64-linux-gnu)
sqlite3 (2.6.0-aarch64-linux-musl)
sqlite3 (2.6.0-arm-linux-gnu)
sqlite3 (2.6.0-arm-linux-musl)
sqlite3 (2.6.0-arm64-darwin)
sqlite3 (2.6.0-x86-linux-gnu)
sqlite3 (2.6.0-x86-linux-musl)
sqlite3 (2.6.0-x86_64-darwin)
sqlite3 (2.6.0-x86_64-linux-gnu)
sqlite3 (2.6.0-x86_64-linux-musl)
sqlite3 (2.9.3-aarch64-linux-gnu)
sqlite3 (2.9.3-aarch64-linux-musl)
sqlite3 (2.9.3-arm-linux-gnu)
sqlite3 (2.9.3-arm-linux-musl)
sqlite3 (2.9.3-arm64-darwin)
sqlite3 (2.9.3-x86-linux-gnu)
sqlite3 (2.9.3-x86-linux-musl)
sqlite3 (2.9.3-x86_64-darwin)
sqlite3 (2.9.3-x86_64-linux-gnu)
sqlite3 (2.9.3-x86_64-linux-musl)
thor (1.3.2)
timeout (0.4.3)
tzinfo (2.0.6)
Expand All @@ -91,7 +94,6 @@ PLATFORMS
DEPENDENCIES
appraisal (~> 2.5)
byebug
concurrent-ruby (< 1.3.5)
cucumber-ci-environment
rake (~> 13.2)
rspec (~> 3.0)
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ Sanitization makes it easy to store slightly cleaner strings to your database.
- Change casing (ie. upcase, downcase, titlecase, etc)


### Built on top of `ActiveRecord::Base.normalizes`

As of 2.0, Sanitization is a thin macro layer on top of Rails 7.1's [`ActiveRecord::Base.normalizes`](https://api.rubyonrails.org/classes/ActiveRecord/Normalization/ClassMethods.html). The `sanitizes` DSL is unchanged — under the hood, each declared column is registered with `normalizes`, and Sanitization composes a single normalizer per column from your `:strip`, `:collapse`, `:nullify`, and `:case` options (plus their global defaults).

Because `normalizes` runs at attribute **assignment** time (not `before_save`), this gives you a few useful behaviors automatically:

- **Read-after-write returns the normalized value.** `person.name = " john "; person.name` returns `"john"` immediately — validations and any pre-save reads see the cleaned value.
- **`where`/`find_by` hash conditions normalize the lookup value.** `User.where(email: " RAW@X.COM ")` matches a row stored as `"raw@x.com"`. (Raw-SQL conditions still bypass normalization.)
- **Legacy un-normalized rows are not silently rewritten on save.** Reload + save no longer "heals" old data. To migrate, reassign the attribute or call `record.normalize_attribute(:col)` (a built-in helper from `normalizes`).

If you're upgrading from 1.x, see the CHANGELOG for the full list of behavior changes.


### Defaults

By default, Sanitization has all options disabled. It is recommended you use a configuration block to set
Expand Down Expand Up @@ -45,6 +58,11 @@ Sanitization.simple_defaults!
- Change casing: (`case: :none|:up|:down|:custom`)


## Compatibility

- Ruby >= 3.2
- Rails >= 7.1

## Installation

```sh
Expand Down
86 changes: 0 additions & 86 deletions gemfiles/rails_6.gemfile.lock

This file was deleted.

9 changes: 3 additions & 6 deletions gemfiles/rails_6.gemfile → gemfiles/rails_7.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ gem "rake", "~> 13.2"
gem "rspec", "~> 3.0"
gem "appraisal", "~> 2.5"
gem "cucumber-ci-environment"
gem "sqlite3", "~> 1.4"
gem "sqlite3", "~> 2.6.0"
gem "byebug"
gem "activerecord", "~> 6.1.4"
gem "activesupport", "~> 6.1.4"
gem "mutex_m", "~> 0.3.0"
gem "base64"
gem "activerecord", "~> 7.1.0"
gem "activesupport", "~> 7.1.0"
gem "bigdecimal"
gem "concurrent-ruby", "< 1.3.5"

gemspec path: "../"
Loading
Loading