redis_object 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.coveralls.yml +1 -0
- data/.gitignore +6 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/README.markdown +179 -0
- data/Rakefile +10 -0
- data/lib/redis_object.rb +47 -0
- data/lib/redis_object/base.rb +408 -0
- data/lib/redis_object/collection.rb +388 -0
- data/lib/redis_object/defaults.rb +42 -0
- data/lib/redis_object/experimental/history.rb +49 -0
- data/lib/redis_object/ext/benchmark.rb +34 -0
- data/lib/redis_object/ext/cleaner.rb +14 -0
- data/lib/redis_object/ext/filters.rb +68 -0
- data/lib/redis_object/ext/script_cache.rb +92 -0
- data/lib/redis_object/ext/shardable.rb +18 -0
- data/lib/redis_object/ext/triggers.rb +101 -0
- data/lib/redis_object/ext/view_caching.rb +258 -0
- data/lib/redis_object/ext/views.rb +102 -0
- data/lib/redis_object/external_index.rb +25 -0
- data/lib/redis_object/indices.rb +97 -0
- data/lib/redis_object/inheritance_tracking.rb +23 -0
- data/lib/redis_object/keys.rb +37 -0
- data/lib/redis_object/storage.rb +93 -0
- data/lib/redis_object/storage/adapter.rb +46 -0
- data/lib/redis_object/storage/aws.rb +71 -0
- data/lib/redis_object/storage/mysql.rb +47 -0
- data/lib/redis_object/storage/redis.rb +119 -0
- data/lib/redis_object/timestamps.rb +74 -0
- data/lib/redis_object/tpl.rb +17 -0
- data/lib/redis_object/types.rb +276 -0
- data/lib/redis_object/validation.rb +89 -0
- data/lib/redis_object/version.rb +5 -0
- data/redis_object.gemspec +26 -0
- data/spec/adapter_spec.rb +43 -0
- data/spec/base_spec.rb +90 -0
- data/spec/benchmark_spec.rb +46 -0
- data/spec/collections_spec.rb +144 -0
- data/spec/defaults_spec.rb +56 -0
- data/spec/filters_spec.rb +29 -0
- data/spec/indices_spec.rb +45 -0
- data/spec/rename_class_spec.rb +96 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/timestamp_spec.rb +28 -0
- data/spec/trigger_spec.rb +51 -0
- data/spec/types_spec.rb +103 -0
- data/spec/view_caching_spec.rb +130 -0
- data/spec/views_spec.rb +72 -0
- metadata +172 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Views
|
3
|
+
|
4
|
+
ViewFieldGetter = "local out = {}
|
5
|
+
local key
|
6
|
+
local val
|
7
|
+
for i=1,#ARGV do
|
8
|
+
key = ARGV[i]
|
9
|
+
val = redis.call('HGET',KEYS[1],key)
|
10
|
+
if val then
|
11
|
+
table.insert(out,key)
|
12
|
+
table.insert(out,val)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
return out".gsub(/\t/,'').freeze
|
16
|
+
|
17
|
+
def view_as_hash(name)
|
18
|
+
out = {}
|
19
|
+
if requested_set = self.class.named_views[name]
|
20
|
+
if requested_set.is_a?(Symbol) and self.respond_to?(requested_set)
|
21
|
+
out = send(requested_set)
|
22
|
+
else
|
23
|
+
methods = requested_set[:fields].select {|f| self.respond_to?(f.to_sym) }
|
24
|
+
if methods.count > 0
|
25
|
+
methods.each do |m|
|
26
|
+
out[m.to_s] = send(m.to_sym)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if requested_set[:fields] && (flds = requested_set[:fields].select {|f| !out.keys.include?(f.to_s) }.map {|f| f.to_s }) && flds.count > 0
|
30
|
+
res = Hash[*store.eval(ViewFieldGetter, [hkey], flds)]
|
31
|
+
out.merge!(res)
|
32
|
+
end
|
33
|
+
if requested_set[:procs]
|
34
|
+
requested_set[:procs].each do |k,proc|
|
35
|
+
out[k.to_s] = proc.call(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
if requested_set[:hashes]
|
39
|
+
requested_set[:hashes].each do |k,v|
|
40
|
+
case v
|
41
|
+
when String, Symbol
|
42
|
+
out[k.to_s] = get(v)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
out
|
49
|
+
end
|
50
|
+
|
51
|
+
def view_as_json(name)
|
52
|
+
Yajl::Encoder.encode(view_as_hash(name))
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
|
57
|
+
def named_view(name,*fields)
|
58
|
+
named_views[name] = normalize_field_options(fields)
|
59
|
+
end
|
60
|
+
|
61
|
+
def named_views
|
62
|
+
@named_views ||= {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def normalize_field_options(fields)
|
66
|
+
fields.flatten!
|
67
|
+
fields.uniq!
|
68
|
+
|
69
|
+
options = {}
|
70
|
+
if fields.last.is_a?(Hash) # assume an option hash
|
71
|
+
options.merge!(fields.slice!(fields.size - 1, 1)[0])
|
72
|
+
end
|
73
|
+
|
74
|
+
# assign a the method as a symbol to be exclusively invoked on view
|
75
|
+
# so instead of returning a hash on view, it will return only what was
|
76
|
+
# produced by calling the method.
|
77
|
+
if options.keys.size > 0 and options[:method]
|
78
|
+
out = options[:method].to_sym
|
79
|
+
else
|
80
|
+
hash = fields.select {|f| f.is_a?(Hash) }.inject({},:merge)
|
81
|
+
out = {}
|
82
|
+
if (h = hash.select {|k,v| !v.is_a?(Proc) }) && h.count > 0
|
83
|
+
out[:hashes] = h
|
84
|
+
end
|
85
|
+
if (h = hash.select {|k,v| v.is_a?(Proc) }) && h.count > 0
|
86
|
+
out[:procs] = h
|
87
|
+
end
|
88
|
+
if (h = fields.select {|o| o.is_a?(String) || o.is_a?(Symbol) }) && h.count > 0
|
89
|
+
out[:fields] = h
|
90
|
+
end
|
91
|
+
end
|
92
|
+
out
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.included(base)
|
98
|
+
base.extend(ClassMethods)
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Seabright
|
2
|
+
class ExternalIndex
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def buildthisout
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def redis
|
12
|
+
@@redis ||= Seabright::RedisPool.connection
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def redis
|
20
|
+
@@redis ||= self.class.redis
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Indices
|
3
|
+
|
4
|
+
def index_key(idx)
|
5
|
+
self.class.index_key(idx)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def intercept_sets_for_indices!
|
11
|
+
return if @intercepted_sets_for_indices
|
12
|
+
self.class_eval do
|
13
|
+
alias_method :unindexed_set, :set unless method_defined?(:unindexed_set)
|
14
|
+
def set(k,v)
|
15
|
+
ret = unindexed_set(k,v)
|
16
|
+
if self.class.has_sort_index?(k)
|
17
|
+
store.zrem(index_key(k), hkey)
|
18
|
+
store.zadd(index_key(k), score_format(k,v), hkey)
|
19
|
+
end
|
20
|
+
ret
|
21
|
+
end
|
22
|
+
alias_method :unindexed_mset, :mset unless method_defined?(:unindexed_mset)
|
23
|
+
def mset(dat)
|
24
|
+
ret = unindexed_mset(dat)
|
25
|
+
dat.select {|k,v| self.class.has_sort_index?(k) }.each do |k,v|
|
26
|
+
store.zrem(index_key(k), hkey)
|
27
|
+
store.zadd(index_key(k), score_format(k,v), hkey)
|
28
|
+
end
|
29
|
+
ret
|
30
|
+
end
|
31
|
+
alias_method :unindexed_setnx, :setnx unless method_defined?(:unindexed_setnx)
|
32
|
+
def setnx(k,v)
|
33
|
+
ret = unindexed_setnx(k,v)
|
34
|
+
if self.class.has_sort_index?(k)
|
35
|
+
store.zrem(index_key(k), hkey)
|
36
|
+
store.zadd(index_key(k), score_format(k,v), hkey)
|
37
|
+
end
|
38
|
+
ret
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
@intercepted_sets_for_indices = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def indexed(idx,num=-1,reverse=false)
|
46
|
+
out = Enumerator.new do |yielder|
|
47
|
+
store.send(reverse ? :zrevrange : :zrange, index_key(idx), 0, num-1).each do |member|
|
48
|
+
if a = self.find_by_key(member)
|
49
|
+
yielder << a
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if block_given?
|
54
|
+
out.each do |itm|
|
55
|
+
yield itm
|
56
|
+
end
|
57
|
+
else
|
58
|
+
out
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def index_key(idx)
|
63
|
+
"#{self.plname}::#{idx}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def sort_indices
|
67
|
+
@@sort_indices ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
def sort_by(k)
|
71
|
+
sort_indices << k.to_sym
|
72
|
+
intercept_sets_for_indices!
|
73
|
+
end
|
74
|
+
|
75
|
+
def reindex(k)
|
76
|
+
store.del index_key(k)
|
77
|
+
all.each do |obj|
|
78
|
+
obj.set(k,obj.get(k))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_sort_index?(k)
|
83
|
+
sort_indices.include?(k.to_sym)
|
84
|
+
end
|
85
|
+
|
86
|
+
def latest
|
87
|
+
indexed(:created_at,999,true).first
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.included(base)
|
93
|
+
base.extend(ClassMethods)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Seabright
|
2
|
+
module InheritanceTracking
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def inherited(child_class)
|
6
|
+
child_classes_set.add(child_class)
|
7
|
+
end
|
8
|
+
|
9
|
+
def child_classes_set
|
10
|
+
@child_classes_set ||= Set.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def child_classes
|
14
|
+
child_classes_set.to_a
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
base.extend(ClassMethods)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Keys
|
3
|
+
|
4
|
+
def key(ident = id)
|
5
|
+
"#{self.class.cname}:#{ident.gsub(/^.*:/,'')}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def reserve_key(ident = id)
|
9
|
+
"#{key(ident)}_reserve"
|
10
|
+
end
|
11
|
+
|
12
|
+
def hkey(ident = nil)
|
13
|
+
"#{key}_h"
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
def key(ident=nil)
|
19
|
+
"#{cname}#{ident ? ":#{ident.gsub(/^.*:/,'')}" : ""}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def reserve_key(ident=nil)
|
23
|
+
"#{key(ident)}_reserve"
|
24
|
+
end
|
25
|
+
|
26
|
+
def hkey(ident = nil)
|
27
|
+
"#{key(ident)}_h"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.included(base)
|
33
|
+
base.extend(ClassMethods)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Storage
|
3
|
+
|
4
|
+
require 'redis_object/storage/adapter'
|
5
|
+
autoload :Redis, 'redis_object/storage/redis'
|
6
|
+
autoload :MySQL, 'redis_object/storage/mysql'
|
7
|
+
|
8
|
+
def store
|
9
|
+
self.class.store
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def set_storage(adp=adapter)
|
15
|
+
@adapter = adp
|
16
|
+
end
|
17
|
+
|
18
|
+
def store(id=store_name)
|
19
|
+
adapters[id] ||= const_get(adapter).new(config(id))
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure_store(conf,id=store_name,*ids)
|
23
|
+
configs[id] = conf
|
24
|
+
ids.each do |i|
|
25
|
+
configs[i] = conf
|
26
|
+
end
|
27
|
+
store(id)
|
28
|
+
end
|
29
|
+
|
30
|
+
def use_store(id)
|
31
|
+
raise "Cannot use non-existent store: #{id}" unless config(id)
|
32
|
+
@store_name = id.to_sym
|
33
|
+
end
|
34
|
+
|
35
|
+
def reconnect!
|
36
|
+
adapters.each do |k,v|
|
37
|
+
v.reconnect!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def adapters
|
42
|
+
@@adapters ||= {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def adapter
|
46
|
+
@adapter ||= config[:adapter].to_sym || :Redis
|
47
|
+
end
|
48
|
+
|
49
|
+
def store_name
|
50
|
+
@store_name ||= :global
|
51
|
+
end
|
52
|
+
|
53
|
+
def configs
|
54
|
+
@@conf ||= {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def config(id=store_name)
|
58
|
+
configs[id]
|
59
|
+
end
|
60
|
+
|
61
|
+
def stores
|
62
|
+
adapters
|
63
|
+
end
|
64
|
+
|
65
|
+
def dump_stores_to_files(path)
|
66
|
+
raise "Directory does not exist!" unless Dir.exists?(File.dirname(path))
|
67
|
+
adapters.each do |name,adptr|
|
68
|
+
if adptr.respond_to? :dump_to_file
|
69
|
+
puts "Dumping #{name} into #{path}/#{name.to_s}.dump"
|
70
|
+
adptr.dump_to_file("#{path}/#{name.to_s}.dump")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def restore_stores_from_files(path)
|
76
|
+
raise "Directory does not exist!" unless Dir.exists?(File.dirname(path))
|
77
|
+
Dir.glob(path + "/*.dump").each do |file|
|
78
|
+
name = file.gsub(/\.[^\.]+$/,'').gsub(/.*\//,'').to_sym
|
79
|
+
if (stor = store(name)) && stor.respond_to?(:restore_from_file)
|
80
|
+
puts "Restoring #{name} from #{file}"
|
81
|
+
stor.restore_from_file(file)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.included(base)
|
89
|
+
base.extend(ClassMethods)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Seabright
|
2
|
+
module Storage
|
3
|
+
class Adapter
|
4
|
+
|
5
|
+
def initialize(config={})
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
def configure(conf)
|
10
|
+
@config = conf
|
11
|
+
reset
|
12
|
+
end
|
13
|
+
|
14
|
+
def config
|
15
|
+
@config ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def config_opts(*opts)
|
19
|
+
opts.inject({}) do |a,k|
|
20
|
+
a[k] = config[k]
|
21
|
+
a
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
connections.each_index do |i|
|
27
|
+
connections[i] = nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :reconnect!, :reset
|
31
|
+
|
32
|
+
def connection(num=0)
|
33
|
+
connections[num] ||= new_connection
|
34
|
+
end
|
35
|
+
|
36
|
+
def connections
|
37
|
+
@connections ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def new_connection
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
module Seabright
|
3
|
+
module Storage
|
4
|
+
class AWS
|
5
|
+
|
6
|
+
def initialize(config={})
|
7
|
+
puts "Got config: '#{config.inspect}'"
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure(conf)
|
12
|
+
@config = conf
|
13
|
+
reset
|
14
|
+
end
|
15
|
+
|
16
|
+
def set
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def sadd
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def del
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def srem
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def smembers
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def exists
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def hget
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def hset
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def reset
|
51
|
+
@connection = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def connection
|
55
|
+
@connection ||= new_connection
|
56
|
+
end
|
57
|
+
|
58
|
+
def new_connection
|
59
|
+
require 'fog'
|
60
|
+
require 'aws-sdk'
|
61
|
+
|
62
|
+
opts = [:path, :db, :password].inject({}) {|a,k|
|
63
|
+
a[k] = @config[k]
|
64
|
+
a
|
65
|
+
}
|
66
|
+
::Redis.new(opts)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|