yam-aws-s3 0.6.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,319 @@
1
+ module AWS
2
+ module S3
3
+ # Buckets are containers for objects (the files you store on S3). To create a new bucket you just specify its name.
4
+ #
5
+ # # Pick a unique name, or else you'll get an error
6
+ # # if the name is already taken.
7
+ # Bucket.create('jukebox')
8
+ #
9
+ # Bucket names must be unique across the entire S3 system, sort of like domain names across the internet. If you try
10
+ # to create a bucket with a name that is already taken, you will get an error.
11
+ #
12
+ # Assuming the name you chose isn't already taken, your new bucket will now appear in the bucket list:
13
+ #
14
+ # Service.buckets
15
+ # # => [#<AWS::S3::Bucket @attributes={"name"=>"jukebox"}>]
16
+ #
17
+ # Once you have succesfully created a bucket you can you can fetch it by name using Bucket.find.
18
+ #
19
+ # music_bucket = Bucket.find('jukebox')
20
+ #
21
+ # The bucket that is returned will contain a listing of all the objects in the bucket.
22
+ #
23
+ # music_bucket.objects.size
24
+ # # => 0
25
+ #
26
+ # If all you are interested in is the objects of the bucket, you can get to them directly using Bucket.objects.
27
+ #
28
+ # Bucket.objects('jukebox').size
29
+ # # => 0
30
+ #
31
+ # By default all objects will be returned, though there are several options you can use to limit what is returned, such as
32
+ # specifying that only objects whose name is after a certain place in the alphabet be returned, and etc. Details about these options can
33
+ # be found in the documentation for Bucket.find.
34
+ #
35
+ # To add an object to a bucket you specify the name of the object, its value, and the bucket to put it in.
36
+ #
37
+ # file = 'black-flowers.mp3'
38
+ # S3Object.store(file, open(file), 'jukebox')
39
+ #
40
+ # You'll see your file has been added to it:
41
+ #
42
+ # music_bucket.objects
43
+ # # => [#<AWS::S3::S3Object '/jukebox/black-flowers.mp3'>]
44
+ #
45
+ # You can treat your bucket like a hash and access objects by name:
46
+ #
47
+ # jukebox['black-flowers.mp3']
48
+ # # => #<AWS::S3::S3Object '/jukebox/black-flowers.mp3'>
49
+ #
50
+ # In the event that you want to delete a bucket, you can use Bucket.delete.
51
+ #
52
+ # Bucket.delete('jukebox')
53
+ #
54
+ # Keep in mind, like unix directories, you can not delete a bucket unless it is empty. Trying to delete a bucket
55
+ # that contains objects will raise a BucketNotEmpty exception.
56
+ #
57
+ # Passing the :force => true option to delete will take care of deleting all the bucket's objects for you.
58
+ #
59
+ # Bucket.delete('photos', :force => true)
60
+ # # => true
61
+ class Bucket < Base
62
+ class << self
63
+ # Creates a bucket named <tt>name</tt>.
64
+ #
65
+ # Bucket.create('jukebox')
66
+ #
67
+ # Your bucket name must be unique across all of S3. If the name
68
+ # you request has already been taken, you will get a 409 Conflict response, and a BucketAlreadyExists exception
69
+ # will be raised.
70
+ #
71
+ # By default new buckets have their access level set to private. You can override this using the <tt>:access</tt> option.
72
+ #
73
+ # Bucket.create('internet_drop_box', :access => :public_read_write)
74
+ #
75
+ # The full list of access levels that you can set on Bucket and S3Object creation are listed in the README[link:files/README.html]
76
+ # in the section called 'Setting access levels'.
77
+ def create(name, options = {})
78
+ validate_name!(name)
79
+ put("/#{name}", options).success?
80
+ end
81
+
82
+ # Fetches the bucket named <tt>name</tt>.
83
+ #
84
+ # Bucket.find('jukebox')
85
+ #
86
+ # If a default bucket is inferable from the current connection's subdomain, or if set explicitly with Base.set_current_bucket,
87
+ # it will be used if no bucket is specified.
88
+ #
89
+ # MusicBucket.current_bucket
90
+ # => 'jukebox'
91
+ # MusicBucket.find.name
92
+ # => 'jukebox'
93
+ #
94
+ # By default all objects contained in the bucket will be returned (sans their data) along with the bucket.
95
+ # You can access your objects using the Bucket#objects method.
96
+ #
97
+ # Bucket.find('jukebox').objects
98
+ #
99
+ # There are several options which allow you to limit which objects are retrieved. The list of object filtering options
100
+ # are listed in the documentation for Bucket.objects.
101
+ def find(name = nil, options = {})
102
+ new(get(path(name, options)).bucket)
103
+ end
104
+
105
+ # Return just the objects in the bucket named <tt>name</tt>.
106
+ #
107
+ # By default all objects of the named bucket will be returned. There are options, though, for filtering
108
+ # which objects are returned.
109
+ #
110
+ # === Object filtering options
111
+ #
112
+ # * <tt>:max_keys</tt> - The maximum number of keys you'd like to see in the response body.
113
+ # The server may return fewer than this many keys, but will not return more.
114
+ #
115
+ # Bucket.objects('jukebox').size
116
+ # # => 3
117
+ # Bucket.objects('jukebox', :max_keys => 1).size
118
+ # # => 1
119
+ #
120
+ # * <tt>:prefix</tt> - Restricts the response to only contain results that begin with the specified prefix.
121
+ #
122
+ # Bucket.objects('jukebox')
123
+ # # => [<AWS::S3::S3Object '/jazz/miles.mp3'>, <AWS::S3::S3Object '/jazz/dolphy.mp3'>, <AWS::S3::S3Object '/classical/malher.mp3'>]
124
+ # Bucket.objects('jukebox', :prefix => 'classical')
125
+ # # => [<AWS::S3::S3Object '/classical/malher.mp3'>]
126
+ #
127
+ # * <tt>:marker</tt> - Marker specifies where in the result set to resume listing. It restricts the response
128
+ # to only contain results that occur alphabetically _after_ the value of marker. To retrieve the next set of results,
129
+ # use the last key from the current page of results as the marker in your next request.
130
+ #
131
+ # # Skip 'mahler'
132
+ # Bucket.objects('jukebox', :marker => 'mb')
133
+ # # => [<AWS::S3::S3Object '/jazz/miles.mp3'>]
134
+ #
135
+ # === Examples
136
+ #
137
+ # # Return no more than 2 objects whose key's are listed alphabetically after the letter 'm'.
138
+ # Bucket.objects('jukebox', :marker => 'm', :max_keys => 2)
139
+ # # => [<AWS::S3::S3Object '/jazz/miles.mp3'>, <AWS::S3::S3Object '/classical/malher.mp3'>]
140
+ #
141
+ # # Return no more than 2 objects whose key's are listed alphabetically after the letter 'm' and have the 'jazz' prefix.
142
+ # Bucket.objects('jukebox', :marker => 'm', :max_keys => 2, :prefix => 'jazz')
143
+ # # => [<AWS::S3::S3Object '/jazz/miles.mp3'>]
144
+ def objects(name = nil, options = {})
145
+ find(name, options).object_cache
146
+ end
147
+
148
+ # Deletes the bucket named <tt>name</tt>.
149
+ #
150
+ # All objects in the bucket must be deleted before the bucket can be deleted. If the bucket is not empty,
151
+ # BucketNotEmpty will be raised.
152
+ #
153
+ # You can side step this issue by passing the :force => true option to delete which will take care of
154
+ # emptying the bucket before deleting it.
155
+ #
156
+ # Bucket.delete('photos', :force => true)
157
+ #
158
+ # Only the owner of a bucket can delete a bucket, regardless of the bucket's access control policy.
159
+ def delete(name = nil, options = {})
160
+ find(name).delete_all if options[:force]
161
+
162
+ name = path(name)
163
+ Base.delete(name).success?
164
+ end
165
+
166
+ # List all your buckets. This is a convenient wrapper around AWS::S3::Service.buckets.
167
+ def list(reload = false)
168
+ Service.buckets(reload)
169
+ end
170
+
171
+ private
172
+ def validate_name!(name)
173
+ raise InvalidBucketName.new(name) unless name =~ /^[-\w.]{3,255}$/
174
+ end
175
+
176
+ def path(name, options = {})
177
+ if name.is_a?(Hash)
178
+ options = name
179
+ name = nil
180
+ end
181
+ "/#{bucket_name(name)}#{RequestOptions.process(options).to_query_string}"
182
+ end
183
+ end
184
+
185
+ attr_reader :object_cache #:nodoc:
186
+
187
+ include Enumerable
188
+
189
+ def initialize(attributes = {}) #:nodoc:
190
+ super
191
+ @object_cache = []
192
+ build_contents!
193
+ end
194
+
195
+ # Fetches the object named <tt>object_key</tt>, or nil if the bucket does not contain an object with the
196
+ # specified key.
197
+ #
198
+ # bucket.objects
199
+ # => [#<AWS::S3::S3Object '/marcel_molina/beluga_baby.jpg'>,
200
+ # #<AWS::S3::S3Object '/marcel_molina/tongue_overload.jpg'>]
201
+ # bucket['beluga_baby.jpg']
202
+ # => #<AWS::S3::S3Object '/marcel_molina/beluga_baby.jpg'>
203
+ def [](object_key)
204
+ detect {|file| file.key == object_key.to_s}
205
+ end
206
+
207
+ # Initializes a new S3Object belonging to the current bucket.
208
+ #
209
+ # object = bucket.new_object
210
+ # object.value = data
211
+ # object.key = 'classical/mahler.mp3'
212
+ # object.store
213
+ # bucket.objects.include?(object)
214
+ # => true
215
+ def new_object(attributes = {})
216
+ object = S3Object.new(attributes)
217
+ register(object)
218
+ object
219
+ end
220
+
221
+ # List S3Object's of the bucket.
222
+ #
223
+ # Once fetched the objects will be cached. You can reload the objects by passing <tt>:reload</tt>.
224
+ #
225
+ # bucket.objects(:reload)
226
+ #
227
+ # You can also filter the objects using the same options listed in Bucket.objects.
228
+ #
229
+ # bucket.objects(:prefix => 'jazz')
230
+ #
231
+ # Using these filtering options will implictly reload the objects.
232
+ #
233
+ # To reclaim all the objects for the bucket you can pass in :reload again.
234
+ def objects(options = {})
235
+ if options.is_a?(Hash)
236
+ reload = !options.empty?
237
+ else
238
+ reload = options
239
+ options = {}
240
+ end
241
+
242
+ reload!(options) if reload || object_cache.empty?
243
+ object_cache
244
+ end
245
+
246
+ # Iterates over the objects in the bucket.
247
+ #
248
+ # bucket.each do |object|
249
+ # # Do something with the object ...
250
+ # end
251
+ def each(&block)
252
+ # Dup the collection since we might be destructively modifying the object_cache during the iteration.
253
+ objects.dup.each(&block)
254
+ end
255
+
256
+ # Returns true if there are no objects in the bucket.
257
+ def empty?
258
+ objects.empty?
259
+ end
260
+
261
+ # Returns the number of objects in the bucket.
262
+ def size
263
+ objects.size
264
+ end
265
+
266
+ # Deletes the bucket. See its class method counter part Bucket.delete for caveats about bucket deletion and how to ensure
267
+ # a bucket is deleted no matter what.
268
+ def delete(options = {})
269
+ self.class.delete(name, options)
270
+ end
271
+
272
+ # Delete all files in the bucket. Use with caution. Can not be undone.
273
+ def delete_all
274
+ each do |object|
275
+ object.delete
276
+ end
277
+ self
278
+ end
279
+ alias_method :clear, :delete_all
280
+
281
+ # Buckets observe their objects and have this method called when one of their objects
282
+ # is either stored or deleted.
283
+ def update(action, object) #:nodoc:
284
+ case action
285
+ when :stored then add object unless objects.include?(object)
286
+ when :deleted then object_cache.delete(object)
287
+ end
288
+ end
289
+
290
+ private
291
+ def build_contents!
292
+ return unless has_contents?
293
+ attributes.delete('contents').each do |content|
294
+ add new_object(content)
295
+ end
296
+ end
297
+
298
+ def has_contents?
299
+ attributes.has_key?('contents')
300
+ end
301
+
302
+ def add(object)
303
+ register(object)
304
+ object_cache << object
305
+ end
306
+
307
+ def register(object)
308
+ object.bucket = self
309
+ end
310
+
311
+ def reload!(options = {})
312
+ object_cache.clear
313
+ self.class.objects(name, options).each do |object|
314
+ add object
315
+ end
316
+ end
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,278 @@
1
+ module AWS
2
+ module S3
3
+ class Connection #:nodoc:
4
+ class << self
5
+ def connect(options = {})
6
+ new(options)
7
+ end
8
+
9
+ def prepare_path(path)
10
+ path = path.remove_extended unless path.valid_utf8?
11
+ URI.escape(path).gsub('[', '%5B').gsub(']', '%5D')
12
+ end
13
+ end
14
+
15
+ attr_reader :access_key_id, :secret_access_key, :http, :options
16
+
17
+ # Creates a new connection. Connections make the actual requests to S3, though these requests are usually
18
+ # called from subclasses of Base.
19
+ #
20
+ # For details on establishing connections, check the Connection::Management::ClassMethods.
21
+ def initialize(options = {})
22
+ @options = Options.new(options)
23
+ connect
24
+ end
25
+
26
+ def request(verb, path, headers = {}, body = nil, attempts = 0, &block)
27
+ body.rewind if body.respond_to?(:rewind) unless attempts.zero?
28
+
29
+ requester = Proc.new do
30
+ path = self.class.prepare_path(path) if attempts.zero? # Only escape the path once
31
+ request = request_method(verb).new(path, headers)
32
+ ensure_content_type!(request)
33
+ add_user_agent!(request)
34
+ authenticate!(request)
35
+ if body
36
+ if body.respond_to?(:read)
37
+ request.body_stream = body
38
+ else
39
+ request.body = body
40
+ end
41
+ request.content_length = body.respond_to?(:lstat) ? body.stat.size : body.size
42
+ else
43
+ request.content_length = 0
44
+ end
45
+ http.request(request, &block)
46
+ end
47
+
48
+ if persistent?
49
+ http.start unless http.started?
50
+ requester.call
51
+ else
52
+ http.start(&requester)
53
+ end
54
+ rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
55
+ @http = create_connection
56
+ attempts == 3 ? raise : (attempts += 1; retry)
57
+ end
58
+
59
+ def url_for(path, options = {})
60
+ authenticate = options.delete(:authenticated)
61
+ # Default to true unless explicitly false
62
+ authenticate = true if authenticate.nil?
63
+ path = self.class.prepare_path(path)
64
+ request = request_method(:get).new(path, {})
65
+ query_string = query_string_authentication(request, options)
66
+ returning "#{protocol(options)}#{http.address}#{port_string}#{path}" do |url|
67
+ url << "?#{query_string}" if authenticate
68
+ end
69
+ end
70
+
71
+ def subdomain
72
+ http.address[/^([^.]+).#{DEFAULT_HOST}$/, 1]
73
+ end
74
+
75
+ def persistent?
76
+ options[:persistent]
77
+ end
78
+
79
+ def protocol(options = {})
80
+ # This always trumps http.use_ssl?
81
+ if options[:use_ssl] == false
82
+ 'http://'
83
+ elsif options[:use_ssl] || http.use_ssl?
84
+ 'https://'
85
+ else
86
+ 'http://'
87
+ end
88
+ end
89
+
90
+ private
91
+ def extract_keys!
92
+ missing_keys = []
93
+ extract_key = Proc.new {|key| options[key] || (missing_keys.push(key); nil)}
94
+ @access_key_id = extract_key[:access_key_id]
95
+ @secret_access_key = extract_key[:secret_access_key]
96
+ raise MissingAccessKey.new(missing_keys) unless missing_keys.empty?
97
+ end
98
+
99
+ def create_connection
100
+ http = http_class.new(options[:server], options[:port])
101
+ http.use_ssl = !options[:use_ssl].nil? || options[:port] == 443
102
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
103
+ http
104
+ end
105
+
106
+ def http_class
107
+ if options.connecting_through_proxy?
108
+ Net::HTTP::Proxy(*options.proxy_settings)
109
+ else
110
+ Net::HTTP
111
+ end
112
+ end
113
+
114
+ def connect
115
+ extract_keys!
116
+ @http = create_connection
117
+ end
118
+
119
+ def port_string
120
+ default_port = options[:use_ssl] ? 443 : 80
121
+ http.port == default_port ? '' : ":#{http.port}"
122
+ end
123
+
124
+ def ensure_content_type!(request)
125
+ request['Content-Type'] ||= 'binary/octet-stream'
126
+ end
127
+
128
+ # Just do Header authentication for now
129
+ def authenticate!(request)
130
+ request['Authorization'] = Authentication::Header.new(request, access_key_id, secret_access_key)
131
+ end
132
+
133
+ def add_user_agent!(request)
134
+ request['User-Agent'] ||= "AWS::S3/#{Version}"
135
+ end
136
+
137
+ def query_string_authentication(request, options = {})
138
+ Authentication::QueryString.new(request, access_key_id, secret_access_key, options)
139
+ end
140
+
141
+ def request_method(verb)
142
+ Net::HTTP.const_get(verb.to_s.capitalize)
143
+ end
144
+
145
+ def method_missing(method, *args, &block)
146
+ options[method] || super
147
+ end
148
+
149
+ module Management #:nodoc:
150
+ def self.included(base)
151
+ base.cattr_accessor :connections
152
+ base.connections = {}
153
+ base.extend ClassMethods
154
+ end
155
+
156
+ # Manage the creation and destruction of connections for AWS::S3::Base and its subclasses. Connections are
157
+ # created with establish_connection!.
158
+ module ClassMethods
159
+ # Creates a new connection with which to make requests to the S3 servers for the calling class.
160
+ #
161
+ # AWS::S3::Base.establish_connection!(:access_key_id => '...', :secret_access_key => '...')
162
+ #
163
+ # You can set connections for every subclass of AWS::S3::Base. Once the initial connection is made on
164
+ # Base, all subsequent connections will inherit whatever values you don't specify explictly. This allows you to
165
+ # customize details of the connection, such as what server the requests are made to, by just specifying one
166
+ # option.
167
+ #
168
+ # AWS::S3::Bucket.established_connection!(:use_ssl => true)
169
+ #
170
+ # The Bucket connection would inherit the <tt>:access_key_id</tt> and the <tt>:secret_access_key</tt> from
171
+ # Base's connection. Unlike the Base connection, all Bucket requests would be made over SSL.
172
+ #
173
+ # == Required arguments
174
+ #
175
+ # * <tt>:access_key_id</tt> - The access key id for your S3 account. Provided by Amazon.
176
+ # * <tt>:secret_access_key</tt> - The secret access key for your S3 account. Provided by Amazon.
177
+ #
178
+ # If any of these required arguments is missing, a MissingAccessKey exception will be raised.
179
+ #
180
+ # == Optional arguments
181
+ #
182
+ # * <tt>:server</tt> - The server to make requests to. You can use this to specify your bucket in the subdomain,
183
+ # or your own domain's cname if you are using virtual hosted buckets. Defaults to <tt>s3.amazonaws.com</tt>.
184
+ # * <tt>:port</tt> - The port to the requests should be made on. Defaults to 80 or 443 if the <tt>:use_ssl</tt>
185
+ # argument is set.
186
+ # * <tt>:use_ssl</tt> - Whether requests should be made over SSL. If set to true, the <tt>:port</tt> argument
187
+ # will be implicitly set to 443, unless specified otherwise. Defaults to false.
188
+ # * <tt>:persistent</tt> - Whether to use a persistent connection to the server. Having this on provides around a two fold
189
+ # performance increase but for long running processes some firewalls may find the long lived connection suspicious and close the connection.
190
+ # If you run into connection errors, try setting <tt>:persistent</tt> to false. Defaults to false.
191
+ # * <tt>:proxy</tt> - If you need to connect through a proxy, you can specify your proxy settings by specifying a <tt>:host</tt>, <tt>:port</tt>, <tt>:user</tt>, and <tt>:password</tt>
192
+ # with the <tt>:proxy</tt> option.
193
+ # The <tt>:host</tt> setting is required if specifying a <tt>:proxy</tt>.
194
+ #
195
+ # AWS::S3::Bucket.established_connection!(:proxy => {
196
+ # :host => '...', :port => 8080, :user => 'marcel', :password => 'secret'
197
+ # })
198
+ def establish_connection!(options = {})
199
+ # After you've already established the default connection, just specify
200
+ # the difference for subsequent connections
201
+ options = default_connection.options.merge(options) if connected?
202
+ connections[connection_name] = Connection.connect(options)
203
+ end
204
+
205
+ # Returns the connection for the current class, or Base's default connection if the current class does not
206
+ # have its own connection.
207
+ #
208
+ # If not connection has been established yet, NoConnectionEstablished will be raised.
209
+ def connection
210
+ if connected?
211
+ connections[connection_name] || default_connection
212
+ else
213
+ raise NoConnectionEstablished
214
+ end
215
+ end
216
+
217
+ # Returns true if a connection has been made yet.
218
+ def connected?
219
+ !connections.empty?
220
+ end
221
+
222
+ # Removes the connection for the current class. If there is no connection for the current class, the default
223
+ # connection will be removed.
224
+ def disconnect(name = connection_name)
225
+ name = default_connection unless connections.has_key?(name)
226
+ connection = connections[name]
227
+ connection.http.finish if connection.persistent?
228
+ connections.delete(name)
229
+ end
230
+
231
+ # Clears *all* connections, from all classes, with prejudice.
232
+ def disconnect!
233
+ connections.each_key {|connection| disconnect(connection)}
234
+ end
235
+
236
+ private
237
+ def connection_name
238
+ name
239
+ end
240
+
241
+ def default_connection_name
242
+ 'AWS::S3::Base'
243
+ end
244
+
245
+ def default_connection
246
+ connections[default_connection_name]
247
+ end
248
+ end
249
+ end
250
+
251
+ class Options < Hash #:nodoc:
252
+ VALID_OPTIONS = [:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy].freeze
253
+
254
+ def initialize(options = {})
255
+ super()
256
+ validate(options)
257
+ replace(:server => DEFAULT_HOST, :port => (options[:use_ssl] ? 443 : 80))
258
+ merge!(options)
259
+ end
260
+
261
+ def connecting_through_proxy?
262
+ !self[:proxy].nil?
263
+ end
264
+
265
+ def proxy_settings
266
+ self[:proxy].values_at(:host, :port, :user, :password)
267
+ end
268
+
269
+ private
270
+ def validate(options)
271
+ invalid_options = options.keys - VALID_OPTIONS
272
+ raise InvalidConnectionOption.new(invalid_options) unless invalid_options.empty?
273
+ raise ArgumentError, "Missing proxy settings. Must specify at least :host." if options[:proxy] && !options[:proxy][:host]
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,69 @@
1
+ module AWS
2
+ module S3
3
+ # Anything you do that makes a request to S3 could result in an error. If it does, the AWS::S3 library will raise an exception
4
+ # specific to the error. All exception that are raised as a result of a request returning an error response inherit from the
5
+ # ResponseError exception. So should you choose to rescue any such exception, you can simple rescue ResponseError.
6
+ #
7
+ # 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
8
+ # errors listed at http://docs.amazonwebservices.com/AmazonS3/2006-03-01/ErrorCodeList.html):
9
+ #
10
+ # begin
11
+ # Bucket.delete('jukebox')
12
+ # rescue ResponseError => error
13
+ # # ...
14
+ # end
15
+ #
16
+ # Once you've captured the exception, you can extract the error message from S3, as well as the full error response, which includes
17
+ # things like the HTTP response code:
18
+ #
19
+ # error
20
+ # # => #<AWS::S3::BucketNotEmpty The bucket you tried to delete is not empty>
21
+ # error.message
22
+ # # => "The bucket you tried to delete is not empty"
23
+ # error.response.code
24
+ # # => 409
25
+ #
26
+ # You could use this information to redisplay the error in a way you see fit, or just to log the error and continue on.
27
+ class Error
28
+ #:stopdoc:
29
+ attr_accessor :response
30
+ def initialize(error, response = nil)
31
+ @error = error
32
+ @response = response
33
+ @container = AWS::S3
34
+ find_or_create_exception!
35
+ end
36
+
37
+ def raise
38
+ Kernel.raise exception.new(message, response)
39
+ end
40
+
41
+ private
42
+ attr_reader :error, :exception, :container
43
+
44
+ def find_or_create_exception!
45
+ @exception = container.const_defined?(code) ? find_exception : create_exception
46
+ end
47
+
48
+ def find_exception
49
+ exception_class = container.const_get(code)
50
+ Kernel.raise ExceptionClassClash.new(exception_class) unless exception_class.ancestors.include?(ResponseError)
51
+ exception_class
52
+ end
53
+
54
+ def create_exception
55
+ container.const_set(code, Class.new(ResponseError))
56
+ end
57
+
58
+ def method_missing(method, *args, &block)
59
+ # We actually want nil if the attribute is nil. So we use has_key? rather than [] + ||.
60
+ if error.has_key?(method.to_s)
61
+ error[method.to_s]
62
+ else
63
+ super
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ #:startdoc: