dupe 0.6.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/dupe/active_resource_extensions.rb +6 -1
- data/spec/lib_specs/active_resource_extensions_spec.rb +7 -4
- data/spec/lib_specs/attribute_template_spec.rb +2 -2
- data/spec/lib_specs/database_spec.rb +1 -1
- data/spec/lib_specs/dupe_spec.rb +1 -1
- data/spec/lib_specs/hash_pruner_spec.rb +1 -1
- data/spec/lib_specs/log_spec.rb +2 -2
- data/spec/lib_specs/logged_request_spec.rb +2 -2
- data/spec/lib_specs/mock_definitions_spec.rb +2 -2
- data/spec/lib_specs/mock_spec.rb +1 -1
- data/spec/lib_specs/model_spec.rb +2 -2
- data/spec/lib_specs/network_spec.rb +1 -1
- data/spec/lib_specs/record_spec.rb +2 -2
- data/spec/lib_specs/rest_validation_spec.rb +2 -2
- data/spec/lib_specs/schema_spec.rb +1 -1
- data/spec/lib_specs/sequence_spec.rb +1 -1
- data/spec/lib_specs/string_spec.rb +2 -2
- data/spec/lib_specs/symbol_spec.rb +2 -2
- data/spec/spec_helper.rb +0 -4
- metadata +28 -26
- data/README.rdoc +0 -642
- data/rails_generators/dupe/dupe_generator.rb +0 -20
- data/rails_generators/dupe/templates/custom_mocks.rb +0 -4
- data/rails_generators/dupe/templates/definitions.rb +0 -9
- data/rails_generators/dupe/templates/load_dupe.rb +0 -9
@@ -19,7 +19,12 @@ module ActiveResource #:nodoc:
|
|
19
19
|
response = request(:get, path, build_request_headers(headers, :get, self.site.merge(path)))
|
20
20
|
ActiveResource::HttpMock.delete_mock(:get, path)
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
|
+
if ActiveResource::VERSION::MAJOR == 3 && ActiveResource::VERSION::MINOR >= 1
|
24
|
+
response
|
25
|
+
else
|
26
|
+
format.decode(response.body)
|
27
|
+
end
|
23
28
|
end
|
24
29
|
|
25
30
|
def post(path, body = '', headers = {}) #:nodoc:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe ActiveResource::Connection do
|
4
4
|
before do
|
@@ -9,7 +9,8 @@ describe ActiveResource::Connection do
|
|
9
9
|
before do
|
10
10
|
@book = Dupe.create :book, :title => 'Rooby', :label => 'rooby'
|
11
11
|
class Book < ActiveResource::Base
|
12
|
-
self.site
|
12
|
+
self.site = 'http://www.example.com'
|
13
|
+
self.format = :xml
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
@@ -81,7 +82,8 @@ describe ActiveResource::Connection do
|
|
81
82
|
context "put methods that return HTTP 204" do
|
82
83
|
before(:each) do
|
83
84
|
class ExpirableBook < ActiveResource::Base
|
84
|
-
self.site
|
85
|
+
self.site = 'http://www.example.com'
|
86
|
+
self.format = :xml
|
85
87
|
attr_accessor :state
|
86
88
|
|
87
89
|
def expire_copyrights!
|
@@ -140,7 +142,8 @@ describe ActiveResource::Connection do
|
|
140
142
|
before do
|
141
143
|
@book = Dupe.create :book, :label => 'rooby', :title => 'Rooby'
|
142
144
|
class Book < ActiveResource::Base
|
143
|
-
self.site
|
145
|
+
self.site = 'http://www.example.com'
|
146
|
+
self.format = :xml
|
144
147
|
end
|
145
148
|
@ar_book = Book.find(1)
|
146
149
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Dupe::Model::Schema::AttributeTemplate do
|
4
4
|
describe "new" do
|
@@ -170,4 +170,4 @@ describe Dupe::Model::Schema::AttributeTemplate do
|
|
170
170
|
end
|
171
171
|
end
|
172
172
|
end
|
173
|
-
end
|
173
|
+
end
|
data/spec/lib_specs/dupe_spec.rb
CHANGED
data/spec/lib_specs/log_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Dupe::Network::Log::Request do
|
4
4
|
describe "##new" do
|
@@ -19,4 +19,4 @@ describe Dupe::Network::Log::Request do
|
|
19
19
|
" who's there?"
|
20
20
|
end
|
21
21
|
end
|
22
|
-
end
|
22
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "Mock Definition Methods" do
|
4
4
|
before do
|
@@ -55,4 +55,4 @@ describe "Mock Definition Methods" do
|
|
55
55
|
Dupe.network.request(:post, '/books.xml', book_post).should == [Dupe.find(:book) {|b| b.id == 4}.to_xml_safe(:root => 'book'), "/books/4.xml"]
|
56
56
|
end
|
57
57
|
end
|
58
|
-
end
|
58
|
+
end
|
data/spec/lib_specs/mock_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Dupe::Network::RestValidation do
|
4
4
|
before do
|
@@ -14,4 +14,4 @@ describe Dupe::Network::RestValidation do
|
|
14
14
|
}.should raise_error(Dupe::Network::UnknownRestVerbError)
|
15
15
|
end
|
16
16
|
end
|
17
|
-
end
|
17
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe String do
|
4
4
|
describe "plural?" do
|
@@ -28,4 +28,4 @@ describe String do
|
|
28
28
|
"apples\noranges".indent(2).should == " apples\n oranges"
|
29
29
|
end
|
30
30
|
end
|
31
|
-
end
|
31
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dupe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
- 6
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 1.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matt Parker
|
@@ -15,8 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
19
|
-
default_executable:
|
18
|
+
date: 2011-08-04 00:00:00 Z
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
22
21
|
name: activeresource
|
@@ -24,31 +23,38 @@ dependencies:
|
|
24
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
24
|
none: false
|
26
25
|
requirements:
|
27
|
-
- -
|
26
|
+
- - ~>
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
28
|
+
hash: 7
|
30
29
|
segments:
|
31
30
|
- 3
|
32
31
|
- 0
|
33
|
-
|
34
|
-
- beta2
|
35
|
-
version: 3.0.0.beta2
|
32
|
+
version: "3.0"
|
36
33
|
type: :runtime
|
37
34
|
version_requirements: *id001
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: TDD your services outside in by starting at the client, then working your way back to the server.
|
42
50
|
email: moonmaster9000@gmail.com
|
43
51
|
executables: []
|
44
52
|
|
45
53
|
extensions: []
|
46
54
|
|
47
|
-
extra_rdoc_files:
|
48
|
-
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
49
57
|
files:
|
50
|
-
- README.rdoc
|
51
|
-
- lib/dupe.rb
|
52
58
|
- lib/dupe/active_resource_extensions.rb
|
53
59
|
- lib/dupe/attribute_template.rb
|
54
60
|
- lib/dupe/cucumber_hooks.rb
|
@@ -67,10 +73,7 @@ files:
|
|
67
73
|
- lib/dupe/singular_plural_detection.rb
|
68
74
|
- lib/dupe/string.rb
|
69
75
|
- lib/dupe/symbol.rb
|
70
|
-
-
|
71
|
-
- rails_generators/dupe/templates/custom_mocks.rb
|
72
|
-
- rails_generators/dupe/templates/definitions.rb
|
73
|
-
- rails_generators/dupe/templates/load_dupe.rb
|
76
|
+
- lib/dupe.rb
|
74
77
|
- spec/lib_specs/active_resource_extensions_spec.rb
|
75
78
|
- spec/lib_specs/attribute_template_spec.rb
|
76
79
|
- spec/lib_specs/database_spec.rb
|
@@ -89,7 +92,6 @@ files:
|
|
89
92
|
- spec/lib_specs/string_spec.rb
|
90
93
|
- spec/lib_specs/symbol_spec.rb
|
91
94
|
- spec/spec_helper.rb
|
92
|
-
has_rdoc: true
|
93
95
|
homepage: http://github.com/moonmaster9000/dupe
|
94
96
|
licenses: []
|
95
97
|
|
@@ -119,10 +121,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
121
|
requirements: []
|
120
122
|
|
121
123
|
rubyforge_project:
|
122
|
-
rubygems_version: 1.
|
124
|
+
rubygems_version: 1.8.5
|
123
125
|
signing_key:
|
124
126
|
specification_version: 3
|
125
|
-
summary:
|
127
|
+
summary: Dupe - a testing library for ActiveResource
|
126
128
|
test_files:
|
127
129
|
- spec/lib_specs/active_resource_extensions_spec.rb
|
128
130
|
- spec/lib_specs/attribute_template_spec.rb
|
data/README.rdoc
DELETED
@@ -1,642 +0,0 @@
|
|
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
|
-
|
5
|
-
But what about prototyping ActiveResource records? That's where Dupe steps in.
|
6
|
-
|
7
|
-
== Motivation
|
8
|
-
|
9
|
-
If you're going to create a service-oriented rails app with ActiveResource, why not cuke the front end first?
|
10
|
-
Let the behavior of the front-end drive the services you build on the backend. That's exactly what Dupe makes possible.
|
11
|
-
|
12
|
-
== Installation
|
13
|
-
|
14
|
-
If you want to install this for use in something other than a rails project, simply:
|
15
|
-
|
16
|
-
# gem install dupe
|
17
|
-
|
18
|
-
== Rails 3
|
19
|
-
|
20
|
-
Dupe versions 0.6.0 and greater only work with Rails 3 / ActiveResource 3. If you're working on a Rails 2.* project, use Dupe version 0.5.3.
|
21
|
-
|
22
|
-
= Tutorial
|
23
|
-
|
24
|
-
Checkout the {dupe example application}[http://github.com/moonmaster9000/dupe_example_app] for a tutorial on cuking an application with
|
25
|
-
ActiveResource and Dupe.
|
26
|
-
|
27
|
-
= Features
|
28
|
-
|
29
|
-
==Creating resources
|
30
|
-
|
31
|
-
Dupe allows you to quickly create resources, even if you have yet to define them. For example:
|
32
|
-
|
33
|
-
irb# require 'dupe'
|
34
|
-
==> true
|
35
|
-
|
36
|
-
irb# b = Dupe.create :book, :title => '2001'
|
37
|
-
==> <#Duped::Book title="2001" id=1>
|
38
|
-
|
39
|
-
irb# a = Dupe.create :author, :name => 'Arthur C. Clarke'
|
40
|
-
==> <#Duped::Author name="Arthur C. Clarke" id=1>
|
41
|
-
|
42
|
-
irb# b.author
|
43
|
-
==> nil
|
44
|
-
|
45
|
-
irb# b.author = a
|
46
|
-
==> <#Duped::Author name="Arthur C. Clarke" id=1>
|
47
|
-
|
48
|
-
irb# b
|
49
|
-
==> <#Duped::Book author=<#Duped::Author name="Arthur C. Clarke" id=1> title="2001" id=1>
|
50
|
-
|
51
|
-
|
52
|
-
Dupe also provides a way for us to quickly to generate a large number of resources. For example, suppose we have a cucumber scenario that tests paginating through lists of books. To easily create 50 unique books, we could use the Dupe.stub method:
|
53
|
-
|
54
|
-
irb# Dupe.stub 50, :books, :like => {:title => proc {|n| "book ##{n} title"}}
|
55
|
-
==> [<#Duped::Book title="book #1 title" id=1>, <#Duped::Book title="book #2 title" id=2>, ...]
|
56
|
-
|
57
|
-
Notice that each book has a unique title, achieved by passing the "proc {|n| "book ##{n} title"}" as the value for the title.
|
58
|
-
|
59
|
-
|
60
|
-
==Finding Resources
|
61
|
-
|
62
|
-
Dupe also has a built-in querying system for finding resources you create. In your tests / cucumber step definitions, you'll most likely be using this approach for finding resources. If you're wondering how your app (i.e., ActiveResource) can find resources you create, skip down to the section on ActiveResource.
|
63
|
-
|
64
|
-
irb# a = Dupe.create :author, :name => 'Monkey'
|
65
|
-
==> <#Duped::Author name="Monkey" id=1>
|
66
|
-
|
67
|
-
irb# b = Dupe.create :book, :title => 'Bananas', :author => a
|
68
|
-
==> <#Duped::Book author=<#Duped::Author name="Monkey" id=1> title="Bananas" id=1>
|
69
|
-
|
70
|
-
irb# Dupe.find(:author) {|a| a.name == 'Monkey'}
|
71
|
-
==> <#Duped::Author name="Monkey" id=1>
|
72
|
-
|
73
|
-
irb# Dupe.find(:book) {|b| b.author.name == 'Monkey'}
|
74
|
-
==> <#Duped::Book author=<#Duped::Author name="Monkey" id=1> title="Bananas" id=1>
|
75
|
-
|
76
|
-
irb# Dupe.find(:author) {|a| a.id == 1}
|
77
|
-
==> <#Duped::Author name="Monkey" id=1>
|
78
|
-
|
79
|
-
irb# Dupe.find(:author) {|a| a.id == 2}
|
80
|
-
==> nil
|
81
|
-
|
82
|
-
In all cases, notice that we provided the singular form of a model name to Dupe.find. This ensures that we either get back either a single resource (if the query was successful), or _nil_.
|
83
|
-
|
84
|
-
If we'd like to find several resources, we can use the plural form of the model name. For example:
|
85
|
-
|
86
|
-
irb# a = Dupe.create :author, :name => 'Monkey', :published => true
|
87
|
-
==> <#Duped::Author published=true name="Monkey" id=1>
|
88
|
-
|
89
|
-
irb# b = Dupe.create :book, :title => 'Bananas', :author => a
|
90
|
-
==> <#Duped::Book author=<#Duped::Author published=true name="Monkey" id=1> title="Bananas" id=1>
|
91
|
-
|
92
|
-
irb# Dupe.create :author, :name => 'Tiger', :published => false
|
93
|
-
==> <#Duped::Author published=false name="Tiger" id=2>
|
94
|
-
|
95
|
-
irb# Dupe.find(:authors)
|
96
|
-
==> [<#Duped::Author published=true name="Monkey" id=1>, <#Duped::Author published=false name="Tiger" id=2>]
|
97
|
-
|
98
|
-
irb# Dupe.find(:authors) {|a| a.published == true}
|
99
|
-
==> [<#Duped::Author published=true name="Monkey" id=1>]
|
100
|
-
|
101
|
-
irb# Dupe.find(:books)
|
102
|
-
==> [<#Duped::Book author=<#Duped::Author published=true name="Monkey" id=1> title="Bananas" id=1>]
|
103
|
-
|
104
|
-
irb# Dupe.find(:books) {|b| b.author.published == false}
|
105
|
-
==> []
|
106
|
-
|
107
|
-
Notice that by using the plural form of the model name, we ensure that we receive back an array - even in the case that the query did not find any results (it simply returns an empty array).
|
108
|
-
|
109
|
-
|
110
|
-
==Finding or Creating Resources
|
111
|
-
|
112
|
-
You might have seen this one coming.
|
113
|
-
|
114
|
-
Let's assume no genres currently exist. If we call the "find_or_create" method, it will create a new :genre.
|
115
|
-
|
116
|
-
irb# Dupe.find_or_create :genre
|
117
|
-
==> <#Duped::Genre id=1>
|
118
|
-
|
119
|
-
If we call it again, it will find the :genre we already created:
|
120
|
-
|
121
|
-
irb# Dupe.find_or_create :genre
|
122
|
-
==> <#Duped::Genre id=1>
|
123
|
-
|
124
|
-
You can also pass conditions to find_or_create as a hash:
|
125
|
-
|
126
|
-
irb# Dupe.find_or_create :genre, :name => 'Science Fiction', :label => 'sci-fi'
|
127
|
-
==> <#Duped::Genre label="sci-fi" name="Science Fiction" id=2>
|
128
|
-
|
129
|
-
irb# Dupe.find_or_create :genre, :name => 'Science Fiction', :label => 'sci-fi'
|
130
|
-
==> <#Duped::Genre label="sci-fi" name="Science Fiction" id=2>
|
131
|
-
|
132
|
-
== Defining a resource
|
133
|
-
|
134
|
-
Though often we may get away with creating resources willy-nilly, it's sometimes quite handy to define a resource, giving it default attributes and callbacks.
|
135
|
-
|
136
|
-
=== Attributes with default values
|
137
|
-
|
138
|
-
Suppose we're creating a 'book' resource. Perhaps our app assumes every book has a title, so let's define a book resource
|
139
|
-
that specifies just that:
|
140
|
-
|
141
|
-
irb# Dupe.define :book do |attrs|
|
142
|
-
--# attrs.title 'Untitled'
|
143
|
-
--# attrs.author
|
144
|
-
--# end
|
145
|
-
==> #<Dupe::Model:0x17b2694 ...>
|
146
|
-
|
147
|
-
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.
|
148
|
-
|
149
|
-
irb# b = Dupe.create :book
|
150
|
-
==> <#Duped::Book author=nil title="Untitled" id=1>
|
151
|
-
|
152
|
-
|
153
|
-
If we provide our own title, it should allow us to override the default value:
|
154
|
-
|
155
|
-
irb# b = Dupe.create :book, :title => 'Monkeys!'
|
156
|
-
==> <#Duped::Book author=nil title="Monkeys!" id=2>
|
157
|
-
|
158
|
-
=== Attributes with procs as default values
|
159
|
-
|
160
|
-
Sometimes it might be convenient to procedurally define the default value for an attribute:
|
161
|
-
|
162
|
-
irb# Dupe.define :book do |attrs|
|
163
|
-
--# attrs.title 'Untitled'
|
164
|
-
--# attrs.author
|
165
|
-
--# attrs.isbn do
|
166
|
-
--# rand(1000000)
|
167
|
-
--# end
|
168
|
-
--# end
|
169
|
-
|
170
|
-
Now, every time we create a book, it will get assigned a random ISBN number:
|
171
|
-
|
172
|
-
irb# b = Dupe.create :book
|
173
|
-
==> <#Duped::Book author=nil title="Untitled" id=1 isbn=895825>
|
174
|
-
|
175
|
-
irb# b = Dupe.create :book
|
176
|
-
==> <#Duped::Book author=nil title="Untitled" id=2 isbn=606472>
|
177
|
-
|
178
|
-
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:
|
179
|
-
|
180
|
-
irb# Dupe.define :book do |attrs|
|
181
|
-
--# attrs.title 'Untitled'
|
182
|
-
--# attrs.author
|
183
|
-
--# attrs.isbn do
|
184
|
-
--# rand(1000000)
|
185
|
-
--# end
|
186
|
-
--# attrs.genre do
|
187
|
-
--# Dupe.find_or_create :genre
|
188
|
-
--# end
|
189
|
-
--# end
|
190
|
-
|
191
|
-
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.
|
192
|
-
|
193
|
-
First, let's confirm that no genres currently exist:
|
194
|
-
|
195
|
-
irb# Dupe.find :genre
|
196
|
-
Dupe::Database::TableDoesNotExistError: The table ':genre' does not exist.
|
197
|
-
from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/database.rb:30:in `select'
|
198
|
-
from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/dupe.rb:295:in `find'
|
199
|
-
from (irb):135
|
200
|
-
|
201
|
-
Next, let's create a book:
|
202
|
-
|
203
|
-
irb# b = Dupe.create :book
|
204
|
-
==> <#Duped::Book genre=<#Duped::Genre id=1> author=nil title="Untitled" id=1 isbn=62572>
|
205
|
-
|
206
|
-
Notice that it create a genre. If we tried to do another Dupe.find for the genre:
|
207
|
-
|
208
|
-
irb# Dupe.find :genre
|
209
|
-
==> <#Duped::Genre id=1>
|
210
|
-
|
211
|
-
Now, if create another book, it will associate with the genre that was just created:
|
212
|
-
|
213
|
-
irb# b = Dupe.create :book
|
214
|
-
==> <#Duped::Book genre=<#Duped::Genre id=1> author=nil title="Untitled" id=2 isbn=729317>
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
=== Attributes with transformers
|
219
|
-
|
220
|
-
Occasionally, you may find it useful to have attribute values transformed upon creation.
|
221
|
-
|
222
|
-
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.
|
223
|
-
|
224
|
-
irb# Dupe.define :book do |attrs|
|
225
|
-
--# attrs.title 'Untitled'
|
226
|
-
--# attrs.author
|
227
|
-
--# attrs.isbn do
|
228
|
-
--# rand(1000000)
|
229
|
-
--# end
|
230
|
-
--# attrs.publish_date do |publish_date|
|
231
|
-
--# Date.parse(publish_date)
|
232
|
-
--# end
|
233
|
-
--# end
|
234
|
-
|
235
|
-
Now, let's create a book:
|
236
|
-
|
237
|
-
irb# b = Dupe.create :book, :publish_date => '2009-12-29'
|
238
|
-
==> <#Duped::Book author=nil title="Untitled" publish_date=Tue, 29 Dec 2009 id=1 isbn=826291>
|
239
|
-
|
240
|
-
irb# b.publish_date
|
241
|
-
==> Tue, 29 Dec 2009
|
242
|
-
|
243
|
-
irb# b.publish_date.class
|
244
|
-
==> Date
|
245
|
-
|
246
|
-
|
247
|
-
=== Uniquify attributes
|
248
|
-
|
249
|
-
If you'd just like to make sure that some attributes get a unique value, then you can use the uniquify
|
250
|
-
method:
|
251
|
-
|
252
|
-
irb# Dupe.define :book do |attrs|
|
253
|
-
--# attrs.uniquify :title, :genre, :author
|
254
|
-
--# end
|
255
|
-
|
256
|
-
Now, Dupe will do its best to assign unique values to the :title, :genre, and :author attributes on
|
257
|
-
any records it creates:
|
258
|
-
|
259
|
-
irb# b = Dupe.create :book
|
260
|
-
==> <#Duped::Book author="book 1 author" title="book 1 title" genre="book 1 genre" id=1>
|
261
|
-
|
262
|
-
irb# b2 = Dupe.create :book, :title => 'Rooby'
|
263
|
-
==> <#Duped::Book author="book 2 author" title="Rooby" genre="book 2 genre" id=2>
|
264
|
-
|
265
|
-
|
266
|
-
=== Sequences
|
267
|
-
|
268
|
-
The "uniquify" method is great if don't care too much about the format of the values it creates. But what if you'd like to ensure
|
269
|
-
that the value of an attribute conforms to a specific format?
|
270
|
-
|
271
|
-
irb# Dupe.sequence :email do |n|
|
272
|
-
--# "email-#{n}@somewhere.com"
|
273
|
-
--# end
|
274
|
-
|
275
|
-
irb# Dupe.define :user do |user|
|
276
|
-
--# user.uniquify :name
|
277
|
-
--# user.email do
|
278
|
-
--# Dupe.next :email
|
279
|
-
--# end
|
280
|
-
--# end
|
281
|
-
|
282
|
-
irb# Dupe.create :user
|
283
|
-
==> <#Duped::User name="user 1 name" id=1 email="email-1@somewhere.com">
|
284
|
-
|
285
|
-
irb# Dupe.create :user
|
286
|
-
==> <#Duped::User name="user 2 name" id=2 email="email-2@somewhere.com">
|
287
|
-
|
288
|
-
|
289
|
-
=== Callbacks
|
290
|
-
|
291
|
-
Suppose we'd like to make sure that our books get a unique label. We can accomplish that with an after_create callback:
|
292
|
-
|
293
|
-
irb# Dupe.define :book do |attrs|
|
294
|
-
--# attrs.title 'Untitled'
|
295
|
-
--# attrs.author
|
296
|
-
--# attrs.isbn do
|
297
|
-
--# rand(1000000)
|
298
|
-
--# end
|
299
|
-
--# attrs.publish_date do |publish_date|
|
300
|
-
--# Date.parse(publish_date)
|
301
|
-
--# end
|
302
|
-
--# attrs.after_create do |book|
|
303
|
-
--# book.label = book.title.downcase.gsub(/\ +/, '-') + "--#{book.id}"
|
304
|
-
--# end
|
305
|
-
--# end
|
306
|
-
|
307
|
-
irb# b = Dupe.create :book, :title => 'Rooby on Rails'
|
308
|
-
==> <#Duped::Book author=nil label="rooby-on-rails--1" title="Rooby on Rails" publish_date=nil id=1 isbn=842518>
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
= ActiveResource
|
315
|
-
|
316
|
-
So how does Dupe actually help us to spec/test ActiveResource-based applications? It uses a simple, yet sophisticated "intercept-mocking" technique, whereby failed network requests sent by ActiveResource fallback to the "Duped" network. Consider the following:
|
317
|
-
|
318
|
-
irb# Dupe.create :book, :title => 'Monkeys!'
|
319
|
-
==> <#Duped::Book title="Monkeys!" id=1>
|
320
|
-
|
321
|
-
irb# class Book < ActiveResource::Base; self.site = ''; end
|
322
|
-
==> ""
|
323
|
-
|
324
|
-
irb# Book.find(1)
|
325
|
-
==> #<Book:0x1868a20 @attributes={"title"=>"Monkeys!", "id"=>1}, prefix_options{}
|
326
|
-
|
327
|
-
Voila! When the _Book_ class was unable to find the book with id 1, it asked Dupe if it knew about any book resources with id 1. Check out the Dupe network log for a clue as to what happened behind the scenes:
|
328
|
-
|
329
|
-
irb# puts Dupe.network.log.pretty_print
|
330
|
-
|
331
|
-
Logged Requests:
|
332
|
-
Request: GET /books/1.xml
|
333
|
-
Response:
|
334
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
335
|
-
<book>
|
336
|
-
<title>Monkeys!</title>
|
337
|
-
<id type="integer">1</id>
|
338
|
-
</book>
|
339
|
-
|
340
|
-
Similarly:
|
341
|
-
|
342
|
-
irb# Book.find(:all)
|
343
|
-
==> [#<Book:0x185608c @attributes={"title"=>"Monkeys!", "id"=>1}, prefix_options{}]
|
344
|
-
|
345
|
-
irb# puts Dupe.network.log.pretty_print
|
346
|
-
|
347
|
-
Logged Requests:
|
348
|
-
Request: GET /books.xml
|
349
|
-
Response:
|
350
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
351
|
-
<books type="array">
|
352
|
-
<book>
|
353
|
-
<title>Monkeys!</title>
|
354
|
-
<id type="integer">1</id>
|
355
|
-
</book>
|
356
|
-
</books>
|
357
|
-
|
358
|
-
|
359
|
-
==Intercept Mocking
|
360
|
-
|
361
|
-
Dupe knew how to handle simple find by id and find :all lookups from ActiveResource. But what about other requests we might potentially make?
|
362
|
-
|
363
|
-
===GET requests
|
364
|
-
|
365
|
-
In this section, you'll learn how to mock custom GET requests.
|
366
|
-
|
367
|
-
irb# Dupe.create :author, :name => 'Monkey', :published => true
|
368
|
-
==> <#Duped::Author name="Monkey" published=true id=1>
|
369
|
-
|
370
|
-
irb# Dupe.create :author, :name => 'Tiger', :published => false
|
371
|
-
==> <#Duped::Author name="Tiger" published=false id=2>
|
372
|
-
|
373
|
-
irb# class Author < ActiveResource::Base; self.site = ''; end
|
374
|
-
==> ""
|
375
|
-
|
376
|
-
irb# Author.find :all, :from => :published
|
377
|
-
==> Dupe::Network::RequestNotFoundError: No mocked service response found for '/authors/published.xml'
|
378
|
-
|
379
|
-
Obviously, Dupe had no way of anticipating this possibility. However, you can create your own custom intercept mock for this:
|
380
|
-
|
381
|
-
irb# Get %r{/authors/published.xml} do
|
382
|
-
--# Dupe.find(:authors) {|a| a.published == true}
|
383
|
-
--# end
|
384
|
-
==> #<Dupe::Network::Mock:0x1833e88 @url_pattern=/\/authors\/published.xml/, @verb=:get, @response=#<Proc:0x01833f14@(irb):13>
|
385
|
-
|
386
|
-
irb# Author.find :all, :from => :published
|
387
|
-
==> [#<Author:0x1821d3c @attributes={"name"=>"Monkey", "published"=>true, "id"=>1}, prefix_options{}]
|
388
|
-
|
389
|
-
irb# puts Dupe.network.log.pretty_print
|
390
|
-
|
391
|
-
Logged Requests:
|
392
|
-
Request: GET /authors/published.xml
|
393
|
-
Response:
|
394
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
395
|
-
<authors type="array">
|
396
|
-
<author>
|
397
|
-
<name>Monkey</name>
|
398
|
-
<published type="boolean">true</published>
|
399
|
-
<id type="integer">1</id>
|
400
|
-
</author>
|
401
|
-
</authors>
|
402
|
-
|
403
|
-
|
404
|
-
The "Get" method requires a url pattern and a block. In most cases, your block will return a Dupe.find result. Internally, Dupe will transform that into XML. However, if your "Get" block returns a string, Dupe will use that as the response body and not attempt to do any transformations on it.
|
405
|
-
|
406
|
-
Suppose instead the service expected us to pass published as a query string parameter:
|
407
|
-
|
408
|
-
irb# Author.find :all, :params => {:published => true}
|
409
|
-
Dupe::Network::RequestNotFoundError: No mocked service response found for '/authors.xml?published=true'
|
410
|
-
from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/network.rb:32:in `match'
|
411
|
-
from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/network.rb:17:in `request'
|
412
|
-
from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/active_resource_extensions.rb:15:in `get'
|
413
|
-
from /Library/Ruby/Gems/1.8/gems/activeresource-2.3.5/lib/active_resource/base.rb:639:in `find_every'
|
414
|
-
from /Library/Ruby/Gems/1.8/gems/activeresource-2.3.5/lib/active_resource/base.rb:582:in `find'
|
415
|
-
from (irb):18
|
416
|
-
|
417
|
-
We can mock this with the following:
|
418
|
-
|
419
|
-
irb# Get %r{/authors\.xml\?published=(true|false)$} do |published|
|
420
|
-
--# if published == 'true'
|
421
|
-
--# Dupe.find(:authors) {|a| a.published == true}
|
422
|
-
--# else
|
423
|
-
--# Dupe.find(:authors) {|a| a.published == false}
|
424
|
-
--# end
|
425
|
-
--# end
|
426
|
-
|
427
|
-
irb# Author.find :all, :params => {:published => true}
|
428
|
-
==> [#<Author:0x17db094 @attributes={"name"=>"Monkey", "published"=>true, "id"=>1}, prefix_options{}]
|
429
|
-
|
430
|
-
irb# Author.find :all, :params => {:published => false}
|
431
|
-
==> [#<Author:0x17c68c4 @attributes={"name"=>"Tiger", "published"=>false, "id"=>2}, prefix_options{}]
|
432
|
-
|
433
|
-
irb# puts Dupe.network.log.pretty_print
|
434
|
-
|
435
|
-
Logged Requests:
|
436
|
-
Request: GET /authors.xml?published=true
|
437
|
-
Response:
|
438
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
439
|
-
<authors type="array">
|
440
|
-
<author>
|
441
|
-
<name>Monkey</name>
|
442
|
-
<published type="boolean">true</published>
|
443
|
-
<id type="integer">1</id>
|
444
|
-
</author>
|
445
|
-
</authors>
|
446
|
-
|
447
|
-
Request: GET /authors.xml?published=false
|
448
|
-
Response:
|
449
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
450
|
-
<authors type="array">
|
451
|
-
<author>
|
452
|
-
<name>Tiger</name>
|
453
|
-
<published type="boolean">false</published>
|
454
|
-
<id type="integer">2</id>
|
455
|
-
</author>
|
456
|
-
</authors>
|
457
|
-
|
458
|
-
===POST requests
|
459
|
-
|
460
|
-
Out of the box you get a POST intercept mock:
|
461
|
-
|
462
|
-
irb# Dupe.define :author
|
463
|
-
|
464
|
-
irb# class Author < ActiveResource::Base; self.site = ''; end
|
465
|
-
==> ""
|
466
|
-
|
467
|
-
irb# Author.create :name => "CS Lewis"
|
468
|
-
==> #<Author:0x1a4ca58 @attributes={"name"=>"CS Lewis", "id"=>1}, @prefix_options={}>
|
469
|
-
|
470
|
-
Author.create sent a network POST to /authors.xml and Dupe responded by creating the resource with the requested parameters:
|
471
|
-
|
472
|
-
irb# Dupe.find :authors
|
473
|
-
==> [<#Duped::Author name="CS Lewis" id=1>]
|
474
|
-
|
475
|
-
You can also overwrite the default POST intercept mock for your resource by using the Post method:
|
476
|
-
|
477
|
-
irb# Post %r{/authors\.xml} do |post_data|
|
478
|
-
raise Dupe::UnprocessableEntity.new(:name => " must be present.") unless post_data["name"]
|
479
|
-
Dupe.create :author, post_data
|
480
|
-
end
|
481
|
-
==> #<Dupe::Network::PostMock:0x1a1afe4 @url_pattern=/\/authors\.xml/, @response=#<Proc:0x01a1b084@(irb):13>, @verb=:post>
|
482
|
-
|
483
|
-
Now, when you try to create an Author without a name, it will respond with the appropriate mocked errors.
|
484
|
-
|
485
|
-
irb# Dupe.find(:authors)
|
486
|
-
==> []
|
487
|
-
|
488
|
-
irb# a = Author.create
|
489
|
-
==> a = #<Author:0x1a19fb8 @attributes={}, @errors=#<ActiveResource::Errors:0x1a10bc0 @errors={"base"=>["name must be present."]}, @base=#<Author:0x1a19fb8 ...>>, @prefix_options={}>
|
490
|
-
|
491
|
-
irb# a = a.valid?
|
492
|
-
==> false
|
493
|
-
|
494
|
-
irb# a = a.new?
|
495
|
-
==> true
|
496
|
-
|
497
|
-
Because our custom Post mock determined that the resource was invalid, Dupe did not mock the resource:
|
498
|
-
|
499
|
-
irb# Dupe.find(:authors)
|
500
|
-
==> []
|
501
|
-
|
502
|
-
When we create the Author with the required attributes, it will now be considered valid.
|
503
|
-
|
504
|
-
irb# a = Author.create :name => "CS Lewis"
|
505
|
-
==> #<Author:0x19f1edc @attributes={"name"=>"CS Lewis", "id"=>1}, @prefix_options={}>
|
506
|
-
|
507
|
-
irb# a.valid?
|
508
|
-
==> true
|
509
|
-
|
510
|
-
irb# a.new?
|
511
|
-
==> false
|
512
|
-
|
513
|
-
Since our custom Post mock considered the resource valid, it went ahead and created the resource:
|
514
|
-
|
515
|
-
irb# Dupe.find(:authors)
|
516
|
-
==> [<#Duped::Author name="CS Lewis" id=1>]
|
517
|
-
|
518
|
-
|
519
|
-
===PUT requests
|
520
|
-
|
521
|
-
In ActiveResource, when you update a resource that already exists via the "save" method, it translates to a PUT request.
|
522
|
-
Dupe provides basic PUT intercept mocks out of the box, and like GET and POST mocks,
|
523
|
-
it allows you to override the default PUT intercept mock, and create new ones.
|
524
|
-
|
525
|
-
Let's again examine an "author" resource:
|
526
|
-
|
527
|
-
irb# Dupe.define :author
|
528
|
-
|
529
|
-
irb# class Author < ActiveResource::Base; self.site = ''; end
|
530
|
-
==> ""
|
531
|
-
|
532
|
-
irb# Author.create :name => "CS Lewis" # --> Dupe intercepts this POST request
|
533
|
-
==> #<Author:0x1a4ca58 @attributes={"name"=>"CS Lewis", "id"=>1}, @prefix_options={}>
|
534
|
-
|
535
|
-
irb# Dupe.find :authors
|
536
|
-
==> [<#Duped::Author name="CS Lewis" id=1>]
|
537
|
-
|
538
|
-
irb# a = Author.find 1 # --> Dupe intercepts this GET request
|
539
|
-
==> #<Author:0x1a4ca58 @attributes={"name"=>"CS Lewis", "id"=>1}, @prefix_options={}>
|
540
|
-
|
541
|
-
So far, we've created a resource (via dupe's POST intercept mocking), and we've also found the resource we create (via dupe's GET
|
542
|
-
intercept mocking). Now, let's attempt to update (PUT) the resource:
|
543
|
-
|
544
|
-
irb# a.name = "Frank Herbert"
|
545
|
-
|
546
|
-
irb# a.save # --> Dupe intercepts this PUT request
|
547
|
-
==> true
|
548
|
-
|
549
|
-
Dupe intercepted the PUT request that ActiveResource attempted to send, and updated the Duped resource accordingly:
|
550
|
-
|
551
|
-
irb# a
|
552
|
-
==> #<Author:0x1a4ca58 @attributes={"name"=>"Frank Herbert", "id"=>1}, @prefix_options={}>
|
553
|
-
|
554
|
-
irb# Dupe.find :authors
|
555
|
-
==> [<#Duped::Author name="Frank Herbert" id=1>]
|
556
|
-
|
557
|
-
You can also overwrite the default PUT intercept mock for your resource by using the "Put" method:
|
558
|
-
|
559
|
-
irb# Put %r{/authors/(\d+)\.xml} do |id, put_data|
|
560
|
-
raise Dupe::UnprocessableEntity.new(:name => " must be present.") unless put_data[:name]
|
561
|
-
Dupe.find(:author) {|a| a.id == id.to_i}.merge! put_data
|
562
|
-
end
|
563
|
-
==> #<Dupe::Network::PostMock:0x1a1afe4 @url_pattern=/\/authors\.xml/, @response=#<Proc:0x01a1b084@(irb):13>, @verb=:post>
|
564
|
-
|
565
|
-
Now, if we try to update our Author without a name, it will respond with the appropriate errors.
|
566
|
-
|
567
|
-
irb# Dupe.find :authors
|
568
|
-
==> [<#Duped::Author name="Frank Herbert" id=1>]
|
569
|
-
|
570
|
-
irb# a = Author.find 1 # --> Dupe intercepts this GET request
|
571
|
-
==> #<Author:0x1a4ca58 @attributes={"name"=>"Frank Herbert", "id"=>1}, @prefix_options={}>
|
572
|
-
|
573
|
-
irb# a.name = nil
|
574
|
-
|
575
|
-
irb# a.save
|
576
|
-
==> false
|
577
|
-
|
578
|
-
irb# a.errors.on_base
|
579
|
-
==> ["name must be present"]
|
580
|
-
|
581
|
-
Since our Put intercept mock raise the Dupe::UnprocessableEntity exception,
|
582
|
-
the underlying Duped record remains unchanged, just as we would expect the real service to have operated:
|
583
|
-
|
584
|
-
irb# Dupe.find :authors
|
585
|
-
==> [<#Duped::Author name="Frank Herbert" id=1>]
|
586
|
-
|
587
|
-
We can, of course, at this point still update the name to a non-nil value and attempt to save it again:
|
588
|
-
|
589
|
-
irb# a.name = "Matt Parker"
|
590
|
-
|
591
|
-
irb# a.save
|
592
|
-
==> true
|
593
|
-
|
594
|
-
irb# Dupe.find :authors
|
595
|
-
==> [<#Duped::Author name="Matt Parker" id=1>]
|
596
|
-
|
597
|
-
===DELETE requests
|
598
|
-
|
599
|
-
As you might have guessed, Dupe also supports DELETE intercept mocking (ActiveResource::Base#destroy):
|
600
|
-
|
601
|
-
irb# Dupe.define :author
|
602
|
-
|
603
|
-
irb# class Author < ActiveResource::Base; self.site = ''; end
|
604
|
-
==> ""
|
605
|
-
|
606
|
-
irb# Author.create :name => "CS Lewis" # --> Dupe intercepts this POST request
|
607
|
-
==> #<Author:0x1a4ca58 @attributes={"name"=>"CS Lewis", "id"=>1}, @prefix_options={}>
|
608
|
-
|
609
|
-
irb# Dupe.find :authors
|
610
|
-
==> [<#Duped::Author name="CS Lewis" id=1>]
|
611
|
-
|
612
|
-
irb# a = Author.find 1 # --> Dupe intercepts this GET request
|
613
|
-
==> #<Author:0x1a4ca58 @attributes={"name"=>"CS Lewis", "id"=>1}, @prefix_options={}>
|
614
|
-
|
615
|
-
irb# a.destroy
|
616
|
-
==> #<ActiveResource::Response:0x181c1c0 @body="", @message="200", @code=200, @headers={"Content-Length"=>"0"}
|
617
|
-
|
618
|
-
irb# Dupe.find :authors
|
619
|
-
==> []
|
620
|
-
|
621
|
-
And also, as you might have guessed, you can override the default DELETE intercept mock for a resource:
|
622
|
-
|
623
|
-
irb# Delete %r{/books/(\d+)\.xml} do |id|
|
624
|
-
puts "deleting the book with id #{id}"
|
625
|
-
Dupe.delete(:book) {|b| b.id == id.to_i}
|
626
|
-
end
|
627
|
-
|
628
|
-
irb# a = Author.create :name => "some author"
|
629
|
-
|
630
|
-
irb# a.destroy
|
631
|
-
==> "deleting the book with id 1"
|
632
|
-
|
633
|
-
|
634
|
-
== More
|
635
|
-
|
636
|
-
Consult the API documentation at http://moonmaster9000.github.com/dupe/api/
|
637
|
-
|
638
|
-
|
639
|
-
== TODO List
|
640
|
-
|
641
|
-
* We need "Put", "Post", and "Delete", and "Head" intercept mocking methods. Currently we only have "Get".
|
642
|
-
* We need a rake task that will run your cucumber scenarios and create service documentation based on the dupe log output (i.e., example requests and example responses) that the programmers implementing the service can use as a reference.
|
@@ -1,20 +0,0 @@
|
|
1
|
-
class DupeGenerator < Rails::Generator::Base
|
2
|
-
def manifest
|
3
|
-
record do |m|
|
4
|
-
# make sure the features and features/support directories exist
|
5
|
-
m.directory 'features/support'
|
6
|
-
m.directory 'features/dupe'
|
7
|
-
m.directory 'features/dupe/custom_mocks'
|
8
|
-
m.directory 'features/dupe/definitions'
|
9
|
-
|
10
|
-
# copy the custom_mocks.rb example file into features/dupe/custom_mocks
|
11
|
-
m.template 'custom_mocks.rb', 'features/dupe/custom_mocks/custom_mocks.rb'
|
12
|
-
|
13
|
-
# copy the definitions.rb example file into features/dupe/definitions
|
14
|
-
m.template 'definitions.rb', 'features/dupe/definitions/definitions.rb'
|
15
|
-
|
16
|
-
# copy the load_dupe.rb into the features/support directory
|
17
|
-
m.template 'load_dupe.rb', 'features/support/load_dupe.rb'
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,9 +0,0 @@
|
|
1
|
-
# First, load the definitions
|
2
|
-
Dir[File.join(File.dirname(__FILE__), '../dupe/definitions/*.rb')].each do |file|
|
3
|
-
require file
|
4
|
-
end
|
5
|
-
|
6
|
-
# next, load the custom mocks
|
7
|
-
Dir[File.join(File.dirname(__FILE__), '../dupe/custom_mocks/*.rb')].each do |file|
|
8
|
-
require file
|
9
|
-
end
|