Skip to content

Instantly share code, notes, and snippets.

@coderberry
Last active December 20, 2023 19:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coderberry/58803662769475af6e8e642ea01d194c to your computer and use it in GitHub Desktop.
Save coderberry/58803662769475af6e8e642ea01d194c to your computer and use it in GitHub Desktop.
FactoryBot#create with ActiveRecord Callbacks
# freeze_string_literal: true
module CreateWithCallbacks
extend ActiveSupport::Concern
# Mimic the FactoryBot `create` method but with callbacks. All traits and overrides are applied.
#
# @example with traits and attributes
# source_course = create_with_callbacks!(
# :course,
# :with_section_and_lessons,
# :with_published_sections_and_lessons,
# :with_events,
# site: @seeds.site
# )
#
# @param name [Symbol] the name of the factory to use
# @param traits_and_overrides [Array<Symbol, Hash>] the traits and overrides to use
# @param block [Proc] the block to use
# @return [ActiveRecord::Base] the created object
def create_with_callbacks!(name, *traits_and_overrides, &block)
fb = FactoryBot.build(name, *traits_and_overrides, &block)
obj = fb.class.create!(fb.attributes)
obj.save!
obj
end
end

FactoryBot, a popular fixture replacement tool in the Ruby on Rails ecosystem, is designed for creating objects in a test database. While it does support ActiveRecord callbacks, the way it handles them can sometimes lead to confusion or unexpected behavior, especially with the create strategy. Understanding why FactoryBot behaves as it does with callbacks requires delving into its philosophy and design goals.

Philosophy and Design Goals:

  1. Testing Isolation and Speed: One of the primary goals of FactoryBot is to facilitate isolated and fast tests. Callbacks in ActiveRecord can introduce dependencies and side effects that complicate test setup and slow down test execution. By avoiding or minimizing callbacks, FactoryBot encourages cleaner, more focused tests.

  2. Explicit over Implicit: FactoryBot favors explicit setup over implicit side effects. The idea is that tests should be as clear and explicit as possible about their setup and dependencies. ActiveRecord callbacks, while useful in application code, can sometimes create implicit dependencies that make tests harder to understand and maintain.

  3. Control and Predictability: In testing, having control over the object's state and behavior is crucial. Callbacks can sometimes modify the state of an object in ways that aren't immediately obvious, which can lead to tests that are less predictable and harder to debug.

Handling ActiveRecord Callbacks:

While FactoryBot doesn't prevent ActiveRecord callbacks from running, it provides mechanisms to control when and how they are triggered:

  1. Using Build vs. Create: FactoryBot's build method constructs an instance without saving it to the database, hence not triggering create callbacks. The create method, however, constructs the instance and saves it, thereby triggering create callbacks.

  2. Custom Strategies: You can define custom creation strategies in FactoryBot. This allows you to control exactly how and when callbacks are triggered, giving you the flexibility to handle specific testing scenarios.

  3. Manual Triggering: In scenarios where you need to test the effect of callbacks explicitly, you can trigger them manually within your tests, ensuring that their behavior is intentionally invoked and observed.

Conclusion:

FactoryBot does support ActiveRecord callbacks, but its approach is designed to encourage explicitness and control in testing environments. This design choice aligns with broader testing principles in the Ruby on Rails community, emphasizing clear, isolated, and fast tests. Understanding these principles helps in effectively using FactoryBot and writing better tests for Rails applications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment