Rails FactoryGirl instance variable - ruby-on-rails

I would like to create factory using local variable.
Currently I have the following factory:
FactoryGirl.define do
factory :offer_item, class: BackOffice::OfferItem do
service
variant
end
end
My expectation is to create something like below
FactoryGirl.define do
variant = FactroyGirl.create(:variant)
factory :offer_item, class: BackOffice::OfferItem do
service
variant { variant }
after(:create) do |offer_item|
offer_item.service.variants << variant
end
end
end
but then I get:
/.rvm/gems/ruby-2.2.3/gems/factory_girl-4.7.0/lib/factory_girl/registry.rb:24:in `find': Factory not registered: variant (ArgumentError)
All models are nested inside BackOffice module. Generally I want the same object has association with two other objects. I think there is a some problem with scope in my factory.
Variant factory is inside other separated file.

The issue is you are trying to create a factory before FactoryGirl has finished loading all of the factory definitions. This is because you defined the variable at the scope of a factory definition. Even if this did work, you would likely end up sharing the same variant record between multiple offer_items that you create in your test because this code would only get executed once during initialization.
Since you defined an association to Variant, you probably don't need to create it as an extra step. We can leverage FactoryGirl's ability to create our associations for us and then copy the object into your #service in your after(:create) callback. Maybe it would look something like this (untested):
FactoryGirl.define do
factory :offer_item, class: BackOffice::OfferItem do
service
variant
after(:create) do |offer_item|
offer_item.service.variants << offer_item.variant
end
end
end

Related

FactoryBot - How can I pass a transient array?

I currently have a FactoryBot trait set up as follows:
trait :with_role do
transient do
role { nil }
end
after(:create) do |staff_member, factory|
staff_member.staff_roles << StaffRole.fetch(factory.role) if factory.role
end
end
However, this trait only allows the factory to be passed a single role. I'm refactoring a series of tests wherein mutliple roles need to be assigned. This is a M-N relationship supported both by the DB and ORM via a junction table, and clearly functions as expected outside of FactoryBot because the original test implementation passes. My approach is as follows:
# Inside the factory
trait :with_roles do
transient do
roles { [] }
end
after(:create) do |staff_member, factory|
roles.each { |role| staff_member.staff_roles << StaffRole.fetch(factory.send(role)) }
end
end
# Invocation
staff_member = FactoryBot.create(:staff_member, :with_roles, roles: [
:role_1,
:role_2,
:role_3
], some_other_attribute: 1)
The problem is that when I try to invoke in this way, I get the following error:
NameError: undefined local variable or method `roles' for #<FactoryBot::SyntaxRunner:...>
I get the exact same error if I initialise roles as nil instead of an empty array. The previous implementation works fine, and I've followed it as closely as possible. Why is roles undefined despite being defined as a transient variable? Am I missing something about how FactoryBot works, and trying to do something impossible? Or am I just appproaching this wrong?
You should call a transient attribute on a factory:
after(:create) do |staff_member, factory|
factory.roles.each { |role| staff_member.staff_roles << StaffRole.fetch(factory.send(role)) }
end

How to make FactoryBot return the right STI sub class?

