object-daddy 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +130 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +312 -0
- data/Rakefile +34 -0
- data/VERSION +1 -0
- data/lib/generators/object_daddy_generator.rb +8 -0
- data/lib/object-daddy.rb +1 -0
- data/lib/object_daddy/railtie.rb +19 -0
- data/lib/object_daddy.rb +266 -0
- data/object-daddy.gemspec +83 -0
- data/spec/generators/object_daddy_generator_spec.rb +124 -0
- data/spec/object_daddy_spec.rb +1024 -0
- data/spec/resources/schema +55 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/tmp/.git_sucks +0 -0
- metadata +185 -0
data/lib/object_daddy.rb
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
module ObjectDaddy
|
2
|
+
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend ClassMethods
|
5
|
+
if defined? ActiveRecord and klass < ActiveRecord::Base
|
6
|
+
klass.extend RailsClassMethods
|
7
|
+
|
8
|
+
class << klass
|
9
|
+
alias_method :validates_presence_of_without_object_daddy, :validates_presence_of
|
10
|
+
alias_method :validates_presence_of, :validates_presence_of_with_object_daddy
|
11
|
+
alias_method :validates_without_object_daddy, :validates
|
12
|
+
alias_method :validates, :validates_with_object_daddy
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
attr_accessor :exemplars_generated, :exemplar_path, :generators
|
19
|
+
attr_reader :presence_validated_attributes
|
20
|
+
protected :exemplars_generated=
|
21
|
+
|
22
|
+
# :call-seq:
|
23
|
+
# spawn()
|
24
|
+
# spawn() do |obj| ... end
|
25
|
+
# spawn(args)
|
26
|
+
# spawn(args) do |obj| ... end
|
27
|
+
#
|
28
|
+
# Creates a valid instance of this class, using any known generators. The
|
29
|
+
# generated instance is yielded to a block if provided.
|
30
|
+
def spawn(args = {})
|
31
|
+
gather_exemplars
|
32
|
+
if @concrete_subclass_name
|
33
|
+
return block_given? \
|
34
|
+
? const_get(@concrete_subclass_name).spawn(args) {|instance| yield instance} \
|
35
|
+
: const_get(@concrete_subclass_name).spawn(args)
|
36
|
+
end
|
37
|
+
generate_values(args)
|
38
|
+
instance = new
|
39
|
+
args.each_pair do |attribute, value|
|
40
|
+
instance.send("#{attribute}=", value) # support setting of mass-assignment protected attributes
|
41
|
+
end
|
42
|
+
yield instance if block_given?
|
43
|
+
instance
|
44
|
+
end
|
45
|
+
|
46
|
+
# register a generator for an attribute of this class
|
47
|
+
# generator_for :foo do |prev| ... end
|
48
|
+
# generator_for :foo do ... end
|
49
|
+
# generator_for :foo, value
|
50
|
+
# generator_for :foo => value
|
51
|
+
# generator_for :foo, :class => GeneratorClass
|
52
|
+
# generator_for :foo, :method => :method_name
|
53
|
+
def generator_for(handle, args = {}, &block)
|
54
|
+
if handle.is_a?(Hash)
|
55
|
+
raise ArgumentError, "only specify one attr => value pair at a time" unless handle.keys.length == 1
|
56
|
+
gen_data = handle
|
57
|
+
handle = gen_data.keys.first
|
58
|
+
args = gen_data[handle]
|
59
|
+
end
|
60
|
+
|
61
|
+
raise ArgumentError, "an attribute name must be specified" unless handle = handle.to_sym
|
62
|
+
|
63
|
+
unless args.is_a?(Hash)
|
64
|
+
unless block
|
65
|
+
retval = args
|
66
|
+
block = lambda { retval } # lambda { args } results in returning the empty hash that args gets changed to
|
67
|
+
end
|
68
|
+
args = {} # args is assumed to be a hash for the rest of the method
|
69
|
+
end
|
70
|
+
|
71
|
+
if args[:start]
|
72
|
+
block ||= lambda { |prev| prev.succ }
|
73
|
+
end
|
74
|
+
|
75
|
+
if args[:method]
|
76
|
+
h = { :method => args[:method].to_sym }
|
77
|
+
h[:start] = args[:start] if args[:start]
|
78
|
+
record_generator_for(handle, h)
|
79
|
+
elsif args[:class]
|
80
|
+
raise ArgumentError, "generator class [#{args[:class].name}] does not have a :next method" unless args[:class].respond_to?(:next)
|
81
|
+
record_generator_for(handle, :class => args[:class])
|
82
|
+
elsif block
|
83
|
+
raise ArgumentError, "generator block must take an optional single argument" unless (-1..1).include?(block.arity) # NOTE: lambda {} has an arity of -1, while lambda {||} has an arity of 0
|
84
|
+
h = { :block => block }
|
85
|
+
h[:start] = args[:start] if args[:start]
|
86
|
+
record_generator_for(handle, h)
|
87
|
+
else
|
88
|
+
raise ArgumentError, "a block, :class generator, :method generator, or value must be specified to generator_for"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def generates_subclass(subclass_name)
|
93
|
+
@concrete_subclass_name = subclass_name.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def gather_exemplars
|
97
|
+
return if exemplars_generated
|
98
|
+
|
99
|
+
self.generators ||= {}
|
100
|
+
if superclass.respond_to?(:gather_exemplars)
|
101
|
+
superclass.gather_exemplars
|
102
|
+
self.generators = (superclass.generators).merge(generators).dup
|
103
|
+
end
|
104
|
+
|
105
|
+
exemplar_path.each do |raw_path|
|
106
|
+
path = File.join(raw_path, "#{underscore(name)}_exemplar.rb")
|
107
|
+
load(path) if File.exists?(path)
|
108
|
+
end
|
109
|
+
|
110
|
+
self.exemplars_generated = true
|
111
|
+
end
|
112
|
+
|
113
|
+
def presence_validated_attributes
|
114
|
+
@presence_validated_attributes ||= {}
|
115
|
+
attrs = @presence_validated_attributes
|
116
|
+
if superclass.respond_to?(:presence_validated_attributes)
|
117
|
+
attrs = superclass.presence_validated_attributes.merge(attrs)
|
118
|
+
end
|
119
|
+
attrs
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
# we define an underscore helper ourselves since the ActiveSupport isn't available if we're not using Rails
|
125
|
+
def underscore(string)
|
126
|
+
string.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
127
|
+
end
|
128
|
+
|
129
|
+
def record_generator_for(handle, generator)
|
130
|
+
self.generators ||= {}
|
131
|
+
raise ArgumentError, "a generator for attribute [:#{handle}] has already been specified" if (generators[handle] || {})[:source] == self
|
132
|
+
generators[handle] = { :generator => generator, :source => self }
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def generate_values(args)
|
138
|
+
(generators || {}).each_pair do |handle, gen_data|
|
139
|
+
next if args.include?(handle) or args.include?(handle.to_s)
|
140
|
+
|
141
|
+
generator = gen_data[:generator]
|
142
|
+
if generator[:block]
|
143
|
+
process_generated_value(args, handle, generator, generator[:block])
|
144
|
+
elsif generator[:method]
|
145
|
+
method = method(generator[:method])
|
146
|
+
if method.arity == 1
|
147
|
+
process_generated_value(args, handle, generator, method)
|
148
|
+
else
|
149
|
+
args[handle] = method.call
|
150
|
+
end
|
151
|
+
elsif generator[:class]
|
152
|
+
args[handle] = generator[:class].next
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
generate_missing(args)
|
157
|
+
end
|
158
|
+
|
159
|
+
def process_generated_value(args, handle, generator, block)
|
160
|
+
if generator[:start]
|
161
|
+
value = generator[:start]
|
162
|
+
generator.delete(:start)
|
163
|
+
else
|
164
|
+
if block.arity == 0
|
165
|
+
value = block.call
|
166
|
+
else
|
167
|
+
value = block.call(generator[:prev])
|
168
|
+
end
|
169
|
+
end
|
170
|
+
generator[:prev] = args[handle] = value
|
171
|
+
end
|
172
|
+
|
173
|
+
def generate_missing(args)
|
174
|
+
if presence_validated_attributes and !presence_validated_attributes.empty?
|
175
|
+
req = {}
|
176
|
+
(presence_validated_attributes.keys - args.keys).each {|a| req[a.to_s] = true } # find attributes required by validates_presence_of not already set
|
177
|
+
|
178
|
+
belongs_to_associations = reflect_on_all_associations(:belongs_to).to_a
|
179
|
+
fk_method = Rails.version >= "3.1.0" ? :foreign_key : :primary_key_name
|
180
|
+
missing = belongs_to_associations.select { |a| req[a.name.to_s] or req[a.send(fk_method).to_s] }
|
181
|
+
# Rails 3.1 compatibility jazz - current_scoped_methods was deprecated.
|
182
|
+
if scope = respond_to?(:current_scoped_methods) ? current_scoped_methods : current_scope
|
183
|
+
missing.reject! { |a| scope.scope_for_create.include?(a.send(fk_method)) }
|
184
|
+
end
|
185
|
+
missing.reject! { |a| [a.name, a.send(fk_method)].any? { |n| args.stringify_keys.include?(n.to_s) } }
|
186
|
+
missing.each {|a| args[a.name] = a.class_name.constantize.generate }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
module RailsClassMethods
|
192
|
+
def exemplar_path
|
193
|
+
paths = ['spec', 'test'].inject([]) do |array, dir|
|
194
|
+
if File.directory?(File.join(Rails.root, dir))
|
195
|
+
array << File.join(Rails.root, dir, 'exemplars')
|
196
|
+
end
|
197
|
+
array
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def validates_presence_of_with_object_daddy(*attr_names)
|
202
|
+
@presence_validated_attributes ||= {}
|
203
|
+
new_attr = attr_names.dup
|
204
|
+
new_attr.pop if new_attr.last.is_a?(Hash)
|
205
|
+
new_attr.each {|a| @presence_validated_attributes[a] = true }
|
206
|
+
validates_presence_of_without_object_daddy(*attr_names)
|
207
|
+
end
|
208
|
+
|
209
|
+
def validates_with_object_daddy(*args)
|
210
|
+
if args.last.is_a?(Hash) && args.last[:presence]
|
211
|
+
@presence_validated_attributes ||= {}
|
212
|
+
@presence_validated_attributes[args.first] = true
|
213
|
+
end
|
214
|
+
validates_without_object_daddy(*args)
|
215
|
+
end
|
216
|
+
|
217
|
+
# :call-seq:
|
218
|
+
# generate()
|
219
|
+
# generate() do |obj| ... end
|
220
|
+
# generate(args)
|
221
|
+
# generate(args) do |obj| ... end
|
222
|
+
#
|
223
|
+
# Creates and tries to save an instance of this class, using any known
|
224
|
+
# generators. The generated instance is yielded to a block if provided.
|
225
|
+
#
|
226
|
+
# This will not raise errors on a failed save. Use generate! if you
|
227
|
+
# want errors raised.
|
228
|
+
def generate(args = {})
|
229
|
+
spawn(args) do |instance|
|
230
|
+
instance.save
|
231
|
+
yield instance if block_given?
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# :call-seq:
|
236
|
+
# generate()
|
237
|
+
# generate() do |obj| ... end
|
238
|
+
# generate(args)
|
239
|
+
# generate(args) do |obj| ... end
|
240
|
+
#
|
241
|
+
# Creates and tries to save! an instance of this class, using any known
|
242
|
+
# generators. The generated instance is yielded to a block if provided.
|
243
|
+
#
|
244
|
+
# This will raise errors on a failed save. Use generate if you do not want
|
245
|
+
# errors raised.
|
246
|
+
def generate!(args = {})
|
247
|
+
spawn(args) do |instance|
|
248
|
+
instance.save!
|
249
|
+
yield instance if block_given?
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
unless ActiveRecord::Base.respond_to? :inherited_with_object_daddy
|
256
|
+
class ActiveRecord::Base
|
257
|
+
def self.inherited_with_object_daddy(subclass)
|
258
|
+
self.inherited_without_object_daddy(subclass)
|
259
|
+
subclass.send(:include, ObjectDaddy) unless subclass < ObjectDaddy
|
260
|
+
end
|
261
|
+
|
262
|
+
class << self
|
263
|
+
alias_method_chain :inherited, :object_daddy
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "object-daddy"
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Rick Bradley", "Yossef Mendelssohn", "Jeremy Holland"]
|
12
|
+
s.date = "2012-04-07"
|
13
|
+
s.description = "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."
|
14
|
+
s.email = ["blogicx@rickbradley.com", "ymendel@pobox.com", "jeremy@jeremypholland.com"]
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".rspec",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.markdown",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/generators/object_daddy_generator.rb",
|
28
|
+
"lib/object-daddy.rb",
|
29
|
+
"lib/object_daddy.rb",
|
30
|
+
"lib/object_daddy/railtie.rb",
|
31
|
+
"object-daddy.gemspec",
|
32
|
+
"spec/generators/object_daddy_generator_spec.rb",
|
33
|
+
"spec/object_daddy_spec.rb",
|
34
|
+
"spec/resources/schema",
|
35
|
+
"spec/spec_helper.rb",
|
36
|
+
"spec/tmp/.git_sucks"
|
37
|
+
]
|
38
|
+
s.homepage = "http://github.com/awebneck/object_daddy"
|
39
|
+
s.licenses = ["MIT"]
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
s.rubygems_version = "1.8.15"
|
42
|
+
s.summary = "Kill Fixtures"
|
43
|
+
|
44
|
+
if s.respond_to? :specification_version then
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
|
49
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
50
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
51
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
52
|
+
s.add_development_dependency(%q<rails>, ["~> 3.1.4"])
|
53
|
+
s.add_development_dependency(%q<generator_spec>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<rspec-rails>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<pry>, [">= 0"])
|
57
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
60
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
61
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
62
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
63
|
+
s.add_dependency(%q<rails>, ["~> 3.1.4"])
|
64
|
+
s.add_dependency(%q<generator_spec>, [">= 0"])
|
65
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
66
|
+
s.add_dependency(%q<rspec-rails>, [">= 0"])
|
67
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
68
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
72
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
73
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
74
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
75
|
+
s.add_dependency(%q<rails>, ["~> 3.1.4"])
|
76
|
+
s.add_dependency(%q<generator_spec>, [">= 0"])
|
77
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
78
|
+
s.add_dependency(%q<rspec-rails>, [">= 0"])
|
79
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
80
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'generators/object_daddy_generator'
|
3
|
+
require 'generator_spec/test_case'
|
4
|
+
|
5
|
+
describe ObjectDaddyGenerator do
|
6
|
+
include GeneratorSpec::TestCase
|
7
|
+
destination File.expand_path("../../tmp/generator/", __FILE__)
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
prepare_destination
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'when there is a spec directory under RAILS_ROOT' do
|
14
|
+
before :each do
|
15
|
+
FileUtils.mkdir "#{destination_root}/spec"
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'and there is a spec/exemplars directory under RAILS_ROOT' do
|
19
|
+
before :each do
|
20
|
+
FileUtils.mkdir "#{destination_root}/spec/exemplars"
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should not create any new directories' do
|
24
|
+
FileUtils.touch "#{destination_root}/spec/exemplars/test_exemplar.rb"
|
25
|
+
run_generator
|
26
|
+
destination_root.should have_structure {
|
27
|
+
directory "spec" do
|
28
|
+
directory "exemplars" do
|
29
|
+
file "test_exemplar.rb"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
after :each do
|
36
|
+
FileUtils.rm_rf "#{destination_root}/spec/exemplars"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'but there is no spec/exemplars directory under RAILS_ROOT' do
|
41
|
+
it 'should create a spec/exemplars directory under RAILS_ROOT' do
|
42
|
+
run_generator
|
43
|
+
destination_root.should have_structure {
|
44
|
+
directory "spec" do
|
45
|
+
directory "exemplars" do
|
46
|
+
file ".gitkeep"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
after :each do
|
54
|
+
FileUtils.rm_rf "#{destination_root}/spec"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'when there is no spec directory under RAILS_ROOT' do
|
59
|
+
describe 'and there is a test directory under RAILS_ROOT' do
|
60
|
+
before :each do
|
61
|
+
FileUtils.mkdir "#{destination_root}/test"
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'and there is a test/exemplars directory under RAILS_ROOT' do
|
65
|
+
before :each do
|
66
|
+
FileUtils.mkdir "#{destination_root}/test/exemplars"
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should not create any new directories' do
|
70
|
+
FileUtils.touch "#{destination_root}/test/exemplars/test_exemplar.rb"
|
71
|
+
run_generator
|
72
|
+
destination_root.should have_structure {
|
73
|
+
directory "test" do
|
74
|
+
directory "exemplars" do
|
75
|
+
file "test_exemplar.rb"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
after :each do
|
82
|
+
FileUtils.rm_rf "#{destination_root}/test/exemplars"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'but there is no test/exemplars directory under RAILS_ROOT' do
|
87
|
+
it 'should create a test/exemplars directory under RAILS_ROOT' do
|
88
|
+
run_generator
|
89
|
+
destination_root.should have_structure {
|
90
|
+
directory "test" do
|
91
|
+
directory "exemplars" do
|
92
|
+
file ".gitkeep"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
after :each do
|
100
|
+
FileUtils.rm_rf "#{destination_root}/test"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'and there is no test directory under RAILS_ROOT' do
|
105
|
+
it 'should create a spec directory under RAILS_ROOT' do
|
106
|
+
run_generator
|
107
|
+
destination_root.should have_structure {
|
108
|
+
directory "spec"
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should create a spec/exemplars directory under RAILS_ROOT' do
|
113
|
+
run_generator
|
114
|
+
destination_root.should have_structure {
|
115
|
+
directory "spec" do
|
116
|
+
directory "exemplars" do
|
117
|
+
file ".gitkeep"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|