liveresource 2.0.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/.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
|