module Stepford::FactoryGirl
Automatically recursively creates/builds/stubbed associations using FactoryGirl
.
You can specify the method name and arguments/options to factory girl for the 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
you could do that with:
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
Constants
- OPTIONS
Public Class Methods
build(*args, &block)
click to toggle source
# File lib/stepford/factory_girl/methods.rb, line 161 def build(*args, &block); handle_factory_girl_method(:build, *args, &block); end
build_list(*args, &block)
click to toggle source
# File lib/stepford/factory_girl/methods.rb, line 162 def build_list(*args, &block); handle_factory_girl_method(:build_list, *args, &block); end
build_stubbed(*args, &block)
click to toggle source
# File lib/stepford/factory_girl/methods.rb, line 163 def build_stubbed(*args, &block); handle_factory_girl_method(:build_stubbed, *args, &block); end
column_overrides=(args)
click to toggle source
# File lib/stepford/factory_girl/config.rb, line 34 def column_overrides=(args) # to avoid a lot of processing overhead, we preprocess the arrays into a hash that would look ugly to the user, e.g. # {:model_name => {:attribute_name => {options or just empty}}} result = {} args.each do |k,v| if k.is_a?(Array) && k.size == 2 && v.is_a?(Hash) table_columns = (result[k[0].to_sym] ||= {}) table_column_options = (table_columns[k[1].to_sym] ||= {}) table_column_options.merge(v) else puts "Ignoring bad Stepford::FactoryGirl.column_overrides array value: #{a.inspect}. Should look like [:model_name, :attribute_name, {}]. See documentation for information on defining options hash." end end if args @column_overrides = args @column_overrides_tree = result end
configure(&blk)
click to toggle source
# File lib/stepford/factory_girl/config.rb, line 7 def configure(&blk); class_eval(&blk); end
create(*args, &block)
click to toggle source
switched to this from method_missing
to avoid method trying to handle mistaken calls
# File lib/stepford/factory_girl/methods.rb, line 159 def create(*args, &block); handle_factory_girl_method(:create, *args, &block); end
create_list(*args, &block)
click to toggle source
# File lib/stepford/factory_girl/methods.rb, line 160 def create_list(*args, &block); handle_factory_girl_method(:create_list, *args, &block); end
debugargs(args)
click to toggle source
# File lib/stepford/factory_girl/methods.rb, line 197 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
deep_dup(o)
click to toggle source
# File lib/stepford/factory_girl/methods.rb, line 167 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
force_configure(pathname)
click to toggle source
# File lib/stepford/factory_girl/config.rb, line 28 def force_configure(pathname) load pathname puts "Loaded #{pathname}" ::Stepford::FactoryGirl.config_loaded = pathname end
handle_factory_girl_method(m, *args, &block)
click to toggle source
# File lib/stepford/factory_girl/methods.rb, line 22 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 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 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.column_overrides).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_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 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 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
load_config(pathname = nil)
click to toggle source
Loads the configuration from config/stepford.rb or a specified pathname unless has already been loaded.
# File lib/stepford/factory_girl/config.rb, line 10 def load_config(pathname = nil) if !(pathname || ::Stepford::FactoryGirl.config_loaded) || (pathname && ::Stepford::FactoryGirl.config_loaded.to_sym != pathname.to_sym) begin if pathname # load without checking if exists to raise error if user-specified file is missing force_configure(pathname) else pathname = Rails.root.join('config', 'stepford.rb').to_s if File.exist?(pathname) force_configure(pathname) end end rescue => e puts "Failed to load #{pathname}:\n#{e.message}#{e.backtrace.join("\n")}" end end end
method_missing(m, *args, &block)
click to toggle source
pass everything else to FactoryGirl
to try to handle (can't reflect in current version to find what it handles)
# File lib/stepford/factory_girl/methods.rb, line 165 def method_missing(m, *args, &block); ::FactoryGirl.__send__(m, *args, &block); end