thoughtbot / factory_bot

A library for setting up Ruby objects as test data.
https://thoughtbot.com
MIT License
7.89k stars 2.6k forks source link

Question: Why `Evaluator` class is marked as a `@api private` if its instance is part of `to_create` API? #1664

Open jacob-s-son opened 3 weeks ago

jacob-s-son commented 3 weeks ago

to_create has either arity of one - to_create {|instance| do something } or two to_create {|instance, context| do something with instance and context }

For the context, we are using repository pattern in our app, the entities are immutable structs, so building them is simply initializing a class. Same time persistence is a create(**attrs) method in a repo instance, that returns an entity after saving it to a DB.

This is the snippet I've came up with after some experimenting:

module FactoryBotHelpers
  # no clean way (only chain of private methods and vars) to pass our repo class to both initialize_with and to_create
  # custom strategies also don't provide access to the class of the factory for some reason
  # hence, this helper method
  def factory(name, repo_class:, &)
    # values that are mandatory to initialize our immutable entities,
    # but are set in most cases by ORM when persisting
    missing_build_attributes = {
      id: -> { GenerateULID.call },
    }

    FactoryBot.define do
      # expectation was that class won't be required as we are overriding initialize_with
      # but FactoryBot still tries to figure out class name from factory name
      factory name, class: repo_class do
        initialize_with do
          repo_class.constantize.new.entity_class.new(
            **missing_build_attributes.transform_values(&:call),
            **attributes,
          )
        end

        to_create do |instance, context|
          repo_class.constantize.new.create!(
            **instance.attributes.except(
              # we need to remove default values that we supplied in initialize_with, but
              # we also need to keep the values that were set by the test
              missing_build_attributes.keys - context.__override_names__,
            ),
          )
        end

        instance_eval(&)
      end
    end
  end
  module_function :factory
end

But in this case requires access to the names of the overridden attributes via __override_names__. Is it discouraged?