object-daddy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.8.0"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler", "~> 1.0.0"
12
+ gem "jeweler", "~> 1.8.3"
13
+ gem "rails", "~> 3.1.4"
14
+ gem "generator_spec"
15
+ gem "sqlite3"
16
+ gem "rspec-rails"
17
+ gem "pry"
18
+ gem "mocha"
19
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,130 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionmailer (3.1.4)
5
+ actionpack (= 3.1.4)
6
+ mail (~> 2.3.0)
7
+ actionpack (3.1.4)
8
+ activemodel (= 3.1.4)
9
+ activesupport (= 3.1.4)
10
+ builder (~> 3.0.0)
11
+ erubis (~> 2.7.0)
12
+ i18n (~> 0.6)
13
+ rack (~> 1.3.6)
14
+ rack-cache (~> 1.1)
15
+ rack-mount (~> 0.8.2)
16
+ rack-test (~> 0.6.1)
17
+ sprockets (~> 2.0.3)
18
+ activemodel (3.1.4)
19
+ activesupport (= 3.1.4)
20
+ builder (~> 3.0.0)
21
+ i18n (~> 0.6)
22
+ activerecord (3.1.4)
23
+ activemodel (= 3.1.4)
24
+ activesupport (= 3.1.4)
25
+ arel (~> 2.2.3)
26
+ tzinfo (~> 0.3.29)
27
+ activeresource (3.1.4)
28
+ activemodel (= 3.1.4)
29
+ activesupport (= 3.1.4)
30
+ activesupport (3.1.4)
31
+ multi_json (~> 1.0)
32
+ arel (2.2.3)
33
+ builder (3.0.0)
34
+ coderay (1.0.6)
35
+ diff-lcs (1.1.3)
36
+ erubis (2.7.0)
37
+ generator_spec (0.8.5)
38
+ rails (>= 3.0, < 4.0)
39
+ rspec-rails
40
+ git (1.2.5)
41
+ hike (1.2.1)
42
+ i18n (0.6.0)
43
+ jeweler (1.8.3)
44
+ bundler (~> 1.0)
45
+ git (>= 1.2.5)
46
+ rake
47
+ rdoc
48
+ json (1.6.6)
49
+ mail (2.3.3)
50
+ i18n (>= 0.4.0)
51
+ mime-types (~> 1.16)
52
+ treetop (~> 1.4.8)
53
+ metaclass (0.0.1)
54
+ method_source (0.7.1)
55
+ mime-types (1.18)
56
+ mocha (0.10.5)
57
+ metaclass (~> 0.0.1)
58
+ multi_json (1.2.0)
59
+ polyglot (0.3.3)
60
+ pry (0.9.8.4)
61
+ coderay (~> 1.0.5)
62
+ method_source (~> 0.7.1)
63
+ slop (>= 2.4.4, < 3)
64
+ rack (1.3.6)
65
+ rack-cache (1.2)
66
+ rack (>= 0.4)
67
+ rack-mount (0.8.3)
68
+ rack (>= 1.0.0)
69
+ rack-ssl (1.3.2)
70
+ rack
71
+ rack-test (0.6.1)
72
+ rack (>= 1.0)
73
+ rails (3.1.4)
74
+ actionmailer (= 3.1.4)
75
+ actionpack (= 3.1.4)
76
+ activerecord (= 3.1.4)
77
+ activeresource (= 3.1.4)
78
+ activesupport (= 3.1.4)
79
+ bundler (~> 1.0)
80
+ railties (= 3.1.4)
81
+ railties (3.1.4)
82
+ actionpack (= 3.1.4)
83
+ activesupport (= 3.1.4)
84
+ rack-ssl (~> 1.3.2)
85
+ rake (>= 0.8.7)
86
+ rdoc (~> 3.4)
87
+ thor (~> 0.14.6)
88
+ rake (0.9.2.2)
89
+ rdoc (3.12)
90
+ json (~> 1.4)
91
+ rspec (2.8.0)
92
+ rspec-core (~> 2.8.0)
93
+ rspec-expectations (~> 2.8.0)
94
+ rspec-mocks (~> 2.8.0)
95
+ rspec-core (2.8.0)
96
+ rspec-expectations (2.8.0)
97
+ diff-lcs (~> 1.1.2)
98
+ rspec-mocks (2.8.0)
99
+ rspec-rails (2.8.1)
100
+ actionpack (>= 3.0)
101
+ activesupport (>= 3.0)
102
+ railties (>= 3.0)
103
+ rspec (~> 2.8.0)
104
+ slop (2.4.4)
105
+ sprockets (2.0.3)
106
+ hike (~> 1.2)
107
+ rack (~> 1.0)
108
+ tilt (~> 1.1, != 1.3.0)
109
+ sqlite3 (1.3.5)
110
+ thor (0.14.6)
111
+ tilt (1.3.3)
112
+ treetop (1.4.10)
113
+ polyglot
114
+ polyglot (>= 0.3.1)
115
+ tzinfo (0.3.32)
116
+
117
+ PLATFORMS
118
+ ruby
119
+
120
+ DEPENDENCIES
121
+ bundler (~> 1.0.0)
122
+ generator_spec
123
+ jeweler (~> 1.8.3)
124
+ mocha
125
+ pry
126
+ rails (~> 3.1.4)
127
+ rdoc (~> 3.12)
128
+ rspec (~> 2.8.0)
129
+ rspec-rails
130
+ sqlite3
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Flawed Logic, OG Consulting, Rick Bradley, Yossef Mendelssohn
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,312 @@
1
+ Object Daddy
2
+ ============
3
+ _Version 1.0.0 (April 6, 2012)_
4
+
5
+ __Authors:__ [Rick Bradley](mailto:blogicx@rickbradley.com), [Yossef Mendelssohn](mailto:ymendel@pobox.com), [Jeremy Holland](mailto:jeremy@jeremypholland.com)
6
+
7
+ __Gem Maintainer:__ [Jeremy Holland](mailto:jeremy@jeremypholland.com)
8
+
9
+ __Copyright:__ Copyright (c) 2007, Flawed Logic, OG Consulting, Rick Bradley, Yossef Mendelssohn
10
+
11
+ __License:__ MIT License. See LICENSE.txt file for more details.
12
+
13
+ Object Daddy is a library (as well as a Ruby on Rails plugin) designed to
14
+ assist in automating testing of large collections of objects, especially webs
15
+ of ActiveRecord models. It is a descendent of the "Object Mother" pattern for
16
+ creating objects for testing, and is related to the concept of an "object
17
+ exemplar" or _stereotype_.
18
+
19
+ **WARNING** This code is very much at an _alpha_ development stage. Usage, APIs,
20
+ etc., are all subject to change.
21
+
22
+ See [http://b.logi.cx/2007/11/26/object-daddy](http://b.logi.cx/2007/11/26/object-daddy) for inspiration, historical drama, and too much reading.
23
+
24
+ ## Installation
25
+
26
+ Add the following to your Gemfile and run `bundle install`
27
+
28
+ group :development, :test do
29
+ gem 'object-daddy'
30
+ end
31
+
32
+ Once installed, to set up your exemplars directory, run `rails g object-daddy`
33
+
34
+ ## Using Object Daddy
35
+
36
+ Object Daddy adds a `.generate` method to every ActiveRecord model which can be
37
+ called to generate a valid instance object of that model class, for use in
38
+ testing:
39
+
40
+ it "should have a comment for every forum the user posts to" do
41
+ @user = User.generate
42
+ @post = Post.generate
43
+ @post.comments << Comment.generate
44
+ @user.should have(1).comments
45
+ end
46
+
47
+ This allows us to generate custom model objects without relying on fixtures,
48
+ and without knowing, in our various widespread tests and specs, the details of
49
+ creating a User, Post, Comment, etc. Not having to know this information means
50
+ the information isn't coded into dozens (or hundreds) of tests, and won't need
51
+ to be changed when the User (Post, Comment, ...) model is refactored later.
52
+
53
+ Object Daddy will identify associated classes that need to be instantiated to
54
+ make the main model valid. E.g., given the following models:
55
+
56
+ class User < ActiveRecord::Base
57
+ belongs_to :login
58
+ validates :login, :presence => true
59
+ end
60
+
61
+ class Login < ActiveRecord::Base
62
+ has_one :user
63
+ end
64
+
65
+ A call to `User.generate` will also make a call to `Login.generate` so that
66
+ `User#login` is present, and therefore valid.
67
+
68
+ If all models were able to be created in a valid form by the default Model.new
69
+ call with no knowledge of the model itself, there'd be no need for Object
70
+ Daddy. So, when we deal with models which have validity requirements,
71
+ requiring fields which have format constraints, we need a means of expressing
72
+ how to create those models -- how to satisfy those validity constraints.
73
+
74
+ Object Daddy provides a `generator_for` method which allows the developer to
75
+ specify, for a specific model attribute, how to make a valid value. Note that
76
+ `validates_uniqueness_of` can require that, even if we make 100,000 instances
77
+ of a model that unique attributes cannot have the same values.
78
+
79
+ Object Daddy's `generator_for` method can take three main forms corresponding to
80
+ the means of finding a value for the associated attribute: a block, a method
81
+ call, or using a generator class.
82
+
83
+ class User < ActiveRecord::Base
84
+ validates :email,
85
+ :presence => true,
86
+ :uniqueness => true,
87
+ :format => {
88
+ :with => /^[-a-z_+0-9.]+@(?:[-a-z_+0-9.]\.)+[a-z]+$/i
89
+ }
90
+ validates :username,
91
+ :presence => true,
92
+ :format => {
93
+ :with => /^[a-z0-9_]{4,12}$/i
94
+ }
95
+
96
+ generator_for :email, :start => 'test@domain.com' do |prev|
97
+ user, domain = prev.split('@')
98
+ user.succ + '@' + domain
99
+ end
100
+
101
+ generator_for :username, :method => :next_user
102
+
103
+ generator_for :ssn, :class => SSNGenerator
104
+
105
+ def self.next_user
106
+ @last_username ||= 'testuser'
107
+ @last_username.succ
108
+ end
109
+ end
110
+
111
+ class SSNGenerator
112
+ def self.next
113
+ @last ||= '000-00-0000'
114
+ @last = ("%09d" % (@last.gsub('-', '').to_i + 1)).sub(/^(\d{3})(\d{2})(\d{4})$/, '\1-\2-\3')
115
+ end
116
+ end
117
+
118
+ Note that the block method of invocation (as used with _:email_ above) takes an
119
+ optional _:start_ argument, to specify the value of that attribute on the first
120
+ run. The block will be called thereafter with the previous value of the
121
+ attribute and will generate the next attribute value to be used.
122
+
123
+ A simple default block is provided for any generator with a :start value.
124
+
125
+ class User < ActiveRecord::Base
126
+ generator_for :name, :start => 'Joe' do |prev|
127
+ prev.succ
128
+ end
129
+
130
+ generator_for :name, :start => 'Joe' # equivalent to the above
131
+ end
132
+
133
+ The _:method_ form takes a symbol naming a class method in the model class to be
134
+ called to generate a new value for the attribute in question. If the method
135
+ takes a single argument, it will act much like the block method of invocation,
136
+ being called with the previous value and generating the next.
137
+
138
+ The _:class_ form calls the .next class method on the named class to generate a
139
+ new value for the attribute in question.
140
+
141
+ The argument (previous value) to the block invocation form can be omitted if
142
+ it's going to be ignored, and simple invocation forms are provided for literal
143
+ values.
144
+
145
+ class User < ActiveRecord::Base
146
+ generator_for(:start_time) { Time.now }
147
+ generator_for :name, 'Joe'
148
+ generator_for :age => 25
149
+ end
150
+
151
+ The developer would then simply call `User.generate` when testing.
152
+
153
+ If some attribute values are known (or are being controlled during testing)
154
+ then these can simply be passed in to `.generate`:
155
+
156
+ @bad_login = Login.generate(:expiry => 1.week.ago)
157
+ @expired_user = User.generate(:login => @bad_login)
158
+
159
+ A `.generate!` method is also provided. The _generate/generate!_ pair of methods
160
+ can be thought of as analogs to create/create!, one merely providing an instance
161
+ that may or may not be valid and the other raising an exception if any
162
+ problem comes up.
163
+
164
+ Finally, a `.spawn` method is provided that only gives a new, unsaved object. Note
165
+ that this is the only method of the three that is available if you happen to be
166
+ using Object Daddy outside of Rails.
167
+
168
+ ## Exemplars
169
+
170
+ In the examples given above we are using `generator_for` in the bodies of the
171
+ models themselves. Given that Object Daddy is primarily geared towards
172
+ annotating models with information useful for testing, we anticipate that
173
+ `generator_for` should not normally be included inline in models. Rather, we
174
+ will provide a place where model classes can be re-opened and `generator_for`
175
+ calls (and support methods) can be written without polluting the model files
176
+ with Object Daddy information.
177
+
178
+ when the Object Daddy generator is run, it will create
179
+ *RAILS_ROOT/spec/exemplars/* as a place to hold __exemplar__ files for Rails model
180
+ classes. (We are seeking perhaps some better terminology)
181
+
182
+ An __exemplar__ for the User model would then be found in
183
+ *RAILS_ROOT/spec/exemplars/user_exemplar.rb* (when you are using a testing tool
184
+ which works from *RAILS_ROOT/test*, Object Daddy will create
185
+ *RAILS_ROOT/test/exemplars* and look for your exemplars in that directory
186
+ instead). Exemplar files are completely optional, and no model need have
187
+ exemplar files. The `.generate` method will still exist and be callable, and
188
+ `generator_for` can be declared in the model files themselves. If an exemplar
189
+ file is available when `.generate` is called on a model, the exemplar file will
190
+ be loaded and used. An example *user_exemplar.rb* appears below:
191
+
192
+ require 'ssn_generator'
193
+
194
+ class User < ActiveRecord::Base
195
+ generator_for :email, :start => 'test@domain.com' do |prev|
196
+ user, domain = prev.split('@')
197
+ user.succ + '@' + domain
198
+ end
199
+
200
+ generator_for :username, :method => :next_user
201
+
202
+ generator_for :ssn, :class => SSNGenerator
203
+
204
+ def self.next_user
205
+ @last_username ||= 'testuser'
206
+ @last_username.succ
207
+ end
208
+ end
209
+
210
+ ## Blocks
211
+
212
+ The `spawn`, `generate` and `generate!` methods can all accept a block, to which
213
+ they'll yield the generated object. This provides a nice scoping mechanism in
214
+ your code examples. Consider:
215
+
216
+ describe "admin user" do
217
+ it "should be authorized to create company profiles"
218
+ admin_user = User.generate!
219
+ admin_user.activate!
220
+ admin_user.add_role("admin")
221
+
222
+ admin_user.should be_authorized(:create, Company)
223
+ end
224
+ end
225
+
226
+ This could be refactored to:
227
+
228
+ describe "admin user" do
229
+ it "should be authorized to create company profiles" do
230
+ admin_user = User.generate! do |user|
231
+ user.activate!
232
+ user.add_role("admin")
233
+ end
234
+
235
+ admin_user.should be_authorized(:create, Company)
236
+ end
237
+ end
238
+
239
+ Or:
240
+
241
+ describe "admin user" do
242
+ it "should be authorized to create company profiles"
243
+ User.generate! do |user|
244
+ user.activate!
245
+ user.add_role("admin")
246
+ end.should be_authorized(:create, Company)
247
+ end
248
+ end
249
+
250
+ Or even:
251
+
252
+ describe "admin user" do
253
+ def admin_user
254
+ @admin_user ||= User.generate! do |user|
255
+ user.activate!
256
+ user.add_role("admin")
257
+ end
258
+ end
259
+
260
+ it "should be authorized to create company profiles"
261
+ admin_user.should be_authorized(:create, Company)
262
+ end
263
+ end
264
+
265
+ This last refactoring allows you to reuse the admin_user method across
266
+ multiple code examples, balancing DRY with local data.
267
+
268
+ ## Object Daddy and Fixtures
269
+
270
+ While Object Daddy is meant to obviate the hellish devilspawn that are test
271
+ fixtures, Object Daddy should work alongside fixtures just fine. To each his
272
+ own, I suppose.
273
+
274
+ ## Known Issues
275
+
276
+ The simple invocation forms for `generator_for` when using literal values do not
277
+ work if the literal value is a Hash. Don't do that.
278
+
279
+ class User < ActiveRecord::Base
280
+ generator_for :thing_hash, { 'some key' => 'some value' }
281
+ generator_for :other_hash => { 'other key' => 'other value' }
282
+ end
283
+
284
+ I'm not sure why this would even ever come up, but seriously, don't.
285
+
286
+ Required `belongs_to` associations are automatically generated when generating an instance,
287
+ but only if necessary.
288
+
289
+ class Category < ActiveRecord::Base
290
+ has_many :items
291
+ end
292
+
293
+ class Item < ActiveRecord::Base
294
+ belongs_to :category
295
+ validates :category, :presence => true
296
+ end
297
+
298
+ `Item.generate` will generate a new category, but `some_category.items.generate` will not.
299
+ Unless, of course, you are foolish enough to define a generator in the exemplar.
300
+
301
+ class Item
302
+ generator_for(:category) { Category.generate }
303
+ end
304
+
305
+ Once again, don't do that.
306
+
307
+ ## Rails _surprises_
308
+
309
+ Due to the way Rails handles associations, cascading generations (as a result of
310
+ required associations) are always generated-and-saved, even if the original generation
311
+ call was a mere `spawn` (`new`). This may come as a surprise, but it would probably be more
312
+ of a surprise if `User.spawn.save` and `User.generate` weren't comparable.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "object-daddy"
18
+ gem.homepage = "http://github.com/awebneck/object_daddy"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Kill Fixtures}
21
+ gem.description = %Q{Object Daddy is a library (as well as a Ruby on Rails plugin) designed to assist in automating testing of large collections of objects, especially webs of ActiveRecord models. It is a descendent of the "Object Mother" pattern for creating objects for testing, and is related to the concept of an "object exemplar" or stereotype.}
22
+ gem.email = ["blogicx@rickbradley.com", "ymendel@pobox.com", "jeremy@jeremypholland.com"]
23
+ gem.authors = ["Rick Bradley", "Yossef Mendelssohn", "Jeremy Holland"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,8 @@
1
+ class ObjectDaddyGenerator < Rails::Generators::Base
2
+ desc "create exemplars directory"
3
+ def create_exemplars_dir
4
+ testfw = File.exists?("#{destination_root}/test") ? "test" : "spec"
5
+ empty_directory "#{testfw}/exemplars"
6
+ create_file "#{testfw}/exemplars/.gitkeep", "git sucks"
7
+ end
8
+ end
@@ -0,0 +1 @@
1
+ require 'object_daddy'
@@ -0,0 +1,19 @@
1
+ module ObjectDaddy
2
+ class Railtie < Rails::Railtie
3
+ railtie_name :object_daddy
4
+ initializer 'object_daddy.extend.activerecord' do
5
+ ::ActiveSupport.on_load :active_record do
6
+ class ::ActiveRecord::Base
7
+ def self.inherited_with_object_daddy(subclass)
8
+ self.inherited_without_object_daddy(subclass)
9
+ subclass.send(:include, ObjectDaddy) unless subclass < ObjectDaddy
10
+ end
11
+
12
+ class << self
13
+ alias_method_chain :inherited, :object_daddy
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end