How do I Remove Null Bytes from ActiveRecord String Attributes in Ruby?

As a Rails developer working with PostgreSQL, I ran into an issue that had me scratching attributes in ruby my head for a bit. Every time I tried to save a string that contained a null byte ("\u0000"), PostgreSQL would throw an error like this:

ActiveRecord::StatementInvalid
PG::UntranslatableCharacter: ERROR: unsupported Unicode escape sequence

This wasn’t just annoying it was breaking real functionality in production. So, I decided to write a solution that automatically removes null bytes from every string attribute in all of my ActiveRecord models ruby without manually editing each one.

Let me walk you through the journey, the first bug I hit, how I fixed it, and how I made it scalable and testable.

First Error in the Code

My first attempt looked something like this:

def remove_null_bytes
my_field.delete!("\u0000")
end

The Problem

The issue here is that my_field isn’t defined anywhere. It’s just a placeholder a hardcoded assumption about what attribute I wanted to clean. If that attribute using ruby doesn’t exist or is nil, this method will crash.

The Fix

Instead of hardcoding, I needed a way to loop through all string attributes dynamically and clean them one by one. That led me to think in terms of Rails Concerns

Explanation of the Original Code

Here’s my initial model:

class MyModel < ApplicationRecord
before_save :remove_null_bytes

private

def remove_null_bytes
my_field.delete!("\u0000")
end
end

What It Does:

  • before_save :remove_null_bytes: This hook is triggered before saving the record.
  • remove_null_bytes: Attempts to remove null bytes from my_field.

But There Are Limitations:

  • It only targets one field (my_field).
  • Doesn’t scale to other models or attributes.
  • Crashes if the attribute is nil.

Clearly, this wasn’t going to work for the whole application

Improved & Practical Version for All Models

To solve this the right way, I wrote a Rails concern that works for all models. It automatically strips null bytes from every string column, and it does so without touching binary data (which might legitimately contain null bytes).

Here’s the Concern:

# app/models/concerns/remove_null_bytes.rb
module RemoveNullBytes
extend ActiveSupport::Concern

included do
before_save :sanitize_string_attributes
end

private

def sanitize_string_attributes
self.class.columns.each do |column|
next unless column.type == :string

value = read_attribute(column.name)
next unless value.is_a?(String)

cleaned = value.delete("\u0000")
write_attribute(column.name, cleaned) if cleaned != value
end
end
end

And I included it in ApplicationRecord like this:

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include RemoveNullBytes
end

This now applies to every model in my application, ensuring null bytes are removed only from string attributes using ruby, leaving binary and other data types untouched.

Extra Practice Functionalities You Can Add

If you want to take this further, here are some real-world enhancements you can include:

Logging when null bytes are removed

Rails.logger.info "Removed null byte from #{self.class.name}##{column.name}"

Add a dry-run mode for testing

Use an environment variable to toggle the behavior without modifying data:

next if ENV['NULL_BYTE_SANITIZER'] == 'off'

Write a unit test for the concern

describe RemoveNullBytes do
it "removes null bytes from string attributes" do
model = MyModel.create(name: "test\u0000ing")
expect(model.name).to eq("testing")
end
end

Add a metrics counter (if using Datadog or Prometheus)

MyApp::Metrics.increment('null_bytes.cleaned') if cleaned != value

Final Thoughts

This small but powerful concern made my Rails app more stable and production-ready. It fixed a PostgreSQL compatibility issue and did so in a clean, DRY, and scalable way.

If you’re running into similar issues — or just want to sanitize your string attributes before saving I highly recommend adopting this pattern. It’s a one-time setup that adds a silent layer of protection across your app.

Related blog posts