moonmaster9000-dupe 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,84 @@
1
+ = Dupe
2
+
3
+ There are lots of great tools out there to ease the burden of prototyping ActiveRecord objects while cuking your application (e.g., thoughtbot's "Factory Girl"[http://www.thoughtbot.com/projects/factory_girl]).
4
+ But what about prototyping ActiveResource records? That's where Dupe steps in.
5
+
6
+ == Installation
7
+
8
+ # gem install moonmaster9000-dupe
9
+
10
+ == Example
11
+ Let's suppose your cuking a book search application for a library that consumes a RESTFUL book datastore service via ActiveResource.
12
+ You might start by writing the following feature in <em>RAILS_ROOT/features/library/find_book.feature</em>:
13
+
14
+ Feature: find a book
15
+ As a reader
16
+ I want to search for books
17
+ so that I can check them out and read them.
18
+
19
+ Scenario: search by author
20
+ Given an author "Arthur C. Clarke"
21
+ And a book "2001: A Space Odyssey" by "Arthur C. Clarke"
22
+ When I search for "Arthur C. Clarke"
23
+ I should see "2001: A Space Odyssey"
24
+
25
+ To get this to pass, you might first create ActiveResource[http://api.rubyonrails.org/classes/ActiveResource/Base.html] models for Books and Authors that connect to the Library service:
26
+
27
+ class Book < ActiveResource::Base
28
+ self.site = 'http://bookservice.domain'
29
+ end
30
+
31
+ class Author < ActiveResource::Base
32
+ self.site = 'http://bookservice.domain'
33
+ end
34
+
35
+ Then you might create the following resource definition via Dupe.define (put it in a file with a .rb extension and place it in RAILS_ROOT/features/support/):
36
+
37
+ Dupe.define :book do |book|
38
+ book.author do |author_name|
39
+ Dupe.find(:author) {|a| a.name == author_name}
40
+ end
41
+ end
42
+
43
+ and the following cucumber step definitions (utilizing Dupe.create):
44
+
45
+ Given /^an author "([^\"]*)"$/ do |author|
46
+ Dupe.create :author, :name => author
47
+ end
48
+
49
+ Given /^a book "([^\"]*)" by "([^\"]*)"$/ do |book, author|
50
+ Dupe.create :book, :title => book, :author => author
51
+ end
52
+
53
+ Dupe.create will in turn mock two service responses for each resource. For example,
54
+ for the Book resource, it will mock:
55
+
56
+ # Book.find(:all) --> GET /books.xml
57
+ <?xml version="1.0" encoding="UTF-8"?>
58
+ <books type="array">
59
+ <book>
60
+ <id type="integer">1</id>
61
+ <title>2001: A Space Odyssey</title>
62
+ <author>
63
+ <id type="integer">1</id>
64
+ <name>Arthur C. Clarke</name>
65
+ </author>
66
+ </book>
67
+ </books>
68
+
69
+ # Book.find(1) --> GET /books/1.xml
70
+ <?xml version="1.0" encoding="UTF-8"?>
71
+ <book>
72
+ <id type="integer">1</id>
73
+ <title>2001: A Space Odyssey</title>
74
+ <author>
75
+ <id type="integer">1</id>
76
+ <name>Arthur C. Clarke</name>
77
+ </author>
78
+ </book>
79
+
80
+ From here, you could start scaffolding your controllers, with the assumption that Dupe will mock the responses to Book.find(<id or :all>) and Author.find(<id or :all>).
81
+
82
+ == More
83
+
84
+ Dupe supports attribute defaults, attribute transoformations, stubbing, resource associations, custom resource mocks, and more. Want to learn more? Consult the API documentation at http://moonmaster9000.github.com/dupe/api/
@@ -0,0 +1,27 @@
1
+ # this allows us to control when we flush out the HttpMock requests / responses
2
+ ActiveResource::HttpMock.instance_eval do #:nodoc:
3
+ def reset! #:nodoc:
4
+ end
5
+
6
+ def reset_from_dupe! #:nodoc:
7
+ requests.clear
8
+ responses.clear
9
+ end
10
+ end
11
+
12
+ # makes it possible to override existing request/response definitions
13
+ module ActiveResource #:nodoc:
14
+ class HttpMock #:nodoc:
15
+ class Responder #:nodoc:
16
+ for method in [ :post, :put, :get, :delete, :head ]
17
+ module_eval <<-EOE, __FILE__, __LINE__
18
+ def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
19
+ @responses.reject! {|r| r[0].path == path && r[0].method == :#{method}}
20
+ @responses << [Request.new(:#{method}, path, nil, request_headers), Response.new(body || "", status, response_headers)]
21
+ end
22
+ EOE
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,17 @@
1
+ class Dupe
2
+ class Attribute #:nodoc:
3
+ def initialize(name, value=nil, prock=nil)
4
+ @name, @value, @prock = name.to_sym, value, prock
5
+ end
6
+
7
+ def value(v=nil)
8
+ v = @value.dup if @value and !v
9
+ @prock && v ? @prock.call(v) : v
10
+ end
11
+
12
+ def to_hash
13
+ {@name => value}
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ class Dupe
2
+ class Configuration #:nodoc:
3
+ attr_reader :config
4
+
5
+ def initialize
6
+ @config ||= {}
7
+ @config[:record_identifiers] = [:id]
8
+ end
9
+
10
+ def method_missing(method_name, *args, &block)
11
+ set_config_option(method_name.to_sym, args)
12
+ end
13
+
14
+
15
+ private
16
+ def set_config_option(key, value)
17
+ @config[key] = value
18
+ end
19
+ end
20
+ end
21
+
22
+
@@ -0,0 +1,3 @@
1
+ After do |scenario| #:nodoc:
2
+ Dupe.flush
3
+ end
data/lib/dupe/dupe.rb ADDED
@@ -0,0 +1,524 @@
1
+ # Dupe allows you to define resources, create a pool of resources,
2
+ # extend those resources with your own custom response mocks, and even override the default
3
+ # mocks Dupe provides (<em>find(:all)</em> and <em>find(id)</em>).
4
+ #
5
+ # Dupe is ideally suited for working with Cucumber[http://cukes.info]. It also relies on ActiveResource::HttpMock[http://api.rubyonrails.org/classes/ActiveResource/HttpMock.html] for mocking
6
+ # resource responses.
7
+ #
8
+ # Let's suppose your cuking a book search application for a library that consumes a RESTFUL book datastore service via ActiveResource.
9
+ # You might start by writing the following feature in <em>RAILS_ROOT/features/library/find_book.feature</em>:
10
+ #
11
+ # Feature: find a book
12
+ # As a reader
13
+ # I want to search for books
14
+ # so that I can check them out and read them.
15
+ #
16
+ # Scenario: search by author
17
+ # Given an author "Arthur C. Clarke"
18
+ # And a book "2001: A Space Odyssey" by "Arthur C. Clarke"
19
+ # When I search for "Arthur C. Clarke"
20
+ # I should see "2001: A Space Odyssey"
21
+ #
22
+ # To get this to pass, you might first create an ActiveResource[http://api.rubyonrails.org/classes/ActiveResource/Base.html] model for a Book and an Author that will connect
23
+ # to the RESTful book service:
24
+ #
25
+ # class Book < ActiveResource::Base
26
+ # self.site = 'http://bookservice.domain'
27
+ # end
28
+ #
29
+ # class Author < ActiveResource::Base
30
+ # self.site = 'http://bookservice.domain'
31
+ # end
32
+ #
33
+ # Then you might create the following resource definition via Dupe.define (put it in a file with a .rb extension and place it in RAILS_ROOT/features/support/):
34
+ #
35
+ # Dupe.define :book do |define|
36
+ # define.author do |author_name|
37
+ # Dupe.find(:author) {|a| a.name == author_name}
38
+ # end
39
+ # end
40
+ #
41
+ # and the following cucumber step definitions (utilizing Dupe.create):
42
+ #
43
+ # Given /^an author "([^\"]*)"$/ do |author|
44
+ # Dupe.create :author, :name => author
45
+ # end
46
+ #
47
+ # Given /^a book "([^\"]*)" by "([^\"]*)"$/ do |book, author|
48
+ # Dupe.create :book, :title => book, :author => author
49
+ # end
50
+ #
51
+ # Dupe.create will in turn mock two service responses for each resource. For example,
52
+ # for the Book resource, it will mock:
53
+ #
54
+ # # Book.find(:all) --> GET /books.xml
55
+ # <?xml version="1.0" encoding="UTF-8"?>
56
+ # <books type="array">
57
+ # <book>
58
+ # <id type="integer">1</id>
59
+ # <title>2001: A Space Odyssey</title>
60
+ # <author>
61
+ # <id type="integer">1</id>
62
+ # <name>Arthur C. Clarke</name>
63
+ # </author>
64
+ # </book>
65
+ # </books>
66
+ #
67
+ # # Book.find(1) --> GET /books/1.xml
68
+ # <?xml version="1.0" encoding="UTF-8"?>
69
+ # <book>
70
+ # <id type="integer">1</id>
71
+ # <title>2001: A Space Odyssey</title>
72
+ # <author>
73
+ # <id type="integer">1</id>
74
+ # <name>Arthur C. Clarke</name>
75
+ # </author>
76
+ # </book>
77
+
78
+ # Author:: Matt Parker (mailto:moonmaster9000@gmail.com)
79
+ # License:: Distributes under the same terms as Ruby
80
+
81
+ class Dupe
82
+ attr_reader :factory_name #:nodoc:
83
+ attr_reader :configuration #:nodoc:
84
+ attr_reader :attributes #:nodoc:
85
+ attr_reader :config #:nodoc:
86
+ attr_reader :mocker #:nodoc:
87
+ attr_reader :records #:nodoc:
88
+
89
+ class << self
90
+ attr_accessor :factories #:nodoc:
91
+
92
+ # Create data definitions for your resources. This allows you to setup default values for columns
93
+ # and even provide data transformations.
94
+ #
95
+ # For example, suppose you had the following cucumber scenario:
96
+ #
97
+ # # RAILS_ROOT/features/library/find_book.feature
98
+ # Feature: Find a book
99
+ # As a reader
100
+ # I want to find books in my library
101
+ # So that I can read them
102
+ #
103
+ # Scenario: Browsing books
104
+ # Given the following author:
105
+ # | name | date_of_birth |
106
+ # | Arthur C. Clarke | 1917-12-16 |
107
+ #
108
+ # And the following book:
109
+ # | name | author |
110
+ # | 2001: A Space Odyssey | Arthur C. Clarke |
111
+ #
112
+ # When....
113
+ #
114
+ #
115
+ # We can use Dupe.define to
116
+ # * Transform data (e.g., turn the string '1917-12-16' into a Date object)
117
+ # * Provide default values for attributes (e.g., give all author's a default biography)
118
+ # * Associate records (e.g., given an author name, return the author record associated with that name)
119
+ #
120
+ # To accomplish the afore mentioned definitions:
121
+ #
122
+ # # RAILS_ROOT/features/dupe_definitions/book.rb
123
+ #
124
+ # Dupe.define :author do |define|
125
+ # define.bio 'Lorem ipsum delor.'
126
+ # define.date_of_birth do |d|
127
+ # Date.parse(t)
128
+ # end
129
+ # end
130
+ #
131
+ # Dupe.define :book do |define|
132
+ # define.author do |author_name|
133
+ # Dupe.find(:author) {|a| a.name == author_name}
134
+ # end
135
+ # end
136
+ #
137
+ # -----------------------------------------------------------------------------------------------------------------
138
+ #
139
+ # # RAILS_ROOT/features/step_definitions/library/find_book_steps.rb
140
+ #
141
+ # Given /^the following author:$/ do |author_table|
142
+ # Dupe.create(:author, author_table.hashes)
143
+ # end
144
+ #
145
+ # Given /^the following book:$/ do |book_table|
146
+ # Dupe.create(:book, book_table.hashes)
147
+ # end
148
+ #
149
+ # When cucumber encounters the "Given the following author:" line, the corresponding step definition
150
+ # will ask Dupe to mock ActiveResource responses to find(:all) and find(:id) with the data
151
+ # specified in the cucumber hash table immediately following the "Given the following author:" line.
152
+ # Since we didn't specify a 'bio' value in our cucumber hash table, Dupe will give it the
153
+ # default value 'Bio stub.'. Also, it will transform the 'date_of_birth' value we provided in the hash
154
+ # table into a time object.
155
+ #
156
+ # Similarly, for the :book cucumber hash table, Dupe will transform the author name we provided
157
+ # into the author object we had already specified in the :author table.
158
+ #
159
+ # In terms of mocked responses, we could expect something like:
160
+ #
161
+ # # Author.find(1) --> GET /authors/1.xml
162
+ # <?xml version="1.0" encoding="UTF-8"?>
163
+ # <author>
164
+ # <id type="integer">1</id>
165
+ # <name>Arthur C. Clarke</name>
166
+ # <bio>Bio stub.</bio>
167
+ # <date_of_birth>1917-12-16T00:00:00Z</date_of_birth>
168
+ # </author>
169
+ #
170
+ # # Book.find(1) --> GET /books/1.xml
171
+ # <?xml version="1.0" encoding="UTF-8"?>
172
+ # <book>
173
+ # <id type="integer">1</id>
174
+ # <name>2001: A Space Odyssey</name>
175
+ # <author>
176
+ # <id type="integer">1</id>
177
+ # <name>Arthur C. Clarke</name>
178
+ # <bio>Bio stub.</bio>
179
+ # <date_of_birth>1917-12-16T00:00:00Z</date_of_birth>
180
+ # </author>
181
+ # </book>
182
+ def define(factory) # yield: define
183
+ setup_factory(factory)
184
+ yield @factories[factory]
185
+ end
186
+
187
+ # This method will cause Dupe to mock resources for the record(s) provided.
188
+ # The "records" value may be either a hash or an array of hashes.
189
+ # For example, suppose you'd like to mock a single author ActiveResource object:
190
+ #
191
+ # Dupe.create :author, :name => 'Arthur C. Clarke'
192
+ #
193
+ # This will translate into the following two mocked resource calls:
194
+ #
195
+ # # Author.find(:all) --> GET /authors.xml
196
+ # <?xml version="1.0" encoding="UTF-8"?>
197
+ # <authors>
198
+ # <author>
199
+ # <id type="integer">1</id>
200
+ # <name>Arthur C. Clarke</name>
201
+ # </author>
202
+ # </authors>
203
+ #
204
+ # # Author.find(1) --> GET /authors/1.xml
205
+ # <?xml version="1.0" encoding="UTF-8"?>
206
+ # <author>
207
+ # <id type="integer">1</id>
208
+ # <name>Arthur C. Clarke</name>
209
+ # </author>
210
+ #
211
+ # However, suppose you wanted to mock two or more authors.
212
+ #
213
+ # Dupe.create :author, [{:name => 'Arthur C. Clarke'}, {:name => 'Robert Heinlein'}]
214
+ #
215
+ # This will translate into the following three mocked resource calls:
216
+ #
217
+ # # Author.find(:all) --> GET /authors.xml
218
+ # <?xml version="1.0" encoding="UTF-8"?>
219
+ # <authors>
220
+ # <author>
221
+ # <id type="integer">1</id>
222
+ # <name>Arthur C. Clarke</name>
223
+ # </author>
224
+ # <author>
225
+ # <id type="integer">2</id>
226
+ # <name>Robert Heinlein</name>
227
+ # </author>
228
+ # </authors>
229
+ #
230
+ # # Author.find(1) --> GET /authors/1.xml
231
+ # <?xml version="1.0" encoding="UTF-8"?>
232
+ # <author>
233
+ # <id type="integer">1</id>
234
+ # <name>Arthur C. Clarke</name>
235
+ # </author>
236
+ #
237
+ # # Author.find(2) --> GET /authors/2.xml
238
+ # <?xml version="1.0" encoding="UTF-8"?>
239
+ # <author>
240
+ # <id type="integer">2</id>
241
+ # <name>Robert Heinlein</name>
242
+ # </author>
243
+ def create(factory, records={})
244
+ setup_factory(factory)
245
+ raise Exception, "unknown records type" if !records.nil? and !records.is_a?(Array) and !records.is_a?(Hash)
246
+ records = [records] if records.is_a?(Hash)
247
+ @factories[factory].generate_services_for(records)
248
+ end
249
+
250
+ # You can use this method to quickly stub out a large number of resources. For example:
251
+ #
252
+ # Dupe.stub(
253
+ # :author,
254
+ # :template => {:name => 'author'},
255
+ # :count => 20
256
+ # )
257
+ #
258
+ # would generate 20 author records like:
259
+ #
260
+ # {:name => 'author 1', :id => 1}
261
+ # ....
262
+ # {:name => 'author 20', :id => 20}
263
+ #
264
+ # and it would also mock find(id) and find(:all) responses for these records (along with any other custom mocks you've
265
+ # setup via Dupe.define_mocks).
266
+ #
267
+ # You may override both the sequence starting value and the attribute to sequence:
268
+ #
269
+ # Dupe.stub(
270
+ # :book,
271
+ # :template => {:author => 'Arthur C. Clarke', :title => 'moonmaster'},
272
+ # :count => 20,
273
+ # :sequence_start_value => 9000,
274
+ # :sequence => :title
275
+ # )
276
+ #
277
+ # This would generate 20 book records like:
278
+ #
279
+ # {:id => 1, :author => 'Arthur C. Clarke', :title => 'moonmaster 9000'}
280
+ # ....
281
+ # {:id => 20, :author => 'Arthur C. Clarke', :title => 'moonmaster 9019'}
282
+ #
283
+ # Naturally, stub will consult the Dupe.define definitions for anything it's attempting to stub
284
+ # and will honor those definitions (default values, transformations) as you would expect.
285
+ def stub(factory, options)
286
+ setup_factory(factory)
287
+ @factories[factory].stub_services_with(options[:template], options[:count].to_i, (options[:sequence_start_value] || 1), options[:sequence])
288
+ end
289
+
290
+ # This allows you to override the array record identifiers for your resources ([:id], by default)
291
+ #
292
+ # For example, suppose the RESTful application your trying to consume supports lookups by both a textual 'label'
293
+ # and a numeric 'id', and that it contains an author service where the author with id '1' has the label 'arthur-c-clarke'.
294
+ # Your application should expect the same response whether or not you call <tt>Author.find(1)</tt> or <tt>Author.find('arthur-c-clarke')</tt>.
295
+ #
296
+ # Thus, to ensure that Dupe mocks both, do the following:
297
+ # Dupe.configure :author do |configure|
298
+ # configure.record_identifiers :id, :label
299
+ # end
300
+ #
301
+ # With this configuration, a <tt>Dupe.create :author, :name => 'Arthur C. Clarke', :label => 'arthur-c-clarke'</tt>
302
+ # will result in the following mocked service calls:
303
+ #
304
+ # <tt>Author.find(1) --> (GET /authors/1.xml)</tt>
305
+ #
306
+ # <?xml version="1.0" encoding="UTF-8"?>
307
+ # <author>
308
+ # <id type="integer">1</id>
309
+ # <name>Arthur C. Clarke</name>
310
+ # <label>arthur-c-clarke</label>
311
+ # </author>
312
+ #
313
+ #
314
+ # <tt>Author.find('arthur-c-clarke') --> (GET /authors/arthur-c-clarke.xml)</tt>
315
+ #
316
+ # <?xml version="1.0" encoding="UTF-8"?>
317
+ # <author>
318
+ # <id type="integer">1</id>
319
+ # <name>Arthur C. Clarke</name>
320
+ # <label>arthur-c-clarke</label>
321
+ # </author>
322
+ def configure(factory) # yield: configure
323
+ setup_factory(factory)
324
+ yield @factories[factory].config
325
+ end
326
+
327
+ # By default, Dupe will mock responses to ActiveResource <tt>find(:all)</tt> and <tt>find(id)</tt>.
328
+ # However, it's likely that your cucumber scenarios will eventually fire off an ActiveResource request that's
329
+ # something other than these basic lookups.
330
+ #
331
+ # Dupe.define_mocks allows you to add new resource mocks and override the built-in resource mocks.
332
+ #
333
+ # For example, suppose you had a Book ActiveResource model, and you want to use it to get the :count of all
334
+ # Books in the back end system your consuming. <tt>Book.get(:count)</tt> would fire off an HTTP request to the
335
+ # backend service like <tt>"GET /books/count.xml"</tt>, and assuming the service is set up to respond to that
336
+ # request, you might expect to get something back like:
337
+ #
338
+ # <?xml version="1.0" encoding="UTF-8"?>
339
+ # <hash>
340
+ # <count type="integer">3</count>
341
+ # </hash>
342
+ #
343
+ # To mock this for the purposes of cuking, you could do the following:
344
+ #
345
+ # Dupe.define_mocks :book do |define|
346
+ # define.count do |mock, records|
347
+ # mock.get "/books/count.xml", {}, {:count => records.size}.to_xml
348
+ # end
349
+ # end
350
+ #
351
+ # The <tt>mock</tt> object is the ActiveResource::HttpMock object. Please see the documentation for that
352
+ # if you would like to know more about what's possible with it.
353
+ def define_mocks(factory) # yield: define
354
+ setup_factory(factory)
355
+ yield @factories[factory].mocker
356
+ end
357
+
358
+
359
+ # Search for a resource. This works a bit differently from both ActiveRecord's find and ActiveResource's find.
360
+ # This is most often used for defining associations between objects (Dupe.define).
361
+ # It will return a hash representation of the resource (or an array of hashes if we asked for multiple records).
362
+ #
363
+ # For example, suppose we have an author resource, and a book resource with a nested author attribute (in ActiveRecord
364
+ # parlance, Book belongs_to Author, Author has_many Book).
365
+ #
366
+ # Now suppose we've created the following cucumber scenario:
367
+ #
368
+ # Scenario: Browsing books
369
+ # Given the following author:
370
+ # | name | date_of_birth |
371
+ # | Arthur C. Clarke | 1917-12-16 |
372
+ #
373
+ # And the following books:
374
+ # | name | author | published | genre |
375
+ # | 2001: A Space Odyssey | Arthur C. Clarke | 1968 | sci-fi |
376
+ # | A Fall of Moondust | Arthur C. Clarke | 1961 | fantasy |
377
+ # | Rendezvous with Rama | Arthur C. Clarke | 1972 | sci-fi |
378
+ #
379
+ # When....
380
+ #
381
+ # To link up the book and author, we could create the following book definition
382
+ #
383
+ # Dupe.define :book do |book|
384
+ # book.author {|name| Dupe.find(:author) {|a| a.name == name}}
385
+ # end
386
+ #
387
+ # The line <tt>Dupe.find(:author) {|a| a.name == name}</tt> could be translated as
388
+ # "find the first author record where the author's name equals `name`".
389
+ #
390
+ # Dupe decided to return only a single record because we specified <tt>find(:author)</tt>.
391
+ # Had we instead specified <tt>find(:authors)</tt>, resource factory would have instead returned an array of results.
392
+ #
393
+ # More examples:
394
+ #
395
+ # # find all books written in the 1960's
396
+ # Dupe.find(:books) {|b| b.published >= 1960 and b.published <= 1969}
397
+ #
398
+ # # return the first book found that was written by Arthur C. Clarke (nested resources example)
399
+ # Dupe.find(:book) {|b| b.author.name == 'Arthur C. Clarke'}
400
+ #
401
+ # # find all sci-fi and fantasy books
402
+ # Dupe.find(:books) {|b| b.genre == 'sci-fi' or b.genre == 'fantasy'}
403
+ #
404
+ # # find all books written by people named 'Arthur'
405
+ # Dupe.find(:books) {|b| b.author.name.match /Arthur/}
406
+ #
407
+ # Also, if you have the need to explicitly specify :all or :first instead of relying on specifying the singular v. plural
408
+ # version of your resource name (perhaps the singular and plural version of your resource are exactly the same):
409
+ #
410
+ # Dupe.find(:all, :deer) {|d| d.type == 'doe'}
411
+ # Dupe.find(:first, :deer) {|d| d.name == 'Bambi'}
412
+ def find(*args, &block) # yield: record
413
+ all_or_first, factory_name = args[-2], args[-1]
414
+ match = block ? block : proc {true}
415
+ all_or_first = ((factory_name.to_s.pluralize == factory_name.to_s) ? :all : :first) unless all_or_first
416
+ factory_name = factory_name.to_s.singularize.to_sym
417
+ verify_factory_exists factory_name
418
+ result = factories[factory_name].find_records_like match
419
+ all_or_first == :all ? result : result.first
420
+ end
421
+
422
+ def get_factory(factory) #:nodoc:
423
+ setup_factory(factory)
424
+ @factories[factory]
425
+ end
426
+
427
+ def flush(factory=nil, destroy_definitions=false) #:nodoc:
428
+ if factory and factories[factory]
429
+ factories[factory].flush(destroy_definitions)
430
+ else
431
+ factories.each {|factory_name, factory| factory.flush(destroy_definitions)}
432
+ end
433
+ end
434
+
435
+ def factories #:nodoc:
436
+ @factories ||= {}
437
+ end
438
+
439
+ private
440
+
441
+ def setup_factory(factory)
442
+ factories[factory] = Dupe.new(factory) unless factories[factory]
443
+ end
444
+
445
+ def reset(factory)
446
+ factories[factory].flush if factories[factory]
447
+ end
448
+
449
+ def verify_factory_exists(factory_name)
450
+ raise "Dupe doesn't know about the '#{factory_name}' resource" unless factories[factory_name]
451
+ end
452
+ end
453
+
454
+ def flush(destroy_definitions=false) #:nodoc:
455
+ @records = []
456
+ @sequence = Sequence.new
457
+ @attributes = {} if destroy_definitions
458
+ ActiveResource::HttpMock.reset_from_dupe!
459
+ end
460
+
461
+ def stub_services_with(record_template, count=1, starting_value=1, sequence_attribute=nil) #:nodoc:
462
+ sequence_attribute ||= record_template.keys.first
463
+ records = stub_records(record_template, count, starting_value, sequence_attribute)
464
+ generate_services_for(records, true)
465
+ end
466
+
467
+ def initialize(factory) #:nodoc:
468
+ @factory_name = factory
469
+ @attributes = {}
470
+ @config = Configuration.new
471
+ @mocker = MockServiceResponse.new(@factory_name)
472
+ @records = []
473
+ end
474
+
475
+ def method_missing(method_name, *args, &block) #:nodoc:
476
+ args = [nil] if args.empty?
477
+ args << block
478
+ define_attribute(method_name.to_sym, *args)
479
+ end
480
+
481
+ def generate_services_for(records, records_already_processed=false) #:nodoc:
482
+ records = process_records records unless records_already_processed
483
+ @mocker.run_mocks(@records, @config.config[:record_identifiers])
484
+ end
485
+
486
+ def find_records_like(match) #:nodoc:
487
+ @records.select {|r| match.call Record.new(r)}
488
+ end
489
+
490
+ private
491
+ def define_attribute(name, default_value=nil, prock=nil)
492
+ @attributes[name] = Attribute.new(name, default_value, prock)
493
+ end
494
+
495
+ def process_records(records)
496
+ records.map {|r| generate_record({:id => sequence}.merge(r))}
497
+ end
498
+
499
+ def generate_record(overrides={})
500
+ define_missing_attributes(overrides.keys)
501
+ record = {}
502
+ @attributes.each do |attr_key, attr_class|
503
+ override_default_value = overrides[attr_key] || overrides[attr_key.to_s]
504
+ record[attr_key] = attr_class.value(override_default_value)
505
+ end
506
+ @records << record
507
+ record
508
+ end
509
+
510
+ def sequence
511
+ (@sequence ||= Sequence.new).next
512
+ end
513
+
514
+ def define_missing_attributes(keys)
515
+ keys.each {|k| define_attribute(k.to_sym) unless @attributes[k.to_sym]}
516
+ end
517
+
518
+ def stub_records(record_template, count, starting_value, sequence_attribute)
519
+ overrides = record_template.merge({sequence_attribute => (record_template[sequence_attribute].to_s + starting_value.to_s), :id => sequence})
520
+ return [generate_record(overrides)] if count <= 1
521
+ [generate_record(overrides)] + stub_records(record_template, count-1, starting_value+1, sequence_attribute)
522
+ end
523
+
524
+ end
@@ -0,0 +1,52 @@
1
+ class Dupe
2
+ class MockServiceResponse #:nodoc:
3
+ attr_reader :mocks
4
+ attr_reader :resource_name
5
+ attr_reader :format
6
+
7
+ def initialize(resource_name, format=:xml)
8
+ @mocks = []
9
+ @resource_name = resource_name
10
+ @to_format = "to_#{format}"
11
+ end
12
+
13
+ def define_mock(prock)
14
+ @mocks << prock
15
+ end
16
+
17
+ def method_missing(method_name, *args, &block)
18
+ @mocks << block
19
+ end
20
+
21
+ def run_mocks(records, identifiers)
22
+ ActiveResource::HttpMock.respond_to do |mock|
23
+ @mocks.each do |a_mock|
24
+ a_mock.call mock, records
25
+ end
26
+ end
27
+ find_all(records)
28
+ records.each {|r| find_one(r, identifiers)}
29
+ end
30
+
31
+
32
+ private
33
+ def find_all(records)
34
+ ActiveResource::HttpMock.respond_to do |mock|
35
+ mock.get "/#{@resource_name.to_s.pluralize}.xml", {}, format_for_service_response(records)
36
+ end
37
+ end
38
+
39
+ def find_one(record, identifiers)
40
+ ActiveResource::HttpMock.respond_to do |mock|
41
+ identifiers.each do |identifier|
42
+ mock.get "/#{@resource_name.to_s.pluralize}/#{record[identifier]}.xml", {}, format_for_service_response(record)
43
+ end
44
+ end
45
+ end
46
+
47
+ def format_for_service_response(records)
48
+ root = (records.is_a? Array) ? @resource_name.to_s.pluralize : @resource_name.to_s
49
+ @format == :json ? records.to_json({:root => root}): records.to_xml({:root => root})
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ class Dupe
2
+ class Record #:nodoc:
3
+ def initialize(hash)
4
+ @attributes = hash.merge(hash) {|k,v| v.is_a?(Hash) ? Record.new(v) : v}
5
+ end
6
+
7
+ def method_missing(method_name, *args, &block)
8
+ @attributes[method_name.to_sym]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class Dupe
2
+ class Sequence #:nodoc:
3
+ def initialize(start=0)
4
+ @sequence_value = start
5
+ end
6
+
7
+ def next
8
+ @sequence_value += 1
9
+ end
10
+ end
11
+ end
data/lib/dupe.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'active_resource'
2
+ require 'active_resource/http_mock'
3
+ require 'dupe/dupe'
4
+ require 'dupe/sequence'
5
+ require 'dupe/mock_service_response'
6
+ require 'dupe/configuration'
7
+ require 'dupe/attribute'
8
+ require 'dupe/active_resource'
9
+ require 'dupe/cucumber_hooks'
10
+ require 'dupe/record'
11
+
12
+ path = defined?(RAILS_ROOT) ? RAILS_ROOT + '/features/dupe_definitions' : '../features/dupe_definitions'
13
+ if File.directory? path
14
+ Dir[File.join(path, '*.rb')].each do |file|
15
+ require file
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: moonmaster9000-dupe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Matt Parker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-14 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activeresource
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: cucumber
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.98
34
+ version:
35
+ description: Dupe rides on top of ActiveResource to allow you to cuke the client side of a service-oriented app without having to worry about whether or not the service is live or available while cuking.
36
+ email: moonmaster9000@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - README.rdoc
45
+ - lib/dupe.rb
46
+ - lib/dupe/active_resource.rb
47
+ - lib/dupe/attribute.rb
48
+ - lib/dupe/configuration.rb
49
+ - lib/dupe/cucumber_hooks.rb
50
+ - lib/dupe/dupe.rb
51
+ - lib/dupe/mock_service_response.rb
52
+ - lib/dupe/record.rb
53
+ - lib/dupe/sequence.rb
54
+ has_rdoc: false
55
+ homepage: http://github.com/moonmaster9000/dupe
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --charset=UTF-8
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.2.0
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: A tool that helps you mock services while cuking.
80
+ test_files: []
81
+