= Redis::Objects - Lightweight, atomic object layer around redis-rb This is *not* an ORM. People that are wrapping ORM's around Redis are missing the point. The killer feature of Redis that it allows you to perform atomic operations on _individual_ data structures, like counters, lists, and sets. You can then use these *with* your existing ActiveRecord/DataMapper/etc models, or in classes that have nothing to do with an ORM or even a database. That's where this gem comes in. This gem originally arose out of a need for high-concurrency atomic operations; for a fun rant on the topic, see {ATOMICITY}[http://github.com/nateware/redis-objects/blob/master/ATOMICITY.rdoc], or scroll down to "Atomicity" in this README. There are two ways to use Redis::Objects, either as an +include+ in a class, or by using +new+ with the type of data structure you want to create. == Installation gem install gemcutter gem tumble gem install redis-objects == Example 1: Class Usage === Initialization # If on Rails, config/initializers/redis.rb is a good place for this require 'redis' require 'redis/objects' Redis::Objects.redis = Redis.new(:host => 127.0.0.1, :port => 6379) === Model Class Include in any type of class: class Team < ActiveRecord::Base include Redis::Objects counter :hits counter :runs counter :outs counter :inning, :start => 1 list :on_base set :outfielders value :at_bat end Familiar Ruby array operations Just Work (TM): @team = Team.find(1) @team.on_base << 'player1' @team.on_base << 'player2' @team.on_base << 'player3' puts @team.on_base # ['player1', 'player2'] @team.on_base.pop @team.on_base.shift @team.on_base.length # 1 @team.on_base.delete('player3') Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class: @team.outfielders['1b'] = 'outfielder1' @team.outfielders['lf'] = 'outfielder3' @team.outfielders['lf'] = 'outfielder2' @team.outfielders.keys @team.outfielders.each do |position,player| puts "#{player} is playing #{position}" end position = @team.outfielders.detect{|pos,of| of == 'outfielder3'} Note counters cannot be assigned to, only incremented/decremented: @team.hits.increment # or incr @team.hits.decrement # or decr @team.runs = 4 # exception @team.runs += 1 # exception It would be cool to get that last one working, but Ruby's implementation of += is problematic. == Example 2: Instance Usage Each data type can be used independently. === Initialization Can follow the +$redis+ global variable pattern: $redis = Redis.new(:host => 'localhost', :port => 6379) @value = Redis::Value.new('myvalue') Or can pass the handle into the new method: redis = Redis.new(:host => 'localhost', :port => 6379) @value = Redis::Value.new('myvalue', redis) === Counters @counter = Redis::Counter.new('counter_name') @counter.increment @counter.decrement puts @counter puts @counter.get # force re-fetch === Lists @list = Redis::List.new('list_name') @list << 'a' @list << 'b' puts @list == Atomicity You are probably not handling atomicity correctly in your app. For a fun rant on the topic, see {ATOMICITY}[ATOMICITY.doc] Atomic counters are a good way to handle concurrency: @team = Team.find(1) if @team.drafted_players.increment <= @team.max_players # do stuff @team.team_players.create!(:player_id => 221) @team.active_players.increment else # reset counter state @team.drafted_players.decrement end Atomic block - a cleaner way to do the above. Exceptions or return nil rewind counter back to previous state: @team.drafted_players.increment do |val| raise Team::TeamFullError if val > @team.max_players @team.team_players.create!(:player_id => 221) @team.active_players.increment end Similar approach, using an if block (failure rewinds counter): @team.drafted_players.increment do |val| if val <= @team.max_players @team.team_players.create!(:player_id => 221) @team.active_players.increment end end Class methods work too - notice we override ActiveRecord counters: Team.increment_counter :drafted_players, team_id Team.decrement_counter :drafted_players, team_id, 2 Team.increment_counter :total_online_players # no ID on global counter Class-level atomic block (may save a DB fetch depending on your app): Team.increment_counter(:drafted_players, team_id) do |val| TeamPitcher.create!(:team_id => team_id, :pitcher_id => 181) Team.increment_counter(:active_players, team_id) end Locks with Redis. On completion or exception the lock is released: @team.reorder_lock.lock do @team.reorder_all_players end Class-level lock (same concept) Team.obtain_lock(:reorder, team_id) do Team.reorder_all_players(team_id) end == Author Copyright (c) 2009 {Nate Wiger}[http://nate.wiger.org]. All Rights Reserved. Released under the {Artistic License}[http://www.opensource.org/licenses/artistic-license-2.0.php].