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 CHANGED
@@ -0,0 +1,6 @@
1
+ === 0.9.7
2
+
3
+ * 2 major enhancements
4
+
5
+ * Reworked unique model generator.
6
+ * Improved documentation.
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 recylce some already generated tags instead.
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
- :message => /\@#{User.pick.name} [:sentence:]/.gen[0..140], #an @reply for some user
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
- Go forth, and populate your data.
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.
@@ -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
- begin
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(NoFixturesExist, "no #{name} context fixtures have been generated for the #{klass} class")
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 not proc.nil?
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
@@ -1,5 +1,5 @@
1
1
  module DataMapper
2
2
  class Sweatshop
3
- VERSION = "0.9.6"
3
+ VERSION = "0.9.7"
4
4
  end
5
5
  end
data/lib/dm-sweatshop.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
2
 
3
- gem 'dm-core', '=0.9.6'
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
- it "should add a fixture proc for the model" do
36
- Widget.fixture {{
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
- DataMapper::Sweatshop.model_map[Widget][:default].should_not be_empty
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
- Order.fix {{
53
- :widgets => (1..5).of { Widget.gen }
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
- Order.fix(:wonket_order) {{
57
- :widgets => (5..10).of { Wonket.gen }
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
- wonket_order = Order.gen(:wonket_order)
61
- wonket_order.widgets.should_not be_empty
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
- Order.fix {{
71
- :widgets => (1..5).of { Wonket.gen }
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
- Order.gen.widgets.should_not be_empty
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) }.should raise_error("default fixture was not found")
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.6
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-10-12 00:00:00 -06:00
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.6
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.7.0
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.2.0
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