superdupe 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.markdown +10 -0
  2. data/lib/superdupe/active_resource_extensions.rb +161 -0
  3. data/lib/superdupe/attribute_template.rb +71 -0
  4. data/lib/superdupe/cucumber_hooks.rb +16 -0
  5. data/lib/superdupe/custom_mocks.rb +116 -0
  6. data/lib/superdupe/database.rb +69 -0
  7. data/lib/superdupe/dupe.rb +534 -0
  8. data/lib/superdupe/hash_pruner.rb +37 -0
  9. data/lib/superdupe/log.rb +38 -0
  10. data/lib/superdupe/mock.rb +127 -0
  11. data/lib/superdupe/model.rb +59 -0
  12. data/lib/superdupe/network.rb +56 -0
  13. data/lib/superdupe/record.rb +42 -0
  14. data/lib/superdupe/rest_validation.rb +16 -0
  15. data/lib/superdupe/schema.rb +52 -0
  16. data/lib/superdupe/sequence.rb +20 -0
  17. data/lib/superdupe/singular_plural_detection.rb +9 -0
  18. data/lib/superdupe/string.rb +7 -0
  19. data/lib/superdupe/symbol.rb +3 -0
  20. data/lib/superdupe.rb +20 -0
  21. data/rails_generators/dupe/dupe_generator.rb +20 -0
  22. data/rails_generators/dupe/templates/custom_mocks.rb +4 -0
  23. data/rails_generators/dupe/templates/definitions.rb +9 -0
  24. data/rails_generators/dupe/templates/load_dupe.rb +9 -0
  25. data/spec/lib_specs/active_resource_extensions_spec.rb +141 -0
  26. data/spec/lib_specs/attribute_template_spec.rb +173 -0
  27. data/spec/lib_specs/database_spec.rb +163 -0
  28. data/spec/lib_specs/dupe_spec.rb +522 -0
  29. data/spec/lib_specs/hash_pruner_spec.rb +73 -0
  30. data/spec/lib_specs/log_spec.rb +78 -0
  31. data/spec/lib_specs/logged_request_spec.rb +22 -0
  32. data/spec/lib_specs/mock_definitions_spec.rb +58 -0
  33. data/spec/lib_specs/mock_spec.rb +194 -0
  34. data/spec/lib_specs/model_spec.rb +95 -0
  35. data/spec/lib_specs/network_spec.rb +130 -0
  36. data/spec/lib_specs/record_spec.rb +70 -0
  37. data/spec/lib_specs/rest_validation_spec.rb +17 -0
  38. data/spec/lib_specs/schema_spec.rb +109 -0
  39. data/spec/lib_specs/sequence_spec.rb +39 -0
  40. data/spec/lib_specs/string_spec.rb +31 -0
  41. data/spec/lib_specs/symbol_spec.rb +17 -0
  42. data/spec/spec_helper.rb +8 -0
  43. metadata +142 -0
