aliyun-ess 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG +5 -0
- data/INSTALL +13 -0
- data/bin/ess +6 -0
- data/bin/setup.rb +11 -0
- data/lib/aliyun/ess.rb +33 -0
- data/lib/aliyun/ess/authentication.rb +121 -0
- data/lib/aliyun/ess/base.rb +128 -0
- data/lib/aliyun/ess/collection.rb +24 -0
- data/lib/aliyun/ess/connection.rb +226 -0
- data/lib/aliyun/ess/error.rb +84 -0
- data/lib/aliyun/ess/exceptions.rb +82 -0
- data/lib/aliyun/ess/extensions.rb +174 -0
- data/lib/aliyun/ess/parsing.rb +67 -0
- data/lib/aliyun/ess/response.rb +161 -0
- data/lib/aliyun/ess/scaling_group.rb +50 -0
- data/lib/aliyun/ess/scaling_rule.rb +45 -0
- data/lib/aliyun/ess/service.rb +31 -0
- data/lib/aliyun/ess/version.rb +14 -0
- metadata +104 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Aliyun
|
3
|
+
module ESS
|
4
|
+
# Anything you do that makes a request to OSS could result in an error. If it does, the Aliyun::OSS library will raise an exception
|
5
|
+
# specific to the error. All exception that are raised as a result of a request returning an error response inherit from the
|
6
|
+
# ResponseError exception. So should you choose to rescue any such exception, you can simple rescue ResponseError.
|
7
|
+
#
|
8
|
+
# Say you go to delete a bucket, but the bucket turns out to not be empty. This results in a BucketNotEmpty error (one of the many
|
9
|
+
# errors listed at http://docs.aliyunwebservices.com/AliyunOSS/2006-03-01/ErrorCodeList.html):
|
10
|
+
#
|
11
|
+
# begin
|
12
|
+
# Bucket.delete('jukebox')
|
13
|
+
# rescue ResponseError => error
|
14
|
+
# # ...
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Once you've captured the exception, you can extract the error message from OSS, as well as the full error response, which includes
|
18
|
+
# things like the HTTP response code:
|
19
|
+
#
|
20
|
+
# error
|
21
|
+
# # => #<Aliyun::OSS::BucketNotEmpty The bucket you tried to delete is not empty>
|
22
|
+
# error.message
|
23
|
+
# # => "The bucket you tried to delete is not empty"
|
24
|
+
# error.response.code
|
25
|
+
# # => 409
|
26
|
+
#
|
27
|
+
# You could use this information to redisplay the error in a way you see fit, or just to log the error and continue on.
|
28
|
+
class Error
|
29
|
+
#:stopdoc:
|
30
|
+
attr_accessor :response
|
31
|
+
attr_reader :error, :exception, :container
|
32
|
+
|
33
|
+
def initialize(error, response = nil)
|
34
|
+
@error = error
|
35
|
+
@response = response
|
36
|
+
@container = Aliyun::ESS
|
37
|
+
find_or_create_exception!
|
38
|
+
end
|
39
|
+
|
40
|
+
def raise
|
41
|
+
Kernel.raise exception.new(message, response)
|
42
|
+
end
|
43
|
+
|
44
|
+
def code
|
45
|
+
@code ||= error['code'].sub('.', '::')
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def find_or_create_module!(modul)
|
51
|
+
container.const_defined?(modul) ? container.const_get(modul) : container.const_set(modul, Module.new)
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_or_create_exception!
|
55
|
+
@exception = container.const_defined?(code) ? find_exception : create_exception
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_exception
|
59
|
+
exception_class = container.const_get(code)
|
60
|
+
Kernel.raise ExceptionClassClash.new(exception_class) unless exception_class.ancestors.include?(ResponseError)
|
61
|
+
exception_class
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_exception
|
65
|
+
modul_or_clazz, clazz = code.split('::')
|
66
|
+
if clazz
|
67
|
+
find_or_create_module!(modul_or_clazz).const_set(clazz, Class.new(ResponseError))
|
68
|
+
else
|
69
|
+
container.const_set(modul_or_clazz, Class.new(ResponseError))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_missing(method, *args, &block)
|
74
|
+
# We actually want nil if the attribute is nil. So we use has_key? rather than [] + ||.
|
75
|
+
if error.has_key?(method.to_s)
|
76
|
+
error[method.to_s]
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
#:startdoc:
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Aliyun
|
3
|
+
module ESS
|
4
|
+
|
5
|
+
# Abstract super class of all Aliyun::OSS exceptions
|
6
|
+
class Exception < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
# All responses with a code between 300 and 599 that contain an <Error></Error> body are wrapped in an
|
10
|
+
# ErrorResponse which contains an Error object. This Error class generates a custom exception with the name
|
11
|
+
# of the xml Error and its message. All such runtime generated exception classes descend from ResponseError
|
12
|
+
# and contain the ErrorResponse object so that all code that makes a request can rescue ResponseError and get
|
13
|
+
# access to the ErrorResponse.
|
14
|
+
class ResponseError < Exception
|
15
|
+
attr_reader :response
|
16
|
+
def initialize(message, response)
|
17
|
+
@response = response
|
18
|
+
super(message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#:stopdoc:
|
23
|
+
|
24
|
+
# Most ResponseError's are created just time on a need to have basis, but we explicitly define the
|
25
|
+
# InternalError exception because we want to explicitly rescue InternalError in some cases.
|
26
|
+
class InternalError < ResponseError
|
27
|
+
end
|
28
|
+
|
29
|
+
class NoSuchKey < ResponseError
|
30
|
+
end
|
31
|
+
|
32
|
+
class RequestTimeout < ResponseError
|
33
|
+
end
|
34
|
+
|
35
|
+
class InvalidParameter < ResponseError
|
36
|
+
end
|
37
|
+
|
38
|
+
# Abstract super class for all invalid options.
|
39
|
+
class InvalidOption < Exception
|
40
|
+
end
|
41
|
+
|
42
|
+
module IncorrectCapacity
|
43
|
+
end
|
44
|
+
|
45
|
+
# Raised if an invalid value is passed to the <tt>:access</tt> option when creating a Bucket or an OSSObject.
|
46
|
+
class InvalidAccessControlLevel < InvalidOption
|
47
|
+
def initialize(valid_levels, access_level)
|
48
|
+
super("Valid access control levels are #{valid_levels.inspect}. You specified `#{access_level}'.")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Raised if either the access key id or secret access key arguments are missing when establishing a connection.
|
53
|
+
class MissingAccessKey < InvalidOption
|
54
|
+
def initialize(missing_keys)
|
55
|
+
key_list = missing_keys.map {|key| key.to_s}.join(' and the ')
|
56
|
+
super("You did not provide both required access keys. Please provide the #{key_list}.")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Raised if a request is attempted before any connections have been established.
|
61
|
+
class NoConnectionEstablished < Exception
|
62
|
+
end
|
63
|
+
|
64
|
+
# Raised if an unrecognized option is passed when establishing a connection.
|
65
|
+
class InvalidConnectionOption < InvalidOption
|
66
|
+
def initialize(invalid_options)
|
67
|
+
message = "The following connection options are invalid: #{invalid_options.join(', ')}. " +
|
68
|
+
"The valid connection options are: #{Connection::Options::VALID_OPTIONS.join(', ')}."
|
69
|
+
super(message)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class ExceptionClassClash < Exception #:nodoc:
|
74
|
+
def initialize(klass)
|
75
|
+
message = "The exception class you tried to create (`#{klass}') exists and is not an exception"
|
76
|
+
super(message)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#:startdoc:
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
#:stopdoc:
|
3
|
+
|
4
|
+
class Hash
|
5
|
+
def slice(*keys)
|
6
|
+
keys.map! { |key| key.to_s }
|
7
|
+
keys.inject(Hash.new) { |hash, k| hash[k] = self[k] if has_key?(k); hash }
|
8
|
+
end unless public_method_defined? :slice
|
9
|
+
end
|
10
|
+
|
11
|
+
class String
|
12
|
+
# ActiveSupport adds an underscore method to String so let's just use that one if
|
13
|
+
# we find that the method is already defined
|
14
|
+
def underscore
|
15
|
+
gsub(/::/, '/').
|
16
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
17
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
18
|
+
tr("-", "_").downcase
|
19
|
+
end unless public_method_defined? :underscore
|
20
|
+
|
21
|
+
def camelize
|
22
|
+
string = to_s.sub(/^[a-z\d]*/) { $&.capitalize }
|
23
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
|
24
|
+
string.gsub!(/\//, '::')
|
25
|
+
string
|
26
|
+
end unless public_method_defined? :camelize
|
27
|
+
|
28
|
+
if RUBY_VERSION >= '1.9'
|
29
|
+
def valid_utf8?
|
30
|
+
dup.force_encoding('UTF-8').valid_encoding?
|
31
|
+
end
|
32
|
+
else
|
33
|
+
def valid_utf8?
|
34
|
+
scan(Regexp.new('[^\x00-\xa0]', nil, 'u')) { |s| s.unpack('U') }
|
35
|
+
true
|
36
|
+
rescue ArgumentError
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# All paths in in OSS have to be valid unicode so this takes care of
|
42
|
+
# cleaning up any strings that aren't valid utf-8 according to String#valid_utf8?
|
43
|
+
if RUBY_VERSION >= '1.9'
|
44
|
+
def remove_extended!
|
45
|
+
sanitized_string = ''
|
46
|
+
each_byte do |byte|
|
47
|
+
character = byte.chr
|
48
|
+
sanitized_string << character if character.ascii_only?
|
49
|
+
end
|
50
|
+
sanitized_string
|
51
|
+
end
|
52
|
+
else
|
53
|
+
def remove_extended!
|
54
|
+
#gsub!(/[\x80-\xFF]/) { "%02X" % $&[0] }
|
55
|
+
gsub!(Regext.new('/[\x80-\xFF]')) { "%02X" % $&[0] }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def remove_extended
|
60
|
+
dup.remove_extended!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class CoercibleString < String
|
65
|
+
class << self
|
66
|
+
def coerce(string)
|
67
|
+
new(string).coerce
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def coerce
|
72
|
+
case self
|
73
|
+
when 'true'; true
|
74
|
+
when 'false'; false
|
75
|
+
# Don't coerce numbers that start with zero
|
76
|
+
when /^[1-9]+\d*$/; Integer(self)
|
77
|
+
when datetime_format; Time.parse(self)
|
78
|
+
else
|
79
|
+
self
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
# Lame hack since Date._parse is so accepting. OSS dates are of the form: '2006-10-29T23:14:47.000Z'
|
85
|
+
# so unless the string looks like that, don't even try, otherwise it might convert an object's
|
86
|
+
# key from something like '03 1-2-3-Apple-Tree.mp3' to Sat Feb 03 00:00:00 CST 2001.
|
87
|
+
def datetime_format
|
88
|
+
/^\d{4}-\d{2}-\d{2}\w\d{2}:\d{2}:\d{2}/
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Class # :nodoc:
|
93
|
+
def cattr_reader(*syms)
|
94
|
+
syms.flatten.each do |sym|
|
95
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
96
|
+
unless defined? @@#{sym}
|
97
|
+
@@#{sym} = nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.#{sym}
|
101
|
+
@@#{sym}
|
102
|
+
end
|
103
|
+
|
104
|
+
def #{sym}
|
105
|
+
@@#{sym}
|
106
|
+
end
|
107
|
+
EOS
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def cattr_writer(*syms)
|
112
|
+
syms.flatten.each do |sym|
|
113
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
114
|
+
unless defined? @@#{sym}
|
115
|
+
@@#{sym} = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.#{sym}=(obj)
|
119
|
+
@@#{sym} = obj
|
120
|
+
end
|
121
|
+
|
122
|
+
def #{sym}=(obj)
|
123
|
+
@@#{sym} = obj
|
124
|
+
end
|
125
|
+
EOS
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def cattr_accessor(*syms)
|
130
|
+
cattr_reader(*syms)
|
131
|
+
cattr_writer(*syms)
|
132
|
+
end
|
133
|
+
end if Class.instance_methods(false).grep(/^cattr_(?:reader|writer|accessor)$/).empty?
|
134
|
+
|
135
|
+
module SelectiveAttributeProxy
|
136
|
+
def self.included(klass)
|
137
|
+
klass.extend(ClassMethods)
|
138
|
+
klass.class_eval(<<-EVAL, __FILE__, __LINE__)
|
139
|
+
cattr_accessor :attribute_proxy
|
140
|
+
cattr_accessor :attribute_proxy_options
|
141
|
+
|
142
|
+
# Default name for attribute storage
|
143
|
+
self.attribute_proxy = :attributes
|
144
|
+
self.attribute_proxy_options = {:exclusively => true}
|
145
|
+
|
146
|
+
private
|
147
|
+
# By default proxy all attributes
|
148
|
+
def proxiable_attribute?(name)
|
149
|
+
return true unless self.class.attribute_proxy_options[:exclusively]
|
150
|
+
send(self.class.attribute_proxy).has_key?(name)
|
151
|
+
end
|
152
|
+
|
153
|
+
def method_missing(method, *args, &block)
|
154
|
+
# Autovivify attribute storage
|
155
|
+
if method == self.class.attribute_proxy
|
156
|
+
ivar = "@\#{method}"
|
157
|
+
instance_variable_set(ivar, {}) unless instance_variable_get(ivar).is_a?(Hash)
|
158
|
+
instance_variable_get(ivar)
|
159
|
+
# Delegate to attribute storage
|
160
|
+
elsif method.to_s =~ /^(\\w+)(=?)$/ && proxiable_attribute?($1)
|
161
|
+
attributes_hash_name = self.class.attribute_proxy
|
162
|
+
$2.empty? ? send(attributes_hash_name)[$1] : send(attributes_hash_name)[$1] = args.first
|
163
|
+
else
|
164
|
+
super
|
165
|
+
end
|
166
|
+
end
|
167
|
+
EVAL
|
168
|
+
end
|
169
|
+
|
170
|
+
module ClassMethods
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
#:startdoc:
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
#:stopdoc:
|
3
|
+
module Aliyun
|
4
|
+
module ESS
|
5
|
+
module Parsing
|
6
|
+
|
7
|
+
module Typecasting
|
8
|
+
def typecast(object)
|
9
|
+
case object
|
10
|
+
when Hash
|
11
|
+
typecast_hash(object)
|
12
|
+
when Array
|
13
|
+
object.map {|element| typecast(element)}
|
14
|
+
when String
|
15
|
+
CoercibleString.coerce(object)
|
16
|
+
else
|
17
|
+
object
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def typecast_hash(hash)
|
22
|
+
keys = hash.keys.map {|key| key.underscore}
|
23
|
+
values = hash.values.map {|value| typecast(value)}
|
24
|
+
keys.inject({}) do |new_hash, key|
|
25
|
+
new_hash[key] = values.slice!(0)
|
26
|
+
new_hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class JsonParser < Hash
|
32
|
+
include Typecasting
|
33
|
+
|
34
|
+
attr_reader :body, :code
|
35
|
+
|
36
|
+
def initialize(body)
|
37
|
+
@body = body
|
38
|
+
unless body.strip.empty?
|
39
|
+
parse
|
40
|
+
typecast_json_parsed
|
41
|
+
set_code
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def parse
|
48
|
+
@json_parsed = JSON.parse(body)
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_code
|
52
|
+
@code = self['code']
|
53
|
+
end
|
54
|
+
|
55
|
+
def typecast_json_parsed
|
56
|
+
typecast_json = {}
|
57
|
+
@json_parsed.dup.each do |key, value| # Some typecasting is destructive so we dup
|
58
|
+
typecast_json[key.underscore] = typecast(value)
|
59
|
+
end
|
60
|
+
# An empty body will try to update with a string so only update if the result is a hash
|
61
|
+
update(typecast_json) unless typecast_json.keys.size==0
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
#:startdoc:
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
#:stopdoc:
|
3
|
+
module Aliyun
|
4
|
+
module ESS
|
5
|
+
class Base
|
6
|
+
class Response < String
|
7
|
+
attr_reader :response, :body, :parsed
|
8
|
+
def initialize(response)
|
9
|
+
@response = response
|
10
|
+
@body = response.body.to_s
|
11
|
+
super(body)
|
12
|
+
end
|
13
|
+
|
14
|
+
def headers
|
15
|
+
@headers ||= begin
|
16
|
+
headers = {}
|
17
|
+
response.each do |header, value|
|
18
|
+
headers[header] = value
|
19
|
+
end
|
20
|
+
headers
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](header)
|
25
|
+
headers[header]
|
26
|
+
end
|
27
|
+
|
28
|
+
def each(&block)
|
29
|
+
headers.each(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def code
|
33
|
+
response.code.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
{:success => 200..299, :redirect => 300..399,
|
37
|
+
:client_error => 400..499, :server_error => 500..599}.each do |result, code_range|
|
38
|
+
class_eval(<<-EVAL, __FILE__, __LINE__)
|
39
|
+
def #{result}?
|
40
|
+
return false unless response
|
41
|
+
(#{code_range}).include? code
|
42
|
+
end
|
43
|
+
EVAL
|
44
|
+
end
|
45
|
+
|
46
|
+
def error?
|
47
|
+
!success? && !parsed.code.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def error
|
51
|
+
@error ||= Error.new(parsed, self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def parsed
|
55
|
+
# XmlSimple is picky about what kind of object it parses, so we pass in body rather than self
|
56
|
+
@parsed ||= Parsing::JsonParser.new(body)
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect
|
60
|
+
"#<%s:0x%s %s %s>" % [self.class, object_id, response.code, response.message]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ScalingGroup
|
66
|
+
class Response < Base::Response
|
67
|
+
def items
|
68
|
+
(parsed['scaling_groups'] && parsed['scaling_groups']['scaling_group']) || []
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class ScalingRule
|
74
|
+
class Response < Base::Response
|
75
|
+
def items
|
76
|
+
(parsed['scaling_rules'] && parsed['scaling_rules']['scaling_rule']) || []
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Requests whose response code is between 300 and 599 and contain an <Error></Error> in their body
|
83
|
+
# are wrapped in an Error::Response. This Error::Response contains an Error object which raises an exception
|
84
|
+
# that corresponds to the error in the response body. The exception object contains the ErrorResponse, so
|
85
|
+
# in all cases where a request happens, you can rescue ResponseError and have access to the ErrorResponse and
|
86
|
+
# its Error object which contains information about the ResponseError.
|
87
|
+
#
|
88
|
+
# begin
|
89
|
+
# Bucket.create(..)
|
90
|
+
# rescue ResponseError => exception
|
91
|
+
# exception.response
|
92
|
+
# # => <Error::Response>
|
93
|
+
# exception.response.error
|
94
|
+
# # => <Error>
|
95
|
+
# end
|
96
|
+
class Error
|
97
|
+
class Response < Base::Response
|
98
|
+
def error?
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
"#<%s:0x%s %s %s: '%s'>" % [self.class.name, object_id, response.code, error.code, error.message]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Guess response class name from current class name. If the guessed response class doesn't exist
|
109
|
+
# do the same thing to the current class's parent class, up the inheritance heirarchy until either
|
110
|
+
# a response class is found or until we get to the top of the heirarchy in which case we just use
|
111
|
+
# the the Base response class.
|
112
|
+
#
|
113
|
+
# Important: This implemantation assumes that the Base class has a corresponding Base::Response.
|
114
|
+
class FindResponseClass #:nodoc:
|
115
|
+
class << self
|
116
|
+
def for(start)
|
117
|
+
new(start).find
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize(start)
|
122
|
+
@container = Aliyun::ESS
|
123
|
+
@current_class = start
|
124
|
+
end
|
125
|
+
|
126
|
+
def find
|
127
|
+
self.current_class = current_class.superclass until response_class_found?
|
128
|
+
target.const_get(class_to_find)
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
attr_reader :container
|
133
|
+
attr_accessor :current_class
|
134
|
+
|
135
|
+
def target
|
136
|
+
container.const_get(current_name)
|
137
|
+
end
|
138
|
+
|
139
|
+
def target?
|
140
|
+
container.const_defined?(current_name)
|
141
|
+
end
|
142
|
+
|
143
|
+
def response_class_found?
|
144
|
+
target? && target.const_defined?(class_to_find)
|
145
|
+
end
|
146
|
+
|
147
|
+
def class_to_find
|
148
|
+
:Response
|
149
|
+
end
|
150
|
+
|
151
|
+
def current_name
|
152
|
+
truncate(current_class)
|
153
|
+
end
|
154
|
+
|
155
|
+
def truncate(klass)
|
156
|
+
klass.name[/[^:]+$/]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
#:startdoc:
|