JamieFlournoy-machinist 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,7 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ at.clear_mappings
3
+
4
+ at.add_mapping(%r%^spec/(.*)_spec.rb$%) {|filename, _| filename }
5
+ at.add_mapping(%r%^lib/(.*).rb$%) {|_, match| "spec/#{match[1]}_spec.rb" }
6
+ at.add_mapping(%r%^spec/spec_helper.rb$%) { at.files_matching(%r%^spec/(.*)_spec.rb$%) }
7
+ end
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ coverage
2
+ doc
3
+ *.gem
4
+ *.project
data/FAQ.markdown ADDED
@@ -0,0 +1,18 @@
1
+ Machinist FAQ
2
+ =============
3
+
4
+ ### My blueprint is giving me really weird errors. Any ideas?
5
+
6
+ If your object has an attribute that happens to correspond to a Ruby standard function, it won't work properly in a blueprint.
7
+
8
+ For example:
9
+
10
+ OpeningHours.blueprint do
11
+ open { Time.now }
12
+ end
13
+
14
+ This will result in Machinist attempting to run ruby's open command. To work around this use self.open instead.
15
+
16
+ OpeningHours.blueprint do
17
+ self.open { Time.now }
18
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Peter Yandell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,319 @@
1
+ Machinist
2
+ =========
3
+
4
+ *Fixtures aren't fun. Machinist is.*
5
+
6
+ Machinist makes it easy to create test data within your tests. It generates data for the fields you don't care about, and constructs any necessary associated objects, leaving you to only specify the fields you *do* care about in your tests. For example:
7
+
8
+ describe Comment do
9
+ before do
10
+ # This will make a Comment, a Post, and a User (the author of
11
+ # the Post), and generate values for all their attributes:
12
+ @comment = Comment.make(:spam => true)
13
+ end
14
+
15
+ it "should not include comments marked as spam in the without_spam named scope" do
16
+ Comment.without_spam.should_not include(@comment)
17
+ end
18
+ end
19
+
20
+ You tell Machinist how to do this with blueprints:
21
+
22
+ require 'machinist/active_record'
23
+ require 'sham'
24
+ require 'faker'
25
+
26
+ Sham.name { Faker::Name.name }
27
+ Sham.email { Faker::Internet.email }
28
+ Sham.title { Faker::Lorem.sentence }
29
+ Sham.body { Faker::Lorem.paragraph }
30
+
31
+ User.blueprint do
32
+ name
33
+ email
34
+ end
35
+
36
+ Post.blueprint do
37
+ title
38
+ author
39
+ body
40
+ end
41
+
42
+ Comment.blueprint do
43
+ post
44
+ author_name { Sham.name }
45
+ author_email { Sham.email }
46
+ body
47
+ end
48
+
49
+
50
+ Download & Install
51
+ ==================
52
+
53
+ ### Installing as a Rails plugin
54
+
55
+ ./script/plugin install git://github.com/notahat/machinist.git
56
+
57
+ ### Installing as a Gem
58
+
59
+ sudo gem install machinist --source http://gemcutter.org
60
+
61
+ ### Setting up your project
62
+
63
+ Create a `blueprints.rb` file to hold your blueprints in your test (or spec) directory. It should start with:
64
+
65
+ require 'machinist/active_record'
66
+ require 'sham'
67
+
68
+ Substitute `data_mapper` or `sequel` for `active_record` if that's your weapon of choice.
69
+
70
+ Require `blueprints.rb` in your `test_helper.rb` (or `spec_helper.rb`):
71
+
72
+ require File.expand_path(File.dirname(__FILE__) + "/blueprints")
73
+
74
+ Set Sham to reset before each test. In the `class ActiveSupport::TestCase` block in your `test_helper.rb`, add:
75
+
76
+ setup { Sham.reset }
77
+
78
+ or, if you're on RSpec, in the `Spec::Runner.configure` block in your `spec_helper.rb`, add:
79
+
80
+ config.before(:all) { Sham.reset(:before_all) }
81
+ config.before(:each) { Sham.reset(:before_each) }
82
+
83
+
84
+ Documentation
85
+ =============
86
+
87
+ Sham - Generating Attribute Values
88
+ ----------------------------------
89
+
90
+ Sham lets you generate random but repeatable unique attributes values.
91
+
92
+ For example, you could define a way to generate random names as:
93
+
94
+ Sham.name { (1..10).map { ('a'..'z').to_a.rand }.join }
95
+
96
+ Then, to generate a name, call:
97
+
98
+ Sham.name
99
+
100
+ So why not just define a helper method to do this? Sham ensures two things for you:
101
+
102
+ 1. You get the same sequence of values each time your test is run
103
+ 2. You don't get any duplicate values
104
+
105
+ Sham works very well with the excellent [Faker gem](http://faker.rubyforge.org/) by Benjamin Curtis. Using this, a much nicer way to generate names is:
106
+
107
+ Sham.name { Faker::Name.name }
108
+
109
+ Sham also supports generating numbered sequences if you prefer.
110
+
111
+ Sham.name {|index| "Name #{index}" }
112
+
113
+ If you want to allow duplicate values for a sham, you can pass the `:unique` option:
114
+
115
+ Sham.coin_toss(:unique => false) { rand(2) == 0 ? 'heads' : 'tails' }
116
+
117
+ You can create a bunch of sham definitions in one hit like this:
118
+
119
+ Sham.define do
120
+ title { Faker::Lorem.words(5).join(' ') }
121
+ name { Faker::Name.name }
122
+ body { Faker::Lorem.paragraphs(3).join("\n\n") }
123
+ end
124
+
125
+
126
+ Blueprints - Generating Objects
127
+ -------------------------------
128
+
129
+ A blueprint describes how to generate an object. The idea is that you let the blueprint take care of making up values for attributes that you don't care about in your test, leaving you to focus on the just the things that you're testing.
130
+
131
+ A simple blueprint might look like this:
132
+
133
+ Post.blueprint do
134
+ title { Sham.title }
135
+ author { Sham.name }
136
+ body { Sham.body }
137
+ end
138
+
139
+ You can then construct a Post from this blueprint with:
140
+
141
+ Post.make
142
+
143
+ When you call `make`, Machinist calls `Post.new`, then runs through the attributes in your blueprint, calling the block for each attribute to generate a value. The Post is then saved and reloaded. An exception is thrown if Post can't be saved.
144
+
145
+ You can override values defined in the blueprint by passing a hash to make:
146
+
147
+ Post.make(:title => "A Specific Title")
148
+
149
+ If you don't supply a block for an attribute in the blueprint, Machinist will look for a Sham definition with the same name as the attribute, so you can shorten the above blueprint to:
150
+
151
+ Post.blueprint do
152
+ title
153
+ author { Sham.name }
154
+ body
155
+ end
156
+
157
+ If you want to generate an object without saving it to the database, replace `make` with `make_unsaved`. (`make_unsaved` also ensures that any associated objects that need to be generated are not saved - although not if you are using Sequel. See the section on associations below.)
158
+
159
+ You can refer to already assigned attributes when constructing a new attribute:
160
+
161
+ Post.blueprint do
162
+ title
163
+ author { Sham.name }
164
+ body { "Post by #{author}" }
165
+ end
166
+
167
+
168
+ ### Named Blueprints
169
+
170
+ Named blueprints let you define variations on an object. For example, suppose some of your Users are administrators:
171
+
172
+ User.blueprint do
173
+ name
174
+ email
175
+ end
176
+
177
+ User.blueprint(:admin) do
178
+ name { Sham.name + " (admin)" }
179
+ admin { true }
180
+ end
181
+
182
+ Calling:
183
+
184
+ User.make(:admin)
185
+
186
+ will use the `:admin` blueprint.
187
+
188
+ Named blueprints call the default blueprint to set any attributes not specifically provided, so in this example the `email` attribute will still be generated even for an admin user.
189
+
190
+ You must define a default blueprint for any class that has a named blueprint, even if the default blueprint is empty.
191
+
192
+
193
+ ### Belongs\_to Associations
194
+
195
+ If you're generating an object that belongs to another object, you can generate the associated object like this:
196
+
197
+ Comment.blueprint do
198
+ post { Post.make }
199
+ end
200
+
201
+ Calling `Comment.make` will construct a Comment and its associated Post, and save both.
202
+
203
+ If you want to override the value for post when constructing the comment, you can do this:
204
+
205
+ post = Post.make(:title => "A particular title)
206
+ comment = Comment.make(:post => post)
207
+
208
+ Machinist will not call the blueprint block for the post attribute, so this won't generate two posts.
209
+
210
+ Machinist is smart enough to look at the association and work out what sort of object it needs to create, so you can shorten the above blueprint to:
211
+
212
+ Comment.blueprint do
213
+ post
214
+ end
215
+
216
+
217
+ ### Other Associations
218
+
219
+ For has\_many and has\_and\_belongs\_to\_many associations, ActiveRecord insists that the object be saved before any associated objects can be saved. That means you can't generate the associated objects from within the blueprint.
220
+
221
+ The simplest solution is to write a test helper:
222
+
223
+ def make_post_with_comments(attributes = {})
224
+ post = Post.make(attributes)
225
+ 3.times { post.comments.make }
226
+ post
227
+ end
228
+
229
+ Note here that you can call `make` on a has\_many association. (This isn't yet supported for DataMapper.)
230
+
231
+ Make can take a block, into which it passes the constructed object, so the above can be written as:
232
+
233
+ def make_post_with_comments
234
+ Post.make(attributes) do |post|
235
+ 3.times { post.comments.make }
236
+ end
237
+ end
238
+
239
+
240
+ ### Using Blueprints in Rails Controller Tests
241
+
242
+ The `plan` method behaves like `make`, except it returns a hash of attributes, and doesn't save the object. This is useful for passing in to controller tests:
243
+
244
+ test "should create post" do
245
+ assert_difference('Post.count') do
246
+ post :create, :post => Post.plan
247
+ end
248
+ assert_redirected_to post_path(assigns(:post))
249
+ end
250
+
251
+ `plan` will save any associated objects. In this example, it will create an Author, and it knows that the controller expects an `author_id` attribute, rather than an `author` attribute, and makes this translation for you.
252
+
253
+ You can also call plan on has\_many associations, making it easy to test nested controllers:
254
+
255
+ test "should create comment" do
256
+ post = Post.make
257
+ assert_difference('Comment.count') do
258
+ post :create, :post_id => post.id, :comment => post.comments.plan
259
+ end
260
+ assert_redirected_to post_comment_path(post, assigns(:comment))
261
+ end
262
+
263
+ (Calling plan on associations is not yet supported in DataMapper.)
264
+
265
+
266
+ ### Blueprints on Plain Old Ruby Objects
267
+
268
+ Machinist also works with plain old Ruby objects. Let's say you have a class like:
269
+
270
+ class Post
271
+ attr_accessor :title
272
+ attr_accessor :body
273
+ end
274
+
275
+ You can then do the following in your `blueprints.rb`:
276
+
277
+ require 'machinist/object'
278
+
279
+ Post.blueprint do
280
+ title "A title!"
281
+ body "A body!"
282
+ end
283
+
284
+ Community
285
+ =========
286
+
287
+ You can always find the [latest version on GitHub](http://github.com/notahat/machinist).
288
+
289
+ If you have questions, check out the [Google Group](http://groups.google.com/group/machinist-users).
290
+
291
+ File bug reports and feature requests in the [issue tracker](http://github.com/notahat/machinist/issues).
292
+
293
+ Contributors
294
+ ------------
295
+
296
+ Machinist is maintained by Pete Yandell ([pete@notahat.com](mailto:pete@notahat.com), [@notahat](http://twitter.com/notahat))
297
+
298
+ Other contributors include:
299
+
300
+ [Marcos Arias](http://github.com/yizzreel),
301
+ [Jack Dempsey](http://github.com/jackdempsey),
302
+ [Clinton Forbes](http://github.com/clinton),
303
+ [Perryn Fowler](http://github.com/perryn),
304
+ [Niels Ganser](http://github.com/Nielsomat),
305
+ [Jeremy Grant](http://github.com/jeremygrant),
306
+ [Jon Guymon](http://github.com/gnarg),
307
+ [James Healy](http://github.com/yob),
308
+ [Evan David Light](http://github.com/elight),
309
+ [Chris Lloyd](http://github.com/chrislloyd),
310
+ [Adam Meehan](http://github.com/adzap),
311
+ [Kyle Neath](http://github.com/kneath),
312
+ [Lawrence Pit](http://github.com/lawrencepit),
313
+ [T.J. Sheehy](http://github.com/tjsheehy),
314
+ [Roland Swingler](http://github.com/knaveofdiamonds),
315
+ [Gareth Townsend](http://github.com/quamen),
316
+ [Matt Wastrodowski](http://github.com/towski),
317
+ [Ian White](http://github.com/ianwhite)
318
+
319
+ Thanks to Thoughtbot's [Factory Girl](http://github.com/thoughtbot/factory_girl/tree/master). Machinist was written because I loved the idea behind Factory Girl, but I thought the philosophy wasn't quite right, and I hated the syntax.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gem|
6
+ gem.name = "JamieFlournoy-machinist"
7
+ gem.summary = "Fixtures aren't fun. Machinist is."
8
+ gem.email = "pete@notahat.com"
9
+ gem.homepage = "http://github.com/notahat/machinist"
10
+ gem.authors = ["Pete Yandell"]
11
+ gem.has_rdoc = false
12
+ gem.add_development_dependency "rspec", ">= 1.2.8"
13
+ gem.add_development_dependency "activerecord"
14
+ gem.add_development_dependency "sequel"
15
+ gem.add_development_dependency "dm-core"
16
+ gem.add_development_dependency "dm-validations"
17
+ gem.add_development_dependency "data_objects"
18
+ gem.add_development_dependency "do_sqlite3"
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
+ end
24
+
25
+
26
+ require 'spec/rake/spectask'
27
+ desc 'Run the specs.'
28
+ Spec::Rake::SpecTask.new(:spec) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.spec_files = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ desc 'Run the specs with rcov.'
34
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
35
+ spec.libs << 'lib' << 'spec'
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :spec => :check_dependencies
41
+
42
+ desc 'Run the specs.'
43
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.6
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'machinist' if RAILS_ENV == 'test'
2
+
data/lib/machinist.rb ADDED
@@ -0,0 +1,121 @@
1
+ require 'sham'
2
+
3
+ module Machinist
4
+
5
+ # A Lathe is used to execute the blueprint and construct an object.
6
+ #
7
+ # The blueprint is instance_eval'd against the Lathe.
8
+ class Lathe
9
+ def self.run(adapter, object, *args)
10
+ if args.first.is_a?(Symbol)
11
+ name = args.shift
12
+ named_blueprint = object.class.blueprint(name)
13
+ raise "No blueprint named '#{name}' defined for class #{object.class}" if named_blueprint.nil?
14
+ end
15
+
16
+ blueprint = object.class.blueprint
17
+ if blueprint.nil?
18
+ if named_blueprint
19
+ raise "Can't construct an object from a named blueprint without a default blueprint for class #{object.class}"
20
+ else
21
+ raise "No blueprint for class #{object.class}"
22
+ end
23
+ end
24
+
25
+ attributes = args.pop || {}
26
+
27
+ lathe = self.new(adapter, object, attributes)
28
+ lathe.instance_eval(&named_blueprint) if named_blueprint
29
+ klass = object.class
30
+ while klass
31
+ lathe.instance_eval(&klass.blueprint) if klass.respond_to?(:blueprint) && klass.blueprint
32
+ klass = klass.superclass
33
+ end
34
+ lathe
35
+ end
36
+
37
+ def initialize(adapter, object, attributes = {})
38
+ @adapter = adapter
39
+ @object = object
40
+ attributes.each {|key, value| assign_attribute(key, value) }
41
+ end
42
+
43
+ def object
44
+ yield @object if block_given?
45
+ @object
46
+ end
47
+
48
+ def method_missing(symbol, *args, &block)
49
+ if attribute_assigned?(symbol)
50
+ # If we've already assigned the attribute, return that.
51
+ @object.send(symbol)
52
+ elsif @adapter.has_association?(@object, symbol) && !nil_or_empty?(@object.send(symbol))
53
+ # If the attribute is an association and is already assigned, return that.
54
+ @object.send(symbol)
55
+ else
56
+ # Otherwise generate a value and assign it.
57
+ assign_attribute(symbol, generate_attribute_value(symbol, *args, &block))
58
+ end
59
+ end
60
+
61
+ def assigned_attributes
62
+ @assigned_attributes ||= {}
63
+ end
64
+
65
+ # Undef a couple of methods that are common ActiveRecord attributes.
66
+ # (Both of these are deprecated in Ruby 1.8 anyway.)
67
+ undef_method :id if respond_to?(:id)
68
+ undef_method :type if respond_to?(:type)
69
+
70
+ private
71
+
72
+ def nil_or_empty?(object)
73
+ object.respond_to?(:empty?) ? object.empty? : object.nil?
74
+ end
75
+
76
+ def assign_attribute(key, value)
77
+ assigned_attributes[key.to_sym] = value
78
+ @object.send("#{key}=", value)
79
+ end
80
+
81
+ def attribute_assigned?(key)
82
+ assigned_attributes.has_key?(key.to_sym)
83
+ end
84
+
85
+ def generate_attribute_value(attribute, *args)
86
+ if block_given?
87
+ # If we've got a block, use that to generate the value.
88
+ yield
89
+ else
90
+ # Otherwise, look for an association or a sham.
91
+ if @adapter.has_association?(object, attribute)
92
+ @adapter.class_for_association(object, attribute).make(args.first || {})
93
+ elsif args.empty?
94
+ Sham.send(attribute)
95
+ else
96
+ # If we've got a constant, just use that.
97
+ args.first
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ # This sets a flag that stops make from saving objects, so
105
+ # that calls to make from within a blueprint don't create
106
+ # anything inside make_unsaved.
107
+ def self.with_save_nerfed
108
+ begin
109
+ @@nerfed = true
110
+ yield
111
+ ensure
112
+ @@nerfed = false
113
+ end
114
+ end
115
+
116
+ @@nerfed = false
117
+ def self.nerfed?
118
+ @@nerfed
119
+ end
120
+
121
+ end