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:

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