14 Commits

Author SHA1 Message Date
24112e4705 Merge branch 'release/v0.0.6' 2010-12-01 13:41:44 +00:00
b9752ff92f Version bump to 0.0.6 2010-12-01 13:41:30 +00:00
14a093d79b updated gem dependencies to less specific versions
as older versions should work
2010-12-01 13:40:55 +00:00
84a05363dd Merge branch 'release/v0.0.5' into dev 2010-11-28 11:56:23 +00:00
690d1d9407 Merge branch 'release/v0.0.5' 2010-11-28 11:56:15 +00:00
2aedd4eee3 Version bump to 0.0.5 2010-11-28 11:55:50 +00:00
f906cf068e added a spec for Collection#total 2010-11-28 11:52:23 +00:00
cbb9050c80 fixed a typo 2010-11-28 11:51:50 +00:00
58a2fb560c Merge branch 'feature/multi-conf' into dev 2010-11-28 11:48:27 +00:00
6bae8ce2bc Updated Connection spec so the specs actually pass
without a local Redis server running on the
default port of 6379.
2010-11-28 11:48:10 +00:00
18e6125c6a Added support for connection_ref's down throughout
the code, so models can connect to specific Redis
servers.

I believe a lot of the code needs some
restructuring at some point down the line to
handle multiple connections in a cleaner way, but
for now it'll do.
2010-11-28 11:47:26 +00:00
dc162e0c89 initial work to being able to use per-model redis
configurations
2010-11-28 10:10:58 +00:00
5338676a5f moved all dependencies to gemspec 2010-11-25 22:48:57 +00:00
02fe41082a Merge branch 'release/v0.0.4' into dev 2010-11-24 13:51:13 +00:00
20 changed files with 298 additions and 133 deletions

View File

@@ -2,8 +2,3 @@ source 'http://rubygems.org/'
# Specify your gem's dependencies in redistat.gemspec # Specify your gem's dependencies in redistat.gemspec
gemspec gemspec
group :development do
gem 'rspec', '>= 2.1.0'
gem 'yard', '>= 0.6.3'
end

View File

@@ -1,10 +1,10 @@
PATH PATH
remote: . remote: .
specs: specs:
redistat (0.0.4) redistat (0.0.6)
activesupport (>= 2.3.0) activesupport (>= 2.3.0)
json (>= 1.4.6) json (>= 1.4.0)
redis (>= 2.1.1) redis (>= 2.1.0)
system_timer (>= 1.0.0) system_timer (>= 1.0.0)
time_ext (>= 0.2.8) time_ext (>= 0.2.8)
@@ -35,8 +35,8 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
activesupport (>= 2.3.0) activesupport (>= 2.3.0)
json (>= 1.4.6) json (>= 1.4.0)
redis (>= 2.1.1) redis (>= 2.1.0)
redistat! redistat!
rspec (>= 2.1.0) rspec (>= 2.1.0)
system_timer (>= 1.0.0) system_timer (>= 1.0.0)

View File

@@ -10,6 +10,7 @@ require 'json'
require 'digest/sha1' require 'digest/sha1'
require 'redistat/collection' require 'redistat/collection'
require 'redistat/connection'
require 'redistat/database' require 'redistat/database'
require 'redistat/date' require 'redistat/date'
require 'redistat/event' require 'redistat/event'
@@ -34,51 +35,28 @@ module Redistat
KEY_EVENT_IDS = ".event_ids" KEY_EVENT_IDS = ".event_ids"
class InvalidOptions < ArgumentError; end class InvalidOptions < ArgumentError; end
class RedisServerIsTooOld < Exception; end
# Provides access to the Redis database. This is shared accross all models and instances.
def redis
threaded[:redis] ||= connection(*options)
end
def redis=(connection)
threaded[:redis] = connection
end
def threaded
Thread.current[:redistat] ||= {}
end
# Connect to a redis database.
#
# @param options [Hash] options to create a message with.
# @option options [#to_s] :host ('127.0.0.1') Host of the redis database.
# @option options [#to_s] :port (6379) Port number.
# @option options [#to_s] :db (0) Database number.
# @option options [#to_s] :timeout (0) Database timeout in seconds.
# @example Connect to a database in port 6380.
# Redistat.connect(:port => 6380)
def connect(*options)
self.redis = nil
@options = options
end
# Return a connection to Redis.
#
# This is a wapper around Redis.new(options)
def connection(*options)
Redis.new(*options)
end
def options
@options = [] unless defined? @options
@options
end
# Clear the database.
def flush
redis.flushdb
end
module_function :connect, :connection, :flush, :redis, :redis=, :options, :threaded class << self
def connection(ref = nil)
Connection.get(ref)
end
alias :redis :connection
def connection=(connection)
Connection.add(connection)
end
alias :redis= :connection=
def connect(options)
Connection.create(options)
end
def flush
puts "WARNING: Redistat.flush is deprecated. Use Redistat.redis.flush instead."
connection.flushdb
end
end
end end

