How to skip model's method(after_save method) when creating object with Factory Girl - ruby

I have the model A that has many model B, in model B has its method to do something after it is saved. How to skip it?

You should be able to skip a callback using factory_girl's after(:build) callback.
FactoryGirl.define do
factory :model_b do
after(:build) {|model_b| model_b.class.skip_callback(:save, :after, :your_callback)}
end
end
The factory_girl wiki has a great example file showing how to use some of the options the library provides.

Related

Can an Abstract Factory be responsible for "creating or finding an existing" item?

My Ruby code has a Concrete Factory, which builds some complex objects:
author = Author::Factory.build(email: "john#example.com")
class Author
class Factory < BaseFactory
def self.build(email: nil)
# ... Some data preparation and defaults
Author.new(
email: email
# Map and assign more attributes
)
end
end
end
Now, I've run into a situation where I either need to build a new one,
or assign one from an existing collection. In
database-terms: an UPSERT, or in ActiveRecord: find_or_create_by.
And I am not sure if this:
Is a proper task for an Abstract Factory and
If the proper way to implement this is by passing the collection, or
to make the Factory itself responsible for fetching it.
Passing it in:
author = Author::Factory.build(email: "john#example.com", existing: authors)
class Author
class Factory < BaseFactory
def self.build(email: nil)
author = existing.find {|author| author.email == email }
# If not found, prepare and build a new one, like above.
end
end
end
Letting the Factory find it:
author = Author::Factory.build(email: "john#example.com")
class Author
class Factory < BaseFactory
def self.build(email: nil)
author = Author.find_in_existing_with(email: email)
# If not found, prepare and build a new one, like above.
end
end
end
So: Should a Factory every be responsible for finding-or-building?
And if so, must the Factory be responsible for fetching the items that
it must match against, or should the caller pass them along?
Factory is a creational pattern, so clients will expect fresh new instances out of it.
Sure, what the Factory does internally is of no concern to consuming code. But if Author is a domain entity, I fail to see how an Author-building object could be used by consumers for anything else than the "real world" addition of a new author in the system.
Unless you want be semantically unfair and trick callers by reusing existing authors instead of instantiating new ones. But that doesn't look like something you would typically do in production.

Defining factories with chained associations

I want to create a :membership factory and then create a :comment factory that in this specific case "rolls up" to the same Group that the Membership does. It shouldn't always point to the same Group, so I'm defining my factories like this:
factory :membership do
user
group
end
factory :decision do
group
end
factory :comment do
decision
end
And then I'm creating those two objects like this:
membership = create(:membership)
decision = create(:decision, group: membership.group)
comment = create(:comment, decision: decision)
This works, but it's a minimal example. I'd like to be able to create the Membership and then pass the Membership as an argument to the Comment constructor, making the second line unnecessary. I've had a look at the factory_girl docs and I can't figure out how to change my factory definitions to do this. Is there a way?
Pass the Membership to the Comment factory in a transient attribute. In a before(:create) callback, create a Decision from the Membership and add the Decision to the Comment:
factory :comment do
transient do
membership
end
before(:create) do |comment, evaluator|
decision = create(:decision, group: evaluator.membership.group)
comment.decision = decision
end
end

Have a token/unique url in order to destroy resource

I would like to add following functionality to one of my models:
Once it's created, a token of some sort will be created and this token allows one to destroy the object e.g. http://localhost:3000/items/7AEaC6Nhq946.
Is there a gem or similiar that offers this functionality already?
You could make a 'Tokenable' concern and include it in the models you want to:
In app/models/concerns/tokenable.rb
module Tokenable
extend ActiveSupport::Concern
included do
before_create :generate_token
end
protected
def generate_token
self.random_token = loop do
random_token = SecureRandom.urlsafe_base64(nil, false)
break random_token unless self.class.exists?(random_token: random_token)
end
end
end
In your model:
include Tokenable
Be sure to add the random_token column in the database for the model where you include the concern.
Now in your controller you would do something like Item.find_by(random_token: params[:random_token]) and perform the actions you wish to do with the object.

Sinatra, ActiveRecord, FactoryGirl, and has_many_through associations

