Skip to content

Commit d6492e6

Browse files
committed
Added support for named blueprints (after many, many requests.)
1 parent 1f916bb commit d6492e6

File tree

5 files changed

+69
-43
lines changed

5 files changed

+69
-43
lines changed

README.markdown

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,28 @@ You can also call plan on has\_many associations, making it easy to test nested
244244
end
245245

246246

247+
### Named Blueprints
248+
249+
Named blueprints let you define variations on an object. For example, suppose some of your Users are administrators:
250+
251+
User.blueprint do
252+
name
253+
email
254+
end
255+
256+
User.blueprint(:admin) do
257+
name { Sham.name + " (admin)" }
258+
admin { true }
259+
end
260+
261+
Calling:
262+
263+
User.make(:admin)
264+
265+
will use the `:admin` blueprint.
266+
267+
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.
268+
247269

248270
FAQ
249271
---
@@ -264,33 +286,6 @@ This will result in Machinist attempting to run ruby's open command. To work aro
264286
self.open { Time.now }
265287
end
266288

267-
### I'm a factory_girl user, and I like having multiple factories for a single model. Can Machinist do the same thing?
268-
269-
Short answer: no.
270-
271-
Machinist blueprints are a little different to factory_girl's factories. Your blueprint should only specify how to generate values for attributes that you don't care about. If you care about an attribute's value, then it doesn't belong in the blueprint.
272-
273-
If you have want to construct objects with similar attributes in a number of tests, just make a test helper. For example:
274-
275-
User.blueprint do
276-
login
277-
password
278-
end
279-
280-
def make_admin_user(attributes = {})
281-
User.make(attributes.merge(:role => :admin))
282-
end
283-
284-
This keeps the blueprint very clean and generic, and also makes it clear what differentiates an admin user from a generic user.
285-
286-
If you want to make this look a bit cleaner in your tests, you can try the following in your blueprint:
287-
288-
class User
289-
def self.make_admin(attributes = {})
290-
make(attributes.merge(:role => :admin)
291-
end
292-
end
293-
294289

295290
Credits
296291
-------

lib/machinist.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ module Machinist
99
#
1010
# The blueprint is instance_eval'd against the Lathe.
1111
class Lathe
12-
def self.run(object, attributes = {})
13-
blueprint = object.class.blueprint
12+
def self.run(object, *args)
13+
blueprint = object.class.blueprint
14+
named_blueprint = object.class.blueprint(args.shift) if args.first.is_a?(Symbol)
15+
attributes = args.pop || {}
1416
raise "No blueprint for class #{object.class}" if blueprint.nil?
1517
returning self.new(object, attributes) do |lathe|
18+
lathe.instance_eval(&named_blueprint) if named_blueprint
1619
lathe.instance_eval(&blueprint)
1720
end
1821
end

lib/machinist/active_record.rb

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,45 +51,46 @@ def self.included(base)
5151
end
5252

5353
module ClassMethods
54-
def blueprint(&blueprint)
55-
@blueprint = blueprint if block_given?
56-
@blueprint
54+
def blueprint(name = :master, &blueprint)
55+
@blueprints ||= {}
56+
@blueprints[name] = blueprint if block_given?
57+
@blueprints[name]
5758
end
5859

59-
def make(attributes = {}, &block)
60-
lathe = Lathe.run(self.new, attributes)
60+
def make(*args, &block)
61+
lathe = Lathe.run(self.new, *args)
6162
unless Machinist::ActiveRecord.nerfed?
6263
lathe.object.save!
6364
lathe.object.reload
6465
end
6566
lathe.object(&block)
6667
end
6768

68-
def make_unsaved(attributes = {})
69-
returning(Machinist::ActiveRecord.with_save_nerfed { make(attributes) }) do |object|
69+
def make_unsaved(*args)
70+
returning(Machinist::ActiveRecord.with_save_nerfed { make(*args) }) do |object|
7071
yield object if block_given?
7172
end
7273
end
7374

74-
def plan(attributes = {})
75-
lathe = Lathe.run(self.new, attributes)
75+
def plan(*args)
76+
lathe = Lathe.run(self.new, *args)
7677
Machinist::ActiveRecord.assigned_attributes_without_associations(lathe)
7778
end
7879
end
7980
end
8081

8182
module BelongsToExtensions
82-
def make(attributes = {}, &block)
83-
lathe = Lathe.run(self.build, attributes)
83+
def make(*args, &block)
84+
lathe = Lathe.run(self.build, *args)
8485
unless Machinist::ActiveRecord.nerfed?
8586
lathe.object.save!
8687
lathe.object.reload
8788
end
8889
lathe.object(&block)
8990
end
9091

91-
def plan(attributes = {})
92-
lathe = Lathe.run(self.build, attributes)
92+
def plan(*args)
93+
lathe = Lathe.run(self.build, *args)
9394
Machinist::ActiveRecord.assigned_attributes_without_associations(lathe)
9495
end
9596
end

spec/db/schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
t.column :name, :string
44
t.column :type, :string
55
t.column :password, :string
6+
t.column :admin, :boolean, :default => false
67
end
78

89
create_table :posts, :force => true do |t|

spec/machinist_spec.rb

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class Comment < ActiveRecord::Base
2626
end
2727
Person.make.name.should == "Fred"
2828
end
29-
29+
3030
it "should set an attribute on the constructed object from a block in the blueprint" do
3131
Person.blueprint do
3232
name { "Fred" }
@@ -119,6 +119,32 @@ class Comment < ActiveRecord::Base
119119
Person.blueprint { type "Person" }
120120
Person.make.type.should == "Person"
121121
end
122+
123+
describe "for named blueprints" do
124+
before do
125+
@block_called = false
126+
Person.blueprint do
127+
name { "Fred" }
128+
admin { block_called = true; false }
129+
end
130+
Person.blueprint(:admin) do
131+
admin { true }
132+
end
133+
@person = Person.make(:admin)
134+
end
135+
136+
it "should override an attribute from the parent blueprint in the child blueprint" do
137+
@person.admin.should == true
138+
end
139+
140+
it "should not call the block for an attribute from the parent blueprint if that attribute is overridden in the child" do
141+
@block_called.should be_false
142+
end
143+
144+
it "should set an attribute defined in the parent blueprint" do
145+
@person.name.should == "Fred"
146+
end
147+
end
122148

123149
end # make method
124150

0 commit comments

Comments
 (0)