I'm making a big change in my system, so I changed one of my main tables into a STI, and create subclasses to implement the specific behavior.
class MainProcess < ApplicationRecord
end
class ProcessA < MainProcess
end
class ProcessB < MainProcess
end
In the application code, if I run MainProcess.new(type: 'ProcessA') it will return a ProcessA as I want.
But in the Rspec tests when I run FactoryBot::create(:main_process, type: 'ProcessA') it is returning a MainProcess and breaking my tests.
My factor is something like this
FactoryBot.define do
factory :main_process do
foo { 'bar' }
end
factory :process_a, parent: :main_process, class: 'ProcessA' do
end
factory :process_b, parent: :main_process, class: 'ProcessB' do
end
end
Is there some way to make FactoryBot have the same behavior of normal program?
I found the solution
FactoryBot.define do
factory :main_process do
initialize_with do
klass = type.constantize
klass.new(attributes)
end
end
...
end
The answer was founded here http://indigolain.hatenablog.com/entry/defining-factory-for-sti-defined-model (in japanese)
Edit #1:
⚠⚠⚠ Important ⚠⚠⚠
As mentioned here initialize_with is part of a private FactoryBot API.
According to the documentation:
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
So avoid to use if you can. (although I didn't find any other way to achieve this result without use it)
Edit #2
Besides the warning in the gem documentation (described above), the GETTING_STARTED.md actually suggest you use it
If you want to use factory_bot to construct an object where some attributes are passed to initialize or if you want to do something other than simply calling new on your build class, you can override the default behavior by defining initialize_with on your factory
If you just modify your original code to specify the class as the class type instead of a string, it works:
FactoryBot.define do
factory :main_process do
foo { 'bar' }
end
factory :process_a, parent: :main_process, class: ProcessA do
end
factory :process_b, parent: :main_process, class: ProcessB do
end
end
Here's the relevant section of the FactoryBot documentation.
initialize_with is marked as part of FactoryBot's Private API and not recommended for external use.
I think you can use nested factories to accomplish this.
factory :process do
factory :type_a_process, class: Process::TypeA do
type {"Process::TypeA"}
end
factory :type_b_process, class: Process::TypeB do
type {"Process::TypeB"}
end
end
end
FactoryBot.create(:type_b_process)
This is better:
initialize_with { type.present? ? type.constantize.new : Invoice.new }
https://dev.to/epigene/simple-trick-to-make-factorybot-work-with-sti-j09

FactoryGirl factory for namespaced class

Any links to documentation proving or disproving my thoughts here would be very appreciated; I can't seem to find any.
AFAIK, if you had a Rails application with a Product model, you could define a FactoryGirl factory as
FactoryGirl.define do
factory :product do
# stuffs
end
end
and then call your factory in tests with (RSpec example)
let(:product) { FactoryGirl.create(:product) }
but you may also call it with
let(:product) { FactoryGirl.create(Product) }
This is helpful if you're wanting to keep your model tests a bit more dynamic and free to change with RSpec's described_class helper.
My problem:
I've got a model that happens to be namespaced
class Namespace::MyModel < ActiveRecord::Base
# model stuffs
end
with a factory
FactoryGirl.define do
factory :my_model, class: Namespace::MyModel do
# factory stuffs
end
end
and when attempting to use RSpec's helpers...
RSpec.describe Namespace::MyModel do
let(:my_object) { FactoryGirl.create(described_class) }
# testing stuffs
end
FactoryGirl complains of a missing factory
Factory not registered: Namespace::MyModel
Am I missing this feature of FactoryGirl, without understanding its true purpose? Or is there another way I can define my factory to resolve correctly?
Why don't you try
RSpec.describe Namespace::MyModel do
let(:my_object) { FactoryGirl.create(:my_factory) }
# testing stuffs
end
FactoryGirl is usually used by factory name, but not class name, that is defines.
You can have a multiple factories, that define instances of the same class. The difference between them can be in fields values, for example.
Or you can dynamicly get factory name, from described_class name.
It is already answered at How do you find the namespace/module name programmatically in Ruby on Rails?

Rails + FactoryGirl: What is the "evaluator" in after(:create) do |something, evaluator|?

I've been using FactoryGirl for a while now, but still don't know what evaluator means in the after create hook:
factory :products_color_with_variants do
after(:create) do |pc, evaluator|
pc.variants << FactoryGirl.create(:variant)
end
end
I've only been using the first argument to the block, which is always just the object that was created. What does evaluator do and what can I use it for?
This can help you. You can access transient attributes from it

Rails FactoryGirl defining syntax

While using factory girl gem, we create a factories.rb file with the syntax as
FactoryGirl.define do
factory :model do
...
end
...
end
So what exactly does FactoryGirl.define syntax means ?
Is it similar to
class FactoryGirl
def factory :model do
end
end
Thanks
FactoryGirl, like many Ruby gems, defines a "domain specific language" or DSL for the purpose of simplifying configuration. This is a common pattern.
Your example looks like this:
FactoryGirl.define do
factory :model do
...
end
...
end
What's happening is the factory method is being called with the argument :model which is additionally passed a block. As always, the method in question is free to decide what to do with the block. In this case it is saved and executed later during the factory generation process.
Your re-interpretation of it doesn't make any sense as that's not valid Ruby. You cannot have a symbol as an argument specifier. Remember that factory is a pre-existing method, not one you're defining at that point.
If this is all a bit hazy you'll need to experiment with blocks more to see how they work within Ruby. They're used for a number of things so you need to understand how each sets expectations on what the block can do, what it should return, and how many times it will be called, if at all.
In ruby, anything with do end is a block and all blocks are attached to a method.
So in your example, FactoryGirl.define is a method call with a block as a parameter. factory :model is also a method call with a block as a parameter, but in this case the :model is also a parameter passed in.
You can think of the methods being defined in FactoryGirl conceptually like this:
class FactoryGirl
def self.define
yield # do something w/ the block passed in
...
end
def factory(model_name, &block)
block.call # do something w/ the block passed in
...
end
end

Resources