I am building a Sinatra API. My models use ActiveRecord and have a many-to-many model relationship.
class Workout < ActiveRecord::Base
has_many :workouts_exercises, dependent: :destroy
has_many :exercises, through: :workouts_exercises
end
class Exercise < ActiveRecord::Base
has_many :workouts_exercises
has_many :workouts, through: :workouts_exercises
end
class WorkoutsExercises < ActiveRecord::Base
belongs_to :workouts
belongs_to :exercises
end
I am trying to set up FactoryGirl to use these associations. Here is what I've got from reading all the docs I have found so far.
FactoryGirl.define do
factory :workout do
name 'Default Workout Factory Name'
description 'Default Workout Factory Description'
factory :workout_with_exercises do
after(:create) do |workout|
workout.exercises << FactoryGirl.create(:exercise)
end
end
end
end
FactoryGirl.define do
factory :exercise do
name 'Default Exercise Factory Name'
description 'Default Exercise Factory Description'
end
end
FactoryGirl.define do
factory :workouts_exercises do
workout
exercise
end
end
Here is my test that I would like to run
it 'returns the associated exercises' do
workout = FactoryGirl.create(:workout_with_exercises)
associated_exercises = workout.exercises
expect(associated_exercises.count).to eq(1)
end
However, when I run the specs I receive
1) Workout returns the associated exercises
Failure/Error: workout.exercises << FactoryGirl.create(:exercise)
NameError:
uninitialized constant Workout::WorkoutsExercise
The first method of debugging I tried was to pry before workout= declaration. Workout doesn't know about the exercises attribute. According to the ActiveRecord documentation, setting up a many-to-many association should provide you with the 16 #collection methods. This would mean that #exercises should return all the associated Exercise objects for the Workout object.
I can't, for the life of me, figure out whether or not it's my AR associations in the models that are to blame - or the FactoryGirl configuration I have. I've checked the SQL tables and all of the columns seem to be appropriate. I could really use some help figuring out what the issue is.
I've tried a few other FactoryGirl after_create hooks, using the shovel operator and attempting to declare the workouts_exercises association explicitly:
workout.exercises << [
create(:exercise, name: 'Bench Press', workouts_exercises: workout),
create(:exercise, name: 'Pushups', workouts_exercises: workout),
create(:exercise, name: 'DB Flys', workouts_exercises: workout)
]
Again, failure.
My third attempt was to use the FactoryGirl callback methods from ThoughtBot - ain't no calla back girl.
factory :workout_with_exercises do
after(:create) do |workout|
FactoryGirl.create(:exercise, workout: workout)
end
end
This results in
undefined method `exercise=' for #<Workout:0x007ff6250c2768>
Which makes me believe the AR associations aren't written correctly.
Thanks in advance for any help you can lend!
I use Sinatra, ActiveRecord, and RSpec. Just recently, I added FactoryGirl to the mix.
In your test framework's initialization point (I use RSpec, which is spec_helper.rb), you'll need to require the file that contains your model classes.
Next, do your model classes live within a module?
My model classes don't exist at the top-level, because I instead put them in a module for better organization within the project.
With this pattern, I found that I needed to explicitly define the module + class path for FactoryGirl.
The following would not work:
FactoryGirl.define do
factory :vendor do
name 'Test Vendor
end
end
But things began to work after I told FactoryGirl the full class name, like below:
FactoryGirl.define do
factory :vendor, class: MyAppName::Models::Vendor do
name 'Test Vendor
end
end

setting instance variables in factorygirl

Say I have a model like
class Vehicle < ActiveRecore::Base
after_initialize :set_ivars
def set_ivars
#my_ivar = true
end
end
and somewhere else in my code I do something like
#vehicle.instance_variable_set(:#my_ivar, false)
and then use this ivar to determine what validations get run.
How do I pass this Ivar into FactoryGirl?
FactoryGirl.define do
factory :vehicle do
association1
association2
end
end
How do I encode an ivar_set into the above, after create, before save?
How do I pass it into a FactoryGirl.create()?
FactoryGirl.define do
factory :vehicle do
association1
association2
ignore do
my_ivar true
end
after(:build) do |model, evaluator|
model.instance_variable_set(:#my_ivar, evaluator.my_ivar)
end
end
end
FactoryGirl.create(:vehicle).my_ivar #=> true
FactoryGirl.create(:vehicle, my_ivar: false).my_ivar #=> false
A bit late answer, nonetheless I had the need to setup an instance variable on a model. And since the above answer didn't work for the latest version of factory bot I did a bit of research and found out that the following approach works for me:
FactoryGirl.define do
factory :vehicle do
association1
association2
end
transient do
my_ivar { true }
end
after(:build) do |model, evaluator|
model.instance_variable_set(:#my_ivar, evaluator.my_ivar)
end
end
It's almost identical to the above answer but instead of ignore it uses transient keyword, I assume this is an in-place replacement for ignore.
What it does is that it allows to define a variable you can pass on to the factory but that doesn't end up being set on the resulting object. That in turn gives you an opportunity to do logic based upon it. Like we do in this example (albeit a simple one) where we set an instance variable based on the provided transient variable.
Note that the transient variable is set and available in the evaluator variable.
References:
Transient Attributes - Factory bot documentation

Resources