Permalink
Browse files

version 0.14.0. changed not_null to column_overrides. reorganizing un…

…der Stepford::FactoryGirl module. cleanup of unused vars. other fixes
  • Loading branch information...
1 parent 2ce47de commit ee1470688ba62809d50d073e265541f3bb67bde1 @garysweaver committed Nov 15, 2012
View
@@ -92,6 +92,30 @@ Then run:
bundle install
+### Configuration
+
+You don't have to use a `config/stepford.rb`, but if you have one, it will load it as needed both in CLI and via helpers, etc.
+
+If you don't use the CLI, you can just put it into your `test/test_helper.rb`, `spec/spec_helper.rb`, or similar for the deep_* methods, and with the CLI you could just put it into a Rails environment file.
+
+Debug option:
+
+ Stepford::FactoryGirl.debug = true
+
+Make Stepford think that the schema looks different than it does to allow virtual attributes, etc.:
+
+ Stepford::FactoryGirl.column_overrides = {
+ [:bartender, :experience] => {null: false},
+ [:patron, :time_entered_bar] => {null: false},
+ [:patron, :some_virtual_attribute] => {null: false, virtual: true, type: :string, limit: 5} # if you specify a virtual attribute, be sure to include virtual: true and a valid type
+ }
+
+Override options are: `:virtual`, `:type`, `:limit`, `:default`, `:null`, `:precision`, and `:scale`. Each is equivalent to their [ActiveRecord column equivalents][column_meta].
+
+You can reconfigure it at runtime during tests if you'd like, and you can just call this if you want, but it doesn't have to be loaded this way. No magic involved, but it caches a little, so faster than just doing a `load`:
+
+ Stepford::FactoryGirl.load_config(pathname)
+
### Usage
#### Require
@@ -123,22 +147,12 @@ But, you might want to specify traits, and certain attributes or associations or
Put this in your `spec/spec_helper.rb`:
- require 'stepford/factory_girl_rspec_helpers'
+ require 'stepford/factory_girl/rspec_helpers'
Then you can just use `deep_create`, `deep_create_list`, `deep_build`, `deep_build_list`, or `deep_build_stubbed` in your rspec tests (`deep_create` becomes a shortcut for `::Stepford::FactoryGirl.create`, etc.), e.g.:
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.
@@ -328,7 +342,7 @@ See [Testing all Factories (with RSpec)][test_factories] in the FactoryGirl wiki
Here is a version that tests the FactoryGirl factories and the Stepford deep_creates:
require 'spec_helper'
- require 'stepford/factory_girl_rspec_helpers'
+ require 'stepford/factory_girl/rspec_helpers'
describe 'validate factories build' do
FactoryGirl.factories.each do |factory|
@@ -407,6 +421,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].
+[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
[factory_girl]: https://github.com/thoughtbot/factory_girl/
@@ -76,7 +76,6 @@ 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
- clas_sym = reflection.class_name.underscore.to_sym
begin
next_class = reflection.class_name.constantize
View
@@ -17,8 +17,8 @@ def factories()
# load Rails environment
require './config/environment'
# load FactoryGirl and generate factories
- require 'stepford/factory_girl_generator'
- exit Stepford::FactoryGirlGenerator.generate_factories(options) ? 0 : 1
+ require 'stepford/factory_girl/generator'
+ exit ::Stepford::FactoryGirl::Generator.generate_factories(options) ? 0 : 1
end
desc "circular", "check for circular refs"
@@ -28,9 +28,9 @@ def circular()
require './config/environment'
# load FactoryGirl and generate factories
require 'stepford/circular_ref_checker'
- exit Stepford::CircularRefChecker.check_refs(options) ? 0 : 1
+ exit ::Stepford::CircularRefChecker.check_refs(options) ? 0 : 1
end
end
end
-Stepford::CLI.start
+::Stepford::CLI.start
@@ -0,0 +1,28 @@
+module Stepford
+ # Needed a column representation that would allow user to specify attributes that are used for sample data choice for virtual attributes
+ # e.g. if you have an object_id column in the schema, but in model you have virtual proxy attribute methods to set it like my_object_id/my_object_id=
+ # then you need a way to specify that it should set my_object_id= with a string vs. number, etc.
+ class ColumnRepresentation
+ attr_accessor :name, :type, :limit, :default, :null, :precision, :scale, :virtual
+
+ def initialize(args)
+ if args.is_a?(Symbol)
+ @name = args.to_sym
+ elsif !(args.nil?)
+ # assume initializing with column
+ # see: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column
+ @name = args.name
+ @type = args.type
+ @limit = args.limit
+ @default = args.default
+ @null = args.null # should be called nullable, but using what Rails/AR calls it to be easier to work with as if were a "real" AR column
+ @precision = args.precision
+ @scale = args.scale
+ end
+ end
+
+ def merge_options(options)
+ options.each {|k,v|instance_variable_set("@#{k}", v)}
+ end
+ end
+end
@@ -1,213 +1 @@
-require 'factory_girl'
-require 'bigdecimal'
-
-module Stepford
- # A proxy for FactoryGirl that automatically recursively creates/builds/stubbed factories for null=false and/or presence validated associations.
- #
- # Lets you specify method name and arguments/options to factory girl for associations.
- #
- # e.g. if the following is required:
- # * Bar has a required association called house_special which uses the :beer factory, and we have a block we want to send into it
- # * Beer has specials that you want to build as a list of 3 using the :tuesday_special_offer factory
- # then you could set that up like this:
- # Stepford::FactoryGirl.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
- # # the block you would send to FactoryGirl.create_list(:foo) would go here
- # end
- module FactoryGirl
- OPTIONS = [
- :debug,
- :not_null
- ]
-
- class << self
- 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)
-
- if args && args.size > 0
- # call Stepford::FactoryGirl.* on any not null associations recursively
- 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
- if options.is_a?(Hash)
- # keep them separate
- orig_options = options
- options = deep_dup(options)
- args[(args.size - 1)] = options # need to set the dup'd options
- else
- # keep them separate
- orig_options = {}
- options = {}
- args << options # need to add options to set associations
- 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]]
-
- orig_options[:to_reload] = [] unless orig_options[:to_reload]
- to_reload = orig_options[:to_reload]
-
- 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
- next if options[assc_sym] || options[reflection.foreign_key.to_sym] # || reflection.macro != :belongs_to
-
- 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
- orig_method_args_and_options = with_factory_options ? (with_factory_options[[clas_sym, assc_sym]] || with_factory_options[clas_sym]) : nil
-
- 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 || 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
- method_options = args.last
- blk = method_options.is_a?(Hash) ? method_args_and_options.delete(:blk) : nil
- begin
- if blk
- options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options, &blk)
- else
- options[assc_sym] = ::FactoryGirl.__send__(*method_args_and_options)
- end
- to_reload << options[assc_sym]
- rescue => 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
- 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
- if args.last.is_a?(Hash)
- (args.last).delete(:with_factory_options)
- (args.last).delete(:nesting_breadcrumbs)
- (args.last).delete(:to_reload)
- 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 => 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
- # still handling association/subassociation
- args.last[:nesting_breadcrumbs] = breadcrumbs
- args.last[:to_reload] = to_reload
- 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].each do |i|
- begin
- i.reload
- rescue => e
- puts "#{i} reload failed: #{e}\n#{e.backtrace.join("\n")}" if ::Stepford::FactoryGirl.debug?
- end
- end
- end
-
- result
- end
-
- # switched to this from method_missing to avoid method trying to handle mistaken calls
- def create(*args, &block); handle_factory_girl_method(:create, *args, &block); end
- def create_list(*args, &block); handle_factory_girl_method(:create_list, *args, &block); end
- def build(*args, &block); handle_factory_girl_method(:build, *args, &block); end
- def build_list(*args, &block); handle_factory_girl_method(:build_list, *args, &block); end
- def build_stubbed(*args, &block); handle_factory_girl_method(:build_stubbed, *args, &block); end
- # pass everything else to FactoryGirl to try to handle (can't reflect in current version to find what it handles)
- def method_missing(m, *args, &block); ::FactoryGirl.__send__(m, *args, &block); end
-
- def deep_dup(o)
- result = nil
- if o.is_a?(Hash)
- result = {}
- o.keys.each do |key|
- result[deep_dup(key)] = deep_dup(o[key])
- end
- elsif o.is_a?(Array)
- result = []
- o.each do |value|
- result << deep_dup(value)
- end
- elsif [NilClass,FalseClass,TrueClass,Symbol,Numeric,Class,Module].any?{|c|o.is_a?(c)}
- result = o
- elsif o.is_a?(BigDecimal)
- # ActiveSupport v3.2.8 checks duplicable? for BigDecimal by testing it, so we'll just try to dup the value itself
- result = o
- begin
- result = o.dup
- rescue TypeError
- # can't dup
- end
- elsif o.is_a?(Object)
- result = o.dup
- else
- result = o
- end
- result
- end
-
- def debugargs(args)
- result = []
- args.each do |arg|
- if arg.is_a?(Hash)
- result << "{#{arg.keys.collect{|key|"#{key} = (#{arg[key].class.name})"}.join(', ')}}"
- else
- result << "#{arg.inspect},"
- end
- end
- result.join('')
- end
- end
- end
-end
+require 'stepford/factory_girl/methods'
Oops, something went wrong.

0 comments on commit ee14706

Please sign in to comment.