redis_object 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|