RedisAssist - Easy Redis Backed Object Modeling
RedisAssist is a Persistant Object Model backed by Redis for Ruby.
Store and Fetch data of any type in Redis with as little friction as possible. RedisAssist lets you back simple or complex Object Models with Redis while giving you a convenient interface interact with it.
In progress RDoc: http://www.rubydoc.info/github/endlessinc/redis_assist/frames
Getting Started
In your Gemfile:
gem "redis_assist"
Create a model:
class Person < RedisAssist::Base
attr_persist :name
attr_persist :birthday, as: :time
attr_persist :meta_info, as: :json
attr_persist :created_at, as: :time # Magic date fields just like ActiveRecord.
def validate
add_error(:name, "Albert Einstein is dead.") if name.eql?('Albert Eintein')
end
end
Saving
person = Person.new(name: 'Albert Hoffman', birthday: Time.parse('1/11/1906'), meta_info: { profession: 'Scientist' })
person.new_record? # => true
person.name # => "Albert Hoffman"
person.save # => #<Person:0x007f88341662a0>
person.new_record? # => false
Creating
person = Person.create(name: 'Albert Hoffman', birthday: Time.parse('1/11/1906'), meta_info: { profession: 'Scientist' })
Updating
With an instance
person = Person.find(1)
person.name = 'Hubble Love'
person.save
Skip callbacks / validations
person.update_columns(name: 'Tyler Love', birthday: Time.parse('1/11/1908'))
With only an id, this will hit callbacks and validations
Person.update(1, name: 'Tyler Love')
Validating
person = Person.new(name: 'Albert Einstein', birthday: Time.parse('1/11/1906'), meta_info: { profession: 'Scientist' })
persin.valid? # => false
persin.errors # => [{name: "Albert Einstein is dead."}]
Finding
Find by id
person = Person.find(1)
Find an array of people
# returns an array of people
people = Person.find([1, 2])
Find the last people
# Finds the last person created
people = People.last
# Finds the last 10 people created
people = People.last(10)
# Find 10 people, offset from the end of the id index by 30
people = People.last(10, 30)
Find the first people
# Finds the first person created
people = People.first
# Finds the first 10 people created
people = People.first(10)
# Find 10 people offset from the beginning of the id index by 30
people = People.first(10, 30)
Find all of the people.
WARNING: If you have large data sets, you should use find_in_batches
instead.
people = Person.all
Find in batches
Works just like the ActiveRecord find_in_batches
. The most performant way to iterate over large data sets
# Supports options
# `batch_size` the amount of records to find in each batch. Default is `500`
# `offset` offset from the begining of the `id` index
People.find_in_batches do |people|
people.each do |person|
# do something with a person
end
end
Deleting
Deletes all the persisted attributes from redis.
person = Person.find(1)
person.delete # => true
A soft delete feature is built into RedisAssist. Simply add a deleted_at attribute to your model.
attr_persist :deleted_at, as: :time
You can fetch soft deleted records by setting a deleted
property when calling #find
Person.find(1, deleted: true)
Callbacks
RedisAssist supports callbacks through a Rails like interface.
before_save :titleize_blog_title
after_create :send_to_real_time_sphinx_index
after_update :update_in_realtime_sphinx_index
after_delete do |record|
record.cleanup!
end
def titleize_blog_title
blog_title.titleize!
end
def send_to_realtime_sphinx_index
...
end
def update_in_realtime_sphinx_index
...
end
Relationships
Experimental support for has_many and belongs_to relationships.
class Person < RedisAssist::Base
attr_persist :name
has_many :pets
end
class Pet < RedisAssist::Base
attr_persist :name
belongs_to :person
end
person = Person.create(name: 'Tyler Love')
person.add_pet Pet.new('Hubble Love')
person.add_pet Pet.new('Oliver Love')
person.save
person.pet_ids # => [1,2]
person.pets # => [..pets..]
pet = Pet.find(1)
pet.person
Helpful methods
The Redis client used for the class
client = People.redis
Rename an attribute
RedisAssist::Utils.rename_attribute(model: Person, from: :name, to: :full_name)
Transforms
Since Redis only supports string values RedisAssist provides an interface for serialization in and out of Redis.
Currently RedisAssist natively supports the following types.
- String
default
- Boolean
- Float
- Integer
- JSON
- Time
- list native redis list data type
- hash native redis hash data type
RedisAssist also provides an elegant API for defining custom transforms.
# Serialize/Deserialize objects with the MessagePack gem
class MessagePackTransform < RedisAssist::Transform
def self.to(val)
val.to_msgpack
end
def self.from(val)
MessagePack.unpack(val)
end
end
To use the MessagePackTransform we just defined
attr_persist :really_long_biography, as: :message_pack
Useful Info
RedisAssist takes advantage of redis hashes to store each persisted attribute. Using the Person
module as an example, the fields will be stored as a Redis Hash with the key person:[id]:attributes
. Several other design approaches were considered. This method was selected because 1) it keeps the underlying data structures flat and normalized in Redis 2) Offers Redis Hash performance benefits, as outlined here: http://redis.io/topics/memory-optimization
Hundreds of millions of RedisAssist creates, updates, finds, and saves are called on bustle.com every month.
In the works
- Refactor an internal API to add more robust support for native Redis data types. Currently there is basic support for lists and hashes. We intend to add advanced support for all Redis data types.
- Refactoring relationship support. RedisAssist relations are not an attempt to recreate SQL joins. The goal is to provide a convenient API for ordering, sorting, iterating over your data sets. It will never do everything a SQL
JOIN
will do, but it introduces many other practical ways of organizing, reading, and writing your data sets. - Cleanup and 1.0
- Utilities to help with data migrations.
-
redis_assist_index
a seperate gem that adds native support for storing your redis models in Sphinx real-time indexes. Full super advanced full-text search, facets, sorting, etc. Hit me up atproduct@bustle.com
if you're interested.
Requirements
redis-rb
Configuration
You can configure RedisAssist with your own redis client
RedisAssist::Config.redis = Redis.new([connection settings...])