Permalink
Browse files

version 0.12.0. breadcrumbs in messages when there are validation err…

…ors to be able to track down what is failing to create
  • Loading branch information...
1 parent 15cf5aa commit 1dab2d92b952ea62314fe1bac118f839c1c00176 @garysweaver committed Nov 12, 2012
Showing with 70 additions and 49 deletions.
  1. +10 −10 README.md
  2. +51 −36 lib/stepford/factory_girl.rb
  3. +8 −2 lib/stepford/factory_girl_generator.rb
  4. +1 −1 lib/stepford/version.rb
View
@@ -40,10 +40,12 @@ By default autogenerated factories just have required attributes, e.g.:
FactoryGirl.define do
- factory :post do
+ factory :novel do
created_at { 2.weeks.ago }
name 'Test Name'
price 1.23
+ sequence(:isbn)
+ sequence(:ean)
trait :with_summary do; template 'Test Summary'; end
updated_at { 2.weeks.ago }
end
@@ -56,7 +58,7 @@ But you can have it autogenerate lots of the FactoryGirl bells and whistles:
FactoryGirl.define do
- factory :post do
+ factory :novel do
author
association :edited_by, factory: :user
FactoryGirl.create_list :comments, 2
@@ -66,6 +68,8 @@ But you can have it autogenerate lots of the FactoryGirl bells and whistles:
created_at { 2.weeks.ago }
name 'Test Name'
price 1.23
+ sequence(:isbn)
+ sequence(:ean)
trait :with_summary do; template 'Test Summary'; end
updated_at { 2.weeks.ago }
end
@@ -252,9 +256,11 @@ If working with a legacy schema, you may have models with foreign_key columns th
If the ActiveRecord column `null` property for the attribute is true for the attribute or foreign key for the association, or if there is a presence validator for an attribute or foreign key for the association, then that attribute or association will be defined by the default factory.
-Currently uniqueness constraints are ignored and must be handled by FactoryGirl sequence or similar if not automatically populated by your model or the database, e.g. in your factory, if username uniqueness is enforced by a unique constraint on the database-side, you'll need to do something like this manually in the factory:
+Uniqueness constraints on the model are handled by the following being generated in the factory, which works for strings and numbers:
- sequence(:username) {|n| "user#{n}" }
+ sequence(:my_attribute)
+
+If you have a formatting constraint, some other constraint, or don't like the format of the data in the factories, see the [Factory Girl][factory_girl] documentation to find out how to customize your factories.
##### Testing Factories
@@ -347,12 +353,6 @@ or referring to created objects through associations, though he said multiple ne
This is the reason we wrote the Stepford's Factory Girl proxy and helper rspec methods (see above). It automatically determines what needs to be set in what order and does create, create_list or build, build_list, etc. automatically.
-Unfortunately, it still has trouble with fkey constraints:
-
- insert or update on table "foobars" violates foreign key constraint "some_foreign_key_contraint_name"
-
-Working on that.
-
### License
Copyright (c) 2012 Gary S. Weaver, released under the [MIT license][lic].
@@ -18,20 +18,16 @@ module Stepford
# end
module FactoryGirl
OPTIONS = [
- :debug,
- :stop_circular_refs
+ :debug
]
class << self
- @@breadcrumbs = []
-
OPTIONS.each{|o|attr_accessor o; define_method("#{o}?".to_sym){!!send("#{o}")}}
def configure(&blk); class_eval(&blk); end
def handle_factory_girl_method(m, *args, &block)
- @@breadcrumbs << [args[0]] if ::Stepford::FactoryGirl.debug?
+
#puts "#{' ' * @@indent}handling Stepford::FactoryGirl.#{m}(#{args.inspect})" if ::Stepford::FactoryGirl.debug?
- puts "#{@@breadcrumbs.join('>')} start" if ::Stepford::FactoryGirl.debug?
if args && args.size > 0
# call Stepford::FactoryGirl.* on any not null associations recursively
@@ -52,14 +48,16 @@ def handle_factory_girl_method(m, *args, &block)
args << options # need to add options to set associations
end
- if ::Stepford::FactoryGirl.debug?
- print "#{@@breadcrumbs.join('>')} args="
- debugargs(args)
- puts
- end
-
options[:with_factory_options] = {} unless options[:with_factory_options]
with_factory_options = options[:with_factory_options]
+
+ orig_options[:nesting_breadcrumbs] = [] unless orig_options[:nesting_breadcrumbs]
+ breadcrumbs = orig_options[:nesting_breadcrumbs]
+ breadcrumbs << [args[0]]
+
+ if ::Stepford::FactoryGirl.debug?
+ puts "#{breadcrumbs.join('>')} start. args=#{debugargs(args)}"
+ end
model_class.reflections.each do |association_name, reflection|
assc_sym = reflection.name.to_sym
@@ -83,44 +81,61 @@ def handle_factory_girl_method(m, *args, &block)
end
if required
- @@breadcrumbs << ["a:#{assc_sym}"] if ::Stepford::FactoryGirl.debug?
+ breadcrumbs << ["a:#{assc_sym}"]
if orig_method_args_and_options
method_args_and_options = orig_method_args_and_options.dup
method_options = args.last
blk = method_options.is_a?(Hash) ? method_args_and_options.delete(:blk) : nil
- if blk
- #puts "#{' ' * @@indent}FactoryGirl.__send__(#{method_args_and_options.inspect}, &blk)" if ::Stepford::FactoryGirl.debug?
- options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options, &blk)
- else
- #puts "#{' ' * @@indent}FactoryGirl.__send__(#{method_args_and_options.inspect})" if ::Stepford::FactoryGirl.debug?
- options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options)
- end
+ begin
+ if blk
+ #puts "#{' ' * @@indent}FactoryGirl.__send__(#{method_args_and_options.inspect}, &blk)" if ::Stepford::FactoryGirl.debug?
+ options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options, &blk)
+ else
+ #puts "#{' ' * @@indent}FactoryGirl.__send__(#{method_args_and_options.inspect})" if ::Stepford::FactoryGirl.debug?
+ options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options)
+ end
+ rescue ActiveRecord::RecordInvalid => e
+ puts "#{breadcrumbs.join('>')}: FactoryGirl.__send__(#{method_args_and_options.inspect}): #{e}#{::Stepford::FactoryGirl.debug? ? "\n#{e.backtrace.join("\n")}" : ''}"
+ raise e
+ end
else
if reflection.macro == :has_many
options[assc_sym] = ::Stepford::FactoryGirl.create_list(clas_sym, 2, orig_options)
else
options[assc_sym] = ::Stepford::FactoryGirl.create(clas_sym, orig_options)
end
end
- @@breadcrumbs.pop if ::Stepford::FactoryGirl.debug?
+ breadcrumbs.pop
end
end
end
+ if defined?(breadcrumbs)
+ if ::Stepford::FactoryGirl.debug?
+ puts "#{breadcrumbs.join('>')} end"
+ puts "#{breadcrumbs.join('>')} FactoryGirl.#{m}(#{debugargs(args)})"
+ end
+ breadcrumbs.pop
+ end
+
# clean-up before sending to FactoryGirl
- (args.last).delete(:with_factory_options) if args.last.is_a?(Hash)
-
- if ::Stepford::FactoryGirl.debug?
- puts "#{@@breadcrumbs.join('>')} end"
- puts
- print "#{@@breadcrumbs.join('>')} FactoryGirl.#{m}("
- debugargs(args)
- puts ")"
- puts
- @@breadcrumbs.pop
+ if args.last.is_a?(Hash)
+ (args.last).delete(:with_factory_options)
+ (args.last).delete(:nesting_breadcrumbs)
end
- ::FactoryGirl.__send__(m, *args, &block)
+ begin
+ result = ::FactoryGirl.__send__(m, *args, &block)
+ rescue ActiveRecord::RecordInvalid => e
+ puts "#{breadcrumbs.join('>')}: FactoryGirl.#{m}(#{args.inspect}): #{e}#{::Stepford::FactoryGirl.debug? ? "\n#{e.backtrace.join("\n")}" : ''}" if defined?(breadcrumbs)
+ raise e
+ end
+
+ if args.last.is_a?(Hash) && defined?(breadcrumbs) && breadcrumbs.size > 0
+ args.last[:nesting_breadcrumbs] = breadcrumbs
+ end
+
+ result
end
# switched to this from method_missing to avoid method trying to handle mistaken calls
@@ -163,15 +178,15 @@ def deep_dup(o)
end
def debugargs(args)
+ result = []
args.each do |arg|
if arg.is_a?(Hash)
- print "{"
- print arg.keys.collect{|key|"#{key} = (#{arg[key].class.name})"}.join(', ')
- print "}"
+ result << "{#{arg.keys.collect{|key|"#{key} = (#{arg[key].class.name})"}.join(', ')}}"
else
- print "#{arg.inspect},"
+ result << "#{arg.inspect},"
end
end
+ result.join('')
end
end
end
@@ -19,7 +19,7 @@ def self.generate_factories(options={})
excluded_attributes << reflection.foreign_key.to_sym if reflection.foreign_key
assc_sym = reflection.name.to_sym
clas_sym = reflection.class_name.underscore.to_sym
- # if has a foreign key, then if NOT NULL or is a presence validate, the association is required and should be output. unfortunately this could mean a circular reference that will have to be manually fixed
+ # if has a foreign key, then if NOT NULL or is a presence validate, the association is required and should be output. unfortunately this could mean a circular reference that will have to be manually fixed
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(ActiveModel::Validations::PresenceValidator)
required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym}) : false
should_be_trait = !(options[:associations] || (options[:include_required_associations] && required)) && options[:association_traits]
@@ -41,7 +41,13 @@ def self.generate_factories(options={})
}.compact.sort.each {|l|factory << l}
model_class.columns.sort_by {|c|[c.name]}.each {|c|
if !excluded_attributes.include?(c.name.to_sym) && !(c.name.downcase.end_with?('_id') && options[:exclude_all_ids]) && (options[:attributes] || !c.null)
- factory << "#{is_reserved?(c.name) ? 'self.' : ''}#{c.name} #{Stepford::Common.value_for(c)}"
+ has_uniqueness_validator = model_class.validators_on(c.name.to_sym).collect{|v|v.class}.include?(ActiveRecord::Validations::UniquenessValidator)
+ if has_uniqueness_validator
+ #TODO: specialize for different data types
+ factory << "sequence(#{c.name.to_sym.inspect})"
+ else
+ factory << "#{is_reserved?(c.name) ? 'self.' : ''}#{c.name} #{Stepford::Common.value_for(c)}"
+ end
elsif options[:attribute_traits]
if c.type == :boolean
factory << "trait #{c.name.underscore.to_sym.inspect} do; #{is_reserved?(c.name) ? 'self.' : ''}#{c.name} true; end"
View
@@ -1,3 +1,3 @@
module Stepford
- VERSION = '0.11.0'
+ VERSION = '0.12.0'
end

0 comments on commit 1dab2d9

Please sign in to comment.