Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question/Feature - Access traits and overrides arguments in public API #1697

Open
AlexB52 opened this issue Sep 10, 2024 · 1 comment
Open
Labels

Comments

@AlexB52
Copy link

AlexB52 commented Sep 10, 2024

Problem this feature will solve

I'd like to safely bypass the creation of a factory when no overrides or traits were specified. Ultimately, the idea is to reduce the number of records created after a test setup by defaulting to a fixture when no special attributes are required.

Note: This problem may already have been solved, and I need to learn about the solution. See the "Additional Context" section for questions.

Something along these lines:

factory :user do
  to_create do |record, evaluator|
    if evaluator.traits.empty? && evaluator.overrides.empty?
      User.find(1) # users(:one) 
    else
      record.save!
    end
  end

  sequence(:name) { |n| 'Name #{n}' }
end

The formats could fit exactly the ones used in FactoryBot::FactoryRunner initialization

FactoryBot.create(:user)
=> overrides: {}, traits: []

FactoryBot.create(:user, name: 'Alex')
=> overrides: {name: 'Alex'}, traits: []

FactoryBot.create(:user, :with_password, name: 'Alex')
=> overrides: {name: 'Alex'}, traits: [:with_password]

Desired solution

It seems that exposing these methods to the evaluator could be a solution?

Note: Naming can change as the evaluator already has @overrides variable, but unless mistaken is for all the record attributes.

Alternatives considered

Custom FactoryBot::FactoryRunner with hook?

I can access these exact values by monkey patching FactoryBot::FactoryRunner which doesn't feel right.

module FactoryBot
  class FactoryRunner
    alias old_run run
    def run(runner_strategy = @strategy, &block)
      if Strategy::Create === @strategy && @traits.empty? && @overrides.empty?
        record = case @name
                 when :user then User.find(1) # users(:one) 
                 end

        return record if record
      end

      old_run(runner_strategy, &block)
    end
  end
end

The FactoryRunner could be registered/injected like strategies with some hooks?

Additional context

I guess it relates to some extent to #1681

Questions

  • Is there a way to access these values safely and not monkey patch or tinker with private APIs?
  • Is there an existing approach to prevent the creation of a record when no arguments are passed other than the name of the factory?

Any input would be appreciated about that issue, except maybe feedback about how bad the idea is; I already know that 😂
The number of records created in specs with factories is known to have issues.
Any change helping developers with this issue would be significant, especially when dealing with legacy codebases.

EDIT: Maybe using a custom strategy and redefining the #association would be good enough as often the number of records created are from linked associations.

@AlexB52
Copy link
Author

AlexB52 commented Sep 10, 2024

A custom strategy by overriding #association could end up good enough.
Associations are often the problem.

If we could have FactoryRunner attr_reader on :name, :overrides and :traits that would be easy enough maybe?

module FactoryBot
  module Strategy
    class Create
      def association(runner)
        if runner.traits.empty? && if runner.overrides.empty?
          case runner.name
          when :user
            return User.find(1) # users(:one) 
          end
        else
          runner.run
        end
      end
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant