Permalink
Browse files

version 0.13.0. support for missing sequences, ensure clear message i…

…f model invalid
  • Loading branch information...
1 parent d37ef4e commit 2ce47de125a24984d33285c14713b3534ac5e317 @garysweaver committed Nov 13, 2012
View
@@ -129,6 +129,16 @@ Then you can just use `deep_create`, `deep_create_list`, `deep_build`, `deep_bui
deep_create(:foo)
+##### Forcing Attributes and Associations
+
+If you want to force attributes and associations to be set, use the not_null configuration setting, or hand-edit the factories.rb:
+
+ # each entry looks like [:model_name, :association_or_column_name]
+ Stepford::FactoryGirl.not_null = [
+ [:bartender, :experience],
+ [:patron, :time_entered_bar],
+ ]
+
##### Cleaning Up
If you just want to run rspec at command-line, want to be able to create in before hooks, and don't want to mess with database cleaner, here is some code that you can add to your spec_helper to remove all model instances.
@@ -141,7 +151,12 @@ THIS WILL DELETE ALL YOUR DATA! BE EXTREMELY CAREFUL:
ALL_MODEL_CLASSES = Dir[File.join('app','models','*.rb').to_s].collect do |filename|
model_name = File.basename(filename).sub(/.rb$/, '')
load File.join('app','models',"#{model_name}.rb")
- model_class = model_name.camelize.constantize
+ begin
+ model_class = model_name.camelize.constantize
+ rescue => e
+ puts "Problem in #{model_name.camelize}"
+ raise e
+ end
next unless model_class.ancestors.include?(ActiveRecord::Base)
model_class
end.compact
@@ -251,7 +266,7 @@ Specify `--models` and a comma-delimited list of models to only output the model
If you use Stepford::FactoryGirl (or deep_* methods in rspec) to automatically generate factories, you may not need to generate associations, because that sets them for you. If you do choose to use associations, note that these will likely create factories with interdependence issues. When there are NOT NULLs on foreign keys and/or presence validations, etc. you can't just use `after(:create)` or `after(:build)` to set associations, and without those you can have issues with "Trait not registered" or "Factory not registered". Later versions of FactoryGirl may make this easier, and be sure to see notes from Josh down in the troubleshooting section.
-If you are ready to hand-edit to fix things, then copy paste stuff, rename it, etc. instead of just using Stepford::FactoryGirl (or deep_* methods in rspec), then keep reading.
+If you are ready to edit factories, copy and paste stuff, rename things, etc. instead of just using Stepford::FactoryGirl or deep_* methods in rspec, then keep reading.
####### Include Required Assocations
@@ -295,34 +310,33 @@ Uniqueness constraints on the model are handled by the following being generated
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.
+###### Table Sequences
+
+If a table has no sequence, each primary key will get a FactoryGirl sequence, e.g. if you had a tie table with two sequenceless primary key columns, 'a_id' and 'b_id', it will put this in the factory:
+
+ sequence(:a_id)
+ sequence(:b_id)
+
+###### Composite Primary Keys
+
+You can use the [composite_primary_keys][composite_primary_keys] gem and it should work fine.
+
##### Testing Factories
See [Testing all Factories (with RSpec)][test_factories] in the FactoryGirl wiki.
-Here are a few rspecs that test the FactoryGirl factories and the Stepford deep_builds:
+Here is a version that tests the FactoryGirl factories and the Stepford deep_creates:
require 'spec_helper'
require 'stepford/factory_girl_rspec_helpers'
describe 'validate factories build' do
FactoryGirl.factories.each do |factory|
context "with factory for :#{factory.name}" do
- subject { build(factory.name) }
-
- it "is valid" do
- subject.valid?.should be, subject.errors.full_messages
- end
- end
- end
- end
-
- describe 'validate factories deep build' do
- FactoryGirl.factories.each do |factory|
- context "with factory for :#{factory.name}" do
- subject { deep_build(factory.name) }
+ subject { deep_create(factory.name) }
it "is valid" do
- subject.valid?.should be, subject.errors.full_messages
+ subject.valid?.should be, subject.errors.full_messages.join(',')
end
end
end
@@ -393,6 +407,7 @@ or referring to created objects through associations, though he said multiple ne
Copyright (c) 2012 Gary S. Weaver, released under the [MIT license][lic].
+[composite_primary_keys]: https://github.com/drnic/composite_primary_keys
[test_factories]: https://github.com/thoughtbot/factory_girl/wiki/Testing-all-Factories-%28with-RSpec%29
[factory_girl]: https://github.com/thoughtbot/factory_girl/
[lic]: http://github.com/garysweaver/stepford/blob/master/LICENSE
@@ -15,7 +15,14 @@ def self.check_refs(options={})
model_name = File.basename(filename).sub(/.rb$/, '')
next if included_models && !included_models.include?(model_name)
load File.join('app','models',"#{model_name}.rb")
- model_class = model_name.camelize.constantize
+
+ begin
+ model_class = model_name.camelize.constantize
+ rescue => e
+ puts "Problem in #{model_name.camelize}"
+ raise e
+ end
+
next unless model_class.ancestors.include?(ActiveRecord::Base)
models << model_class
end
@@ -70,7 +77,13 @@ def self.check_associations(model_class, model_and_association_names = [])
puts "warning: #{model_class}'s association #{reflection.name}'s foreign_key was nil. can't check." unless reflection.foreign_key
assc_sym = reflection.name.to_sym
clas_sym = reflection.class_name.underscore.to_sym
- next_class = clas_sym.to_s.camelize.constantize
+
+ begin
+ next_class = reflection.class_name.constantize
+ rescue => e
+ puts "Problem in #{model_class.name} with association: #{reflection.macro} #{assc_sym.inspect} which refers to class #{reflection.class_name}"
+ raise e
+ end
has_presence_validator = model_class.validators_on(assc_sym).collect{|v|v.class}.include?(ActiveModel::Validations::PresenceValidator)
required = false
View
@@ -1,5 +1,24 @@
module Stepford
class Common
+ def self.sequence_for(column)
+ case column.type
+ when :string, :text
+ if column.name.to_s['email']
+ # n evaluated at runtime, so pound escaped
+ "sequence(#{column.name.to_sym.inspect}) do |n|; \"person\#{n}@example.com\"; end"
+ else
+ # n evaluated at runtime, so pound escaped
+ "sequence(#{column.name.to_sym.inspect}) do |n|; \"Test #{column.name.titleize} \#{n}\"; end"
+ end
+ when :integer, :decimal, :float, :date, :datetime, :timestamp, :binary, :ts_vector, :boolean
+ "sequence(#{column.name.to_sym.inspect})"
+ when :xml
+ "sequence(#{column.name.to_sym.inspect}) do |n|; \"<test>Test #{column.name.titleize} \#{n}</test>\"; end"
+ else
+ puts "Stepford does not know how to generate a sequence value for column type #{column.type.to_sym}"
+ column.default.nil? ? 'nil' : column.default.to_s
+ end
+ end
def self.value_for(column)
case column.type
when :string, :text
@@ -24,7 +43,7 @@ def self.value_for(column)
when :ts_vector
column.default.nil? ? 'nil' : column.default.to_s
else
- puts "Stepford does not know how to handle type #{column.type.to_sym}"
+ puts "Stepford does not know how to generate a value for column type #{column.type.to_sym}"
column.default.nil? ? 'nil' : column.default.to_s
end
end
@@ -18,7 +18,8 @@ module Stepford
# end
module FactoryGirl
OPTIONS = [
- :debug
+ :debug,
+ :not_null
]
class << self
@@ -29,7 +30,13 @@ def handle_factory_girl_method(m, *args, &block)
if args && args.size > 0
# call Stepford::FactoryGirl.* on any not null associations recursively
- model_class = args[0].to_s.camelize.constantize
+ model_name = args[0]
+ begin
+ model_class = model_name.to_s.camelize.constantize
+ rescue => e
+ puts "Problem in #{model_name.to_s.camelize}" if model_name
+ raise e
+ end
args = args.dup # need local version because we'll be dup'ing the options hash to add things to set prior to create/build
options = args.last
@@ -80,7 +87,7 @@ def handle_factory_girl_method(m, *args, &block)
required = has_presence_validator
end
- if required
+ if required || Array.wrap(::Stepford::FactoryGirl.not_null).compact.include?([model_name.to_sym, assc_sym])
breadcrumbs << ["a:#{assc_sym}"]
if orig_method_args_and_options
method_args_and_options = orig_method_args_and_options.dup
@@ -93,7 +100,7 @@ def handle_factory_girl_method(m, *args, &block)
options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options)
end
to_reload << options[assc_sym]
- rescue ActiveRecord::RecordInvalid => e
+ rescue => e
puts "#{breadcrumbs.join('>')}: FactoryGirl.__send__(#{method_args_and_options.inspect}): #{e}#{::Stepford::FactoryGirl.debug? ? "\n#{e.backtrace.join("\n")}" : ''}"
raise e
end
@@ -125,8 +132,9 @@ def handle_factory_girl_method(m, *args, &block)
end
begin
+ raise "#{breadcrumbs.join('>')} - Huh? args[0] was #{args[0]}. m=#{m.inspect}, args=#{args.inspect}" if args && args.size > 1 && !(args[0].is_a?(String) || args[0].is_a?(Symbol))
result = ::FactoryGirl.__send__(m, *args, &block)
- rescue ActiveRecord::RecordInvalid => e
+ rescue => e
puts "#{breadcrumbs.join('>')}: FactoryGirl.#{m}(#{args.inspect}): #{e}#{::Stepford::FactoryGirl.debug? ? "\n#{e.backtrace.join("\n")}" : ''}" if defined?(breadcrumbs)
raise e
end
@@ -138,7 +146,7 @@ def handle_factory_girl_method(m, *args, &block)
orig_options[:to_reload] << result
else
# ready to return the initially requested instances, so reload children with their parents, in reverse order added
- orig_options[:to_reload].reverse.each do |i|
+ orig_options[:to_reload].each do |i|
begin
i.reload
rescue => e
@@ -10,18 +10,27 @@ def self.generate_factories(options={})
model_name = File.basename(filename).sub(/.rb$/, '')
next if included_models && !included_models.include?(model_name)
load File.join('app','models',"#{model_name}.rb")
- model_class = model_name.camelize.constantize
+
+ begin
+ model_class = model_name.camelize.constantize
+ rescue => e
+ puts "Problem in #{model_name.camelize}"
+ raise e
+ end
+
next unless model_class.ancestors.include?(ActiveRecord::Base)
factory = (factories[model_name.to_sym] ||= [])
- excluded_attributes = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym} + [:updated_at, :created_at, :object_id]
+ pk_syms = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym}
+ excluded_attributes = pk_syms + [:updated_at, :created_at, :object_id]
model_class.reflections.collect {|association_name, reflection|
(expected[reflection.class_name.underscore.to_sym] ||= []) << model_name
- excluded_attributes << reflection.foreign_key.to_sym if reflection.foreign_key
+ fkey_sym = reflection.foreign_key.try(:to_sym)
+ excluded_attributes << fkey_sym if reflection.foreign_key && !(excluded_attributes.include?(fkey_sym))
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
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
+ required = reflection.foreign_key ? (has_presence_validator || model_class.columns.any?{|c| !c.null && c.name.to_sym == fkey_sym}) : false
should_be_trait = !(options[:associations] || (options[:include_required_associations] && required)) && options[:association_traits]
if options[:associations] || (options[:include_required_associations] && required) || should_be_trait
if reflection.macro == :has_many
@@ -39,12 +48,24 @@ def self.generate_factories(options={})
nil
end
}.compact.sort.each {|l|factory << l}
+
+ sequenceless_table = false
+ begin
+ sequenceless_table = true unless m.sequence_name
+ rescue => e
+ # bug in Rails 3.2.8, at least: undefined method `split' for nil:NilClass in activerecord-3.2.8/lib/active_record/connection_adapters/postgresql_adapter.rb:911:in `default_sequence_name'
+ sequenceless_table = true
+ end
+
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)
+ # intentional not checking excluded_attributes/exclude_all_ids when sequenceless. it needs these for create to work.
+ if sequenceless_table && pk_syms.include?(c.name.to_sym)
+ factory << Stepford::Common.sequence_for(c)
+ elsif !excluded_attributes.include?(c.name.to_sym) && !(c.name.to_s.downcase.end_with?('_id') && options[:exclude_all_ids]) && (options[:attributes] || !c.null)
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})"
+ factory << Stepford::Common.sequence_for(c)
else
factory << "#{is_reserved?(c.name) ? 'self.' : ''}#{c.name} #{Stepford::Common.value_for(c)}"
end
View
@@ -1,3 +1,3 @@
module Stepford
- VERSION = '0.12.2'
+ VERSION = '0.13.0'
end

0 comments on commit 2ce47de

Please sign in to comment.