object-daddy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|