moonmaster9000-dupe 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+