@@ -0,0 +1,534 @@
1
+ # Author:: Matt Parker (mailto:moonmaster9000@gmail.com)
2
+ # License:: Distributes under the same terms as Ruby
3
+
4
+ class Dupe
5
+ class << self
6
+
7
+ attr_reader :models #:nodoc:
8
+ attr_reader :sequences #:nodoc:
9
+ attr_reader :database #:nodoc:
10
+
11
+ # set this to "true" if you want Dupe to spit out mocked requests
12
+ # after each of your cucumber scenario's run
13
+ attr_accessor :debug
14
+
15
+ # Suppose we're creating a 'book' resource. Perhaps our app assumes every book has a title, so let's define a book resource
16
+ # that specifies just that:
17
+ #
18
+ # irb# Dupe.define :book do |attrs|
19
+ # --# attrs.title 'Untitled'
20
+ # --# attrs.author
21
+ # --# end
22
+ # ==> #<Dupe::Model:0x17b2694 ...>
23
+ #
24
+ # Basically, this reads like "A book resource has a title attribute with a default value of 'Untitled'. It also has an author attribute." Thus, if we create a book and we don't specify a "title" attribute, it should create a "title" for us, as well as provide a nil "author" attribute.
25
+ #
26
+ # irb# b = Dupe.create :book
27
+ # ==> <#Duped::Book author=nil title="Untitled" id=1>
28
+ #
29
+ #
30
+ # If we provide our own title, it should allow us to override the default value:
31
+ #
32
+ # irb# b = Dupe.create :book, :title => 'Monkeys!'
33
+ # ==> <#Duped::Book author=nil title="Monkeys!" id=2>
34
+ #
35
+ # === Attributes with procs as default values
36
+ #
37
+ # Sometimes it might be convenient to procedurally define the default value for an attribute:
38
+ #
39
+ # irb# Dupe.define :book do |attrs|
40
+ # --# attrs.title 'Untitled'
41
+ # --# attrs.author
42
+ # --# attrs.isbn do
43
+ # --# rand(1000000)
44
+ # --# end
45
+ # --# end
46
+ #
47
+ # Now, every time we create a book, it will get assigned a random ISBN number:
48
+ #
49
+ # irb# b = Dupe.create :book
50
+ # ==> <#Duped::Book author=nil title="Untitled" id=1 isbn=895825>
51
+ #
52
+ # irb# b = Dupe.create :book
53
+ # ==> <#Duped::Book author=nil title="Untitled" id=2 isbn=606472>
54
+ #
55
+ # Another common use of this feature is for associations. Lets suppose we'd like to make sure that a book always has a genre, but a genre should be its own resource. We can accomplish that by taking advantage of Dupe's "find_or_create" method:
56
+ #
57
+ # irb# Dupe.define :book do |attrs|
58
+ # --# attrs.title 'Untitled'
59
+ # --# attrs.author
60
+ # --# attrs.isbn do
61
+ # --# rand(1000000)
62
+ # --# end
63
+ # --# attrs.genre do
64
+ # --# Dupe.find_or_create :genre
65
+ # --# end
66
+ # --# end
67
+ #
68
+ # Now when we create books, Dupe will associate them with an existing genre (the first one it finds), or if none yet exist, it will create one.
69
+ #
70
+ # First, let's confirm that no genres currently exist:
71
+ #
72
+ # irb# Dupe.find :genre
73
+ # Dupe::Database::TableDoesNotExistError: The table ':genre' does not exist.
74
+ # from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/database.rb:30:in `select'
75
+ # from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/dupe.rb:295:in `find'
76
+ # from (irb):135
77
+ #
78
+ # Next, let's create a book:
79
+ #
80
+ # irb# b = Dupe.create :book
81
+ # ==> <#Duped::Book genre=<#Duped::Genre id=1> author=nil title="Untitled" id=1 isbn=62572>
82
+ #
83
+ # Notice that it create a genre. If we tried to do another Dupe.find for the genre:
84
+ #
85
+ # irb# Dupe.find :genre
86
+ # ==> <#Duped::Genre id=1>
87
+ #
88
+ # Now, if create another book, it will associate with the genre that was just created:
89
+ #
90
+ # irb# b = Dupe.create :book
91
+ # ==> <#Duped::Book genre=<#Duped::Genre id=1> author=nil title="Untitled" id=2 isbn=729317>
92
+ #
93
+ #
94
+ #
95
+ # === Attributes with transformers
96
+ #
97
+ # Occasionally, you may find it useful to have attribute values transformed upon creation.
98
+ #
99
+ # For example, suppose we want to create books with publish dates. In our cucumber scenario's, we may prefer to simply specify a date like '2009-12-29', and have that automatically transformed into an ruby Date object.
100
+ #
101
+ # irb# Dupe.define :book do |attrs|
102
+ # --# attrs.title 'Untitled'
103
+ # --# attrs.author
104
+ # --# attrs.isbn do
105
+ # --# rand(1000000)
106
+ # --# end
107
+ # --# attrs.publish_date do |publish_date|
108
+ # --# Date.parse(publish_date)
109
+ # --# end
110
+ # --# end
111
+ #
112
+ # Now, let's create a book:
113
+ #
114
+ # irb# b = Dupe.create :book, :publish_date => '2009-12-29'
115
+ # ==> <#Duped::Book author=nil title="Untitled" publish_date=Tue, 29 Dec 2009 id=1 isbn=826291>
116
+ #
117
+ # irb# b.publish_date
118
+ # ==> Tue, 29 Dec 2009
119
+ #
120
+ # irb# b.publish_date.class
121
+ # ==> Date
122
+ #
123
+ #
124
+ #
125
+ # === Uniquify attributes
126
+ #
127
+ # If you'd just like to make sure that some attributes get a unique value, then you can use the uniquify
128
+ # method:
129
+ #
130
+ # irb# Dupe.define :book do |attrs|
131
+ # --# attrs.uniquify :title, :genre, :author
132
+ # --# end
133
+ #
134
+ # Now, Dupe will do its best to assign unique values to the :title, :genre, and :author attributes on
135
+ # any records it creates:
136
+ #
137
+ # irb# b = Dupe.create :book
138
+ # ==> <#Duped::Book author="book 1 author" title="book 1 title" genre="book 1 genre" id=1>
139
+ #
140
+ # irb# b2 = Dupe.create :book, :title => 'Rooby'
141
+ # ==> <#Duped::Book author="book 2 author" title="Rooby" genre="book 2 genre" id=2>
142
+ #
143
+ #
144
+ #
145
+ # === Callbacks
146
+ #
147
+ # Suppose we'd like to make sure that our books get a unique label. We can accomplish that with an after_create callback:
148
+ #
149
+ # irb# Dupe.define :book do |attrs|
150
+ # --# attrs.title 'Untitled'
151
+ # --# attrs.author
152
+ # --# attrs.isbn do
153
+ # --# rand(1000000)
154
+ # --# end
155
+ # --# attrs.publish_date do |publish_date|
156
+ # --# Date.parse(publish_date)
157
+ # --# end
158
+ # --# attrs.after_create do |book|
159
+ # --# book.label = book.title.downcase.gsub(/\ +/, '-') + "--#{book.id}"
160
+ # --# end
161
+ # --# end
162
+ #
163
+ # irb# b = Dupe.create :book, :title => 'Rooby on Rails'
164
+ # ==> <#Duped::Book author=nil label="rooby-on-rails--1" title="Rooby on Rails" publish_date=nil id=1 isbn=842518>
165
+ #
166
+ def define(*args, &block) # yield: define
167
+ model_name, model_object = create_model_if_definition_parameters_are_valid(args, block)
168
+ model_object.tap do |m|
169
+ models[model_name] = m
170
+ database.create_table model_name
171
+ mocks = %{
172
+ network.define_service_mock(
173
+ :get,
174
+ %r{^#{model_name.to_s.titleize.constantize.prefix rescue '/'}#{model_name.to_s.pluralize}\\.xml$},
175
+ proc { Dupe.find(:#{model_name.to_s.pluralize}) }
176
+ )
177
+ network.define_service_mock(
178
+ :get,
179
+ %r{^#{model_name.to_s.titleize.constantize.prefix rescue '/'}#{model_name.to_s.pluralize}/(\\d+)\\.xml$},
180
+ proc {|id| Dupe.find(:#{model_name}) {|resource| resource.id == id.to_i}}
181
+ )
182
+ network.define_service_mock(
183
+ :post,
184
+ %r{^#{model_name.to_s.titleize.constantize.prefix rescue '/'}#{model_name.to_s.pluralize}\\.xml$},
185
+ proc { |post_body| Dupe.create(:#{model_name.to_s}, post_body) }
186
+ )
187
+ network.define_service_mock(
188
+ :put,
189
+ %r{^#{model_name.to_s.titleize.constantize.prefix rescue '/'}#{model_name.to_s.pluralize}/(\\d+)\\.xml$},
190
+ proc { |id, put_data| Dupe.find(:#{model_name.to_s}) {|resource| resource.id == id.to_i}.merge!(put_data) }
191
+ )
192
+ network.define_service_mock(
193
+ :delete,
194
+ %r{^#{model_name.to_s.titleize.constantize.prefix rescue '/'}#{model_name.to_s.pluralize}/(\\d+)\\.xml$},
195
+ proc { |id| Dupe.delete(:#{model_name.to_s}) {|resource| resource.id == id.to_i} }
196
+ )
197
+ }
198
+ eval(mocks)
199
+ end
200
+ end
201
+
202
+ # This method will cause Dupe to mock resources for the record(s) provided.
203
+ # The "records" value may be either a hash or an array of hashes.
204
+ # For example, suppose you'd like to mock a single author:
205
+ #
206
+ # author = Dupe.create :author, :name => 'Arthur C. Clarke'
207
+ # ==> <#Duped::Author name="Arthur C. Clarke" id=1>
208
+ #
209
+ # This will translate into the following two mocked resource calls:
210
+ #
211
+ # # GET /authors.xml
212
+ # <?xml version="1.0" encoding="UTF-8"?>
213
+ # <authors>
214
+ # <author>
215
+ # <id type="integer">1</id>
216
+ # <name>Arthur C. Clarke</name>
217
+ # </author>
218
+ # </authors>
219
+ #
220
+ # # GET /authors/1.xml
221
+ # <?xml version="1.0" encoding="UTF-8"?>
222
+ # <author>
223
+ # <id type="integer">1</id>
224
+ # <name>Arthur C. Clarke</name>
225
+ # </author>
226
+ #
227
+ # However, suppose you wanted to mock two or more authors.
228
+ #
229
+ # authors = Dupe.create :author, [{:name => 'Arthur C. Clarke'}, {:name => 'Robert Heinlein'}]
230
+ # ==> [<#Duped::Author name="Arthur C. Clarke" id=1>, <#Duped::Author name="Robert Heinlein" id=2>]
231
+ #
232
+ # This will translate into the following three mocked resource calls:
233
+ #
234
+ # # GET /authors.xml
235
+ # <?xml version="1.0" encoding="UTF-8"?>
236
+ # <authors>
237
+ # <author>
238
+ # <id type="integer">1</id>
239
+ # <name>Arthur C. Clarke</name>
240
+ # </author>
241
+ # <author>
242
+ # <id type="integer">2</id>
243
+ # <name>Robert Heinlein</name>
244
+ # </author>
245
+ # </authors>
246
+ #
247
+ # # GET /authors/1.xml
248
+ # <?xml version="1.0" encoding="UTF-8"?>
249
+ # <author>
250
+ # <id type="integer">1</id>
251
+ # <name>Arthur C. Clarke</name>
252
+ # </author>
253
+ #
254
+ # # GET /authors/2.xml
255
+ # <?xml version="1.0" encoding="UTF-8"?>
256
+ # <author>
257
+ # <id type="integer">2</id>
258
+ # <name>Robert Heinlein</name>
259
+ # </author>
260
+ def create(model_name, records={})
261
+ model_name = model_name.to_s.singularize.to_sym
262
+ define model_name unless model_exists(model_name)
263
+ records = records.kind_of?(Array) ? records.map {|r| r.symbolize_keys} : records.symbolize_keys!
264
+ create_and_insert records, :into => model_name
265
+ end
266
+
267
+ # You can use this method to quickly stub out a large number of resources. For example:
268
+ #
269
+ # Dupe.stub 20, :authors
270
+ #
271
+ #
272
+ # Assuming you had an :author resource definition like:
273
+ #
274
+ # Dupe.define :author {|author| author.name('default')}
275
+ #
276
+ #
277
+ # then stub would have generated 20 author records like:
278
+ #
279
+ # <#Duped::Author name="default" id=1>
280
+ # ....
281
+ # <#Duped::Author name="default" id=1>
282
+ #
283
+ # and it would also have mocked find(id) and find(:all) responses for these records (along with any other custom mocks you've
284
+ # setup via Dupe.define_mocks). (Had you not defined an author resource, then the stub would have generated 20 author records
285
+ # where the only attribute is the id).
286
+ #
287
+ # Of course, it's more likely that you wanted to dupe 20 <em>different</em> authors. You can accomplish this by simply doing:
288
+ #
289
+ # Dupe.stub 20, :authors, :like => {:name => proc {|n| "author #{n}"}}
290
+ #
291
+ # which would generate 20 author records like:
292
+ #
293
+ # <#Duped::Author name="author 1" id=1>
294
+ # ....
295
+ # <#Duped::Author name="author 20" id=20>
296
+ #
297
+ # Naturally, stub will consult the Dupe.define definitions for anything it's attempting to stub
298
+ # and will honor those definitions (default values, transformations, callbacks) as you would expect.
299
+ def stub(count, model_name, options={})
300
+ start_at = options[:starting_with] || 1
301
+ record_template = options[:like] || {}
302
+ records = []
303
+ (start_at..(start_at + count - 1)).each do |i|
304
+ records <<
305
+ record_template.map do |k,v|
306
+ { k => (v.kind_of?(Proc) ? v.call(i) : v) }
307
+ end.inject({}) {|h, v| h.merge(v)}
308
+ end
309
+ create model_name, records
310
+ end
311
+
312
+ # Dupe has a built-in querying system for finding resources you create.
313
+ #
314
+ # irb# a = Dupe.create :author, :name => 'Monkey'
315
+ # ==> <#Duped::Author name="Monkey" id=1>
316
+ #
317
+ # irb# b = Dupe.create :book, :title => 'Bananas', :author => a
318
+ # ==> <#Duped::Book author=<#Duped::Author name="Monkey" id=1> title="Bananas" id=1>
319
+ #
320
+ # irb# Dupe.find(:author) {|a| a.name == 'Monkey'}
321
+ # ==> <#Duped::Author name="Monkey" id=1>
322
+ #
323
+ # irb# Dupe.find(:book) {|b| b.author.name == 'Monkey'}
324
+ # ==> <#Duped::Book author=<#Duped::Author name="Monkey" id=1> title="Bananas" id=1>
325
+ #
326
+ # irb# Dupe.find(:author) {|a| a.id == 1}
327
+ # ==> <#Duped::Author name="Monkey" id=1>
328
+ #
329
+ # irb# Dupe.find(:author) {|a| a.id == 2}
330
+ # ==> nil
331
+ #
332
+ # In all cases, notice that we provided the singular form of a model name to Dupe.find.
333
+ # This ensures that we either get back either a single resource (if the query was successful), or _nil_.
334
+ #
335
+ # If we'd like to find several resources, we can use the plural form of the model name. For example:
336
+ #
337
+ # irb# a = Dupe.create :author, :name => 'Monkey', :published => true
338
+ # ==> <#Duped::Author published=true name="Monkey" id=1>
339
+ #
340
+ # irb# b = Dupe.create :book, :title => 'Bananas', :author => a
341
+ # ==> <#Duped::Book author=<#Duped::Author published=true name="Monkey" id=1> title="Bananas" id=1>
342
+ #
343
+ # irb# Dupe.create :author, :name => 'Tiger', :published => false
344
+ # ==> <#Duped::Author published=false name="Tiger" id=2>
345
+ #
346
+ # irb# Dupe.find(:authors)
347
+ # ==> [<#Duped::Author published=true name="Monkey" id=1>, <#Duped::Author published=false name="Tiger" id=2>]
348
+ #
349
+ # irb# Dupe.find(:authors) {|a| a.published == true}
350
+ # ==> [<#Duped::Author published=true name="Monkey" id=1>]
351
+ #
352
+ # irb# Dupe.find(:books)
353
+ # ==> [<#Duped::Book author=<#Duped::Author published=true name="Monkey" id=1> title="Bananas" id=1>]
354
+ #
355
+ # irb# Dupe.find(:books) {|b| b.author.published == false}
356
+ # ==> []
357
+ #
358
+ # Notice that by using the plural form of the model name, we ensure that we receive back an array -
359
+ # even in the case that the query did not find any results (it simply returns an empty array).
360
+ def find(model_name, &block) # yield: record
361
+ results = database.select model_name.to_s.singularize.to_sym, block
362
+ model_name.plural? ? results : results.first
363
+ end
364
+
365
+ # This method will create a resource with the given specifications if one doesn't already exist.
366
+ #
367
+ # irb# Dupe.find :genre
368
+ # Dupe::Database::TableDoesNotExistError: The table ':genre' does not exist.
369
+ # from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/database.rb:30:in `select'
370
+ # from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/dupe.rb:295:in `find'
371
+ # from (irb):40
372
+ #
373
+ # irb# Dupe.find_or_create :genre
374
+ # ==> <#Duped::Genre id=1>
375
+ #
376
+ # irb# Dupe.find_or_create :genre
377
+ # ==> <#Duped::Genre id=1>
378
+ #
379
+ # You can also pass conditions to find_or_create as a hash:
380
+ #
381
+ # irb# Dupe.find_or_create :genre, :name => 'Science Fiction', :label => 'sci-fi'
382
+ # ==> <#Duped::Genre label="sci-fi" name="Science Fiction" id=2>
383
+ #
384
+ # irb# Dupe.find_or_create :genre, :name => 'Science Fiction', :label => 'sci-fi'
385
+ # ==> <#Duped::Genre label="sci-fi" name="Science Fiction" id=2>
386
+ def find_or_create(model_name, attributes={})
387
+ results = nil
388
+ if model_exists(model_name)
389
+ results = eval("find(:#{model_name}) #{build_conditions(attributes)}")
390
+ end
391
+
392
+ if !results
393
+ if model_name.singular?
394
+ create model_name, attributes
395
+ else
396
+ stub((rand(5)+1), model_name, :like => attributes)
397
+ end
398
+ elsif results.kind_of?(Array) && results.empty?
399
+ stub((rand(5)+1), model_name, :like => attributes)
400
+ else
401
+ results
402
+ end
403
+ end
404
+
405
+ def delete(resource, &conditions)
406
+ database.delete resource, conditions
407
+ end
408
+
409
+ def sequence(name, &block)
410
+ sequences[name.to_sym] = Sequence.new 1, block
411
+ end
412
+
413
+ def next(name)
414
+ raise ArgumentError, "Unknown sequence \":#{name}\"" unless sequences.has_key?(name)
415
+ sequences[name].next
416
+ end
417
+
418
+ def models #:nodoc:
419
+ @models ||= {}
420
+ end
421
+
422
+ def network #:nodoc:
423
+ @network ||= Dupe::Network.new
424
+ end
425
+
426
+ def database #:nodoc:
427
+ @database ||= Dupe::Database.new
428
+ end
429
+
430
+ def sequences #:nodoc:
431
+ @sequences ||= {}
432
+ end
433
+
434
+ # clears out all model definitions, sequences, and database records / tables.
435
+ def reset
436
+ reset_models
437
+ reset_database
438
+ reset_network
439
+ reset_sequences
440
+ end
441
+
442
+ def reset_sequences
443
+ @sequences = {}
444
+ end
445
+
446
+ def reset_models
447
+ @models = {}
448
+ end
449
+
450
+ def reset_database
451
+ @database = Dupe::Database.new
452
+ end
453
+
454
+ def reset_network
455
+ @network = Dupe::Network.new
456
+ end
457
+
458
+ # set to true if you want to see mocked results spit out after each cucumber scenario
459
+ def debug
460
+ @debug ||= false
461
+ end
462
+
463
+
464
+
465
+ private
466
+ def build_conditions(conditions)
467
+ return '' if conditions.empty?
468
+ select =
469
+ "{|record| " +
470
+ conditions.map do |k,v|
471
+ "record.#{k} == #{v.kind_of?(String) ? "\"#{v}\"" : v}"
472
+ end.join(" && ") + " }"
473
+ end
474
+
475
+ def model_exists(model_name)
476
+ models[model_name.to_s.singularize.to_sym]
477
+ end
478
+
479
+ def create_model(model_name)
480
+ models[model_name] = Dupe::Model.new(model_name) unless models[model_name]
481
+ end
482
+
483
+ def create_and_insert(records, into)
484
+ raise(
485
+ ArgumentError,
486
+ "You must pass a hash containing :into => :model_name " +
487
+ "as the second parameter to create_and_insert."
488
+ ) if !into || !into.kind_of?(Hash) || !into[:into]
489
+
490
+ # do we have several records to create, and are they each a hash?
491
+ if records.kind_of?(Array) and
492
+ records.inject(true) {|bool, r| bool and r.kind_of?(Hash)}
493
+ [].tap do |results|
494
+ records.each do |record|
495
+ results << models[into[:into]].create(record).tap {|r| database.insert r}
496
+ end
497
+ end
498
+
499
+ # do we only have one record to create, and is it a hash?
500
+ elsif records.kind_of?(Hash)
501
+ models[into[:into]].create(records).tap {|r| database.insert r}
502
+
503
+ else
504
+ raise ArgumentError, "You must call Dupe.create with either a hash or an array of hashes."
505
+ end
506
+ end
507
+
508
+ def create_model_if_definition_parameters_are_valid(args, definition)
509
+ if args.length == 1 and
510
+ args.first.kind_of?(Symbol) and
511
+ definition == nil
512
+
513
+ return args.first, Dupe::Model.new(args.first)
514
+
515
+ elsif args.length == 1 and
516
+ args.first.kind_of?(Symbol) and
517
+ definition.kind_of?(Proc) and
518
+ definition.arity == 1
519
+
520
+ model_name = args.first
521
+ return model_name, Dupe::Model.new(model_name).tap {|m| m.define definition}
522
+
523
+ else
524
+ raise ArgumentError.new(
525
+ "Unknown Dupe.define parameter format. Please consult the API for information on how to use Dupe.define"
526
+ )
527
+ end
528
+ end
529
+ end
530
+ end
531
+
532
+ class Dupe
533
+ class UnprocessableEntity < StandardError; end
534
+ end
@@ -0,0 +1,37 @@
1
+ class HashPruner
2
+ class << self
3
+ def prune(hash)
4
+ HashPruner.new.prune hash
5
+ end
6
+ end
7
+
8
+ def initialize()
9
+ @visited = {}
10
+ end
11
+
12
+ def prune(item)
13
+ if item.kind_of? Array
14
+ item.map {|i| prune(i)}.reject {|v| v==nil}
15
+ elsif item.kind_of? Hash
16
+ if @visited[item.object_id]
17
+ item.dup.delete_if {|k,v| v.kind_of?(Hash) || v.kind_of?(Array)}
18
+ else
19
+ @visited[item.object_id] = true
20
+ new_hash = {}
21
+ item.each do |k,v|
22
+ new_hash[k] = prune(v)
23
+ end
24
+ @visited.delete item.object_id
25
+ new_hash
26
+ end
27
+ else
28
+ item
29
+ end
30
+ end
31
+ end
32
+
33
+ class Hash
34
+ def to_xml_safe(options={})
35
+ HashPruner.prune(self).to_xml(options)
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ class Dupe
2
+ class Network #:nodoc:
3
+ class Log #:nodoc:
4
+ include RestValidation #:nodoc:
5
+ attr_reader :requests #:nodoc:
6
+
7
+ class Request #:nodoc:
8
+ attr_reader :verb, :path, :response_body
9
+
10
+ def initialize(verb, path, response_body)
11
+ @verb, @path, @response_body = verb, path, response_body
12
+ end
13
+
14
+ def pretty_print
15
+ "Request: #{@verb.to_s.upcase} #{@path}\n" +
16
+ "Response:\n" + @response_body.indent
17
+ end
18
+ end
19
+
20
+ def initialize #:nodoc:
21
+ @requests = []
22
+ end
23
+
24
+ def add_request(verb, path, response_body='') #:nodoc:
25
+ validate_request_type verb
26
+ @requests << Request.new(verb, path, response_body)
27
+ end
28
+
29
+ def pretty_print
30
+ "Logged Requests:\n" + requests.map {|r| r.pretty_print.indent }.join("\n\n") + "\n\n"
31
+ end
32
+
33
+ def reset #:nodoc:
34
+ @requests = []
35
+ end
36
+ end
37
+ end
38
+ end