Rails FactoryGirl defining syntax - ruby-on-rails

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

Related

Ruby/Rails testing - access variable outside of scope?

I want to unit test a method with rspec for RoR and have a method like this:
def create_record(obj, params)
begin
obj.add_attributes(params)
result = obj.save
rescue
MyMailer.failed_upload(#other_var, obj.api_class_name, params).deliver_now
end
end
create_record is never invoked directly, but through another method which fills in #other_var appropriately.
How should I go about testing the code to make sure MyMailer is called correctly? Should I have passed #other_var into the method instead of relying on it being filled in elsewhere (aka: is this a code smell?)? Thanks!
In Ruby you can use Object#instance_variable_set to set any instance variable.
RSpec.describe Thing do
describe "#create_record" do
let(:thing) do
t = Thing.new
t.instance_variable_set(:#other_var, "foo")
t
end
# ...
end
end
This completely circumvents any encapsulation which means that the use of instance_variable_set can be considered a code smell.
Another alternative is to use RSpecs mocking and stubbing facilities but stubbing the actual object under test is also a code smell.
You can avoid this by passing the dependency as a parameter or by constructor injection:
class Thing
attr_accessor :other_var
def initialize(other_var: nil)
#other_var = other_var
end
def create_record(obj, attributes)
# ...
end
end
A good pattern for this is service objects.

Rails FactoryGirl instance variable

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

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?

Should I stub the model in Factory girl or in the spec file while testing?

Almost every spec file I come accross I end up writing stuff like:
before :each do
#cimg = Factory.build :cimg_valid
#cimg.stub(:validate_img).and_return true
#cimg.stub(:validate_img_url).and_return true
#cimg.stub(:save_images).and_return true
#cimg.stub(:process_image).and_return true
#cimg.stub(:img).and_return true
end
I mean, the model I get from Factory.build is completely valid. But if I don't stub that stuff it saves things in the filesystem, and validates stuff I'm not testing...
What I mean, I think it would be cleaner to do something like this:
before :each do
#cimg = Factory.build :cimg_for_testing_tags
end
If stubbing within the Factory is even possible.
What is the proper way to stub the model?
#fkreusch's answer works great until you use the new RSpec expect() syntax (3.0+)
Putting this into rails_helper.rb works for me:
FactoryBot::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
In the OP's example, you can now do:
FactoryBot.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after(:build) do |cimg|
allow(cimg).to receive(:validate_img) { true }
end
end
end
Credit: github.com/printercu, see: https://github.com/thoughtbot/factory_bot/issues/703#issuecomment-83960003
In recent versions of factory_girl you have an after_build callback, so I believe you could define your factory like this:
FactoryGirl.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after_build do |cimg|
cimg.stub(:validate_img).and_return true
end
end
end
UPDATE
After factory_girl 3.3.0, the syntax has changed to following:
FactoryGirl.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after(:build) do |cimg|
cimg.stub(:validate_img).and_return true
end
end
end
A factory should produce "real world" objects therefore it's a bad practice (and error prone) to change behaviour (i.e. stub) in a factory.
You can do
let(:user) instance_double(User, FactoryGirl.attributes_for(:user))
before do
allow(user).to receive(:something).and_return('something')
end
and if your before clause gets too big you may want to extract it to a separate method or create a mock child class that overrides methods you want to stub.
You might also consider using FactoryGirl#build_stubbed.

Can't understand Ruby's magic

In railscasts project you can see this code:
before(:each) do
login_as Factory(:user, :admin => true)
end
The corresponding definition for the function is:
Factory.define :user do |f|
f.sequence(:github_username) { |n| "foo#{n}" }
end
I can't understand how the admin parameter is passing to function, while in the function there's no word about admin parameter. Thanks
Factory.define is not a function definition, it is a method that takes a symbol or string (in this case user) and a block that defines the factory you are making. Factory(:user, :admin => true) makes a User object, with admin attributes. It is not calling the code in your second snippet, it is calling Factory() which initializes a factory, and selects one (in this case the one defined in second snippet). Then it passes options in hash form to Factory as well.
Factory selects the :user factory which is very generic. The option :admin=>true just tells Factory to set the admin instance variable on User to true.
This is actually what it is calling in factory.rb in factory girl
def initialize(name, options = {}) #:nodoc:
assert_valid_options(options)
#name = factory_name_for(name)
#options = options
#attributes = []
end
So Factory(name,options) is equivalent to Factory.new(name,options) in this code.
http://www.ruby-doc.org/core/classes/Kernel.html Notice Array and String etc have similar constructs. I am trying to figure out how they did that now.
This is all confusing even for decent Ruby programmers. I recommend strongly the book "Metaprogramming Ruby" It is probably the best book I have read in ruby and it tells you a lot about this magic stuff.
Michael Papile's response is essentially correct. However, I'd like to elaborate upon it a bit as there are some technical nuances that you might wish to be aware of. I looked over the code for railscasts and factory_girl and I believe there are a few extra pieces to the puzzle that explain how the :admin => true arg ends up creating the admin attribute of the user factory. The attribute addition does not actually happen by way of Factory's initialize() method, although as Michael pointed out that method is indeed being called in service of building the new user factory object.
I'm going to include in this explanation all the steps I took in case you'd like to see how to go about investigating similar questions you might have.
Since your original post is dated Feb. 17th I looked at the version of railscasts that closely matches that date.
I looked in its Gemfile:
https://github.com/ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/Gemfile
Line 18:
gem "factory_girl_rails"
I then checked out the commit of factory_girl_rails that most closely matched the Feb 17th date.
https://github.com/thoughtbot/factory_girl_rails/blob/544868740c3e26d8a5e8337940f9de4990b1cd0b/factory_girl_rails.gemspec
Line 16:
s.add_runtime_dependency('factory_girl', '~> 2.0.0.beta')
factory_girl version 2.0.0.beta was actually not so easy to find. There are no github tags with that name so I just checked out the closest in terms of commit date.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/vintage.rb
Lines 122-128:
# Shortcut for Factory.default_strategy.
#
# Example:
# Factory(:user, :name => 'Joe')
def Factory(name, attrs = {})
Factory.default_strategy(name, attrs)
end
So the Factory invocation in railscasts is actually calling a convenience method which invokes the "default strategy", which is located in the same file:
Lines 39-52:
# Executes the default strategy for the given factory. This is usually create,
# but it can be overridden for each factory.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# The result of the default strategy.
def self.default_strategy(name, overrides = {})
self.send(FactoryGirl.find(name).default_strategy, name, overrides)
end
Note that FactoryGirl.find is invoked to get the object on which to invoke default_strategy. The find method resolves to here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/registry.rb
Lines 12-14:
def find(name)
#items[name.to_sym] or raise ArgumentError.new("Not registered: #{name.to_s}")
end
Here the name is :user. Thus we wish to invoke default_strategy on the user factory. As Michael Papile pointed out, this user factory was defined and registered by the railscasts code that you originally thought was the class definition for Factory.
https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb
Lines 23-25:
Factory.define :user do |f|
f.sequence(:github_username) { |n| "foo#{n}" }
end
So in investigating what the default strategy is for the user factory, I looked around in the railscasts project and found this:
https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb
Lines 43-45:
def default_strategy #:nodoc:
#options[:default_strategy] || :create
end
:create is the default strategy. We go back to factory_girl to find the def for create.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/methods.rb
Lines 37-55:
# Generates, saves, and returns an instance from this factory. Attributes can
# be individually overridden by passing in a Hash of attribute => value
# pairs.
#
# Instances are saved using the +save!+ method, so ActiveRecord models will
# raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets.
#
# Arguments:
# * name: +Symbol+ or +String+
# The name of the factory that should be used.
# * overrides: +Hash+
# Attributes to overwrite for this instance.
#
# Returns: +Object+
# A saved instance of the class this factory generates, with generated
# attributes assigned.
def create(name, overrides = {})
FactoryGirl.find(name).run(Proxy::Create, overrides)
end
The create strategy calls the run method defined here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/factory.rb
Lines 86-97:
def run(proxy_class, overrides) #:nodoc:
proxy = proxy_class.new(build_class)
overrides = symbolize_keys(overrides)
overrides.each {|attr, val| proxy.set(attr, val) }
passed_keys = overrides.keys.collect {|k| FactoryGirl.aliases_for(k) }.flatten
#attributes.each do |attribute|
unless passed_keys.include?(attribute.name)
attribute.add_to(proxy)
end
end
proxy.result(#to_create_block)
end
A translation/summarization of what this code is doing:
First, the proxy object is built by calling new on the proxy_class, which in this case is Proxy::Create, which is defined here:
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/create.rb
Basically all you need to know is that proxy is building a new user factory object and invoking callbacks before and after the factory object is created.
Going back to the run method, we see that all the extra args that were originally passed into the Factory convenience method (in this case, :admin => true) are now being labelled as overrides. The proxy object then invokes a set method, passing in each attribute-name/value pair as args.
The set() method is part of the Build class, the parent class of Proxy.
https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/build.rb
Lines 12-14:
def set(attribute, value)
#instance.send(:"#{attribute}=", value)
end
Here #instance refers to the proxied object, the user factory object.
This then, is how :admin => true is set as an attribute on the user factory that the railscasts spec code creates.
If you want, you can google "programming design patterns" and read about the following patterns: Factory, Proxy, Builder, Strategy.
Michael Papile wrote:
http://www.ruby-doc.org/core/classes/Kernel.html Notice Array and
String etc have similar constructs. I am trying to figure out how they
did that now.
If you are still curious, the Array and String you see in the Kernel doc are actually just factory methods used to create new objects of those types. That's why no new method invocation is needed. They are not actually constructor calls per se, but they do allocate and initialize Array and String objects, and hence under the hood they are doing the equivalent of calling initialize() on objects of those types. (In C though, of course, not Ruby)
I don't think that second snippet is the definition for the function. Function definitions have def and end. I think that second snippet looks like a function or method being called with an argument of :user and a block that takes an f parameter.
Of course with metaprogramming you can never really be sure what the hell is going on.

Resources