# strip_attributes normalises whitespace at validation time, not on assignment

> strip_attributes is a MIT licensed Ruby gem that adds a before_validation hook to ActiveModel and ActiveRecord objects, trimming leading and trailing whitespace from string attributes and turning blank results into nil by default. The interesting details are in the options and in the limits: only String values are touched, strings nested in arrays or hashes are left alone, and writes that skip validation bypass the hook entirely.

**rmm5t/strip_attributes** — :hocho: An ActiveModel extension that automatically strips all attributes of leading and trailing whitespace before validation. If the attribute is blank, it strips the value to nil.

- Repository: https://github.com/rmm5t/strip_attributes
- Website: https://rubygems.org/gems/strip_attributes
- Stars: 651 · Forks: 59
- Language: Ruby
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/rmm5t-strip-attributes

## A before_validation hook, not an assignment override

The gem adds a before_validation hook to the record, and that single design decision explains most of its behaviour. By default every string-valued attribute is stripped of leading and trailing whitespace, and if the resulting string is blank the value is converted to nil. The documentation is explicit that attributes are normalised before validation rather than on assignment, so a freshly constructed object still holds the raw value:

```ruby
class User < ApplicationRecord
  strip_attributes only: :name
end

user = User.new(name: "  Ada Lovelace \t")
user.name # => "  Ada Lovelace \t"

user.valid?
user.name # => "Ada Lovelace"
```

Calling `valid?` updates the selected attributes in memory even without saving the record and even if validation fails, and a normal save runs validation and therefore triggers the normalisation. The important exception is written plainly: operations that skip validations, such as `save(validate: false)` and `update_columns`, do not trigger the hook. If your application writes attributes through those paths, a stripped value is not guaranteed there, and that is a limitation of the approach rather than a bug in the gem.

## Only strings, and never the ones nested inside collections

The scope of what gets processed is narrow and clearly stated. Only String values are processed, and numbers, booleans, arrays, hashes and nil are left unchanged. The consequence that catches people is the next sentence: strings nested inside arrays or hashes are not processed. A model with a `tags` array of strings, or a settings hash, will keep whatever whitespace was assigned to the values inside it, even though the attributes themselves are covered by the default rule. That is a sensible design, because a hash value may legitimately be a free form document, but it means the gem is not a general purpose whitespace cleaner. It is a rule for the model's own string columns. Anyone who wants the same treatment inside a nested structure has to apply it there, and the option set does not offer a recursive mode.

## only and except, and the rule that decides between them

By default all string-valued attributes are stripped, and the `only` and `except` options narrow or exclude the set. Both accept a single attribute, in symbol or string form, or an array of attributes. The interaction between them is stated without ambiguity: use either `only` or `except` in a declaration, and if both are supplied `except` takes precedence and `only` is ignored. That is a small decision with a practical consequence, since a model that declares both will not behave the way a reader might expect from the option names. The examples in the documentation use a poker theme to make the point, with a class that strips everything except one attribute and another that strips only three named ones. The options reference also records that each `strip_attributes` declaration registers a separate validation callback, so several declarations can apply different options to different attributes, and overlapping selections can process the same attribute more than once.

## allow_empty decides between nil and an empty string

The default conversion of a blank result to nil is the option most likely to surprise, because it changes the type of the value rather than only its content. The `allow_empty` option, disabled by default, keeps stripped empty strings as an empty string instead of converting them to nil, and existing nil values remain nil either way. The behaviour table in the documentation spells out the cases: a string of a space and a tab becomes nil by default and an empty string with `allow_empty: true`, while a string with internal spaces such as `a  b` is left exactly as it is unless you ask for more. The distinction is not cosmetic in a Rails model, since a presence validation treats nil and an empty string differently, and a `nil` column in the database behaves differently from an empty string. The internal whitespace of a value is preserved by default, so trimming alone will not clean up `a  b`.

## collapse_spaces, replace_newlines and the regex escape hatch

