thoughtbot / factory_bot

A library for setting up Ruby objects as test data.

Home Page:https://thoughtbot.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Question: Possible to get reference to the build_class from initialize_with?

jasonkarns opened this issue · comments

I'm working with STI and am customizing our initialize_with block. However, there are some conditions we'd like to put in place that require introspection on the class in question.

It seems there are a number of layers of indirection around the class (decorator, @component, @build_class, etc). Is there a way to get a reference to the factory's class (essentially new.class, but without invoking new) from within the initialize_with block?

I'd like to bump this questions because I spent a long time digging into this problem before concluding it's just not possible. I'm trying to build a factorybot plugin/integration for an framework we have for managing data from third party api responses. We're using factorybot to build up these non active record data objects which are serialized from the response data. We don't use new when serializing these objects, so I'd like to be able to have a custom factorybot method that users can add to any factory to indicate this is a "API object factory", and have that method setup the correct initialize_with block for them. As is the only way I can do that is write a method like this

  def api_object_factory(build_class)
    initialize_with { build_class.from_hash(attributes) }  
  end

So users are forced to specific the class twice when defining their factory

  factory :api_user, class: "Api::User" do 
    api_object_factory("Api::User")
  end

Would love to make this a little less repetitive

I figured out a way around my particular problem, by doing this

  module FactoryHelpers
    def api_object_factory(name, options = {}, &block)
      raise ArgumentError, "You must specify a class" unless options[:class]
      raise ArgumentError, "You must specify a block" unless block

      factory(name, options) do
        skip_create

        initialize_with do
          options[:class].constantize.from_hash(attributes)
        end

        instance_eval(&block)
      end
    end
  end
  
  class FactoryBot::Syntax::Default::DSL
    include FactoryHelpers
  end

So now consumers don't use the normal factory method and instead write code like

FactoryBot.define do 
  api_object_factory(:api_user, class: 'Api::User') {}

This is a fairly reasonable experience in my opinion. But is extending the default DSL a supported extension from the FactoryBot perspective?