dm-sweatshop 0.9.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +4 -1
- data/README.textile +97 -14
- data/README.txt +45 -0
- data/lib/dm-sweatshop/model.rb +56 -0
- data/lib/dm-sweatshop/sweatshop.rb +72 -9
- data/lib/dm-sweatshop/unique.rb +84 -0
- data/lib/dm-sweatshop/version.rb +1 -1
- data/lib/dm-sweatshop.rb +2 -1
- data/spec/dm-sweatshop/model_spec.rb +128 -26
- data/spec/dm-sweatshop/sweatshop_spec.rb +2 -1
- data/spec/dm-sweatshop/unique_spec.rb +73 -0
- metadata +9 -6
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -2,13 +2,16 @@ History.txt
|
|
2
2
|
LICENSE
|
3
3
|
Manifest.txt
|
4
4
|
README.textile
|
5
|
+
README.txt
|
5
6
|
Rakefile
|
6
7
|
TODO
|
7
8
|
lib/dm-sweatshop.rb
|
8
|
-
lib/dm-sweatshop/sweatshop.rb
|
9
9
|
lib/dm-sweatshop/model.rb
|
10
|
+
lib/dm-sweatshop/sweatshop.rb
|
11
|
+
lib/dm-sweatshop/unique.rb
|
10
12
|
lib/dm-sweatshop/version.rb
|
11
13
|
spec/dm-sweatshop/model_spec.rb
|
12
14
|
spec/dm-sweatshop/sweatshop_spec.rb
|
15
|
+
spec/dm-sweatshop/unique_spec.rb
|
13
16
|
spec/spec.opts
|
14
17
|
spec/spec_helper.rb
|
data/README.textile
CHANGED
@@ -9,6 +9,33 @@ dm-sweatshop is a model factory for DataMapper. It makes it easy & painless to
|
|
9
9
|
* Add context to model patterns, allowing grouping and
|
10
10
|
* Effortlessly generate or fill in associations for creating complex models with few lines of code.
|
11
11
|
|
12
|
+
h2. How it works
|
13
|
+
|
14
|
+
DataMapper Sweatshop is built around idea of storing attribute hashes associated
|
15
|
+
with a particular class. For instance, you can store two attribute hashes named
|
16
|
+
:without_password and :without_email, associated with a Person class. Later, when in the test you need a Person instance without password or email, you
|
17
|
+
use DataMapper Sweatship helper methods to pick an object that has attributes
|
18
|
+
set you need.
|
19
|
+
|
20
|
+
So the workflow is usually the following:
|
21
|
+
|
22
|
+
* Figure out what sets of attributes you need for a good test coverage.
|
23
|
+
* Name those sets.
|
24
|
+
* Store them associated with a particular class.
|
25
|
+
* Use them or objects with those attributes in your tests.
|
26
|
+
|
27
|
+
But there's more. Two hard parts of working with Ruby code fixtures are associations and generation of test data. Dummy data like "foo" and "bar" not just
|
28
|
+
very readable and becomes a mess after a while, it's really annoying to generate
|
29
|
+
a few objects that have, for instance, a title of 20+ characters.
|
30
|
+
|
31
|
+
DataMapper Sweatshop to the rescue. It uses RandExp gem to generate you strings
|
32
|
+
from regular expressions. When you need an email that is 60 characters long,
|
33
|
+
you can relax and use something like "#{/\w{58}/.gen}@somedomain.info" instead of typing 58 characters long foobar string.
|
34
|
+
|
35
|
+
Another nice thing is associations. Say we want to have say 20 tags for a
|
36
|
+
document or 10 orders for account in tests. DataMapper Sweatshop lets us
|
37
|
+
use associations list in attributes hashes described earlier.
|
38
|
+
|
12
39
|
h2. Examples
|
13
40
|
|
14
41
|
Starting off with a simple user model.
|
@@ -41,6 +68,14 @@ A fixture for the user model can be defined using the @fixture@ method.
|
|
41
68
|
</pre>
|
42
69
|
|
43
70
|
Notice the double curly brace (@{{@), a quick little way to pass a block that returns a hash to the fixture method. This is important because it ensures the data is random when we generate a new instance of the model, by calling the block every time.
|
71
|
+
|
72
|
+
Code snippet above stores a Proc that returns attributes hash in model map for
|
73
|
+
class User. Since you did not explicitly specify fixture name, default name is
|
74
|
+
used (99+% of the cases it is :default).
|
75
|
+
|
76
|
+
You can access that attributes hash later in your tests, make objects with those
|
77
|
+
attributes, and use and abuse it any way you want. It's just a way to memoize
|
78
|
+
attributes set associated with a particular class.
|
44
79
|
|
45
80
|
And here's how you generate said model.
|
46
81
|
<pre>
|
@@ -56,6 +91,39 @@ That's it. In fact, it can even be shortened.
|
|
56
91
|
</code>
|
57
92
|
</pre>
|
58
93
|
|
94
|
+
But what if we want to use some name for that attributes set? Just pass an
|
95
|
+
argument to @fixture@ method like this:
|
96
|
+
|
97
|
+
<pre>
|
98
|
+
<code>
|
99
|
+
Person.fixture(:valid) {{
|
100
|
+
:first_name => %w(Michael Adam Guiseppe)[rand(3)],
|
101
|
+
:last_name => %w(Smith Black White)[rand(3)],
|
102
|
+
:email => "#{/\w{10}/.gen}@somedomain.info",
|
103
|
+
:password_salt => (salt = /\w{20}/.gen),
|
104
|
+
:password_hash => Digest::SHA1.hexdigest("#{salt}@--,-`--secret")
|
105
|
+
}}
|
106
|
+
</code>
|
107
|
+
</pre>
|
108
|
+
|
109
|
+
Now to a model that has given attributes, use
|
110
|
+
|
111
|
+
<pre>
|
112
|
+
<code>
|
113
|
+
Person.gen(:valid)
|
114
|
+
</code>
|
115
|
+
</pre>
|
116
|
+
|
117
|
+
@generate@ (or @gen@) method uses @create@ method of DataMapper models. What if
|
118
|
+
we want just a new record? Use @make@ method instead:
|
119
|
+
|
120
|
+
<pre>
|
121
|
+
<code>
|
122
|
+
Person.make(:valid)
|
123
|
+
</code>
|
124
|
+
</pre>
|
125
|
+
|
126
|
+
|
59
127
|
h3. Associations
|
60
128
|
|
61
129
|
The real power of sweatshop is generating working associations.
|
@@ -113,7 +181,7 @@ The real power of sweatshop is generating working associations.
|
|
113
181
|
</code>
|
114
182
|
</pre>
|
115
183
|
|
116
|
-
That's going to generate alot of tags, way more than you would see in the production app. Let's
|
184
|
+
That's going to generate alot of tags, way more than you would see in the production app. Let's recycle some already generated tags instead.
|
117
185
|
|
118
186
|
<pre>
|
119
187
|
<code>
|
@@ -144,7 +212,8 @@ You can add multiple fixtures to a mode, dm-sweatshop will randomly pick between
|
|
144
212
|
<pre>
|
145
213
|
<code>
|
146
214
|
Tweet.fix {{
|
147
|
-
|
215
|
+
# a @reply for some user
|
216
|
+
:message => /\@#{User.pick.name} [:sentence:]/.gen[0..140],
|
148
217
|
:tags => (0..10).of {Tag.pick}
|
149
218
|
}}
|
150
219
|
</code>
|
@@ -168,7 +237,7 @@ To keep track of all of our new fixtures, we can even give them a context.
|
|
168
237
|
|
169
238
|
h3. Overriding a fixture
|
170
239
|
|
171
|
-
Sometimes you will want to change one of your fixtures a little bit. You create a new fixture with a whole new context, but this can be overkill. The other option is to specify attributes in the call to @generate@.
|
240
|
+
Sometimes you will want to change one of your fixtures a little bit. You can create a new fixture with a whole new context, but this can be overkill. The other option is to specify attributes in the call to @generate@.
|
172
241
|
|
173
242
|
<pre>
|
174
243
|
<code>
|
@@ -184,7 +253,30 @@ This works with contexts too.
|
|
184
253
|
</code>
|
185
254
|
</pre>
|
186
255
|
|
187
|
-
|
256
|
+
h2. Unique values
|
257
|
+
|
258
|
+
Data for fields with a uniqueness constraint (for example, e-mail addresses) can be generated using the @unique@ method. The simplest usage is to guarantee that random data is unique - wrap your generator in a @unique@ block with no parameters, and the block will be repeatedly executed until it generates a unique value (don't worry, it raises after a few tries).
|
259
|
+
|
260
|
+
For repeatable data, provide a block with one parameter. An incrementing value will be passed in on each invocation of that block. You can also name a unique block to override the block's identity (yeah that sentence is dense, just see the examples).
|
261
|
+
|
262
|
+
<pre><code>include DataMapper::Sweatshop::Unique # Use DataMapper::Sweatshop.unique if you don't want to pollute your namespace
|
263
|
+
|
264
|
+
User.fix {{
|
265
|
+
:name => unique { /\w+/.gen }
|
266
|
+
:email => unique {|x| "person-#{x}@example.com" }
|
267
|
+
}}
|
268
|
+
|
269
|
+
[User.gen.email, User.gen.email]
|
270
|
+
# => ["person-0@example.com", "person-1@example.com"]
|
271
|
+
|
272
|
+
names = ['bob', 'tom', 'bob']
|
273
|
+
Person.fix {{
|
274
|
+
:name => (name = names.shift)
|
275
|
+
:email => unique(name) {|x| "#{name}-#{x}@example.com" }
|
276
|
+
}}
|
277
|
+
|
278
|
+
[Person.gen.email, Person.gen.email, Person.gen.email]
|
279
|
+
# => ["bob-0@example.com", "tom-0@example.com", "bob-1@example.com"]</code></pre>
|
188
280
|
|
189
281
|
h2. Best Practices
|
190
282
|
|
@@ -208,17 +300,8 @@ h3. Enforcing Validations
|
|
208
300
|
|
209
301
|
Enforce validations at generation time, before the call to @new@/@create@.
|
210
302
|
|
211
|
-
<pre>
|
212
|
-
<code>
|
213
|
-
User.fix {{
|
214
|
-
:username.unique => /\w+/.gen,
|
215
|
-
:tweets => 500.of {Tweet.make}
|
216
|
-
}}
|
217
|
-
</code>
|
218
|
-
</pre>
|
219
|
-
|
220
303
|
h3. Better Exception Handling
|
221
304
|
|
222
305
|
h3. Smarter @pick@
|
223
306
|
|
224
|
-
Add multiple contexts to pick, or an ability to _fall back_ if one context has no generated models.
|
307
|
+
Add multiple contexts to pick, or an ability to _fall back_ if one context has no generated models.
|
data/README.txt
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
= DM sweatshop
|
2
|
+
|
3
|
+
Part of dm-more gems collection.
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
* DM Sweatshop is a YAML fixture alternative that uses pure Ruby.
|
8
|
+
|
9
|
+
== SYNOPSIS:
|
10
|
+
|
11
|
+
See README.textile
|
12
|
+
|
13
|
+
== REQUIREMENTS:
|
14
|
+
|
15
|
+
* randexp
|
16
|
+
* ParseTree (if you want to use uniq records generator)
|
17
|
+
|
18
|
+
== INSTALL:
|
19
|
+
|
20
|
+
* FIX (sudo gem install, anything else)
|
21
|
+
|
22
|
+
== LICENSE:
|
23
|
+
|
24
|
+
(The MIT License)
|
25
|
+
|
26
|
+
Copyright (c) 2008 Ben Burkert & contributors
|
27
|
+
|
28
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
29
|
+
a copy of this software and associated documentation files (the
|
30
|
+
'Software'), to deal in the Software without restriction, including
|
31
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
32
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
33
|
+
permit persons to whom the Software is furnished to do so, subject to
|
34
|
+
the following conditions:
|
35
|
+
|
36
|
+
The above copyright notice and this permission notice shall be
|
37
|
+
included in all copies or substantial portions of the Software.
|
38
|
+
|
39
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
40
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
41
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
42
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
43
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
44
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
45
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/dm-sweatshop/model.rb
CHANGED
@@ -1,11 +1,38 @@
|
|
1
1
|
module DataMapper
|
2
|
+
# Methods added to this module are available on classes
|
3
|
+
# that include DataMapper::Resource.
|
4
|
+
#
|
5
|
+
# This lets you use Person.pick(:michael) instead of
|
6
|
+
# DataMapper::Sweatshop.pick(Person, :michael)
|
2
7
|
module Model
|
8
|
+
# Adds a fixture to record map.
|
9
|
+
# Block is supposed to return a hash of attributes.
|
10
|
+
#
|
11
|
+
# @param name [Symbol, String] Name of the fixture
|
12
|
+
# @param blk [Proc] A proc that returns fixture attributes
|
13
|
+
#
|
14
|
+
# @returns nil
|
15
|
+
#
|
16
|
+
# @api public
|
3
17
|
def fixture(name = default_fauxture_name, &blk)
|
4
18
|
Sweatshop.add(self, name, &blk)
|
5
19
|
end
|
6
20
|
|
7
21
|
alias_method :fix, :fixture
|
8
22
|
|
23
|
+
# Creates an instance from hash of attributes, saves it
|
24
|
+
# and adds it to the record map. Attributes given as the
|
25
|
+
# second argument are merged into attributes from fixture.
|
26
|
+
#
|
27
|
+
# If record is valid because of duplicated property value,
|
28
|
+
# this method does a retry.
|
29
|
+
#
|
30
|
+
# @param name [Symbol]
|
31
|
+
# @param attributes [Hash]
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
#
|
35
|
+
# @returns [DataMapper::Resource] added instance
|
9
36
|
def generate(name = default_fauxture_name, attributes = {})
|
10
37
|
name, attributes = default_fauxture_name, name if name.is_a? Hash
|
11
38
|
Sweatshop.create(self, name, attributes)
|
@@ -13,21 +40,50 @@ module DataMapper
|
|
13
40
|
|
14
41
|
alias_method :gen, :generate
|
15
42
|
|
43
|
+
# Returns a Hash of attributes from the model map.
|
44
|
+
#
|
45
|
+
# @param name [Symbol] name of the fauxture to use
|
46
|
+
#
|
47
|
+
# @returns [Hash] existing instance of a model from the model map
|
48
|
+
# @raises NoFixtureExist when requested fixture does not exist in the model map
|
49
|
+
#
|
50
|
+
# @api public
|
16
51
|
def generate_attributes(name = default_fauxture_name)
|
17
52
|
Sweatshop.attributes(self, name)
|
18
53
|
end
|
19
54
|
|
20
55
|
alias_method :gen_attrs, :generate_attributes
|
21
56
|
|
57
|
+
# Creates an instance from given hash of attributes
|
58
|
+
# and adds it to records map without saving.
|
59
|
+
#
|
60
|
+
# @param name [Symbol] name of the fauxture to use
|
61
|
+
# @param attributes [Hash]
|
62
|
+
#
|
63
|
+
# @api private
|
64
|
+
#
|
65
|
+
# @returns [DataMapper::Resource] added instance
|
22
66
|
def make(name = default_fauxture_name, attributes = {})
|
23
67
|
name, attributes = default_fauxture_name, name if name.is_a? Hash
|
24
68
|
Sweatshop.make(self, name, attributes)
|
25
69
|
end
|
26
70
|
|
71
|
+
# Returns a pre existing instance of a model from the record map
|
72
|
+
#
|
73
|
+
# @param name [Symbol] name of the fauxture to pick
|
74
|
+
#
|
75
|
+
# @returns [DataMapper::Resource] existing instance of a model from the record map
|
76
|
+
# @raises DataMapper::Sweatshop::NoFixtureExist when requested fixture does not exist in the record map
|
77
|
+
#
|
78
|
+
# @api public
|
27
79
|
def pick(name = default_fauxture_name)
|
28
80
|
Sweatshop.pick(self, name)
|
29
81
|
end
|
30
82
|
|
83
|
+
# Default fauxture name. Usually :default.
|
84
|
+
#
|
85
|
+
# @returns [Symbol] default fauxture name
|
86
|
+
# @api public
|
31
87
|
def default_fauxture_name
|
32
88
|
:default
|
33
89
|
end
|
@@ -1,48 +1,111 @@
|
|
1
1
|
module DataMapper
|
2
2
|
class Sweatshop
|
3
|
+
# Raise when requested attributes hash or instance are not
|
4
|
+
# found in model and record maps, respectively.
|
5
|
+
#
|
6
|
+
# This usually happens when you forget to use +make+ or
|
7
|
+
# +generate+ method before trying ti +pick+ an object.
|
8
|
+
class NoFixtureExist < Exception
|
9
|
+
end
|
10
|
+
|
3
11
|
class << self
|
4
12
|
attr_accessor :model_map
|
5
13
|
attr_accessor :record_map
|
6
14
|
end
|
7
15
|
|
16
|
+
# Models map stores named Procs for a class.
|
17
|
+
# Each Proc must return a Hash of attributes.
|
8
18
|
self.model_map = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = []}}
|
19
|
+
# Records map stores named instances of a class.
|
20
|
+
# Those instances may or may not be new records.
|
9
21
|
self.record_map = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = []}}
|
10
22
|
|
23
|
+
# Adds a Proc to model map. Proc must return a Hash of attributes.
|
24
|
+
#
|
25
|
+
# @param klass [Class, DataMapper::Resource]
|
26
|
+
# @param name [Symbol]
|
27
|
+
# @param instance [DataMapper::Resource]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
# @returns [Array] model map
|
11
32
|
def self.add(klass, name, &proc)
|
12
33
|
self.model_map[klass][name.to_sym] << proc
|
13
34
|
end
|
14
35
|
|
36
|
+
# Adds an instance to records map.
|
37
|
+
#
|
38
|
+
# @param klass [Class, DataMapper::Resource]
|
39
|
+
# @param name [Symbol]
|
40
|
+
# @param instance [DataMapper::Resource]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
#
|
44
|
+
# @returns [DataMapper::Resource] added instance
|
15
45
|
def self.record(klass, name, instance)
|
16
46
|
self.record_map[klass][name.to_sym] << instance
|
17
47
|
instance
|
18
48
|
end
|
19
49
|
|
50
|
+
# Creates an instance from given hash of attributes, saves it
|
51
|
+
# and adds it to the record map.
|
52
|
+
#
|
53
|
+
# @param klass [Class, DataMapper::Resource]
|
54
|
+
# @param name [Symbol]
|
55
|
+
# @param attributes [Hash]
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
#
|
59
|
+
# @returns [DataMapper::Resource] added instance
|
20
60
|
def self.create(klass, name, attributes = {})
|
21
|
-
|
22
|
-
record(klass, name, klass.create(attributes(klass, name).merge(attributes)))
|
23
|
-
rescue StandardError => e
|
24
|
-
retry if e.message =~ /^column \w+ is not unique$/
|
25
|
-
raise e
|
26
|
-
end
|
61
|
+
record(klass, name, klass.create(attributes(klass, name).merge(attributes)))
|
27
62
|
end
|
28
63
|
|
64
|
+
# Creates an instance from given hash of attributes
|
65
|
+
# and adds it to records map without saving.
|
66
|
+
#
|
67
|
+
# @param klass [Class, DataMapper::Resource]
|
68
|
+
# @param name [Symbol]
|
69
|
+
# @param attributes [Hash]
|
70
|
+
#
|
71
|
+
# @api private
|
72
|
+
#
|
73
|
+
# @returns [DataMapper::Resource] added instance
|
29
74
|
def self.make(klass, name, attributes = {})
|
30
75
|
record(klass, name, klass.new(attributes(klass, name).merge(attributes)))
|
31
76
|
end
|
32
77
|
|
78
|
+
# Returns a pre existing instance of a model from the record map
|
79
|
+
#
|
80
|
+
# @param klass [Class, DataMapper::Resource]
|
81
|
+
# @param name [Symbol]
|
82
|
+
#
|
83
|
+
# @returns [DataMapper::Resource] existing instance of a model from the record map
|
84
|
+
# @raises DataMapper::Sweatshop::NoFixtureExist when requested fixture does not exist in the record map
|
85
|
+
#
|
86
|
+
# @api private
|
33
87
|
def self.pick(klass, name)
|
34
|
-
self.record_map[klass][name.to_sym].pick || raise(
|
88
|
+
self.record_map[klass][name.to_sym].pick || raise(NoFixtureExist, "no #{name} context fixtures have been generated for the #{klass} class")
|
35
89
|
end
|
36
90
|
|
91
|
+
# Returns a Hash of attributes from the model map
|
92
|
+
#
|
93
|
+
# @param klass [Class, DataMapper::Resource]
|
94
|
+
# @param name [Symbol]
|
95
|
+
#
|
96
|
+
# @returns [Hash] existing instance of a model from the model map
|
97
|
+
# @raises NoFixtureExist when requested fixture does not exist in the model map
|
98
|
+
#
|
99
|
+
# @api private
|
37
100
|
def self.attributes(klass, name)
|
38
101
|
proc = model_map[klass][name.to_sym].pick
|
39
102
|
|
40
|
-
if
|
103
|
+
if proc
|
41
104
|
proc.call
|
42
105
|
elsif klass.superclass.is_a?(DataMapper::Model)
|
43
106
|
attributes(klass.superclass, name)
|
44
107
|
else
|
45
|
-
raise "#{name} fixture was not found"
|
108
|
+
raise NoFixtureExist, "#{name} fixture was not found for class #{klass}"
|
46
109
|
end
|
47
110
|
end
|
48
111
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module DataMapper
|
2
|
+
class Sweatshop
|
3
|
+
module Unique
|
4
|
+
# Yields a value to the block. The value is unique for each invocation
|
5
|
+
# with the same block. Alternatively, you may provide an explicit key to
|
6
|
+
# identify the block.
|
7
|
+
#
|
8
|
+
# If a block with no parameter is supplied, unique keeps track of previous
|
9
|
+
# invocations, and will continue yielding until a unique value is generated.
|
10
|
+
# If a unique value is not generated after @UniqueWorker::MAX_TRIES@, an exception
|
11
|
+
# is raised.
|
12
|
+
#
|
13
|
+
# ParseTree is required unless an explicit key is provided
|
14
|
+
#
|
15
|
+
# (1..3).collect { unique {|x| x }} # => [0, 1, 2]
|
16
|
+
# (1..3).collect { unique {|x| x + 1 }} # => [1, 2, 3]
|
17
|
+
# (1..3).collect { unique {|x| x }} # => [3, 4, 5] # Continued on from above
|
18
|
+
# (1..3).collect { unique(:a) {|x| x }} # => [0, 1, 2] # Explicit key overrides block identity
|
19
|
+
#
|
20
|
+
# a = [1, 1, 1, 2, 2, 3]
|
21
|
+
# (1..3).collect { unique { a.shift }} # => [1, 2, 3]
|
22
|
+
# (1..3).collect { unique { 1 }} # raises TooManyTriesException
|
23
|
+
#
|
24
|
+
# return <Object> the return value of the block
|
25
|
+
def unique(key = nil, &block)
|
26
|
+
if block.arity < 1
|
27
|
+
UniqueWorker.unique_map ||= {}
|
28
|
+
|
29
|
+
key ||= UniqueWorker.key_for(&block)
|
30
|
+
set = UniqueWorker.unique_map[key] || Set.new
|
31
|
+
result = block[]
|
32
|
+
tries = 0
|
33
|
+
while set.include?(result)
|
34
|
+
result = block[]
|
35
|
+
tries += 1
|
36
|
+
|
37
|
+
raise TooManyTriesException.new("Could not generate unique value after #{tries} attempts") if tries >= UniqueWorker::MAX_TRIES
|
38
|
+
end
|
39
|
+
set << result
|
40
|
+
UniqueWorker.unique_map[key] = set
|
41
|
+
else
|
42
|
+
UniqueWorker.count_map ||= Hash.new() { 0 }
|
43
|
+
|
44
|
+
key ||= UniqueWorker.key_for(&block)
|
45
|
+
result = block[UniqueWorker.count_map[key]]
|
46
|
+
UniqueWorker.count_map[key] += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
class TooManyTriesException < RuntimeError; end;
|
53
|
+
end
|
54
|
+
extend(Unique)
|
55
|
+
|
56
|
+
class UniqueWorker
|
57
|
+
MAX_TRIES = 10
|
58
|
+
|
59
|
+
begin
|
60
|
+
require 'parse_tree'
|
61
|
+
rescue LoadError
|
62
|
+
puts "DataMapper::Sweatshop::Unique - ParseTree could not be loaded, anonymous uniques will not be allowed"
|
63
|
+
end
|
64
|
+
|
65
|
+
cattr_accessor :count_map
|
66
|
+
cattr_accessor :unique_map
|
67
|
+
cattr_accessor :parser
|
68
|
+
|
69
|
+
# Use the sexp representation of the block as a unique key for the block
|
70
|
+
# If you copy and paste a block, it will still have the same key
|
71
|
+
#
|
72
|
+
# return <Object> the unique key for the block
|
73
|
+
def self.key_for(&block)
|
74
|
+
raise "You need to install ParseTree to use anonymous an anonymous unique (gem install ParseTree). In the mean time, explicitly declare a key: unique(:my_key) { ... }" unless Object::const_defined?("ParseTree")
|
75
|
+
|
76
|
+
klass = Class.new
|
77
|
+
name = "tmp"
|
78
|
+
klass.send(:define_method, name, &block)
|
79
|
+
self.parser ||= ParseTree.new(false)
|
80
|
+
self.parser.parse_tree_for_method(klass, name).last
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/dm-sweatshop/version.rb
CHANGED
data/lib/dm-sweatshop.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
|
-
gem 'dm-core', '
|
3
|
+
gem 'dm-core', '~>0.9.7'
|
4
4
|
require 'dm-core'
|
5
5
|
require 'randexp'
|
6
6
|
|
@@ -9,3 +9,4 @@ dir = Pathname(__FILE__).dirname.expand_path / 'dm-sweatshop'
|
|
9
9
|
require dir / "version"
|
10
10
|
require dir / "sweatshop"
|
11
11
|
require dir / "model"
|
12
|
+
require dir / "unique"
|
@@ -31,46 +31,148 @@ describe DataMapper::Model do
|
|
31
31
|
DataMapper::Sweatshop.record_map.clear
|
32
32
|
end
|
33
33
|
|
34
|
+
describe ".default_fauxture_name" do
|
35
|
+
it "is :default" do
|
36
|
+
Order.default_fauxture_name.should == :default
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
34
40
|
describe ".fixture" do
|
35
|
-
|
36
|
-
|
41
|
+
describe "without fauxture name" do
|
42
|
+
before :each do
|
43
|
+
Widget.fixture {{
|
44
|
+
:name => /\w+/.gen.capitalize,
|
45
|
+
:price => /\d{4,5}/.gen.to_i
|
46
|
+
}}
|
47
|
+
|
48
|
+
@default = DataMapper::Sweatshop.model_map[Widget][:default]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "add a fixture proc for the model with name :default" do
|
52
|
+
@default.should_not be_empty
|
53
|
+
@default.first.should be_kind_of(Proc)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should allow handle complex named fixtures" do
|
58
|
+
Wonket.fix {{
|
59
|
+
:name => /\w+ Wonket/.gen.capitalize,
|
60
|
+
:price => /\d{2,3}99/.gen.to_i,
|
61
|
+
:size => %w[small medium large xl].pick
|
62
|
+
}}
|
63
|
+
|
64
|
+
Order.fix {{
|
65
|
+
:widgets => (1..5).of { Widget.gen }
|
66
|
+
}}
|
67
|
+
|
68
|
+
Order.fix(:wonket_order) {{
|
69
|
+
:widgets => (5..10).of { Wonket.gen }
|
70
|
+
}}
|
71
|
+
|
72
|
+
wonket_order = Order.gen(:wonket_order)
|
73
|
+
wonket_order.widgets.should_not be_empty
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should allow for STI fixtures" do
|
77
|
+
Widget.fix {{
|
37
78
|
:name => /\w+/.gen.capitalize,
|
38
79
|
:price => /\d{4,5}/.gen.to_i
|
39
80
|
}}
|
40
81
|
|
41
|
-
|
82
|
+
Order.fix {{
|
83
|
+
:widgets => (1..5).of { Wonket.gen }
|
84
|
+
}}
|
85
|
+
|
86
|
+
Order.gen.widgets.should_not be_empty
|
42
87
|
end
|
43
88
|
end
|
44
89
|
|
45
|
-
it "should allow handle complex named fixtures" do
|
46
|
-
Wonket.fix {{
|
47
|
-
:name => /\w+ Wonket/.gen.capitalize,
|
48
|
-
:price => /\d{2,3}99/.gen.to_i,
|
49
|
-
:size => %w[small medium large xl].pick
|
50
|
-
}}
|
51
90
|
|
52
|
-
|
53
|
-
|
54
|
-
|
91
|
+
describe ".make" do
|
92
|
+
before :each do
|
93
|
+
Widget.fix(:red) {{
|
94
|
+
:name => "red",
|
95
|
+
:price => 20
|
96
|
+
}}
|
97
|
+
|
98
|
+
@widget = Widget.make(:red)
|
99
|
+
end
|
55
100
|
|
56
|
-
|
57
|
-
|
58
|
-
|
101
|
+
it "creates an object from named attributes hash" do
|
102
|
+
@widget.name.should == "red"
|
103
|
+
@widget.price.should == 20
|
104
|
+
end
|
59
105
|
|
60
|
-
|
61
|
-
|
106
|
+
it "returns a new object" do
|
107
|
+
@widget.should be_new_record
|
108
|
+
end
|
62
109
|
end
|
63
110
|
|
64
|
-
it "should allow for STI fixtures" do
|
65
|
-
Widget.fix {{
|
66
|
-
:name => /\w+/.gen.capitalize,
|
67
|
-
:price => /\d{4,5}/.gen.to_i
|
68
|
-
}}
|
69
111
|
|
70
|
-
|
71
|
-
|
72
|
-
|
112
|
+
describe ".generate" do
|
113
|
+
before :each do
|
114
|
+
Widget.fix(:red) {{
|
115
|
+
:name => "red",
|
116
|
+
:price => 20
|
117
|
+
}}
|
118
|
+
|
119
|
+
@widget = Widget.gen(:red)
|
120
|
+
end
|
73
121
|
|
74
|
-
|
122
|
+
it "creates an object from named attributes hash" do
|
123
|
+
@widget.name.should == "red"
|
124
|
+
@widget.price.should == 20
|
125
|
+
end
|
126
|
+
|
127
|
+
it "returns a saved object" do
|
128
|
+
@widget.should_not be_new_record
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
describe ".pick" do
|
134
|
+
before :each do
|
135
|
+
Widget.fix(:red) {{
|
136
|
+
:name => "rosso",
|
137
|
+
:price => 20
|
138
|
+
}}
|
139
|
+
|
140
|
+
Widget.fix(:yellow) {{
|
141
|
+
:name => "giallo",
|
142
|
+
:price => 30
|
143
|
+
}}
|
144
|
+
|
145
|
+
@red = Widget.gen(:red)
|
146
|
+
@yellow = Widget.gen(:yellow)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "returns a pre existing object with named attributes hash" do
|
150
|
+
@red.name.should == "rosso"
|
151
|
+
@red.price.should == 20
|
152
|
+
|
153
|
+
@yellow.name.should == "giallo"
|
154
|
+
@yellow.price.should == 30
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
describe ".generate_attributes" do
|
160
|
+
before :each do
|
161
|
+
Widget.fix(:red) {{
|
162
|
+
:name => "red",
|
163
|
+
:price => 20
|
164
|
+
}}
|
165
|
+
|
166
|
+
@hash = Widget.generate_attributes(:red)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "returns a Hash" do
|
170
|
+
@hash.should be_an_instance_of(Hash)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "returns stored attributes hash by name" do
|
174
|
+
@hash[:name].should == "red"
|
175
|
+
@hash[:price].should == 20
|
176
|
+
end
|
75
177
|
end
|
76
178
|
end
|
@@ -76,7 +76,8 @@ describe DataMapper::Sweatshop do
|
|
76
76
|
end
|
77
77
|
|
78
78
|
it "should raise an error if neither the class or it's parent class(es) have been mapped" do
|
79
|
-
lambda { DataMapper::Sweatshop.attributes(Child, :default) }.
|
79
|
+
lambda { DataMapper::Sweatshop.attributes(Child, :default) }.
|
80
|
+
should raise_error(DataMapper::Sweatshop::NoFixtureExist, /default fixture was not found for class/)
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataMapper::Sweatshop::Unique do
|
4
|
+
describe '#unique' do
|
5
|
+
before(:each) do
|
6
|
+
@ss = DataMapper::Sweatshop
|
7
|
+
DataMapper::Sweatshop::UniqueWorker.class_eval do
|
8
|
+
self.count_map = Hash.new() { 0 }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'for the same block, yields an incrementing value' do
|
13
|
+
(1..3).to_a.collect { @ss.unique {|x| "a#{x}"} }.should ==
|
14
|
+
%w(a0 a1 a2)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'for the different blocks, yields separately incrementing values' do
|
18
|
+
(1..3).to_a.collect { @ss.unique {|x| "a#{x}"} }.should ==
|
19
|
+
%w(a0 a1 a2)
|
20
|
+
(1..3).to_a.collect { @ss.unique {|x| "b#{x}"} }.should ==
|
21
|
+
%w(b0 b1 b2)
|
22
|
+
(1..3).to_a.collect { @ss.unique {|x| "a#{x}"} }.should ==
|
23
|
+
%w(a3 a4 a5)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'allows an optional key to be specified' do
|
27
|
+
(1..3).to_a.collect { @ss.unique {|x| "a#{x}"} }.should ==
|
28
|
+
%w(a0 a1 a2)
|
29
|
+
(1..3).to_a.collect { @ss.unique(:a) {|x| "a#{x}"} }.should ==
|
30
|
+
%w(a0 a1 a2)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'when the block has an arity less than 1' do
|
34
|
+
it 'keeps yielding until a unique value is generated' do
|
35
|
+
a = [1,1,1,2]
|
36
|
+
(1..2).collect { @ss.unique { a.shift }}.should ==
|
37
|
+
[1, 2]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'raises when a unique value cannot be generated' do
|
41
|
+
a = [1,1,1, nil]
|
42
|
+
lambda {
|
43
|
+
(1..3).collect { @ss.unique { a.shift }}
|
44
|
+
}.should raise_error(DataMapper::Sweatshop::Unique::TooManyTriesException)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'when ParseTree is unavilable' do
|
49
|
+
it 'raises when no key is provided' do
|
50
|
+
Object.stub!(:const_defined?).with("ParseTree").and_return(false)
|
51
|
+
lambda {
|
52
|
+
@ss.unique {}
|
53
|
+
}.should raise_error
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'does not raise when a key is provided' do
|
57
|
+
lambda {
|
58
|
+
@ss.unique(:a) {}
|
59
|
+
}.should_not raise_error
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'when mixing into an object' do
|
65
|
+
it 'only the unique method is added to the public interface' do
|
66
|
+
obj = Object.new
|
67
|
+
old = obj.public_methods
|
68
|
+
obj.extend(DataMapper::Sweatshop::Unique)
|
69
|
+
new = obj.public_methods
|
70
|
+
(new - old).should == ["unique"]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dm-sweatshop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Burkert
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-11-18 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - "="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.9.
|
23
|
+
version: 0.9.7
|
24
24
|
version:
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: randexp
|
@@ -40,7 +40,7 @@ dependencies:
|
|
40
40
|
requirements:
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 1.
|
43
|
+
version: 1.8.2
|
44
44
|
version:
|
45
45
|
description: DataMapper plugin for building pseudo random models
|
46
46
|
email:
|
@@ -58,14 +58,17 @@ files:
|
|
58
58
|
- LICENSE
|
59
59
|
- Manifest.txt
|
60
60
|
- README.textile
|
61
|
+
- README.txt
|
61
62
|
- Rakefile
|
62
63
|
- TODO
|
63
64
|
- lib/dm-sweatshop.rb
|
64
|
-
- lib/dm-sweatshop/sweatshop.rb
|
65
65
|
- lib/dm-sweatshop/model.rb
|
66
|
+
- lib/dm-sweatshop/sweatshop.rb
|
67
|
+
- lib/dm-sweatshop/unique.rb
|
66
68
|
- lib/dm-sweatshop/version.rb
|
67
69
|
- spec/dm-sweatshop/model_spec.rb
|
68
70
|
- spec/dm-sweatshop/sweatshop_spec.rb
|
71
|
+
- spec/dm-sweatshop/unique_spec.rb
|
69
72
|
- spec/spec.opts
|
70
73
|
- spec/spec_helper.rb
|
71
74
|
has_rdoc: true
|
@@ -91,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
94
|
requirements: []
|
92
95
|
|
93
96
|
rubyforge_project: datamapper
|
94
|
-
rubygems_version: 1.
|
97
|
+
rubygems_version: 1.3.1
|
95
98
|
signing_key:
|
96
99
|
specification_version: 2
|
97
100
|
summary: DataMapper plugin for building pseudo random models
|