Browse files

version 0.15.0. removing circular which is now in modelist

  • Loading branch information...
1 parent d6d1429 commit 2a5394cba55a9ad3e539f7064643deebd2a46fc8 @garysweaver committed Dec 3, 2012
View
62 README.md
@@ -76,7 +76,7 @@ But you can have it autogenerate all attributes, associations, and traits:
end
-However, without modification, if you use the CLI to generate these you could more likely run into association interdependency problems (circular references). To fix those you could hand-edit the factories, or write methods to create more complex structures. Or, to keep it simple, just use the defaults for the factories CLI and then use the deep_* methods in your specs to automatically create dependencies as needed!
+However, without modification, if you use the CLI to generate associations, you may run into association interdependency problems (circular references). To fix those you could hand-edit the factories, or write methods to create what is needed. Or, to keep it simple, just use the defaults for the factories CLI and then use the deep_* methods in your specs to automatically create dependencies as needed!
### Setup
@@ -212,40 +212,6 @@ Add somewhere after the require:
#### CLI
-Stepford has a CLI with a circular reference checker and a generator to automatically create your factories file(s).
-
-##### Circular
-
-Check ActiveRecord circular dependencies find circular chains of dependencies where foreign keys that are not primary keys of the models are all not nullable in the schema or not nullable because of ActiveRecord presence validation:
-
- bundle exec stepford circular
-
-Example of output:
-
- The following non-nullable foreign keys used in ActiveRecord model associations are involved in circular dependencies:
-
- beers.waitress_id -> waitresses.bartender_id -> bartenders.beer_id -> beers.waitress_id
-
- beers.waitress_id -> waitresses.bartender_id -> bartenders.order_id -> order.beer_id -> beers.waitress_id
-
-
- Distinct foreign keys involved in a circular dependency:
-
- beers.waitress_id
- order.beer_id
- bartenders.beer_id
- bartenders.order_id
- waitresses.bartender_id
-
-
- Foreign keys by number of circular dependency chains involved with:
-
- 2 (out of 2): beers.waitress_id -> waitresses
- 2 (out of 2): waitresses.bartender_id -> bartenders
- 1 (out of 2): order.beer_id -> beers
- 1 (out of 2): bartenders.order_id -> order
- 1 (out of 2): bartenders.beer_id -> beers
-
##### Factories
###### Creating Factories
@@ -358,6 +324,8 @@ Here is a version that tests the FactoryGirl factories and the Stepford deep_cre
##### Troubleshooting
+First, please use [modelist][modelist] to help test your models and the backing schema to ensure everything is kosher.
+
If you have duplicate factory definitions during Rails load, it may complain. Just move, rename, or remove the offending files and factories and retry.
The factories CLI produces factories that use Ruby 1.9 hash syntax. If you aren't using Ruby 1.9, it may not fail during generation, but it might later when loading the factories.
@@ -374,7 +342,25 @@ or maybe:
ActiveRecord::RecordInvalid:
Validation failed: Item The item is required., Pricer The pricer is required., Purchased by A purchaser is required.
-then try to use the deep_* methods to build or create.
+then try to use the deep_* methods to build or create. If still you get an error like:
+
+ ActiveRecord::StatementInvalid:
+ PG::Error: ERROR: null value in column "something_id" violates not-null constraint
+ : INSERT INTO "foobars"
+
+ensure that the belongs_to association on the model (e.g. Foobar) is using the proper column name. It may need to explicitly set the `:foreign_key` option.
+
+Stepford needs some help fixing factories for some validations, for example, if attribute foobar on SomeModel can only be "foo" or "bar", then you may get:
+
+ ActiveRecord::RecordInvalid:
+ Validation failed: SomeModel invalid foobar Test Foobar (...)
+
+In which case you need to hand-edit the some_model factory to set the foobar attribute to "foo" or "bar". Keep in mind that if you have a default set for it in the schema, that will be set as part of stepford factories file generation. You may also want to set default values in your models like this if you can't set a default value in the column in the DB schema itself like the example in [this answer][set_default_values] in StackOverflow:
+
+ after_initialize :init
+ def init
+ self.foobar = 'foo'
+ end
If you get:
@@ -383,8 +369,6 @@ If you get:
then note that associations and traits can lead to circular dependencies. Trying generating factories without associations or traits (the default), and use the deep_* methods to create.
-If you still see the 'stack level too deep' error, use the circular CLI to find interreferencing non-nullable foreign keys and fix them.
-
ThoughtBot's Josh Clayton provided some suggestions for this, including using methods to generate more complex object structures:
def post_containing_comment_by_author
@@ -421,6 +405,8 @@ or referring to created objects through associations, though he said multiple ne
Copyright (c) 2012 Gary S. Weaver, released under the [MIT license][lic].
+[set_default_values]: http://stackoverflow.com/questions/328525/what-is-the-best-way-to-set-default-values-in-activerecord/5127684#5127684
+[modelist]: https://github.com/garysweaver/modelist/
[column_meta]: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column
[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
View
122 lib/stepford/circular_ref_checker.rb
@@ -1,122 +0,0 @@
-module Stepford
- class CircularRefChecker
-
- @@offenders = []
- @@circles_sorted = []
- @@circles = []
- @@selected_offenders = []
-
- # Check refs on all models or models specified in comma delimited list in options like:
- # Stepford.CircularRefChecker.check_refs models: 'user, post, comment'
- def self.check_refs(options={})
- models = []
- included_models = options[:models] ? options[:models].split(',').collect{|s|s.strip}.compact : nil
- Dir[File.join('app','models','*.rb').to_s].each do |filename|
- model_name = File.basename(filename).sub(/.rb$/, '')
- next if included_models && !included_models.include?(model_name)
- load File.join('app','models',"#{model_name}.rb")
-
- 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
-
- models.each do |model_class|
- check_associations(model_class)
- end
-
- if @@circles.size == 0
- puts
- puts "No circular dependencies."
- puts
- return true
- end
-
- puts "The following non-nullable foreign keys used in ActiveRecord model associations are involved in circular dependencies:"
- @@circles.sort.each do |c|
- puts
- puts "#{c}"
- end
- puts
- puts
- puts "Distinct foreign keys involved in a circular dependency:"
- puts
- @@offenders.sort.each do |c|
- puts "#{c[0]}.#{c[1]}"
- end
-
- totals = {}
- @@circles_sorted.each do |arr|
- arr.each do |key|
- totals[key] = 0 unless totals[key]
- totals[key] = totals[key] + 1
- end
- end
- puts
- puts
- puts "Foreign keys by number of circular dependency chains involved with:"
- puts
- totals.sort_by {|k,v| v}.reverse.each do |arr|
- c = arr[0]
- t = arr[1]
- puts "#{t} (out of #{@@circles_sorted.size}): #{c[0]}.#{c[1]} -> #{c[2]}"
- end
- puts
-
- return false
- end
-
- def self.check_associations(model_class, model_and_association_names = [])
- model_class.reflections.collect {|association_name, reflection|
- 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
-
- 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
- if reflection.macro == :belongs_to
- # note: supports composite_primary_keys gem which stores primary_key as an array
- foreign_key_is_also_primary_key = Array.wrap(model_class.primary_key).collect{|pk|pk.to_sym}.include?(reflection.foreign_key.to_sym)
- is_not_null_fkey_that_is_not_primary_key = model_class.columns.any?{|c| !c.null && c.name.to_sym == reflection.foreign_key.to_sym && !foreign_key_is_also_primary_key}
- required = is_not_null_fkey_that_is_not_primary_key || has_presence_validator
- else
- # no nullable metadata on column if no foreign key in this table. we'd figure out the null requirement on the column if inspecting the child model
- required = has_presence_validator
- end
-
- if required
- key = [model_class.table_name.to_sym, reflection.foreign_key.to_sym, next_class.table_name.to_sym]
- if model_and_association_names.include?(key)
- @@offenders << model_and_association_names.last unless @@offenders.include?(model_and_association_names.last)
- short = model_and_association_names.dup
- # drop all preceding keys that have nothing to do with the circle
- (short.index(key)).times {short.delete_at(0)}
- sorted = short.sort
- unless @@circles_sorted.include?(sorted)
- @@circles_sorted << sorted
- @@circles << "#{(short + [key]).collect{|b|"#{b[0]}.#{b[1]}"}.join(' -> ')}".to_sym
- end
- else
- model_and_association_names << key
- check_associations(next_class, model_and_association_names)
- end
- end
- }
-
- model_and_association_names.pop
- model_and_association_names
- end
- end
-end
View
10 lib/stepford/cli.rb
@@ -20,16 +20,6 @@ def factories()
require 'stepford/factory_girl/generator'
exit ::Stepford::FactoryGirl::Generator.generate_factories(options) ? 0 : 1
end
-
- desc "circular", "check for circular refs"
- method_option :models, :desc => "A comma delimited list of only the models you want to include"
- def circular()
- # load Rails environment
- require './config/environment'
- # load FactoryGirl and generate factories
- require 'stepford/circular_ref_checker'
- exit ::Stepford::CircularRefChecker.check_refs(options) ? 0 : 1
- end
end
end
View
6 lib/stepford/factory_girl/generator.rb
@@ -29,6 +29,12 @@ def self.generate_factories(options={})
excluded_attribute_syms = [:updated_at, :created_at, :object_id]
excluded_attribute_syms_and_pkeys = pkey_syms + [:updated_at, :created_at, :object_id]
model_class.reflections.collect {|association_name, reflection|
+ begin
+ reflection.class_name
+ rescue
+ puts "#{model_class.name}.#{association_name} failed when attempting to call reflection's class_name method, so skipped association"
+ next
+ end
(expected[reflection.class_name.underscore.to_sym] ||= []) << model_name_sym
fkey_sym = reflection.foreign_key.try(:to_sym)
excluded_attribute_syms_and_pkeys << fkey_sym if reflection.foreign_key && !(excluded_attribute_syms_and_pkeys.include?(fkey_sym))
View
7 lib/stepford/factory_girl/methods.rb
@@ -61,7 +61,12 @@ def handle_factory_girl_method(m, *args, &block)
model_class.reflections.each do |association_name, reflection|
assc_sym = reflection.name.to_sym
next if options[assc_sym] || options[reflection.foreign_key.to_sym] # || reflection.macro != :belongs_to
-
+ begin
+ reflection.class_name
+ rescue
+ puts "#{model_class.name}.#{association_name} failed when attempting to call reflection's class_name method, so skipped association"
+ next
+ end
clas_sym = reflection.class_name.underscore.to_sym
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
View
2 lib/stepford/version.rb
@@ -1,3 +1,3 @@
module Stepford
- VERSION = '0.14.1'
+ VERSION = '0.15.0'
end
View
2 stepford.gemspec
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.email = ['garysweaver@gmail.com']
s.homepage = 'https://github.com/garysweaver/stepford'
s.summary = %q{FactoryGirl becomes easier and automated.}
- s.description = %q{Has deep_* methods for automating FactoryGirl creation with required association trees and small tweaks, ActiveRecord circular required reference checker CLI, and a nice flexible FactoryGirl factories code generator CLI.}
+ s.description = %q{Automates FactoryGirl deep creation of models and their required associations avoiding circulars and provides a generator for FactoryGirl factories that reflects on models.}
s.files = Dir['lib/**/*'] + ['Rakefile', 'README.md']
s.license = 'MIT'
s.add_dependency 'thor'

0 comments on commit 2a5394c

Please sign in to comment.