object-daddy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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