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.
@@ -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