dupe 0.3.7 → 0.4.0
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 +390 -147
- data/lib/dupe/active_resource_extensions.rb +25 -0
- data/lib/dupe/attribute_template.rb +71 -0
- data/lib/dupe/cucumber_hooks.rb +15 -7
- data/lib/dupe/custom_mocks.rb +102 -0
- data/lib/dupe/database.rb +51 -0
- data/lib/dupe/dupe.rb +359 -372
- data/lib/dupe/log.rb +38 -0
- data/lib/dupe/mock.rb +50 -0
- data/lib/dupe/model.rb +55 -0
- data/lib/dupe/network.rb +38 -0
- data/lib/dupe/record.rb +35 -56
- data/lib/dupe/rest_validation.rb +16 -0
- data/lib/dupe/schema.rb +36 -0
- data/lib/dupe/sequence.rb +11 -10
- data/lib/dupe/singular_plural_detection.rb +9 -0
- data/lib/dupe/string.rb +6 -8
- data/lib/dupe/symbol.rb +3 -0
- data/lib/dupe.rb +13 -12
- data/rails_generators/dupe/templates/custom_mocks.rb +4 -34
- data/rails_generators/dupe/templates/dupe_setup.rb +3 -23
- data/spec/lib_specs/active_resource_extensions_spec.rb +29 -0
- data/spec/lib_specs/attribute_template_spec.rb +173 -0
- data/spec/lib_specs/database_spec.rb +133 -0
- data/spec/lib_specs/dupe_spec.rb +307 -0
- data/spec/lib_specs/log_spec.rb +78 -0
- data/spec/lib_specs/logged_request_spec.rb +22 -0
- data/spec/lib_specs/mock_definitions_spec.rb +32 -0
- data/spec/lib_specs/mock_spec.rb +67 -0
- data/spec/lib_specs/model_spec.rb +90 -0
- data/spec/lib_specs/network_spec.rb +77 -0
- data/spec/lib_specs/record_spec.rb +70 -0
- data/spec/lib_specs/rest_validation_spec.rb +17 -0
- data/spec/lib_specs/schema_spec.rb +90 -0
- data/spec/lib_specs/sequence_spec.rb +26 -0
- data/spec/lib_specs/string_spec.rb +31 -0
- data/spec/lib_specs/symbol_spec.rb +17 -0
- data/spec/spec_helper.rb +2 -5
- metadata +29 -7
- data/lib/dupe/active_resource.rb +0 -135
- data/lib/dupe/attribute.rb +0 -17
- data/lib/dupe/configuration.rb +0 -20
- data/lib/dupe/mock_service_response.rb +0 -55
- data/spec/lib_specs/dupe_record_spec.rb +0 -57
data/lib/dupe/dupe.rb
CHANGED
@@ -2,121 +2,175 @@
|
|
2
2
|
# License:: Distributes under the same terms as Ruby
|
3
3
|
|
4
4
|
class Dupe
|
5
|
-
attr_reader :factory_name #:nodoc:
|
6
|
-
attr_reader :configuration #:nodoc:
|
7
|
-
attr_reader :attributes #:nodoc:
|
8
|
-
attr_reader :config #:nodoc:
|
9
|
-
attr_reader :mocker #:nodoc:
|
10
|
-
attr_reader :records #:nodoc:
|
11
|
-
|
12
5
|
class << self
|
13
|
-
attr_accessor :factories #:nodoc:
|
14
|
-
attr_accessor :global_configuration #:nodoc:
|
15
6
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
7
|
+
attr_reader :models #:nodoc:
|
8
|
+
attr_reader :database #:nodoc:
|
9
|
+
|
10
|
+
# set this to "true" if you Dupe to spit out mocked requests
|
11
|
+
# after each of your cucumber scenario's run
|
12
|
+
attr_accessor :debug
|
13
|
+
|
14
|
+
# Suppose we're creating a 'book' resource. Perhaps our app assumes every book has a title, so let's define a book resource
|
15
|
+
# that specifies just that:
|
16
|
+
#
|
17
|
+
# irb# Dupe.define :book do |attrs|
|
18
|
+
# --# attrs.title 'Untitled'
|
19
|
+
# --# attrs.author
|
20
|
+
# --# end
|
21
|
+
# ==> #<Dupe::Model:0x17b2694 ...>
|
22
|
+
#
|
23
|
+
# 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.
|
24
|
+
#
|
25
|
+
# irb# b = Dupe.create :book
|
26
|
+
# ==> <#Duped::Book author=nil title="Untitled" id=1>
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# If we provide our own title, it should allow us to override the default value:
|
30
|
+
#
|
31
|
+
# irb# b = Dupe.create :book, :title => 'Monkeys!'
|
32
|
+
# ==> <#Duped::Book author=nil title="Monkeys!" id=2>
|
33
|
+
#
|
34
|
+
# === Attributes with procs as default values
|
35
|
+
#
|
36
|
+
# Sometimes it might be convenient to procedurally define the default value for an attribute:
|
37
|
+
#
|
38
|
+
# irb# Dupe.define :book do |attrs|
|
39
|
+
# --# attrs.title 'Untitled'
|
40
|
+
# --# attrs.author
|
41
|
+
# --# attrs.isbn do
|
42
|
+
# --# rand(1000000)
|
43
|
+
# --# end
|
44
|
+
# --# end
|
45
|
+
#
|
46
|
+
# Now, every time we create a book, it will get assigned a random ISBN number:
|
47
|
+
#
|
48
|
+
# irb# b = Dupe.create :book
|
49
|
+
# ==> <#Duped::Book author=nil title="Untitled" id=1 isbn=895825>
|
50
|
+
#
|
51
|
+
# irb# b = Dupe.create :book
|
52
|
+
# ==> <#Duped::Book author=nil title="Untitled" id=2 isbn=606472>
|
53
|
+
#
|
54
|
+
# 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 it's own resource. We can accomplish that by taking advantage of Dupe's "find_or_create" method:
|
55
|
+
#
|
56
|
+
# irb# Dupe.define :book do |attrs|
|
57
|
+
# --# attrs.title 'Untitled'
|
58
|
+
# --# attrs.author
|
59
|
+
# --# attrs.isbn do
|
60
|
+
# --# rand(1000000)
|
61
|
+
# --# end
|
62
|
+
# --# attrs.genre do
|
63
|
+
# --# Dupe.find_or_create :genre
|
64
|
+
# --# end
|
65
|
+
# --# end
|
66
|
+
#
|
67
|
+
# 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.
|
68
|
+
#
|
69
|
+
# First, let's confirm that no genres currently exist:
|
70
|
+
#
|
71
|
+
# irb# Dupe.find :genre
|
72
|
+
# Dupe::Database::TableDoesNotExistError: The table ':genre' does not exist.
|
73
|
+
# from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/database.rb:30:in `select'
|
74
|
+
# from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/dupe.rb:295:in `find'
|
75
|
+
# from (irb):135
|
76
|
+
#
|
77
|
+
# Next, let's create a book:
|
78
|
+
#
|
79
|
+
# irb# b = Dupe.create :book
|
80
|
+
# ==> <#Duped::Book genre=<#Duped::Genre id=1> author=nil title="Untitled" id=1 isbn=62572>
|
81
|
+
#
|
82
|
+
# Notice that it create a genre. If we tried to do another Dupe.find for the genre:
|
83
|
+
#
|
84
|
+
# irb# Dupe.find :genre
|
85
|
+
# ==> <#Duped::Genre id=1>
|
86
|
+
#
|
87
|
+
# Now, if create another book, it will associate with the genre that was just created:
|
88
|
+
#
|
89
|
+
# irb# b = Dupe.create :book
|
90
|
+
# ==> <#Duped::Book genre=<#Duped::Genre id=1> author=nil title="Untitled" id=2 isbn=729317>
|
91
|
+
#
|
92
|
+
#
|
93
|
+
#
|
94
|
+
# === Attributes with transformers
|
95
|
+
#
|
96
|
+
# Occasionally, you may find it useful to have attribute values transformed upon creation.
|
97
|
+
#
|
98
|
+
# 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.
|
99
|
+
#
|
100
|
+
# irb# Dupe.define :book do |attrs|
|
101
|
+
# --# attrs.title 'Untitled'
|
102
|
+
# --# attrs.author
|
103
|
+
# --# attrs.isbn do
|
104
|
+
# --# rand(1000000)
|
105
|
+
# --# end
|
106
|
+
# --# attrs.publish_date do |publish_date|
|
107
|
+
# --# Date.parse(publish_date)
|
108
|
+
# --# end
|
109
|
+
# --# end
|
110
|
+
#
|
111
|
+
# Now, let's create a book:
|
112
|
+
#
|
113
|
+
# irb# b = Dupe.create :book, :publish_date => '2009-12-29'
|
114
|
+
# ==> <#Duped::Book author=nil title="Untitled" publish_date=Tue, 29 Dec 2009 id=1 isbn=826291>
|
115
|
+
#
|
116
|
+
# irb# b.publish_date
|
117
|
+
# ==> Tue, 29 Dec 2009
|
118
|
+
#
|
119
|
+
# irb# b.publish_date.class
|
120
|
+
# ==> Date
|
121
|
+
#
|
122
|
+
# === Callbacks
|
123
|
+
#
|
124
|
+
# Suppose we'd like to make sure that our books get a unique label. We can accomplish that with an after_create callback:
|
125
|
+
#
|
126
|
+
# irb# Dupe.define :book do |attrs|
|
127
|
+
# --# attrs.title 'Untitled'
|
128
|
+
# --# attrs.author
|
129
|
+
# --# attrs.isbn do
|
130
|
+
# --# rand(1000000)
|
131
|
+
# --# end
|
132
|
+
# --# attrs.publish_date do |publish_date|
|
133
|
+
# --# Date.parse(publish_date)
|
134
|
+
# --# end
|
135
|
+
# --# attrs.after_create do |book|
|
136
|
+
# --# book.label = book.title.downcase.gsub(/\ +/, '-') + "--#{book.id}"
|
137
|
+
# --# end
|
138
|
+
# --# end
|
139
|
+
#
|
140
|
+
# irb# b = Dupe.create :book, :title => 'Rooby on Rails'
|
141
|
+
# ==> <#Duped::Book author=nil label="rooby-on-rails--1" title="Rooby on Rails" publish_date=nil id=1 isbn=842518>
|
142
|
+
#
|
143
|
+
def define(*args, &block) # yield: define
|
144
|
+
model_name, model_object = create_model_if_definition_parameters_are_valid(args, block)
|
145
|
+
model_object.tap do |m|
|
146
|
+
models[model_name] = m
|
147
|
+
database.create_table model_name
|
148
|
+
mocks = %{
|
149
|
+
network.define_service_mock(
|
150
|
+
:get,
|
151
|
+
%r{/#{model_name.to_s.pluralize}\\.xml$},
|
152
|
+
proc { Dupe.find(:#{model_name.to_s.pluralize}) }
|
153
|
+
)
|
154
|
+
network.define_service_mock(
|
155
|
+
:get,
|
156
|
+
%r{/#{model_name.to_s.pluralize}/(\\d+)\\.xml$},
|
157
|
+
proc {|id| Dupe.find(:#{model_name}) {|resource| resource.id == id.to_i}}
|
158
|
+
)
|
159
|
+
}
|
160
|
+
eval(mocks)
|
161
|
+
end
|
109
162
|
end
|
110
163
|
|
111
164
|
# This method will cause Dupe to mock resources for the record(s) provided.
|
112
165
|
# The "records" value may be either a hash or an array of hashes.
|
113
|
-
# For example, suppose you'd like to mock a single author
|
166
|
+
# For example, suppose you'd like to mock a single author:
|
114
167
|
#
|
115
|
-
# Dupe.create :author, :name => 'Arthur C. Clarke'
|
168
|
+
# author = Dupe.create :author, :name => 'Arthur C. Clarke'
|
169
|
+
# ==> <#Duped::Author name="Arthur C. Clarke" id=1>
|
116
170
|
#
|
117
171
|
# This will translate into the following two mocked resource calls:
|
118
172
|
#
|
119
|
-
# #
|
173
|
+
# # GET /authors.xml
|
120
174
|
# <?xml version="1.0" encoding="UTF-8"?>
|
121
175
|
# <authors>
|
122
176
|
# <author>
|
@@ -125,7 +179,7 @@ class Dupe
|
|
125
179
|
# </author>
|
126
180
|
# </authors>
|
127
181
|
#
|
128
|
-
# #
|
182
|
+
# # GET /authors/1.xml
|
129
183
|
# <?xml version="1.0" encoding="UTF-8"?>
|
130
184
|
# <author>
|
131
185
|
# <id type="integer">1</id>
|
@@ -133,12 +187,13 @@ class Dupe
|
|
133
187
|
# </author>
|
134
188
|
#
|
135
189
|
# However, suppose you wanted to mock two or more authors.
|
136
|
-
#
|
137
|
-
# Dupe.create :author, [{:name => 'Arthur C. Clarke'}, {:name => 'Robert Heinlein'}]
|
138
|
-
#
|
190
|
+
#
|
191
|
+
# authors = Dupe.create :author, [{:name => 'Arthur C. Clarke'}, {:name => 'Robert Heinlein'}]
|
192
|
+
# ==> [<#Duped::Author name="Arthur C. Clarke" id=1>, <#Duped::Author name="Robert Heinlein" id=2>]
|
193
|
+
#
|
139
194
|
# This will translate into the following three mocked resource calls:
|
140
195
|
#
|
141
|
-
# #
|
196
|
+
# # GET /authors.xml
|
142
197
|
# <?xml version="1.0" encoding="UTF-8"?>
|
143
198
|
# <authors>
|
144
199
|
# <author>
|
@@ -151,24 +206,23 @@ class Dupe
|
|
151
206
|
# </author>
|
152
207
|
# </authors>
|
153
208
|
#
|
154
|
-
# #
|
209
|
+
# # GET /authors/1.xml
|
155
210
|
# <?xml version="1.0" encoding="UTF-8"?>
|
156
211
|
# <author>
|
157
212
|
# <id type="integer">1</id>
|
158
213
|
# <name>Arthur C. Clarke</name>
|
159
214
|
# </author>
|
160
215
|
#
|
161
|
-
# #
|
216
|
+
# # GET /authors/2.xml
|
162
217
|
# <?xml version="1.0" encoding="UTF-8"?>
|
163
218
|
# <author>
|
164
219
|
# <id type="integer">2</id>
|
165
220
|
# <name>Robert Heinlein</name>
|
166
221
|
# </author>
|
167
|
-
def create(
|
168
|
-
|
169
|
-
|
170
|
-
records
|
171
|
-
@factories[factory].generate_services_for(records)
|
222
|
+
def create(model_name, records={})
|
223
|
+
model_name = model_name.to_s.singularize.to_sym
|
224
|
+
define model_name unless model_exists(model_name)
|
225
|
+
create_and_insert records, :into => model_name
|
172
226
|
end
|
173
227
|
|
174
228
|
# You can use this method to quickly stub out a large number of resources. For example:
|
@@ -183,9 +237,9 @@ class Dupe
|
|
183
237
|
#
|
184
238
|
# then stub would have generated 20 author records like:
|
185
239
|
#
|
186
|
-
#
|
240
|
+
# <#Duped::Author name="default" id=1>
|
187
241
|
# ....
|
188
|
-
#
|
242
|
+
# <#Duped::Author name="default" id=1>
|
189
243
|
#
|
190
244
|
# and it would also have mocked find(id) and find(:all) responses for these records (along with any other custom mocks you've
|
191
245
|
# setup via Dupe.define_mocks). (Had you not defined an author resource, then the stub would have generated 20 author records
|
@@ -197,282 +251,215 @@ class Dupe
|
|
197
251
|
#
|
198
252
|
# which would generate 20 author records like:
|
199
253
|
#
|
200
|
-
#
|
201
|
-
# ....
|
202
|
-
# {:name => 'author 20', :id => 20}
|
203
|
-
#
|
204
|
-
# You may also override the sequence starting value:
|
205
|
-
#
|
206
|
-
# Dupe.stub 20, :authors, :like => {:name => proc {|n| "author #{n}"}}, :starting_with => 150
|
207
|
-
#
|
208
|
-
# This would generate 20 author records like:
|
209
|
-
#
|
210
|
-
# {:name => 'author 150', :id => 1}
|
254
|
+
# <#Duped::Author name="author 1" id=1>
|
211
255
|
# ....
|
212
|
-
#
|
256
|
+
# <#Duped::Author name="author 20" id=20>
|
213
257
|
#
|
214
258
|
# Naturally, stub will consult the Dupe.define definitions for anything it's attempting to stub
|
215
|
-
# and will honor those definitions (default values, transformations) as you would expect.
|
216
|
-
def stub(count,
|
217
|
-
|
218
|
-
|
219
|
-
|
259
|
+
# and will honor those definitions (default values, transformations, callbacks) as you would expect.
|
260
|
+
def stub(count, model_name, options={})
|
261
|
+
start_at = options[:starting_with] || 1
|
262
|
+
record_template = options[:like] || {}
|
263
|
+
records = []
|
264
|
+
(start_at..(start_at + count - 1)).each do |i|
|
265
|
+
records <<
|
266
|
+
record_template.map do |k,v|
|
267
|
+
{ k => (v.kind_of?(Proc) ? v.call(i) : v) }
|
268
|
+
end.inject({}) {|h, v| h.merge(v)}
|
269
|
+
end
|
270
|
+
create model_name, records
|
220
271
|
end
|
221
272
|
|
222
|
-
#
|
223
|
-
#
|
224
|
-
# On a global level, configure supports the following options (expect this list to grow as the app grows):
|
225
|
-
# debug: list the attempted requests and mocked responses that happened during the course of a scenario
|
273
|
+
# Dupe has a built-in querying system for finding resources you create.
|
226
274
|
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
# global_config.debug true
|
230
|
-
# end
|
231
|
-
#
|
232
|
-
# === Factory Configuration
|
233
|
-
#
|
234
|
-
# On a factory level, configure support the following options (expect this list to grow as the app grows):
|
235
|
-
# record_identifiers: a list of attributes that are unique to each record in that resource factory.
|
236
|
-
#
|
237
|
-
# The "record_identifiers" configuration option allows you to override the array record identifiers for your resources ([:id], by default)
|
275
|
+
# irb# a = Dupe.create :author, :name => 'Monkey'
|
276
|
+
# ==> <#Duped::Author name="Monkey" id=1>
|
238
277
|
#
|
239
|
-
#
|
240
|
-
#
|
241
|
-
# 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>.
|
278
|
+
# irb# b = Dupe.create :book, :title => 'Bananas', :author => a
|
279
|
+
# ==> <#Duped::Book author=<#Duped::Author name="Monkey" id=1> title="Bananas" id=1>
|
242
280
|
#
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
255
|
-
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
260
|
-
#
|
261
|
-
#
|
262
|
-
#
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
266
|
-
#
|
267
|
-
#
|
268
|
-
#
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
281
|
+
# irb# Dupe.find(:author) {|a| a.name == 'Monkey'}
|
282
|
+
# ==> <#Duped::Author name="Monkey" id=1>
|
283
|
+
#
|
284
|
+
# irb# Dupe.find(:book) {|b| b.author.name == 'Monkey'}
|
285
|
+
# ==> <#Duped::Book author=<#Duped::Author name="Monkey" id=1> title="Bananas" id=1>
|
286
|
+
#
|
287
|
+
# irb# Dupe.find(:author) {|a| a.id == 1}
|
288
|
+
# ==> <#Duped::Author name="Monkey" id=1>
|
289
|
+
#
|
290
|
+
# irb# Dupe.find(:author) {|a| a.id == 2}
|
291
|
+
# ==> nil
|
292
|
+
#
|
293
|
+
# In all cases, notice that we provided the singular form of a model name to Dupe.find.
|
294
|
+
# This ensures that we either get back either a single resource (if the query was successful), or _nil_.
|
295
|
+
#
|
296
|
+
# If we'd like to find several resources, we can use the plural form of the model name. For example:
|
297
|
+
#
|
298
|
+
# irb# a = Dupe.create :author, :name => 'Monkey', :published => true
|
299
|
+
# ==> <#Duped::Author published=true name="Monkey" id=1>
|
300
|
+
#
|
301
|
+
# irb# b = Dupe.create :book, :title => 'Bananas', :author => a
|
302
|
+
# ==> <#Duped::Book author=<#Duped::Author published=true name="Monkey" id=1> title="Bananas" id=1>
|
303
|
+
#
|
304
|
+
# irb# Dupe.create :author, :name => 'Tiger', :published => false
|
305
|
+
# ==> <#Duped::Author published=false name="Tiger" id=2>
|
306
|
+
#
|
307
|
+
# irb# Dupe.find(:authors)
|
308
|
+
# ==> [<#Duped::Author published=true name="Monkey" id=1>, <#Duped::Author published=false name="Tiger" id=2>]
|
309
|
+
#
|
310
|
+
# irb# Dupe.find(:authors) {|a| a.published == true}
|
311
|
+
# ==> [<#Duped::Author published=true name="Monkey" id=1>]
|
312
|
+
#
|
313
|
+
# irb# Dupe.find(:books)
|
314
|
+
# ==> [<#Duped::Book author=<#Duped::Author published=true name="Monkey" id=1> title="Bananas" id=1>]
|
315
|
+
#
|
316
|
+
# irb# Dupe.find(:books) {|b| b.author.published == false}
|
317
|
+
# ==> []
|
318
|
+
#
|
319
|
+
# Notice that by using the plural form of the model name, we ensure that we receive back an array -
|
320
|
+
# even in the case that the query did not find any results (it simply returns an empty array).
|
321
|
+
def find(model_name, &block) # yield: record
|
322
|
+
results = database.select model_name.to_s.singularize.to_sym, block
|
323
|
+
model_name.plural? ? results : results.first
|
273
324
|
end
|
274
325
|
|
275
|
-
#
|
276
|
-
# However, it's likely that your cucumber scenarios will eventually fire off an ActiveResource request that's
|
277
|
-
# something other than these basic lookups.
|
278
|
-
#
|
279
|
-
# Dupe.define_mocks allows you to add new resource mocks and override the built-in resource mocks.
|
280
|
-
#
|
281
|
-
# For example, suppose you had a Book ActiveResource model, and you want to use it to get the :count of all
|
282
|
-
# Books in the back end system your consuming. <tt>Book.get(:count)</tt> would fire off an HTTP request to the
|
283
|
-
# backend service like <tt>"GET /books/count.xml"</tt>, and assuming the service is set up to respond to that
|
284
|
-
# request, you might expect to get something back like:
|
285
|
-
#
|
286
|
-
# <?xml version="1.0" encoding="UTF-8"?>
|
287
|
-
# <hash>
|
288
|
-
# <count type="integer">3</count>
|
289
|
-
# </hash>
|
290
|
-
#
|
291
|
-
# To mock this for the purposes of cuking, you could do the following:
|
326
|
+
# This method will create a resource with the given specifications if one doesn't already exist.
|
292
327
|
#
|
293
|
-
# Dupe.
|
294
|
-
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
298
|
-
#
|
299
|
-
#
|
300
|
-
#
|
301
|
-
|
302
|
-
|
303
|
-
|
328
|
+
# irb# Dupe.find :genre
|
329
|
+
# Dupe::Database::TableDoesNotExistError: The table ':genre' does not exist.
|
330
|
+
# from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/database.rb:30:in `select'
|
331
|
+
# from /Library/Ruby/Gems/1.8/gems/dupe-0.4.0/lib/dupe/dupe.rb:295:in `find'
|
332
|
+
# from (irb):40
|
333
|
+
#
|
334
|
+
# irb# Dupe.find_or_create :genre
|
335
|
+
# ==> <#Duped::Genre id=1>
|
336
|
+
#
|
337
|
+
# irb# Dupe.find_or_create :genre
|
338
|
+
# ==> <#Duped::Genre id=1>
|
339
|
+
#
|
340
|
+
# You can also pass conditions to find_or_create as a hash:
|
341
|
+
#
|
342
|
+
# irb# Dupe.find_or_create :genre, :name => 'Science Fiction', :label => 'sci-fi'
|
343
|
+
# ==> <#Duped::Genre label="sci-fi" name="Science Fiction" id=2>
|
344
|
+
#
|
345
|
+
# irb# Dupe.find_or_create :genre, :name => 'Science Fiction', :label => 'sci-fi'
|
346
|
+
# ==> <#Duped::Genre label="sci-fi" name="Science Fiction" id=2>
|
347
|
+
def find_or_create(model_name, attributes={})
|
348
|
+
results = nil
|
349
|
+
if model_exists(model_name)
|
350
|
+
results = eval("find(:#{model_name}) #{build_conditions(attributes)}")
|
351
|
+
end
|
352
|
+
|
353
|
+
if !results
|
354
|
+
if model_name.singular?
|
355
|
+
create model_name, attributes
|
356
|
+
else
|
357
|
+
stub((rand(5)+1), model_name, :like => attributes)
|
358
|
+
end
|
359
|
+
elsif results.kind_of?(Array) && results.empty?
|
360
|
+
stub((rand(5)+1), model_name, :like => attributes)
|
361
|
+
else
|
362
|
+
results
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def models #:nodoc:
|
367
|
+
@models ||= {}
|
304
368
|
end
|
305
369
|
|
306
|
-
|
307
|
-
|
308
|
-
# This is most often used for defining associations between objects (Dupe.define).
|
309
|
-
# It will return a hash representation of the resource (or an array of hashes if we asked for multiple records).
|
310
|
-
#
|
311
|
-
# For example, suppose we have an author resource, and a book resource with a nested author attribute (in ActiveRecord
|
312
|
-
# parlance, Book belongs_to Author, Author has_many Book).
|
313
|
-
#
|
314
|
-
# Now suppose we've created the following cucumber scenario:
|
315
|
-
#
|
316
|
-
# Scenario: Browsing books
|
317
|
-
# Given the following author:
|
318
|
-
# | name | date_of_birth |
|
319
|
-
# | Arthur C. Clarke | 1917-12-16 |
|
320
|
-
#
|
321
|
-
# And the following books:
|
322
|
-
# | name | author | published | genre |
|
323
|
-
# | 2001: A Space Odyssey | Arthur C. Clarke | 1968 | sci-fi |
|
324
|
-
# | A Fall of Moondust | Arthur C. Clarke | 1961 | fantasy |
|
325
|
-
# | Rendezvous with Rama | Arthur C. Clarke | 1972 | sci-fi |
|
326
|
-
#
|
327
|
-
# When....
|
328
|
-
#
|
329
|
-
# To link up the book and author, we could create the following book definition
|
330
|
-
#
|
331
|
-
# Dupe.define :book do |book|
|
332
|
-
# book.author {|name| Dupe.find(:author) {|a| a.name == name}}
|
333
|
-
# end
|
334
|
-
#
|
335
|
-
# The line <tt>Dupe.find(:author) {|a| a.name == name}</tt> could be translated as
|
336
|
-
# "find the first author record where the author's name equals `name`".
|
337
|
-
#
|
338
|
-
# Dupe decided to return only a single record because we specified <tt>find(:author)</tt>.
|
339
|
-
# Had we instead specified <tt>find(:authors)</tt>, Dupe would have instead returned an array of results.
|
340
|
-
#
|
341
|
-
# More examples:
|
342
|
-
#
|
343
|
-
# # find all books written in the 1960's
|
344
|
-
# Dupe.find(:books) {|b| b.published >= 1960 and b.published <= 1969}
|
345
|
-
#
|
346
|
-
# # return the first book found that was written by Arthur C. Clarke (nested resources example)
|
347
|
-
# Dupe.find(:book) {|b| b.author.name == 'Arthur C. Clarke'}
|
348
|
-
#
|
349
|
-
# # find all sci-fi and fantasy books
|
350
|
-
# Dupe.find(:books) {|b| b.genre == 'sci-fi' or b.genre == 'fantasy'}
|
351
|
-
#
|
352
|
-
# # find all books written by people named 'Arthur'
|
353
|
-
# Dupe.find(:books) {|b| b.author.name.match /Arthur/}
|
354
|
-
#
|
355
|
-
# Also, if you have the need to explicitly specify :all or :first instead of relying on specifying the singular v. plural
|
356
|
-
# version of your resource name (perhaps the singular and plural version of your resource are exactly the same):
|
357
|
-
#
|
358
|
-
# Dupe.find(:all, :deer) {|d| d.type == 'doe'}
|
359
|
-
# Dupe.find(:first, :deer) {|d| d.name == 'Bambi'}
|
360
|
-
def find(*args, &block) # yield: record
|
361
|
-
all_or_first, factory_name = args[-2], args[-1]
|
362
|
-
match = block ? block : proc {true}
|
363
|
-
all_or_first = ((factory_name.to_s.plural?) ? :all : :first) unless all_or_first
|
364
|
-
factory_name = factory_name.to_s.singularize.to_sym
|
365
|
-
verify_factory_exists factory_name
|
366
|
-
result = factories[factory_name].find_records_like match
|
367
|
-
all_or_first == :all ? result : result.first
|
370
|
+
def network #:nodoc:
|
371
|
+
@network ||= Dupe::Network.new
|
368
372
|
end
|
369
|
-
|
370
|
-
def
|
371
|
-
|
372
|
-
@factories[factory]
|
373
|
+
|
374
|
+
def database #:nodoc:
|
375
|
+
@database ||= Dupe::Database.new
|
373
376
|
end
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
end
|
377
|
+
|
378
|
+
# clears out all model definitions and database records / tables.
|
379
|
+
def reset
|
380
|
+
reset_models
|
381
|
+
reset_database
|
382
|
+
reset_network
|
381
383
|
end
|
382
|
-
|
383
|
-
def
|
384
|
-
@
|
384
|
+
|
385
|
+
def reset_models
|
386
|
+
@models = {}
|
385
387
|
end
|
386
|
-
|
387
|
-
def
|
388
|
-
@
|
388
|
+
|
389
|
+
def reset_database
|
390
|
+
@database = Dupe::Database.new
|
389
391
|
end
|
390
|
-
|
392
|
+
|
393
|
+
def reset_network
|
394
|
+
@network = Dupe::Network.new
|
395
|
+
end
|
396
|
+
|
397
|
+
# set to true if you want to see mocked results spit out after each cucumber scenario
|
398
|
+
def debug
|
399
|
+
@debug ||= false
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
|
391
404
|
private
|
405
|
+
def build_conditions(conditions)
|
406
|
+
return '' if conditions.empty?
|
407
|
+
select =
|
408
|
+
"{|record| " +
|
409
|
+
conditions.map do |k,v|
|
410
|
+
"record.#{k} == #{v.kind_of?(String) ? "\"#{v}\"" : v}"
|
411
|
+
end.join(" && ") + " }"
|
412
|
+
end
|
392
413
|
|
393
|
-
def
|
394
|
-
|
414
|
+
def model_exists(model_name)
|
415
|
+
models[model_name.to_s.singularize.to_sym]
|
395
416
|
end
|
396
|
-
|
397
|
-
def
|
398
|
-
|
417
|
+
|
418
|
+
def create_model(model_name)
|
419
|
+
models[model_name] = Dupe::Model.new(model_name) unless models[model_name]
|
399
420
|
end
|
400
|
-
|
401
|
-
def
|
402
|
-
raise
|
421
|
+
|
422
|
+
def create_and_insert(records, into)
|
423
|
+
raise(
|
424
|
+
ArgumentError,
|
425
|
+
"You must pass a hash containing :into => :model_name " +
|
426
|
+
"as the second parameter to create_and_insert."
|
427
|
+
) if !into || !into.kind_of?(Hash) || !into[:into]
|
428
|
+
|
429
|
+
if records.kind_of?(Array) and
|
430
|
+
records.inject(true) {|bool, r| bool and r.kind_of?(Hash)}
|
431
|
+
[].tap do |results|
|
432
|
+
records.each do |record|
|
433
|
+
results << models[into[:into]].create(record).tap {|r| database.insert r}
|
434
|
+
end
|
435
|
+
end
|
436
|
+
elsif records.kind_of?(Hash)
|
437
|
+
models[into[:into]].create(records).tap {|r| database.insert r}
|
438
|
+
else
|
439
|
+
raise ArgumentError, "You must call Dupe.create with either a hash or an array of hashes."
|
440
|
+
end
|
403
441
|
end
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
def method_missing(method_name, *args, &block) #:nodoc:
|
427
|
-
args = [nil] if args.empty?
|
428
|
-
args << block
|
429
|
-
define_attribute(method_name.to_sym, *args)
|
430
|
-
end
|
431
|
-
|
432
|
-
def generate_services_for(records, records_already_processed=false) #:nodoc:
|
433
|
-
records = process_records records unless records_already_processed
|
434
|
-
@mocker.run_mocks(@records, @config.config[:record_identifiers])
|
435
|
-
records.length == 1 ? records.first : records
|
436
|
-
end
|
437
|
-
|
438
|
-
def find_records_like(match) #:nodoc:
|
439
|
-
@records.select {|r| match.call Record.new(r)}
|
440
|
-
end
|
441
|
-
|
442
|
-
private
|
443
|
-
def define_attribute(name, default_value=nil, prock=nil)
|
444
|
-
@attributes[name] = Attribute.new(name, default_value, prock)
|
445
|
-
end
|
446
|
-
|
447
|
-
def process_records(records)
|
448
|
-
records.map {|r| generate_record({:id => sequence}.merge(r))}
|
449
|
-
end
|
450
|
-
|
451
|
-
def generate_record(overrides={})
|
452
|
-
define_missing_attributes(overrides.keys)
|
453
|
-
record = {}
|
454
|
-
@attributes.each do |attr_key, attr_class|
|
455
|
-
override_default_value = overrides[attr_key] || overrides[attr_key.to_s]
|
456
|
-
record[attr_key] = attr_class.value(override_default_value)
|
442
|
+
|
443
|
+
def create_model_if_definition_parameters_are_valid(args, definition)
|
444
|
+
if args.length == 1 and
|
445
|
+
args.first.kind_of?(Symbol) and
|
446
|
+
definition == nil
|
447
|
+
|
448
|
+
return args.first, Dupe::Model.new(args.first)
|
449
|
+
|
450
|
+
elsif args.length == 1 and
|
451
|
+
args.first.kind_of?(Symbol) and
|
452
|
+
definition.kind_of?(Proc) and
|
453
|
+
definition.arity == 1
|
454
|
+
|
455
|
+
model_name = args.first
|
456
|
+
return model_name, Dupe::Model.new(model_name).tap {|m| m.define definition}
|
457
|
+
|
458
|
+
else
|
459
|
+
raise ArgumentError.new(
|
460
|
+
"Unknown Dupe.define parameter format. Please consult the API for information on how to use Dupe.define"
|
461
|
+
)
|
462
|
+
end
|
457
463
|
end
|
458
|
-
@records << record
|
459
|
-
record
|
460
|
-
end
|
461
|
-
|
462
|
-
def sequence
|
463
|
-
(@sequence ||= Sequence.new).next
|
464
|
-
end
|
465
|
-
|
466
|
-
def define_missing_attributes(keys)
|
467
|
-
keys.each {|k| define_attribute(k.to_sym) unless @attributes[k.to_sym]}
|
468
464
|
end
|
469
|
-
|
470
|
-
def stub_records(record_template, count, stub_number)
|
471
|
-
overrides = record_template.merge({})
|
472
|
-
overrides.keys.each {|k| overrides[k] = overrides[k].call(stub_number) if overrides[k].respond_to?(:call)}
|
473
|
-
overrides = {:id => sequence}.merge(overrides) unless overrides[:id]
|
474
|
-
return [generate_record(overrides)] if count <= 1
|
475
|
-
[generate_record(overrides)] + stub_records(record_template, count-1, stub_number+1)
|
476
|
-
end
|
477
|
-
|
478
465
|
end
|