abiquo-etk 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,381 @@
1
+ require 'net/http'
2
+
3
+ #
4
+ # Stolen from activesupport 2.3.8
5
+ #
6
+ class Module
7
+ # Provides a delegate class method to easily expose contained objects' methods
8
+ # as your own. Pass one or more methods (specified as symbols or strings)
9
+ # and the name of the target object as the final <tt>:to</tt> option (also a symbol
10
+ # or string). At least one method and the <tt>:to</tt> option are required.
11
+ #
12
+ # Delegation is particularly useful with Active Record associations:
13
+ #
14
+ # class Greeter < ActiveRecord::Base
15
+ # def hello() "hello" end
16
+ # def goodbye() "goodbye" end
17
+ # end
18
+ #
19
+ # class Foo < ActiveRecord::Base
20
+ # belongs_to :greeter
21
+ # delegate :hello, :to => :greeter
22
+ # end
23
+ #
24
+ # Foo.new.hello # => "hello"
25
+ # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
26
+ #
27
+ # Multiple delegates to the same target are allowed:
28
+ #
29
+ # class Foo < ActiveRecord::Base
30
+ # belongs_to :greeter
31
+ # delegate :hello, :goodbye, :to => :greeter
32
+ # end
33
+ #
34
+ # Foo.new.goodbye # => "goodbye"
35
+ #
36
+ # Methods can be delegated to instance variables, class variables, or constants
37
+ # by providing them as a symbols:
38
+ #
39
+ # class Foo
40
+ # CONSTANT_ARRAY = [0,1,2,3]
41
+ # @@class_array = [4,5,6,7]
42
+ #
43
+ # def initialize
44
+ # @instance_array = [8,9,10,11]
45
+ # end
46
+ # delegate :sum, :to => :CONSTANT_ARRAY
47
+ # delegate :min, :to => :@@class_array
48
+ # delegate :max, :to => :@instance_array
49
+ # end
50
+ #
51
+ # Foo.new.sum # => 6
52
+ # Foo.new.min # => 4
53
+ # Foo.new.max # => 11
54
+ #
55
+ # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
56
+ # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
57
+ # delegated to.
58
+ #
59
+ # Person = Struct.new(:name, :address)
60
+ #
61
+ # class Invoice < Struct.new(:client)
62
+ # delegate :name, :address, :to => :client, :prefix => true
63
+ # end
64
+ #
65
+ # john_doe = Person.new("John Doe", "Vimmersvej 13")
66
+ # invoice = Invoice.new(john_doe)
67
+ # invoice.client_name # => "John Doe"
68
+ # invoice.client_address # => "Vimmersvej 13"
69
+ #
70
+ # It is also possible to supply a custom prefix.
71
+ #
72
+ # class Invoice < Struct.new(:client)
73
+ # delegate :name, :address, :to => :client, :prefix => :customer
74
+ # end
75
+ #
76
+ # invoice = Invoice.new(john_doe)
77
+ # invoice.customer_name # => "John Doe"
78
+ # invoice.customer_address # => "Vimmersvej 13"
79
+ #
80
+ # If the object to which you delegate can be nil, you may want to use the
81
+ # :allow_nil option. In that case, it returns nil instead of raising a
82
+ # NoMethodError exception:
83
+ #
84
+ # class Foo
85
+ # attr_accessor :bar
86
+ # def initialize(bar = nil)
87
+ # @bar = bar
88
+ # end
89
+ # delegate :zoo, :to => :bar
90
+ # end
91
+ #
92
+ # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
93
+ #
94
+ # class Foo
95
+ # attr_accessor :bar
96
+ # def initialize(bar = nil)
97
+ # @bar = bar
98
+ # end
99
+ # delegate :zoo, :to => :bar, :allow_nil => true
100
+ # end
101
+ #
102
+ # Foo.new.zoo # returns nil
103
+ #
104
+ def delegate(*methods)
105
+ options = methods.pop
106
+ unless options.is_a?(Hash) && to = options[:to]
107
+ raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
108
+ end
109
+
110
+ if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
111
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
112
+ end
113
+
114
+ prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
115
+
116
+ file, line = caller.first.split(':', 2)
117
+ line = line.to_i
118
+
119
+ methods.each do |method|
120
+ on_nil =
121
+ if options[:allow_nil]
122
+ 'return'
123
+ else
124
+ %(raise "#{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
125
+ end
126
+
127
+ module_eval(<<-EOS, file, line)
128
+ def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
129
+ #{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block)
130
+ rescue NoMethodError # rescue NoMethodError
131
+ if #{to}.nil? # if client.nil?
132
+ #{on_nil}
133
+ else # else
134
+ raise # raise
135
+ end # end
136
+ end # end
137
+ EOS
138
+ end
139
+ end
140
+ # Encapsulates the common pattern of:
141
+ #
142
+ # alias_method :foo_without_feature, :foo
143
+ # alias_method :foo, :foo_with_feature
144
+ #
145
+ # With this, you simply do:
146
+ #
147
+ # alias_method_chain :foo, :feature
148
+ #
149
+ # And both aliases are set up for you.
150
+ #
151
+ # Query and bang methods (foo?, foo!) keep the same punctuation:
152
+ #
153
+ # alias_method_chain :foo?, :feature
154
+ #
155
+ # is equivalent to
156
+ #
157
+ # alias_method :foo_without_feature?, :foo?
158
+ # alias_method :foo?, :foo_with_feature?
159
+ #
160
+ # so you can safely chain foo, foo?, and foo! with the same feature.
161
+ def alias_method_chain(target, feature)
162
+ # Strip out punctuation on predicates or bang methods since
163
+ # e.g. target?_without_feature is not a valid method name.
164
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
165
+ yield(aliased_target, punctuation) if block_given?
166
+
167
+ with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
168
+
169
+ alias_method without_method, target
170
+ alias_method target, with_method
171
+
172
+ case
173
+ when public_method_defined?(without_method)
174
+ public target
175
+ when protected_method_defined?(without_method)
176
+ protected target
177
+ when private_method_defined?(without_method)
178
+ private target
179
+ end
180
+ end
181
+
182
+ # Allows you to make aliases for attributes, which includes
183
+ # getter, setter, and query methods.
184
+ #
185
+ # Example:
186
+ #
187
+ # class Content < ActiveRecord::Base
188
+ # # has a title attribute
189
+ # end
190
+ #
191
+ # class Email < Content
192
+ # alias_attribute :subject, :title
193
+ # end
194
+ #
195
+ # e = Email.find(1)
196
+ # e.title # => "Superstars"
197
+ # e.subject # => "Superstars"
198
+ # e.subject? # => true
199
+ # e.subject = "Megastars"
200
+ # e.title # => "Megastars"
201
+ def alias_attribute(new_name, old_name)
202
+ module_eval <<-STR, __FILE__, __LINE__ + 1
203
+ def #{new_name}; self.#{old_name}; end # def subject; self.title; end
204
+ def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
205
+ def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
206
+ STR
207
+ end
208
+ end
209
+
210
+ class Object
211
+ # Invokes the method identified by the symbol +method+, passing it any arguments
212
+ # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
213
+ #
214
+ # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
215
+ # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
216
+ #
217
+ # ==== Examples
218
+ #
219
+ # Without try
220
+ # @person && @person.name
221
+ # or
222
+ # @person ? @person.name : nil
223
+ #
224
+ # With try
225
+ # @person.try(:name)
226
+ #
227
+ # +try+ also accepts arguments and/or a block, for the method it is trying
228
+ # Person.try(:find, 1)
229
+ # @people.try(:collect) {|p| p.name}
230
+ #--
231
+ # This method definition below is for rdoc purposes only. The alias_method call
232
+ # below overrides it as an optimization since +try+ behaves like +Object#send+,
233
+ # unless called on +NilClass+.
234
+ def try(method, *args, &block)
235
+ send(method, *args, &block)
236
+ end
237
+ remove_method :try
238
+ alias_method :try, :__send__
239
+ end
240
+
241
+ class NilClass
242
+ def try(*args)
243
+ nil
244
+ end
245
+ end
246
+
247
+ module Resourceful
248
+ class Resource
249
+ def options(header = {})
250
+ request(:options, nil, header)
251
+ end
252
+
253
+ def host_with_port
254
+ add = Addressable::URI.parse(uri)
255
+ !add.port.nil? && add.port != 80 ? [add.host, add.port].join(':') : add.host
256
+ end
257
+
258
+ alias_method_chain :host, :port
259
+ end
260
+
261
+ class NetHttpAdapter
262
+ def net_http_request_class_with_options(method)
263
+ if method == :options
264
+ Net::HTTP::Options
265
+ else
266
+ net_http_request_class_without_options(method)
267
+ end
268
+ end
269
+
270
+ alias_method_chain :net_http_request_class, :options
271
+ end
272
+ end
273
+
274
+ class Array
275
+ # Returns a string that represents this array in XML by sending +to_xml+
276
+ # to each element. Active Record collections delegate their representation
277
+ # in XML to this method.
278
+ #
279
+ # All elements are expected to respond to +to_xml+, if any of them does
280
+ # not an exception is raised.
281
+ #
282
+ # The root node reflects the class name of the first element in plural
283
+ # if all elements belong to the same type and that's not Hash:
284
+ #
285
+ # customer.projects.to_xml
286
+ #
287
+ # <?xml version="1.0" encoding="UTF-8"?>
288
+ # <projects type="array">
289
+ # <project>
290
+ # <amount type="decimal">20000.0</amount>
291
+ # <customer-id type="integer">1567</customer-id>
292
+ # <deal-date type="date">2008-04-09</deal-date>
293
+ # ...
294
+ # </project>
295
+ # <project>
296
+ # <amount type="decimal">57230.0</amount>
297
+ # <customer-id type="integer">1567</customer-id>
298
+ # <deal-date type="date">2008-04-15</deal-date>
299
+ # ...
300
+ # </project>
301
+ # </projects>
302
+ #
303
+ # Otherwise the root element is "records":
304
+ #
305
+ # [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
306
+ #
307
+ # <?xml version="1.0" encoding="UTF-8"?>
308
+ # <records type="array">
309
+ # <record>
310
+ # <bar type="integer">2</bar>
311
+ # <foo type="integer">1</foo>
312
+ # </record>
313
+ # <record>
314
+ # <baz type="integer">3</baz>
315
+ # </record>
316
+ # </records>
317
+ #
318
+ # If the collection is empty the root element is "nil-classes" by default:
319
+ #
320
+ # [].to_xml
321
+ #
322
+ # <?xml version="1.0" encoding="UTF-8"?>
323
+ # <nil-classes type="array"/>
324
+ #
325
+ # To ensure a meaningful root element use the <tt>:root</tt> option:
326
+ #
327
+ # customer_with_no_projects.projects.to_xml(:root => "projects")
328
+ #
329
+ # <?xml version="1.0" encoding="UTF-8"?>
330
+ # <projects type="array"/>
331
+ #
332
+ # By default root children have as node name the one of the root
333
+ # singularized. You can change it with the <tt>:children</tt> option.
334
+ #
335
+ # The +options+ hash is passed downwards:
336
+ #
337
+ # Message.all.to_xml(:skip_types => true)
338
+ #
339
+ # <?xml version="1.0" encoding="UTF-8"?>
340
+ # <messages>
341
+ # <message>
342
+ # <created-at>2008-03-07T09:58:18+01:00</created-at>
343
+ # <id>1</id>
344
+ # <name>1</name>
345
+ # <updated-at>2008-03-07T09:58:18+01:00</updated-at>
346
+ # <user-id>1</user-id>
347
+ # </message>
348
+ # </messages>
349
+ #
350
+ def to_xml(options = {})
351
+ raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }
352
+ require 'builder' unless defined?(Builder)
353
+
354
+ options = options.dup
355
+ options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize.tr('/', '-') : "records"
356
+ options[:children] ||= options[:root].singularize
357
+ options[:indent] ||= 2
358
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
359
+
360
+ root = options.delete(:root).to_s
361
+ children = options.delete(:children)
362
+
363
+ if !options.has_key?(:dasherize) || options[:dasherize]
364
+ root = root.dasherize
365
+ end
366
+
367
+ options[:builder].instruct! unless options.delete(:skip_instruct)
368
+
369
+ opts = options.merge({ :root => children })
370
+
371
+ xml = options[:builder]
372
+ if empty?
373
+ xml.tag!(root, options[:skip_types] ? {} : {:type => "array"})
374
+ else
375
+ xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) {
376
+ yield xml if block_given?
377
+ each { |e| e.to_xml(opts.merge({ :skip_instruct => true })) }
378
+ }
379
+ end
380
+ end
381
+ end
@@ -0,0 +1,22 @@
1
+ module Abiquo
2
+
3
+ module ToXml
4
+
5
+ def to_xml_with_links(options = {})
6
+ if options[:convert_links] && options[:builder]
7
+ options[:builder].tag!(:link, :rel => options[:root]) do
8
+ to_xml_without_links(options)
9
+ end
10
+ else
11
+ to_xml_without_links(options)
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ class Array
20
+ include Abiquo::ToXml
21
+ alias_method_chain :to_xml, :links
22
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ feature "Creating new resources" do
4
+
5
+ scenario "Creating a resource in a collection" do
6
+
7
+ datacenters = Abiquo::Resource("http://abiquo.example.com/api/admin/datacenters", auth)
8
+
9
+ stub_auth_request(:options, "http://admin:admin@abiquo.example.com/api/admin/datacenters").
10
+ to_return(:headers => {'Allow' => 'GET, POST, OPTIONS'})
11
+
12
+ stub_auth_request(:post, "http://admin:admin@abiquo.example.com/api/admin/datacenters").with do |req|
13
+ # we parse because comparing strings is too fragile because of order changing, different indentations, etc.
14
+ # we're expecting something very close to this:
15
+ # <datacenter>
16
+ # <name>Wadus</name>
17
+ # </datacenter>
18
+ Nokogiri.parse(req.body).at_xpath("/datacenter/name").text == "Wadus"
19
+ end.to_return(:body => %q{
20
+ <datacenter>
21
+ <name>Wadus</name>
22
+ <link rel="edit" href="http://abiquo.example.com/api/admin/datacenters/1"/>
23
+ </datacenter>
24
+ })
25
+
26
+ datacenter = datacenters.create(:name => "Wadus")
27
+
28
+ datacenter.should be_a(Abiquo::Resource)
29
+ datacenter.name.should == "Wadus"
30
+
31
+ stub_auth_request(:options, "http://admin:admin@abiquo.example.com/api/admin/datacenters/1").
32
+ to_return(:headers => {'Allow' => 'GET, PUT, OPTIONS'})
33
+
34
+ stub_auth_request(:get, "http://admin:admin@abiquo.example.com/api/admin/datacenters/1").to_return(:body => %q{
35
+ <datacenter>
36
+ <name>Wadus</name>
37
+ <link rel="edit" href="http://abiquo.example.com/api/admin/datacenters/1"/>
38
+ </datacenter>
39
+ })
40
+
41
+ datacenter.name == Abiquo::Resource(datacenter.url, auth).name
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ feature "Deleting resources" do
4
+
5
+ scenario "return status 200 when delete is allowed" do
6
+ datacenter = Abiquo::Resource("http://abiquo.example.com/api/admin/datacenters/1", auth)
7
+
8
+ stub_auth_request(:options, "http://admin:admin@abiquo.example.com/api/admin/datacenters/1").
9
+ to_return(:status => 200, :headers => {'Allow' => 'GET, PUT, OPTIONS, HEAD, DELETE'})
10
+
11
+ stub_auth_request(:delete, "http://admin:admin@abiquo.example.com/api/admin/datacenters/1").to_return(:status => 200)
12
+
13
+ datacenter.delete
14
+
15
+ auth_request(:delete, "http://admin:admin@abiquo.example.com/api/admin/datacenters/1").should have_been_made.once
16
+ end
17
+
18
+ scenario "raise Abiquo::NotAllowed when delete is not allowed" do
19
+ datacenter = Abiquo::Resource("http://abiquo.example.com/api/admin/datacenters/1", auth)
20
+
21
+ stub_auth_request(:options, "http://admin:admin@abiquo.example.com/api/admin/datacenters/1").
22
+ to_return(:status => 200, :headers => {'Allow' => 'GET, PUT, OPTIONS, HEAD'})
23
+
24
+ lambda { datacenter.delete }.should raise_error(Abiquo::NotAllowed)
25
+ end
26
+
27
+ end