# Nested classes can collide on key names, and the fix is opt-in

> Redis::Objects maps Redis data types onto Ruby classes over the redis gem, and it is explicitly not an ORM. Its key naming has a legacy style that can make nested classes collide, a modern style you must switch on, and a migration script that needs writes paused.

**nateware/redis-objects** — Map Redis types directly to Ruby objects

- Repository: https://github.com/nateware/redis-objects
- Stars: 2,091 · Forks: 231
- Language: Ruby
- License: Artistic-2.0
- Published: 2026-10-12 · Updated: 2026-10-12 · Language: en
- Canonical page: https://hysenlabs.com/projects/nateware-redis-objects

## Two key naming styles, and the safe one is not the default

The most important operational detail in the README is a key naming switch with a trap. By default, backwards compatibility is maintained, which means legacy key names. That style has a longstanding bug: nested classes such as Dog::Behavior and Cat::Behavior would collide, because everything before the :: is stripped and both key names would start with Behavior. The fix is a prefix_style setting you turn on yourself:

```ruby
Redis::Objects.prefix_style = :modern
```

The default is :legacy, so a fresh install has the collision behavior until you change it, and the README is candid that this is why: compatibility is maintained by default rather than correctness being the default. The documentation reference for the naming change points at issue 213 in the text while the link itself goes to issue 231, which is a small inconsistency in a page that is otherwise precise about this topic. Either way, if your classes are nested with the same leaf name under different parents, this is the setting that decides whether two different models share key space.

## Migrating keys means pausing writes on the server

The migration procedure is documented as something to do once, and the requirement is operational rather than technical. You call a class method on each affected subclass:

```ruby
class Dog::Behavior < ActiveRecord::Base
  include Redis::Objects
  # ... your relevant redis_object definitions here (counters/sets) ...
end

Dog::Behavior.migrate_redis_legacy_keys
```

What you have to do first is find a time when you can temporarily pause writes to your Redis server, because the script walks the keyspace rather than tracking keys as they change. It uses redis.scan internally, so the README says it should handle a high number of keys, but for large data sets it could take a while, which is a second argument for doing it deliberately. The order of operations is also fixed: after migrating, you change prefix_style to :modern, and until you do, the note says your existing data in Redis will not be accessible. That last sentence is the sharp edge, since running the migration and crashing before the second step leaves a keyspace nothing can read.

## The gem argues against wrapping Redis in an ORM

The overview opens with a position rather than a feature list: this is not an ORM, and people who wrap ORMs around Redis are missing the point. The argument is about atomicity. The killer feature of Redis is the ability to perform atomic operations on individual data structures such as counters, lists and sets, and an ORM wrapper that retrieves a record, updates values and sends them back removes that atomicity along with the main advantage. The README's own phrasing is to ask why not just use MySQL. What the gem offers instead is a Rubyish interface mapping Redis data types to Ruby objects through a thin layer over the redis gem, with four stated advantages: easy integration with existing ORMs such as ActiveRecord or DataMapper, automatic marshaling of complex structures when you set :marshal => true, integers returned as integers rather than the string 17, and higher-level types such as Locks that wrap multiple calls. It originated from a need for high concurrency atomic operations, and the author links an essay on that subject.

## Two ways in: include it, or use the classes directly

The first mode is including the module in a model class, which is how you add counters to an existing ActiveRecord, DataMapper or Mongoid class. The requirement is stated loosely and usefully: Redis::Objects works with any class that provides an id method returning a unique value. Key names are derived automatically in the format model_name:id:field_name, and the illustration is a stub class with a counter and a hand-written id returning 1, incremented three times. The second mode is standalone, using classes such as Redis::List and Redis::SortedSet without any model at all, which is the mode for a background job or a script with no ActiveRecord in sight. Since 0.7.0 the appropriate classes are autoloaded on demand, so the older habit of requiring redis/list or redis/set individually is no longer needed. That version note matters for anyone porting code written against the pre-0.7 style.

## The connection pool is recommended for a specific reason

Setup is a Gemfile entry and one assignment, but the assignment has a rationale worth reading. Redis::Objects needs a handle created by Redis.new or a ConnectionPool, and the recommended approach is a pool:

