Permalink
Browse files

version 0.11.0. fixing auto dependency creation. added more helpful d…

…ebugging. updated readme and noted still some fkey problems
  • Loading branch information...
1 parent a6a406c commit 15cf5aa5145851808cec45bd1a34e79ea77733c6 @garysweaver committed Nov 12, 2012
Showing with 165 additions and 105 deletions.
  1. +51 −15 README.md
  2. +11 −6 lib/stepford/circular_ref_checker.rb
  3. +102 −83 lib/stepford/factory_girl.rb
  4. +1 −1 lib/stepford/version.rb
View
@@ -1,32 +1,56 @@
Stepford
=====
-Stepford is an automatic required (non-null or presence validated) association resolving (creating/building/stubbing) wrapper and/or factory generator for [FactoryGirl][factory_girl].
+Now getting started with FactoryGirl is even more simple and DRY!
-For example, with the rspec helper:
+Stepford is an automatic required (non-null or presence validated) association resolver and factory generator for [FactoryGirl][factory_girl].
- create(:foo)
+The Stepford CLI allows can generate a factories.rb or multiple files each defining a single factory, for every existing model or for those specified.
-Would take a :foo FactoryGirl factory that has no associations defined in it (only required attributes) and will travel the dependency tree of the models to create and create_list as needed. Or, you can build the same way:
+The following would create/overwrite `test/factories.rb` with a factory for every model in `app/models`:
- build(:foo)
+ bundle exec stepford factories
+
+If you use rspec, it would be:
+
+ bundle exec stepford factories --path spec
+
+With our rspec helper, you can use this to create a bar and automatically create its dependencies and their dependencies, etc. providing ways to remedy circular dependencies:
+
+ deep_create(:bar)
+
+You can also `create_list`, `build`, `build_list`, and `build_stubbed`:
-Need to just customize a few things? You can use the normal FactoryGirl behavior (args, options, block) but also specify options for each factory.
+ deep_build_list(:bar, 5)
-e.g. maybe Bar has a required association called house_special which uses the :beer factory, and we have a block we want to send into it; oh, and Beer has specials that you want to build as a list of 3 using the :tuesday_special_offer factory. Just set it up like:
+Need to customize it? You can use the normal FactoryGirl behavior (args, options, block), but in addition, you may specify options for each factories that would create direct or indirect associations.
- Stepford::FactoryGirl.create_list(:bar, with_factory_options: {
+e.g. maybe Bar has a required association called house_special which uses the beer factory, and we have a block we want to send into it, and Beer has specials that you want to build as a list of 3, using the tuesday_special_offer factory. In rspec, you'd do:
+
+ deep_create_list(:bar, with_factory_options: {
house_special: [:create, :beer, {blk: ->(beer) do; beer.bubbles.create(attributes_for(:bubbles)); end}],
specials: [:build_list, :tuesday_special_offer, 3]
}) do
# any block you would send to FactoryGirl.create_list(:bar) would go here
end
-What if you have an existing schema, and you want to use FactoryGirl, but don't have any factories yet?
+By default autogenerated factories just have required attributes, e.g.:
-The Stepford CLI allows you to generate your factories.rb or multiple factory files for you.
+ require 'factory_girl_rails'
-e.g. maybe one of your models is called post, then you could generate a factory for post and all of the other models with a one-liner, maybe with the following in the `some/path/factories/post.rb` file:
+ FactoryGirl.define do
+
+ factory :post do
+ created_at { 2.weeks.ago }
+ name 'Test Name'
+ price 1.23
+ trait :with_summary do; template 'Test Summary'; end
+ updated_at { 2.weeks.ago }
+ end
+
+ end
+
+But you can have it autogenerate lots of the FactoryGirl bells and whistles:
require 'factory_girl_rails'
@@ -48,6 +72,10 @@ e.g. maybe one of your models is called post, then you could generate a factory
end
+But, everything from author to with_notes may have association interdependency issues unless you hand edit the generated versions.
+
+Stepford's FactoryGirl (and optionally its rspec helper) can help you avoid the heavy lifting.
+
### Setup
In your Rails 3+ project, add this to your Gemfile:
@@ -108,6 +136,8 @@ If you have a circular reference (A has NOT NULL foreign key to B that has NOT N
[:waiter, :bar] => {on_loop: 2, set_to: Waiter.find(123)},
}
+Leave out :on_loop or set :on_loop to 0 to use the set instead of attempting to build/create.
+
##### Debugging
Add somewhere after the require:
@@ -192,9 +222,9 @@ To generate traits for each association that would be included with `--associati
###### Associations
-If you use the (cache) wrapper to automatically generate factories, you may not need to generate associations. We had interdependence issues with factories. 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" with interdependent factory associations.
+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.
-However, if you don't have anything that complex or don't mind hand-editing the factories to try to fix issues, these might help.
+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.
####### Include Required Assocations
@@ -226,7 +256,7 @@ Currently uniqueness constraints are ignored and must be handled by FactoryGirl
sequence(:username) {|n| "user#{n}" }
-###### Testing Factories
+##### Testing Factories
See [Testing all Factories (with RSpec)][test_factories] in the FactoryGirl wiki.
@@ -315,7 +345,13 @@ or referring to created objects through associations, though he said multiple ne
comment = FactoryGirl.create(:comment, :authored_by_post_author)
comment.author == comment.post.author # true
-This is the reason we wrote the Stepford Factory Girl Wrapper (see above). It automatically determines what needs to be set in what order and does create, create_list or build, build_list, etc. automatically.
+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
@@ -67,19 +67,24 @@ def self.check_refs(options={})
def self.check_associations(model_class, model_and_association_names = [])
model_class.reflections.collect {|association_name, reflection|
- next unless reflection.macro == :belongs_to
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
- # 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)
- # 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 = 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 is_not_null_fkey_that_is_not_primary_key || has_presence_validator
+ 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)
Oops, something went wrong.

0 comments on commit 15cf5aa

Please sign in to comment.