-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathclass_factory.html
More file actions
138 lines (138 loc) · 12.2 KB
/
class_factory.html
File metadata and controls
138 lines (138 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Class Factory: Factory_girl-like syntax for dynamically creating Ruby classes - Pat Shaughnessy</title><meta name="description" content=""><meta name="author" content=""><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/assets/css/normalize.css"><link rel="stylesheet" href="/assets/css/skeleton.css"><link rel="alternate" type="application/atom+xml" title="Pat Shaughnessy - Feed" href="http://feeds2.feedburner.com/patshaughnessy"><link rel="icon" type="image/png" href="images/favicon.png"></head><body><div id="banner"><a href="/"><span id="title">Pat Shaughnessy</span><span id="tagline"> blogger, rubyist, aspiring author</span></a></div><div style="margin-top: 35px"><div class="ten columns"><div class="container"><div class="row"><article class="post"><header><h1>Class Factory: Factory_girl-like syntax for dynamically creating Ruby classes</h1><div class="metadata">March 12th 2010 — <a href="#disqus_thread" data-disqus-identifier="https://patshaughnessy.net/class_factory"> Comments and Reactions</a></div></header><section class="content"><p>Class Factory will dynamically create classes using a factories model similar to <a href="http://github.com/thoughtbot/factory_girl">factory_girl</a>. But instead of passing a block with model attributes into the factory definition, you pass in a migration defining the attributes of the new model class you want to create:</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span>.define <span class="sy">:person</span> <span class="r">do</span> |p|
p.string <span class="sy">:first_name</span>
p.string <span class="sy">:last_name</span>
p.integer <span class="sy">:age</span>
<span class="r">end</span></pre></div>
</div>
<p><br>Now when you need a “Person” class in your tests you create one like this:</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span> <span class="sy">:person</span>
=> Person(id: integer, first_name: string, last_name: string, age: integer)</pre></div>
</div>
<p><br>This can be useful if you're writing tests for a gem or plugin and don't want to load the entire Rails environment, or have access to existing models in a target application. By default Class Factory creates ActiveRecord model classes, but using the :super option you can create any sort of Ruby class. Class Factory also makes it easy for each of your tests to use a different variation on a target class. For example, this will delete the Person model we created above, and create a new Person model that belongs to a group:</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span> <span class="sy">:person</span>, <span class="sy">:class_eval</span> => <span class="s"><span class="dl">'</span><span class="k">belongs_to :group</span><span class="dl">'</span></span> <span class="r">do</span> |p|
p.string <span class="sy">:first_name</span>
p.string <span class="sy">:middle_name</span>
p.string <span class="sy">:last_name</span>
p.string <span class="sy">:group_id</span>
<span class="r">end</span>
=> Person(id: integer, first_name: string, middle_name: string, last_name: string,
group_id: string)</pre></div>
</div>
<p><br>Creating different variations of the same class can be useful if you're writing tests for a generator, plugin or some other code which has different behavior depending on what classes you run it against.</p>
<p><b>Options</b></p>
<p>Default: create a new ActiveRecord model, along with a corresponding table in your database:</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span> <span class="sy">:person</span></pre></div>
</div>
<p><br>Execute a migration on the new table specified as a block, defining the attributes of the new model class:</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span> <span class="sy">:person</span> <span class="r">do</span> |p|
p.string <span class="sy">:first_name</span>
p.string <span class="sy">:last_name</span>
p.integer <span class="sy">:age</span>
<span class="r">end</span></pre></div>
</div>
<p><br>Create a class with a specified superclass (default is ActiveRecord::Base):</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span> <span class="sy">:person_array</span>, <span class="sy">:super</span> => <span class="co">Array</span></pre></div>
</div>
<p><br>If the super class is not a subclass of ActiveRecord::Base then Class Factory won't create a table or run a migration. You can use this to create plain Ruby object classes.</p>
<p>Create a class called “DifferentClass” instead of “Person:”</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span> <span class="sy">:person</span>, <span class="sy">:class</span> => <span class="s"><span class="dl">'</span><span class="k">DifferentClass</span><span class="dl">'</span></span></pre></div>
</div>
<p><br>Run the given code inside the new class using class_eval:</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span> <span class="sy">:person</span>, <span class="sy">:class_eval</span> => <span class="s"><span class="dl">'</span><span class="k">has_many :shoes</span><span class="dl">'</span></span></pre></div>
</div>
<p><br>Create a table with the given name, instead of a table called “people:”</p>
<div class="CodeRay">
<div class="code"><pre><span class="co">ClassFactory</span> <span class="sy">:person</span>, <span class="sy">:class_eval</span> => <span class="s"><span class="dl">'</span><span class="k">set_table_name :table_name</span><span class="dl">'</span></span>,
<span class="sy">:table</span> => <span class="s"><span class="dl">'</span><span class="k">table_name</span><span class="dl">'</span></span></pre></div>
</div>
<p><br>If you provide options when the factory is defined they will be applied to each class created with the factory. You can also provide options when you create a class, in which case they will override the factory options.</p>
<p><b>Install</b></p>
<div class="CodeRay">
<div class="code"><pre>gem install class_factory</pre></div>
</div>
<p><b><br>Code</b></p>
<a href="http://github.com/patshaughnessy/class_factory">http://github.com/patshaughnessy/class_factory</a>
<p><b><br>Detailed Example</b></p>
<p>Start an irb session and require class_factory (this will also require active_record):</p>
<div class="CodeRay">
<div class="code"><pre>$ irb
> require 'rubygems'
=> true
> require 'class_factory'
=> true</pre></div>
</div>
<p><br>Create an in-memory SQLite test database:</p>
<div class="CodeRay">
<div class="code"><pre>> ActiveRecord::Base.establish_connection({ :adapter => 'sqlite3',
:database => ':memory:' })
=> #<ActiveRecord::ConnectionAdapters::ConnectionPool:0x19cfecc...</pre></div>
</div>
<p><br>Define a person class factory, and create a Person class:</p>
<div class="CodeRay">
<div class="code"><pre>> ClassFactory.define :person do |p|
> p.string :first_name
> p.string :last_name
> p.integer :age
> end
=> #<ClassFactory:0x19c6fac @definition={:name=>:person, :migration=>...
> ClassFactory :person
=> Person(id: integer, first_name: string, last_name: string, age: integer)</pre></div>
</div>
<p><br>Now create an instance of a Person and count how many records we have in our test database:</p>
<div class="CodeRay">
<div class="code"><pre>> Person.create :first_name => 'Barack', :last_name => 'Obama', :age => 48
=> #<Person id: 1, first_name: "Barack", last_name: "Obama", age: 48>
> Person.count
=> 1</pre></div>
</div>
<p><br>Next redefine the Person class and override the options set in the factory above; this time it will belong to a group. Note that the existing people table will be dropped and a new, empty people table created:</p>
<div class="CodeRay">
<div class="code"><pre>> ClassFactory :person, :class_eval => 'belongs_to :group' do |p|
> p.string :first_name
> p.string :middle_name
> p.string :last_name
> p.string :group_id
> end
=> Person(id: integer, first_name: string, middle_name: string, last_name: string,
group_id: string)
> Person.count
=> 0</pre></div>
</div>
<p><br>Create a group class that has many people:</p>
<div class="CodeRay">
<div class="code"><pre>> ClassFactory.define :group, :class_eval => 'has_many :people' do |g|
> g.string :name
> end
=> #<ClassFactory:0x18a05d8 @definition={:class_eval=>"has_many :people", ...
> ClassFactory :group
=> Group(id: integer, name: string)</pre></div>
</div>
<p><br>Finally, recreate the Barack person record and add him to the “presidents” group:</p>
<div class="CodeRay">
<div class="code"><pre>> g = Group.create :name => 'Presidents'
=> #<Group id: 1, name: "Presidents">
> p = Person.new :first_name => 'Barack', :last_name => 'Obama', :age => 48,
:group => g
=> #<Person id: nil, first_name: "Barack", last_name: "Obama", group_id: 1,
age: 48>
> p.save
=> true
> g.people
=> [#<Person id: 1, first_name: "Barack", last_name: "Obama", group_id: "1",
age: 48>]</pre></div>
</div>
</section><section class="comments"><div id="disqus_thread"><script type="text/javascript">var disqus_identifier = 'https://patshaughnessy.net/class_factory'; var disqus_shortname = 'patshaughnessy'; var disqus_title = 'Class Factory: Factory_girl-like syntax for dynamically creating Ruby classes';</script></div><script type="text/javascript" src="https://disqus.com/forums/patshaughnessy/embed.js"></script><noscript><a href="https://patshaughnessy.disqus.com/?url=ref">View the discussion thread.</a></noscript></section></article></div></div></div><div class="two columns"><div id="sidebar"><img src="/assets/images/pat.jpg"></img><div class="header">Subscribe</div><div class="links"><ul><li><a id="feed" href="http://feeds.feedburner.com/patshaughnessy"><img src="/assets/images/feed-icon16x16B.png"></img></a><a href="http://twitter.com/pat_shaughnessy"><img width="20" height="20" src="/assets/images/twitter.svg"></img></a></li></ul></div><div class="header">Buy My Book</div><div class="links"><ul><li><a href="/ruby-under-a-microscope"><img src="/assets/images/book-cover.png"></img></a></li><li id="eBook"><a href="/ruby-under-a-microscope">Ruby Under a Microscope</a></li></ul></div><div class="header">More on Ruby</div><div class="links"><ul><li><a href="/2016/10/7/need-a-second-opinion-on-your-ruby-code-ask-crystal">Need a Second Opinion on Your Ruby Code? Ask Crystal</a></li><li><a href="/2016/4/2/two-dumb-ruby-mistakes">Two Dumb Ruby Mistakes</a></li><li><a href="/2015/6/18/dont-let-your-data-out-of-the-database">Don’t Let Your Data Out of the Database</a></li><li><a href="/2015/2/16/mark-methods-private-when-you-dont-test-them">Mark Methods Private When You Don’t Test Them</a></li></ul></div><div class="header">Popular</div><div class="links"><ul><li><a href="/2016/11/26/learning-to-read-x86-assembly-language">Learning to Read x86 Assembly Language</a></li><li><a href="/2012/1/4/never-create-ruby-strings-longer-than-23-characters">Never create Ruby strings longer than 23 characters</a></li><li><a href="/2012/3/23/why-you-should-be-excited-about-garbage-collection-in-ruby-2-0">Why You Should Be Excited About Garbage Collection in Ruby 2.0</a></li><li><a href="/2013/4/3/ruby-2-0-works-hard-so-you-can-be-lazy">Ruby 2.0 Works Hard So You Can Be Lazy</a></li></ul></div><div class="header"><a href="/">More...</a></div></div></div><script type="text/javascript">(function () {
var s = document.createElement('script'); s.async = true;
s.type = 'text/javascript';
s.src = 'https://' + disqus_shortname + '.disqus.com/count.js';
(document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
}());</script></div><div id="footer" class="ten columns"><p>Content and UI design © 2008-2025 Pat Shaughnessy</p></div></body></html>