Three options go beyond trimming. `collapse_spaces` replaces runs of horizontal whitespace with a single space while preserving internal newlines, `replace_newlines` replaces runs of carriage returns and line feeds with a single space, and surrounding spaces remain unless `collapse_spaces` is enabled as well. The table gives the combinations: `a 
  b` with `collapse_spaces: true` becomes `a 
 b`, with `replace_newlines: true` it becomes `a    b`, and with both enabled it becomes `a b`. The third option is `regex`, a regular expression whose matches are removed before the normal whitespace trimming happens, which is the way to deal with characters the built-in handling does not cover. Two further options control when the callback runs: `if` and `unless`, each accepting a predicate method name as a symbol or a proc, with the condition evaluated each time validation runs. A model can therefore normalise an email address only while a `preserve_whitespace` flag is not set, which is a real pattern for data imported from a system where whitespace is meaningful.

## Unicode trimming, with a fallback for older encodings

The whitespace handling is more careful than a plain call to `strip`. With compatible string encodings, trimming also handles Unicode whitespace and selected invisible characters, including the nonbreaking space U+00A0 and the zero-width space U+200B, and a string containing only those characters becomes nil by default just like an ordinary blank string. `collapse_spaces` in the same conditions handles tabs, Unicode horizontal whitespace and those invisible characters. For incompatible encodings the behaviour falls back to Ruby's own `String#strip`, and `collapse_spaces` only collapses repeated ordinary spaces. The practical implication is that the gem's behaviour depends on the encoding of the data it receives, which matters for a legacy table that still stores Latin-1 or an unusual charset. There is no configuration switch for this, so the encoding of the incoming value decides which of the two sets of rules applies.

## MIT licensed, with the 2.x line landing in September 2026

The gem is MIT licensed and published on RubyGems as strip_attributes, with the repository holding a conventional gem layout: `lib/` for the code, `test/` for the suite, a `strip_attributes.gemspec`, a Gemfile, a Rakefile, a Guardfile, a `gemfiles/` directory for testing against multiple dependency sets, a `bin/` directory, and a CHANGELOG.md. The release history shows v1.14.1 on 2024-12-10, v2.0.0 on 2025-02-24 and v2.0.1 on 2026-09-10, with the last push to the repository on 2026-09-10 and the repository not archived. A major version bump is a signal that the option set was reworked at some point, and the documentation for this version documents the options and the whitespace table in detail, including the encoding caveats. For an application that already normalises values in a `before_save` callback of its own, the migration question is which layer wins, and the answer here is the validation callback, since that is where the gem hooks in.

## Conclusion

Rails teams that keep re-writing the same normalisation logic in models, or that want one consistent rule for blank string attributes across an application, will get a small, readable gem with a documented option set and a version 2 line. It is the wrong tool if you need normalisation on assignment, since the hook only runs at validation time and any write that skips validation bypasses it, and it is the wrong tool for nested structures, since strings inside arrays and hashes are not processed. Before adding it, check where your existing code trims values, and decide deliberately whether a blank attribute should become `nil` or an empty string, because that choice changes what a presence validation sees.

## FAQ

### When does strip_attributes run?

It adds a before_validation hook, so normalisation happens when validation runs rather than on assignment. Calling `valid?` updates the attributes in memory even without saving, while operations that skip validation such as `save(validate: false)` and `update_columns` do not trigger the hook.

### Does strip_attributes turn empty strings into nil?

Yes, by default a string that is empty or contains only whitespace becomes nil after stripping. Use the `allow_empty: true` option to keep it as an empty string instead, and existing nil values remain nil either way.

### Does the gem strip strings inside arrays and hashes?

No. Only String attribute values are processed, and the documentation states that strings nested inside arrays or hashes are not processed. Numbers, booleans, arrays, hashes and nil are left unchanged.

### What happens if I pass both :only and :except?

The documentation says to use either one or the other, and that if both are supplied, `except` takes precedence and `only` is ignored. Each declaration registers a separate validation callback, so multiple declarations can apply different options to different attributes.

## Sources

- [License: MIT](https://github.com/rmm5t/strip_attributes/blob/master/LICENSE)
- [Project website](https://rubygems.org/gems/strip_attributes)
- [README](https://github.com/rmm5t/strip_attributes/blob/master/README.md)
- [Releases](https://github.com/rmm5t/strip_attributes/releases)
- [rmm5t/strip_attributes on GitHub](https://github.com/rmm5t/strip_attributes)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rmm5t-strip-attributes