View File

@@ -12,5 +12,9 @@ module Redistat
@depth = options[:depth] ||= nil @depth = options[:depth] ||= nil
end end
def total
@total ||= {}
end
end end
end end

View File

@@ -0,0 +1,67 @@
module Redistat
module Connection
REQUIRED_SERVER_VERSION = "1.3.10"
class << self
def get(ref = nil)
ref ||= :default
connections[references[ref]] || create
end
def add(conn, ref = nil)
ref ||= :default
check_redis_version(conn)
references[ref] = conn.client.id
connections[conn.client.id] = conn
end
def create(options = {})
ref = options.delete(:ref) || :default
options.reverse_merge!(default_options)
conn = (connections[connection_id(options)] ||= connection(options))
references[ref] = conn.client.id
conn
end
def connections
threaded[:connections] ||= {}
end
def references
threaded[:references] ||= {}
end
def threaded
Thread.current[:redistat] ||= {}
end
private
def check_redis_version(conn)
raise RedisServerIsTooOld if conn.info["redis_version"] < REQUIRED_SERVER_VERSION
conn
end
def connection(options)
check_redis_version(Redis.new(options))
end
def connection_id(options = {})
options.reverse_merge!(default_options)
"redis://#{options[:host]}:#{options[:port]}/#{options[:db]}"
end
def default_options
{
:host => '127.0.0.1',
:port => 6379,
:db => 0,
:timeout => 5
}
end
end
end
end

View File

@@ -3,8 +3,8 @@ module Redistat
def self.included(base) def self.included(base)
base.extend(Database) base.extend(Database)
end end
def db def db(ref = nil)
Redistat.redis Redistat.connection(ref)
end end
end end
end end

View File

