liveresource 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/BSDL +24 -0
- data/COPYING +59 -0
- data/GPL +339 -0
- data/README.md +289 -0
- data/Rakefile +47 -0
- data/benchmark/benchmark_helper.rb +19 -0
- data/benchmark/method_benchmark.rb +94 -0
- data/lib/live_resource.rb +66 -0
- data/lib/live_resource/attributes.rb +77 -0
- data/lib/live_resource/declarations.rb +200 -0
- data/lib/live_resource/finders.rb +43 -0
- data/lib/live_resource/log_helper.rb +24 -0
- data/lib/live_resource/methods.rb +41 -0
- data/lib/live_resource/methods/dispatcher.rb +176 -0
- data/lib/live_resource/methods/forward.rb +23 -0
- data/lib/live_resource/methods/future.rb +27 -0
- data/lib/live_resource/methods/method.rb +93 -0
- data/lib/live_resource/methods/token.rb +22 -0
- data/lib/live_resource/redis_client.rb +100 -0
- data/lib/live_resource/redis_client/attributes.rb +40 -0
- data/lib/live_resource/redis_client/methods.rb +194 -0
- data/lib/live_resource/redis_client/registration.rb +25 -0
- data/lib/live_resource/resource.rb +44 -0
- data/lib/live_resource/resource_proxy.rb +180 -0
- data/old/benchmark/attribute_benchmark.rb +58 -0
- data/old/benchmark/thread_benchmark.rb +89 -0
- data/old/examples/attribute.rb +22 -0
- data/old/examples/attribute_rmw.rb +30 -0
- data/old/examples/attribute_subscriber.rb +32 -0
- data/old/examples/method_provider_sleep.rb +22 -0
- data/old/examples/methods.rb +37 -0
- data/old/lib/live_resource/subscriber.rb +98 -0
- data/old/redis_test.rb +127 -0
- data/old/state_publisher_test.rb +139 -0
- data/old/test/attribute_modify_test.rb +52 -0
- data/old/test/attribute_options_test.rb +54 -0
- data/old/test/attribute_subscriber_test.rb +94 -0
- data/old/test/composite_resource_test.rb +61 -0
- data/old/test/method_sender_test.rb +41 -0
- data/old/test/redis_api_test.rb +185 -0
- data/old/test/simple_attribute_test.rb +75 -0
- data/test/attribute_test.rb +212 -0
- data/test/declarations_test.rb +119 -0
- data/test/logger_test.rb +44 -0
- data/test/method_call_test.rb +223 -0
- data/test/method_forward_continue_test.rb +83 -0
- data/test/method_params_test.rb +81 -0
- data/test/method_routing_test.rb +59 -0
- data/test/multiple_class_test.rb +47 -0
- data/test/new_api_DISABLED.rb +127 -0
- data/test/test_helper.rb +9 -0
- data/test/volume_create_DISABLED.rb +74 -0
- metadata +129 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'redis'
|
3
|
+
require 'yaml'
|
4
|
+
require_relative 'log_helper'
|
5
|
+
require_relative 'redis_client/attributes'
|
6
|
+
require_relative 'redis_client/methods'
|
7
|
+
require_relative 'redis_client/registration'
|
8
|
+
|
9
|
+
class Redis
|
10
|
+
def clone
|
11
|
+
# Create independent Redis
|
12
|
+
Redis.new(
|
13
|
+
:host => client.host,
|
14
|
+
:port => client.port,
|
15
|
+
:timeout => client.timeout,
|
16
|
+
:logger => client.logger,
|
17
|
+
:password => client.password,
|
18
|
+
:db => client.db)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module LiveResource
|
23
|
+
class RedisClient
|
24
|
+
include LogHelper
|
25
|
+
attr_writer :redis
|
26
|
+
attr_reader :redis_class, :redis_name
|
27
|
+
|
28
|
+
@@logger = Logger.new(STDERR)
|
29
|
+
@@logger.level = Logger::WARN
|
30
|
+
|
31
|
+
def initialize(resource_class, resource_name)
|
32
|
+
@redis_class = RedisClient.redisized_key(resource_class)
|
33
|
+
@redis_name = RedisClient.redisized_key(resource_name)
|
34
|
+
|
35
|
+
self.logger = self.class.logger
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(method, *params, &block)
|
39
|
+
if self.class.redis.respond_to? method
|
40
|
+
redis_command(method, params, &block)
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to?(method)
|
47
|
+
return true if self.class.redis.respond_to?(method)
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
# Override default (Ruby) exec with Redis exec.
|
52
|
+
def exec
|
53
|
+
redis_command(:exec, nil)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.redis
|
57
|
+
# Hash of Thread -> Redis instances
|
58
|
+
@@redis ||= {}
|
59
|
+
@@proto_redis ||= Redis.new
|
60
|
+
|
61
|
+
if @@redis[Thread.current].nil?
|
62
|
+
@@redis[Thread.current] = @@proto_redis.clone
|
63
|
+
end
|
64
|
+
|
65
|
+
@@redis[Thread.current]
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.redis=(redis)
|
69
|
+
@@proto_redis = redis
|
70
|
+
@@redis = {}
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.logger
|
74
|
+
@@logger
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.logger=(logger)
|
78
|
+
@@logger = logger
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.redisized_key(word)
|
82
|
+
word = word.to_s.dup
|
83
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
84
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
85
|
+
word.tr!("-", "_")
|
86
|
+
word.gsub!('::', '-')
|
87
|
+
word.downcase!
|
88
|
+
word
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def redis_command(method, params, &block)
|
94
|
+
debug ">>", method.to_s, *params
|
95
|
+
response = self.class.redis.send(method, *params, &block)
|
96
|
+
debug "<<", response
|
97
|
+
response
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module LiveResource
|
2
|
+
class RedisClient
|
3
|
+
def remote_attributes_key
|
4
|
+
if @redis_class == "class"
|
5
|
+
"#{@redis_name}.class_attributes"
|
6
|
+
else
|
7
|
+
"#{@redis_class}.attributes"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_attributes(attributes)
|
12
|
+
unless attributes.empty?
|
13
|
+
sadd remote_attributes_key, attributes
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def registered_attributes
|
18
|
+
attributes = smembers remote_attributes_key
|
19
|
+
|
20
|
+
attributes.map { |a| a.to_sym }
|
21
|
+
end
|
22
|
+
|
23
|
+
def attribute_read(key, options={})
|
24
|
+
deserialize(get("#{@redis_class}.#{@redis_name}.attributes.#{key}"))
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute_write(key, value, options={})
|
28
|
+
redis_key = "#{@redis_class}.#{@redis_name}.attributes.#{key}"
|
29
|
+
if options[:no_overwrite]
|
30
|
+
setnx(redis_key, serialize(value))
|
31
|
+
else
|
32
|
+
set(redis_key, serialize(value))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def attribute_watch(key)
|
37
|
+
watch("#{@redis_class}.#{redis_name}.attributes.#{key}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module LiveResource
|
4
|
+
class RedisClient
|
5
|
+
def remote_methods_key
|
6
|
+
if @redis_class == "class"
|
7
|
+
"#{@redis_name}.class_methods"
|
8
|
+
else
|
9
|
+
"#{@redis_class}.methods"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def methods_list
|
14
|
+
"#{@redis_class}.#{@redis_name}.methods_pending"
|
15
|
+
end
|
16
|
+
|
17
|
+
def methods_in_progress_list
|
18
|
+
"#{@redis_class}.#{@redis_name}.methods_in_progress"
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_details(token)
|
22
|
+
"#{token.redis_class}.#{token.redis_name}.method.#{token.seq}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def result_details(token)
|
26
|
+
"#{token.redis_class}.#{token.redis_name}.result.#{token.seq}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def register_methods(methods)
|
30
|
+
unless methods.empty?
|
31
|
+
sadd remote_methods_key, methods
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def registered_methods
|
36
|
+
methods = smembers remote_methods_key
|
37
|
+
|
38
|
+
methods.map { |m| m.to_sym }
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_wait
|
42
|
+
token = brpoplpush methods_list, methods_in_progress_list, 0
|
43
|
+
deserialize(token)
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_push(token)
|
47
|
+
lpush methods_list, serialize(token)
|
48
|
+
end
|
49
|
+
|
50
|
+
def method_done(token)
|
51
|
+
lrem methods_in_progress_list, 0, serialize(token)
|
52
|
+
end
|
53
|
+
|
54
|
+
def method_get(token)
|
55
|
+
method = get method_details(token)
|
56
|
+
|
57
|
+
deserialize(method)
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_send(method)
|
61
|
+
unless method.token
|
62
|
+
# Choose unique token for this action; retry if token is already in
|
63
|
+
# use by another action.
|
64
|
+
loop do
|
65
|
+
method.token = RemoteMethodToken.new(
|
66
|
+
@redis_class,
|
67
|
+
@redis_name,
|
68
|
+
sprintf("%05d", Kernel.rand(100000)))
|
69
|
+
|
70
|
+
break if setnx(method_details(method.token), serialize(method))
|
71
|
+
end
|
72
|
+
else
|
73
|
+
# Re-serialize current state of method to existing location.
|
74
|
+
set method_details(method.token), serialize(method)
|
75
|
+
end
|
76
|
+
|
77
|
+
method_push method.token
|
78
|
+
method
|
79
|
+
end
|
80
|
+
|
81
|
+
def method_result(method, result)
|
82
|
+
token = method.token
|
83
|
+
|
84
|
+
# Need to watch the method while setting the result; if the caller
|
85
|
+
# has given up waiting before we set the result, we don't want to
|
86
|
+
# leave extra crud in Redis.
|
87
|
+
watch method_details(token)
|
88
|
+
|
89
|
+
unless exists(method_details(token))
|
90
|
+
# Caller must have deleted method
|
91
|
+
warn "setting result for method #{token}, but caller deleted it"
|
92
|
+
unwatch
|
93
|
+
return
|
94
|
+
end
|
95
|
+
|
96
|
+
begin
|
97
|
+
multi
|
98
|
+
lpush result_details(token), serialize(result)
|
99
|
+
exec
|
100
|
+
rescue RuntimeError => e
|
101
|
+
# Must have been deleted while we were working on it, bail out.
|
102
|
+
warn e
|
103
|
+
discard
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def method_wait_for_result(method, timeout)
|
108
|
+
token = method.token
|
109
|
+
result = nil
|
110
|
+
|
111
|
+
begin
|
112
|
+
list, result = brpop result_details(token), timeout
|
113
|
+
|
114
|
+
if result.nil?
|
115
|
+
raise RuntimeError.new("timed out waiting for method #{token}")
|
116
|
+
end
|
117
|
+
|
118
|
+
result = deserialize(result)
|
119
|
+
rescue
|
120
|
+
# Clean token from any lists before passing up exception
|
121
|
+
method_cleanup(token)
|
122
|
+
raise
|
123
|
+
ensure
|
124
|
+
# Clear out original method call details
|
125
|
+
del method_details(token)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def method_discard_result(token)
|
130
|
+
del result_details(token)
|
131
|
+
del method_details(token)
|
132
|
+
end
|
133
|
+
|
134
|
+
def method_done_with?(method)
|
135
|
+
st = serialize(method.token)
|
136
|
+
|
137
|
+
# Need to do a multi/exec so we can atomically look in 3 lists
|
138
|
+
# for the token
|
139
|
+
multi
|
140
|
+
lrange methods_list, 0, -1
|
141
|
+
lrange methods_in_progress_list, 0, -1
|
142
|
+
lrange result_details(method.token), 0, -1
|
143
|
+
result = exec
|
144
|
+
|
145
|
+
if (result[2] != [])
|
146
|
+
# Result already pending
|
147
|
+
true
|
148
|
+
elsif result[0].include?(st) or result[1].include?(st)
|
149
|
+
# Still in methods or methods-in-progress
|
150
|
+
false
|
151
|
+
else
|
152
|
+
raise ArgumentError.new("No method #{token} pending")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def method_cleanup(token)
|
159
|
+
st = serialize(token)
|
160
|
+
|
161
|
+
# Need to do a multi/exec so we can atomically delete from all 3 lists
|
162
|
+
multi
|
163
|
+
lrem methods_list, 0, st
|
164
|
+
lrem methods_in_progress_list, 0, st
|
165
|
+
lrem result_details(token), 0, st
|
166
|
+
exec
|
167
|
+
end
|
168
|
+
|
169
|
+
def serialize(value)
|
170
|
+
if value.is_a? Exception
|
171
|
+
# YAML can't dump an exception properly, it loses the message.
|
172
|
+
# and the backtrace. Save those separately as strings.
|
173
|
+
YAML::dump [value, value.message, value.backtrace]
|
174
|
+
else
|
175
|
+
YAML::dump value
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def deserialize(value)
|
180
|
+
return nil if value.nil?
|
181
|
+
|
182
|
+
result = YAML::load(value)
|
183
|
+
|
184
|
+
if result.is_a?(Array) and result[0].is_a?(Exception)
|
185
|
+
# Inverse of what serialize() is doing with exceptions.
|
186
|
+
e = result[0].class.new(result[1])
|
187
|
+
e.set_backtrace result[2]
|
188
|
+
result = e
|
189
|
+
end
|
190
|
+
|
191
|
+
result
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module LiveResource
|
2
|
+
class RedisClient
|
3
|
+
def instances_key
|
4
|
+
"#{@redis_class}.instances"
|
5
|
+
end
|
6
|
+
|
7
|
+
def register
|
8
|
+
hincrby instances_key, @redis_name, 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def unregister
|
12
|
+
hincrby instances_key, @redis_name, -1
|
13
|
+
end
|
14
|
+
|
15
|
+
def all
|
16
|
+
names = []
|
17
|
+
|
18
|
+
hgetall(instances_key).each_pair do |i, count|
|
19
|
+
names << i if (count.to_i > 0)
|
20
|
+
end
|
21
|
+
|
22
|
+
names
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative 'log_helper'
|
2
|
+
require_relative 'declarations'
|
3
|
+
require_relative 'finders'
|
4
|
+
require_relative 'attributes'
|
5
|
+
require_relative 'methods'
|
6
|
+
require_relative 'methods/forward'
|
7
|
+
|
8
|
+
module LiveResource
|
9
|
+
|
10
|
+
# Module for all Resource providers. Any instances of resources should
|
11
|
+
# be registered with LiveResource::register. The class may also be
|
12
|
+
# registered, if any class attributes/methods should be remotely
|
13
|
+
# callable.
|
14
|
+
module Resource
|
15
|
+
include LiveResource::LogHelper
|
16
|
+
include LiveResource::Declarations
|
17
|
+
include LiveResource::Finders
|
18
|
+
include LiveResource::Attributes
|
19
|
+
include LiveResource::Methods
|
20
|
+
|
21
|
+
# Extends resource classes with proper class methods and
|
22
|
+
# class-level method dispatcher.
|
23
|
+
def self.included(base)
|
24
|
+
base.extend(LiveResource::Declarations::ClassMethods)
|
25
|
+
|
26
|
+
# The class is also extended with attribute and method support
|
27
|
+
# (i.e, the method dispatcher).
|
28
|
+
base.extend(LiveResource::Attributes)
|
29
|
+
base.extend(LiveResource::Methods)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create forward instruction that can be returned by a remote
|
33
|
+
# method, instructing LiveResource to forward to a different
|
34
|
+
# remote method instead of returing directly to the caller.
|
35
|
+
#
|
36
|
+
# @param [LiveResource::ResourceProxy] resource the resource to forward to
|
37
|
+
# @param [Symbol] method the resource's method to call
|
38
|
+
# @param params any parameters to pass with the method call
|
39
|
+
# @return [LiveResource::RemoteMethodForward] a forward instruction, used internally by LiveResource
|
40
|
+
def forward(resource, method, *params)
|
41
|
+
LiveResource::RemoteMethodForward.new(resource, method, params)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require_relative 'log_helper'
|
2
|
+
require_relative 'redis_client'
|
3
|
+
require_relative 'methods/method'
|
4
|
+
require_relative 'methods/future'
|
5
|
+
|
6
|
+
module LiveResource
|
7
|
+
|
8
|
+
# Client object that represents a resource, allowing method calls
|
9
|
+
# and getting/setting attributes. Typically these are returned from
|
10
|
+
# LiveResource finder methods (all, find, etc).
|
11
|
+
class ResourceProxy
|
12
|
+
include LiveResource::LogHelper
|
13
|
+
|
14
|
+
attr_reader :redis_class, :redis_name
|
15
|
+
|
16
|
+
# Create a new proxy given its Redis class and name; typically NOT
|
17
|
+
# USED by client code -- use methods of LiveResource::Finders
|
18
|
+
# instead.
|
19
|
+
def initialize(redis_class, redis_name)
|
20
|
+
@redis_class = redis_class
|
21
|
+
@redis_name = redis_name
|
22
|
+
@redis = RedisClient.new(redis_class, redis_name)
|
23
|
+
@remote_methods = @redis.registered_methods
|
24
|
+
@remote_attributes = @redis.registered_attributes
|
25
|
+
end
|
26
|
+
|
27
|
+
# Proxies attribute and remote method calls to the back-end provider.
|
28
|
+
def method_missing(m, *params, &block)
|
29
|
+
# Strip trailing ?, ! for seeing if we support method
|
30
|
+
sm = m.to_s.sub(/[!,?]$/, '').to_sym
|
31
|
+
|
32
|
+
if @remote_attributes.include?(m)
|
33
|
+
# Attribute get/set
|
34
|
+
if m.match(/\=$/)
|
35
|
+
m = m.to_s.sub(/\=$/, '').to_sym # Strip trailing equal
|
36
|
+
|
37
|
+
remote_attribute_write(m, *params)
|
38
|
+
else
|
39
|
+
remote_attribute_read(m)
|
40
|
+
end
|
41
|
+
elsif @remote_methods.include?(sm)
|
42
|
+
# Method call
|
43
|
+
method = RemoteMethod.new(
|
44
|
+
:method => sm,
|
45
|
+
:params => params)
|
46
|
+
|
47
|
+
if m.match(/!$/)
|
48
|
+
# Async call, discard result
|
49
|
+
method.flags[:discard_result] = true
|
50
|
+
|
51
|
+
remote_send method
|
52
|
+
elsif m.match(/\?$/)
|
53
|
+
# Async call with future
|
54
|
+
method = remote_send method
|
55
|
+
Future.new(self, method)
|
56
|
+
else
|
57
|
+
# Synchronous method call
|
58
|
+
wait_for_done remote_send(method)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Checks if method is a supported attribute or remote method.
|
66
|
+
#
|
67
|
+
# @param [LiveResource::RemoteMethod] method method to send
|
68
|
+
# @param [Object] include_private unused
|
69
|
+
def respond_to_missing?(method, include_private)
|
70
|
+
stripped_method = method.to_s.sub(/[!,?]$/, '').to_sym
|
71
|
+
|
72
|
+
@remote_methods.include?(stripped_method) or
|
73
|
+
@remote_attributes.include?(method)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Send a already-created method object; not typically used by
|
77
|
+
# clients -- use method_missing interface instead.
|
78
|
+
#
|
79
|
+
# @param [LiveResource::RemoteMethod] method method to send
|
80
|
+
def remote_send(method)
|
81
|
+
@redis.method_send method
|
82
|
+
end
|
83
|
+
|
84
|
+
# Wait for method to finish, blocks if method not complete. An
|
85
|
+
# exception raised by the remote resource will be captured and
|
86
|
+
# raised in the client's thread. Clients may only wait once for
|
87
|
+
# completion.
|
88
|
+
#
|
89
|
+
# @param [LiveResource::RemoteMethod] method method to wait for
|
90
|
+
# @param [Numeric] timeout seconds to wait for method completion
|
91
|
+
def wait_for_done(method, timeout = 0)
|
92
|
+
result = @redis.method_wait_for_result(method, timeout)
|
93
|
+
|
94
|
+
if result.is_a?(Exception)
|
95
|
+
# Merge the backtrace from the passed exception with this
|
96
|
+
# stack trace so the final backtrace looks like the method_sender
|
97
|
+
# called the method_provider directly.
|
98
|
+
# trace = merge_backtrace caller, result.backtrace
|
99
|
+
# result.set_backtrace trace
|
100
|
+
|
101
|
+
result.set_backtrace result.backtrace
|
102
|
+
raise result
|
103
|
+
else
|
104
|
+
result
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Check if remote method is already complete. May be called multiple times.
|
109
|
+
#
|
110
|
+
# @param [LiveResource::RemoteMethod] method method to check on
|
111
|
+
def done_with?(method)
|
112
|
+
@redis.method_done_with? method
|
113
|
+
end
|
114
|
+
|
115
|
+
# Reads remote attribute.
|
116
|
+
#
|
117
|
+
# @param [Symbol] key attribute name
|
118
|
+
# @return [Object] remote attribute value
|
119
|
+
def remote_attribute_read(key, options = {})
|
120
|
+
@redis.attribute_read(key, options)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Writes remote attribute to new value.
|
124
|
+
#
|
125
|
+
# @param [Symbol] key attribute name
|
126
|
+
# @param [Object] value new value for attribute
|
127
|
+
# @return new value for attribute
|
128
|
+
def remote_attribute_write(key, value, options = {})
|
129
|
+
@redis.attribute_write(key, value, options)
|
130
|
+
end
|
131
|
+
|
132
|
+
def inspect
|
133
|
+
"#{self.class}: #{@redis_class} #{@redis_name}"
|
134
|
+
end
|
135
|
+
|
136
|
+
# Specify custom format when YAML encoding
|
137
|
+
def encode_with coder
|
138
|
+
coder.tag = '!live_resource:resource'
|
139
|
+
coder['class'] = @redis_class
|
140
|
+
coder['name'] = @redis_name
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# Merge the stack trace from the method sendor and method
|
146
|
+
# provider so it looks like one, seamless stack trace.
|
147
|
+
# LiveResource traces are removed and replaced with a simple
|
148
|
+
# 'via LiveResource' type message.
|
149
|
+
def merge_backtrace(sender_trace, provider_trace)
|
150
|
+
return nil if provider_trace.nil?
|
151
|
+
return provider_trace if sender_trace.nil?
|
152
|
+
|
153
|
+
# Find the first live resource stack trace
|
154
|
+
index = provider_trace.index do |t|
|
155
|
+
t =~ /lib\/live_resource\/method_provider/ ## FIXME
|
156
|
+
end
|
157
|
+
|
158
|
+
# Slice off everything starting at that index
|
159
|
+
result = provider_trace[0 .. (index - 1)]
|
160
|
+
|
161
|
+
# Add a trace that indicates that live resource was used
|
162
|
+
# to link the sender to the provider.
|
163
|
+
result << 'via LiveResource'
|
164
|
+
|
165
|
+
# For the sender trace, remove the 'method_sendor'
|
166
|
+
# part of the trace.
|
167
|
+
index = sender_trace.index do |t|
|
168
|
+
t =~ /lib\/live_resource\/method_sender/ ## FIXME
|
169
|
+
end
|
170
|
+
result += sender_trace[(index + 1) .. (sender_trace.length - 1)]
|
171
|
+
|
172
|
+
result
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Make YAML parser create ResourceProxy objects from our custom type.
|
178
|
+
Psych.add_domain_type('live_resource', 'resource') do |type, val|
|
179
|
+
LiveResource::ResourceProxy.new(val['class'], val['name'])
|
180
|
+
end
|