```ruby
require 'connection_pool'
Redis::Objects.redis = ConnectionPool.new(size: 5, timeout: 5) { Redis.new(:host => '127.0.0.1', :port => 6379) }
```

The stated reason is that it guarantees most timeouts in the redis client do not pollute your existing connection. In a multithreaded environment you must set both :timeout and :size appropriately, which is the sentence to notice because a pool with a default size in a threaded server is a bottleneck rather than a protection. The simpler form, assigning Redis::Objects.redis = Redis.new(...), is offered as an alternative, and if nothing is set at all the gem falls back to Redis.current. For a Rails application the README points at config/initializers/redis.rb, while being clear that there are no dependencies on Rails and the gem works in Sinatra, Resque or standalone Ruby.

## Different classes can hold different Redis connections

The last setup feature is per-class handles, and it is the one that surprises an operations person, because it is easy to end up with data in two places. You can assign a handle on the class itself:

```ruby
class User
  include Redis::Objects
end
class Post
  include Redis::Objects
end

User.redis = Redis.new(:host => '1.2.3.4')
Post.redis = Redis.new(:host => '5.6.7.8')
```

The comment in the example notes a pool can be used here as well. So the effective destination of a key depends on the class you call it on, which is convenient for sharding or for a test double and is a genuine footgun in production, because a typo or a refactor that moves a counter to another class silently sends it to a different server. If you use this, put it in one initializer rather than scattering it, and remember that the global Redis::Objects.redis setting is only the default for classes with no handle of their own.

## Where redis-objects is the wrong choice

Four cases. The first is a project that wants Redis as its primary store rather than as a companion to a relational database. The whole value proposition here is adding atomic counters and sets next to models you already have, and a design where Redis is the database has different durability and consistency questions that this gem does not address. The second is a JavaScript or Python team. This is a Ruby library mapping to Ruby idioms, with :marshal => true relying on Ruby's object serialization format, so nothing here transfers to another runtime without losing the marshaling behavior. The third is anyone who cannot pause writes during a key migration. If your service cannot take a write outage, the legacy prefix style stays, and with it the nested class collision, and there is no incremental mode described in the README. The fourth is a team that has standardized on a different Redis client abstraction. The gem sits on a thin layer over the redis gem specifically, so adopting it means adopting that client version and its configuration, including the timeout and pool behavior described above.

## Conclusion

redis-objects suits a Ruby application that keeps counters, sets and locks beside its relational models and needs those operations to stay atomic rather than round-tripped through an ORM. It does not suit a project treating Redis as its primary database, or a team standardized on a different Redis client. Before adopting, decide between the legacy and modern prefix_style, run migrate_redis_legacy_keys only while writes are paused, and check that every class you mix in provides an id method returning something unique.

## FAQ

### What is redis-objects and is it an ORM?

It maps Redis data types to Ruby objects through a thin layer over the redis gem, and the README states plainly that it is not an ORM, because wrapping Redis in an ORM removes the atomicity of operations on individual data structures.

### What does Redis::Objects.prefix_style do?

It selects how internal key names are built. The default is :legacy for backwards compatibility, which lets nested classes like Dog::Behavior and Cat::Behavior collide because everything before :: is stripped. Set Redis::Objects.prefix_style = :modern to make them unique.

### How do I migrate legacy keys?

Call migrate_redis_legacy_keys on each affected subclass. You need to pause writes to Redis while it runs, it uses redis.scan so it can handle many keys but may take a while on large data sets, and your existing data stays inaccessible until you then change prefix_style to :modern.

### How do I configure the Redis connection?

Assign Redis::Objects.redis to a Redis handle or to a ConnectionPool, with a pool recommended so client timeouts do not pollute your existing connection. In a multithreaded environment set both :timeout and :size, and individual classes can hold their own handles.

### Does redis-objects require Rails?

No. The README says there are no dependencies on Rails and that the gem works in any Ruby code, naming Sinatra, Resque and standalone use, with config/initializers/redis.rb suggested only as a good place in a Rails app.

## Sources

- [Issues](https://github.com/nateware/redis-objects/issues)
- [License: Artistic-2.0](https://github.com/nateware/redis-objects/blob/master/LICENSE)
- [nateware/redis-objects on GitHub](https://github.com/nateware/redis-objects)
- [README](https://github.com/nateware/redis-objects/blob/master/README.md)

---

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