@@ -4,21 +4,34 @@ module Redistat
attr_reader :id attr_reader :id
attr_reader :key attr_reader :key
attr_reader :connection_ref
attr_accessor :stats attr_accessor :stats
attr_accessor :meta attr_accessor :meta
attr_accessor :options attr_accessor :options
def initialize(scope, label = nil, date = nil, stats = {}, options = {}, meta = {}, is_new = true) def initialize(scope, label = nil, date = nil, stats = {}, options = {}, meta = {}, is_new = true)
@options = default_options.merge(options) @options = parse_options(options)
@connection_ref = @options[:connection_ref]
@key = Key.new(scope, label, date, @options) @key = Key.new(scope, label, date, @options)
@stats = stats ||= {} @stats = stats ||= {}
@meta = meta ||= {} @meta = meta ||= {}
@new = is_new @new = is_new
end end
def db
super(@connection_ref)
end
def parse_options(options)
default_options.each do |opt, val|
options[opt] = val if options[opt].nil?
end
options
end
def default_options def default_options
{ :depth => :hour, :store_event => false } { :depth => :hour, :store_event => false, :connection_ref => nil }
end end
def new? def new?
@@ -59,7 +72,7 @@ module Redistat
def save def save
return false if !self.new? return false if !self.new?
Summary.update_all(@key, @stats, depth_limit) Summary.update_all(@key, @stats, depth_limit, @connection_ref)
if @options[:store_event] if @options[:store_event]
@id = self.next_id @id = self.next_id
db.hmset("#{self.scope}#{KEY_EVENT}#{@id}", db.hmset("#{self.scope}#{KEY_EVENT}#{@id}",

View File

@@ -8,6 +8,10 @@ module Redistat
@options = options @options = options
end end
def db
super(@options[:connection_ref])
end
def valid_options? def valid_options?
return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank? return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank?
false false

View File

@@ -3,11 +3,16 @@ module Redistat
include Database include Database
attr_reader :raw attr_reader :raw
attr_reader :connection_ref
def initialize(str, options = {}) def initialize(str, options = {})
@options = options @options = options
@raw = str.to_s @raw = str.to_s
end end
def db
super(@options[:connection_ref])
end
def name def name
@options[:hashed_label] ? hash : @raw @options[:hashed_label] ? hash : @raw

View File

@@ -1,5 +1,6 @@
module Redistat module Redistat
module Model module Model
include Redistat::Database
def self.included(base) def self.included(base)
base.extend(self) base.extend(self)
@@ -10,6 +11,16 @@ module Redistat
end end
alias :event :store alias :event :store
def connect_to(opts = {})
Connection.create(opts.merge(:ref => name))
options[:connection_ref] = name
end
def connection
db(options[:connection_ref])
end
alias :redis :connection
def fetch(label, from, till, opts = {}) def fetch(label, from, till, opts = {})
Finder.find({ Finder.find({
:scope => name, :scope => name,

View File

@@ -2,21 +2,21 @@ module Redistat
class Summary class Summary
include Database include Database
def self.update_all(key, stats = {}, depth_limit = nil) def self.update_all(key, stats = {}, depth_limit = nil, connection_ref = nil)
stats ||= {} stats ||= {}
depth_limit ||= key.depth depth_limit ||= key.depth
return nil if stats.size == 0 return nil if stats.size == 0
Date::DEPTHS.each do |depth| Date::DEPTHS.each do |depth|
update(key, stats, depth) update(key, stats, depth, connection_ref)
break if depth == depth_limit break if depth == depth_limit
end end
end end
private private
def self.update(key, stats, depth) def self.update(key, stats, depth, connection_ref = nil)
stats.each do |field, value| stats.each do |field, value|
db.hincrby key.to_s(depth), field, value db(connection_ref).hincrby key.to_s(depth), field, value
end end
end end

View File

@@ -1,3 +1,3 @@
module Redistat module Redistat
VERSION = "0.0.4" VERSION = "0.0.6"
end end

View File

@@ -20,8 +20,11 @@ Gem::Specification.new do |s|
s.require_paths = ["lib"] s.require_paths = ["lib"]
s.add_runtime_dependency 'activesupport', '>= 2.3.0' s.add_runtime_dependency 'activesupport', '>= 2.3.0'
s.add_runtime_dependency 'json', '>= 1.4.6' s.add_runtime_dependency 'json', '>= 1.4.0'
s.add_runtime_dependency 'redis', '>= 2.1.1' s.add_runtime_dependency 'redis', '>= 2.1.0'
s.add_runtime_dependency 'system_timer', '>= 1.0.0' s.add_runtime_dependency 'system_timer', '>= 1.0.0'
s.add_runtime_dependency 'time_ext', '>= 0.2.8' s.add_runtime_dependency 'time_ext', '>= 0.2.8'
s.add_development_dependency 'rspec', '>= 2.1.0'
s.add_development_dependency 'yard', '>= 0.6.3'
end end

View File

@@ -1,34 +0,0 @@
require "spec_helper"
describe Redistat do
include Redistat::Database
before(:each) do
db.flushdb
end
it "should have a valid Redis client instance" do
db.should_not be_nil
end
it "should be connected to the testing server" do
db.client.port.should == 8379
db.client.host.should == "127.0.0.1"
end
it "should be able to set and get data" do
db.set("hello", "world")
db.get("hello").should == "world"
db.del("hello").should be_true
end
it "should be able to store hashes to Redis" do
db.hset("key", "field", "1")
db.hget("key", "field").should == "1"
db.hincrby("key", "field", 1)
db.hget("key", "field").should == "2"
db.hincrby("key", "field", -1)
db.hget("key", "field").should == "1"
end
end

View File

@@ -2,7 +2,7 @@ require "spec_helper"
describe Redistat::Collection do describe Redistat::Collection do
it "should should initialize properly" do it "should initialize properly" do
options = {:from => "from", :till => "till", :depth => "depth"} options = {:from => "from", :till => "till", :depth => "depth"}
result = Redistat::Collection.new(options) result = Redistat::Collection.new(options)
result.from.should == options[:from] result.from.should == options[:from]
@@ -10,4 +10,11 @@ describe Redistat::Collection do
result.depth.should == options[:depth] result.depth.should == options[:depth]
end end
it "should have a total property" do
col = Redistat::Collection.new()
col.total.should == {}
col.total = {:foo => "bar"}
col.total.should == {:foo => "bar"}
end
end end

61
spec/connection_spec.rb Normal file
View File

@@ -0,0 +1,61 @@
require "spec_helper"
include Redistat
describe Redistat::Connection do
it "should have a valid Redis client instance" do
Redistat.redis.should_not be_nil
end
it "should have initialized custom testing connection" do
redis = Redistat.redis
redis.client.host.should == '127.0.0.1'
redis.client.port.should == 8379
redis.client.db.should == 15
end
it "should be able to set and get data" do
redis = Redistat.redis
redis.set("hello", "world")
redis.get("hello").should == "world"
redis.del("hello").should be_true
end
it "should be able to store hashes to Redis" do
redis = Redistat.redis
redis.hset("hash", "field", "1")
redis.hget("hash", "field").should == "1"
redis.hincrby("hash", "field", 1)
redis.hget("hash", "field").should == "2"
redis.hincrby("hash", "field", -1)
redis.hget("hash", "field").should == "1"
redis.del("hash")
end
it "should be accessible from Redistat module" do
Redistat.redis.should == Connection.get
Redistat.redis.should == Redistat.connection
end
it "should handle multiple connections with refs" do
Redistat.redis.client.db.should == 15
Redistat.connect(:port => 8379, :db => 14, :ref => "Custom")
Redistat.redis.client.db.should == 15
Redistat.redis("Custom").client.db.should == 14
end
it "should be able to overwrite default and custom refs" do
Redistat.redis.client.db.should == 15
Redistat.connect(:port => 8379, :db => 14)
Redistat.redis.client.db.should == 14
Redistat.redis("Custom").client.db.should == 14
Redistat.connect(:port => 8379, :db => 15, :ref => "Custom")
Redistat.redis("Custom").client.db.should == 15
# Reset the default connection to the testing server or all hell
# might brake loose from the rest of the specs
Redistat.connect(:port => 8379, :db => 15)
end
end

10
spec/database_spec.rb Normal file
View File

@@ -0,0 +1,10 @@
require "spec_helper"
describe Redistat::Database do
include Redistat::Database
it "should make #db method available when included" do
db.should == Redistat.redis
end
end

View File

@@ -1,6 +1,6 @@
require "redistat" require "redistat"
class ModelHelper class ModelHelper1
include Redistat::Model include Redistat::Model
@@ -13,4 +13,11 @@ class ModelHelper2
store_event true store_event true
hashed_label true hashed_label true
end
class ModelHelper3
include Redistat::Model
connect_to :port => 8379, :db => 14
end end

View File

@@ -5,11 +5,13 @@ describe Redistat::Model do
include Redistat::Database include Redistat::Database
before(:each) do before(:each) do
db.flushdb ModelHelper1.redis.flushdb
ModelHelper2.redis.flushdb
ModelHelper3.redis.flushdb
end end
it "should should name itself correctly" do it "should should name itself correctly" do
ModelHelper.send(:name).should == "ModelHelper" ModelHelper1.send(:name).should == "ModelHelper1"
ModelHelper2.send(:name).should == "ModelHelper2" ModelHelper2.send(:name).should == "ModelHelper2"
end end
@@ -18,49 +20,81 @@ describe Redistat::Model do
ModelHelper2.store_event.should == true ModelHelper2.store_event.should == true
ModelHelper2.hashed_label.should == true ModelHelper2.hashed_label.should == true
ModelHelper.depth.should == nil ModelHelper1.depth.should == nil
ModelHelper.store_event.should == nil ModelHelper1.store_event.should == nil
ModelHelper.hashed_label.should == nil ModelHelper1.hashed_label.should == nil
ModelHelper.depth(:hour) ModelHelper1.depth(:hour)
ModelHelper.depth.should == :hour ModelHelper1.depth.should == :hour
ModelHelper.store_event(true) ModelHelper1.store_event(true)
ModelHelper.store_event.should == true ModelHelper1.store_event.should == true
ModelHelper.hashed_label(true) ModelHelper1.hashed_label(true)
ModelHelper.hashed_label.should == true ModelHelper1.hashed_label.should == true
ModelHelper.options[:depth] = nil ModelHelper1.options[:depth] = nil
ModelHelper.options[:store_event] = nil ModelHelper1.options[:store_event] = nil
ModelHelper.options[:hashed_label] = nil ModelHelper1.options[:hashed_label] = nil
ModelHelper.depth.should == nil ModelHelper1.depth.should == nil
ModelHelper.store_event.should == nil ModelHelper1.store_event.should == nil
ModelHelper.hashed_label.should == nil ModelHelper1.hashed_label.should == nil
end end
it "should store and fetch stats" do it "should store and fetch stats" do
ModelHelper.store("sheep.black", {:count => 6, :weight => 461}, 4.hours.ago) ModelHelper1.store("sheep.black", {:count => 6, :weight => 461}, 4.hours.ago)
ModelHelper.store("sheep.black", {:count => 2, :weight => 156}) ModelHelper1.store("sheep.black", {:count => 2, :weight => 156})
stats = ModelHelper.fetch("sheep.black", 2.hours.ago, 1.hour.from_now) stats = ModelHelper1.fetch("sheep.black", 2.hours.ago, 1.hour.from_now)
stats.total["count"].should == 2 stats.total["count"].should == 2
stats.total["weight"].should == 156 stats.total["weight"].should == 156
stats.first.should == stats.total stats.first.should == stats.total
stats = ModelHelper.fetch("sheep.black", 5.hours.ago, 1.hour.from_now) stats = ModelHelper1.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats.total[:count].should == 8 stats.total[:count].should == 8
stats.total[:weight].should == 617 stats.total[:weight].should == 617
stats.first.should == stats.total stats.first.should == stats.total
ModelHelper.store("sheep.white", {:count => 5, :weight => 393}, 4.hours.ago) ModelHelper1.store("sheep.white", {:count => 5, :weight => 393}, 4.hours.ago)
ModelHelper.store("sheep.white", {:count => 4, :weight => 316}) ModelHelper1.store("sheep.white", {:count => 4, :weight => 316})
stats = ModelHelper.fetch("sheep.white", 2.hours.ago, 1.hour.from_now) stats = ModelHelper1.fetch("sheep.white", 2.hours.ago, 1.hour.from_now)
stats.total[:count].should == 4 stats.total[:count].should == 4
stats.total[:weight].should == 316 stats.total[:weight].should == 316
stats.first.should == stats.total stats.first.should == stats.total
stats = ModelHelper.fetch("sheep.white", 5.hours.ago, 1.hour.from_now) stats = ModelHelper1.fetch("sheep.white", 5.hours.ago, 1.hour.from_now)
stats.total[:count].should == 9 stats.total[:count].should == 9
stats.total[:weight].should == 709 stats.total[:weight].should == 709
stats.first.should == stats.total stats.first.should == stats.total
end end
end it "should connect to different Redis servers on a per-model basis" do
ModelHelper3.redis.client.db.should == 14
ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, 4.hours.ago)
ModelHelper3.store("sheep.black", {:count => 2, :weight => 156})
db.keys("*").should be_empty
ModelHelper1.redis.keys("*").should be_empty
db("ModelHelper3").keys("*").should have(5).items
ModelHelper3.redis.keys("*").should have(5).items
stats = ModelHelper3.fetch("sheep.black", 2.hours.ago, 1.hour.from_now)
stats.total["count"].should == 2
stats.total["weight"].should == 156
stats = ModelHelper3.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats.total[:count].should == 8
stats.total[:weight].should == 617
ModelHelper3.connect_to(:port => 8379, :db => 13)
ModelHelper3.redis.client.db.should == 13
stats = ModelHelper3.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats.total.should == {}
ModelHelper3.connect_to(:port => 8379, :db => 14)
ModelHelper3.redis.client.db.should == 14
stats = ModelHelper3.fetch("sheep.black", 5.hours.ago, 1.hour.from_now)
stats.total[:count].should == 8
stats.total[:weight].should == 617
end
end

View File

@@ -8,5 +8,5 @@ require 'rspec'
require 'rspec/autorun' require 'rspec/autorun'
# use the test Redistat instance # use the test Redistat instance
Redistat.connect({:port => 8379, :db => 15}) Redistat.connect(:port => 8379, :db => 15)
Redistat.flush Redistat.redis.flushdb