I have three models : User, Product and Ownership. Ownership belongs to User and Product. Product and User have many Ownerships.
I created the following factories with the FactoryGirl gem :
factory :user do
sequence(:name) { |n| "Robot #{n}" }
sequence(:email) { |n| "numero#{n}#robots.com"}
association :location, factory: :location
end
factory :product do
sequence(:name) { |n| "Objet #{n}" }
association :location, factory: :location
end
factory :ownership do
association :user, factory: :user
association :product, factory: :product
end
And I use it like that :
let(:user) { FactoryGirl.create(:user) }
let(:product) { FactoryGirl.create(:product) }
let(:ownership) { FactoryGirl.create(:current_ownership, user: user, product: product) }
But I want to improve my factories, in order to do this :
let(:user) { FactoryGirl.create(:user) }
let(:product) { FactoryGirl.create(:product, owner: user) }
Do you have any idea how to do that ?
You can do this using the factory girl after_create callback:
FactoryGirl.define do
factory :product do
# ...
ignore do
owner nil
end
after :create do |product, ev|
if ev.owner
create :ownership, user: ev.owner, product: product
end
end
end
end
This way you configure factory so, that you can pass owner attribute into it. The ignore block ensures this attribute won't be passed to the object's constructor, but you can use it in the factory girl's callbacks.
You can find more information on factory girl callbacks here.
Related
I have a factory for an account that has a relationship with a product. When adding an after create callback it is referring to the product and not the account.
account factory
FactoryBot.define do
factory :account do
billing_method { 'monthly' }
workflow_state { 'active' }
end
after :create, &:create_api_credential
end
product factory
FactoryBot.define do
factory :product do
...
account
end
end
test
subject { create(:product) }
it 'has an account' do
subject
assert_not_nil(subject.account)
end
the error
NoMethodError: undefined method `create_api_credential' for #<Product:0x00007fab7cefe2f8>
create_api_credential exist on account not product
Did you try to move:
after :create, &:create_api_credential to :account block?
factory :account do
billing_method { 'monthly' }
workflow_state { 'active' }
after :create, &:create_api_credential
end
In my application an account can have a single owner (user) and multiple users.
In my tests I do this:
# account_factory_static.rb
FactoryGirl.define do
factory :account do
name 'demoaccount'
association :owner, :factory => :user
end
end
# user_factory_static.rb
FactoryGirl.define do
factory :user do
email 'demo#example.com'
first_name 'Jon'
last_name 'Doe'
password 'password'
end
end
and use them like below:
let(:account) { FactoryGirl.create(:account) }
The problem is that right nowaccount.users.count equals 0 because I have no way to do something like #account.users << #account.owner like I do in my controllers when a user signs up.
The question is how can I add the associated account's id to the account_id attribute of the user in FactoryGirl?
In other words how do you do it in FactoryGirl?
Thanks.
You can use after :create block for it:
FactoryGirl.define do
factory :account do
name 'demoaccount'
association :owner, :factory => :user
after :create do |account|
account.users << account.owner
end
end
end
I created factory client and contract. I run test, but display error
FactoryGirl.define do
factory :client, class: User do
role 'client'
first_name 'John'
sequence(:last_name) { |n| "client#{n}" }
sequence(:email) { |n| "client#{n}#example.com" }
# avatar { Rack::Test::UploadedFile.new(File.join(Rails.root, 'public', 'images', '128.jpg')) }
password 'password'
password_confirmation 'password'
end
end
support/controller_macros.rb
module ControllerMacros
def login_client
before do
#client = create(:client)
##request.env['devise.mapping'] = Devise.mappings[:client]
sign_in #client
end
end
end
FactoryGirl.define do
factory :contract do
sequence(:title) { |n| "translation#{n}" }
amount 150
additional_information 'X' * 500
due_date { 21.days.from_now }
association :user, factory: :client
association :user, factory: :contractor
end
end
I run test
rspec spec/controllers/contracts_controller_spec.rb
require 'rails_helper'
describe ContractsController do
login_client
let(:contract) { create(:contract) }
describe 'POST #create' do
context 'with valid attributes' do
it 'redirects to payment page' do
post :create, contract: attributes_for(:contract)
expect(response).to redirect_to payment_new_path
end
end
end
end
Error display:
Failure/Error: post :create, contract: attributes_for(:contract)
FactoryGirl::AttributeDefinitionError:
Attribute already defined: user
What is wrong in factory or test?
Factory :contract defines two attributes named user, which isn't allowed.
Give them unique (within the factory) labels, e.g.:
FactoryGirl.define do
factory :contract do
sequence(:title) { |n| "translation#{n}" }
amount 150
additional_information 'X' * 500
due_date { 21.days.from_now }
association :client, factory: :client
association :contractor, factory: :contractor
end
end
As they seem fitting, I've chosen attribute names corresponding with the factory names. This allows to even shorten this, by leaving out the factory name:
FactoryGirl.define do
factory :contract do
sequence(:title) { |n| "translation#{n}" }
amount 150
additional_information 'X' * 500
due_date { 21.days.from_now }
client
contractor
end
end
(See http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md, section "Associations":
If the factory name is the same as the association name, the factory name can be left out.
)
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.has_role? :student
can :create, Atendimento
end
if user.has_role? :professor
can :create, Atendimento
end
if user.has_role? :administrative
can [:read, :create], [Atendimento]
can [:edit, :update], Atendimento
can :manage, [Type, Place]
end
if user.has_role? :admin
can :manage, :all
end
end
end
and my factory
FactoryGirl.define do
factory :user do |f|
f.name "Alessandro"
f.username "alessandrocb"
f.matricula "123456789"
f.password "123456789"
f.password_confirmation "123456789"
f.after(:create) {|user| user.add_role(:student)}
end
I need those mocks receive all roles , but now I can only student role
my test with rspec
subject(:ability){ Ability.new(user) }
let(:user){ nil }
context "when is an User" do
let(:user) { FactoryGirl.create(:user) }
what is happening is this: I can only test with rspec with only 1 paper, but would like to test with all the cancan, I need to create the factory with all these possibilities for different roles
First solution
FactoryGirl.define do
factory :user do
name "Alessandro"
username "alessandrocb"
(...)
trait :student do
after(:create) {|user| user.add_role(:student)}
end
trait :professor do
after(:create) {|user| user.add_role(:professor)}
end
trait :administrative do
after(:create) {|user| user.add_role(:administrative)}
end
trait :admin do
after(:create) {|user| user.add_role(:admin)}
end
end
end
You can then use and combine these traits like this:
# Create a student
create :user, :student
# Create a user who is both professor and admin
create :user, :professor, :admin
Second solution
FactoryGirl.define do
factory :user do
name "Alessandro"
username "alessandrocb"
(...)
ignore do
role
end
after(:create) do |user, params|
user.add_role(params.role) if params.role
end
end
end
And then:
# Create a student
create :user, role: :student
Note that the second solution does not allow you to combine roles as it is. But you could use an array to achieve this.
I recently ran into a similar issue. Here's my users factory:
FactoryGirl.define do
sequence :email do |n|
"user#{n}#example.com"
end
factory :user do
email
password 'password'
factory :admin_user do
role 'administrator'
end
factory :support_user do
role 'support'
end
factory :editor_user do
role 'editor'
end
factory :sales_user do
role 'sales'
end
factory :author_user do
role 'author'
end
factory :guest_user do
role 'guest'
end
end
end
From there I can just call the relevant factory for a spec:
create(:editor_user)
Or, depending on your User model and it's attendant properties, you could also build factories like:
create(:user, role: 'guest') # my User model has a properly called 'role'
I have 3 different users in my project: default, merchant, admin.
I have one file that handles the conditions. Note: this is FactoryBot and specifically factory bot rails. I am also using the gem Faker.
edit: the numbered roles are using enum, which converts the number in a string according to an array I defined. More on enums: https://naturaily.com/blog/ruby-on-rails-enum
factories/user.rb
// factories/user.rb
FactoryBot.define do
factory :user do
name { Faker::Name.first_name }
street_address { Faker::Address.street_address }
city { Faker::Address.city }
state { Faker::Address.state }
zip { Faker::Address.zip }
email { Faker::Internet.email }
password { Faker::Internet.password }
trait :default_user do
role { 0 }
end
trait :admin_user do
role { 1 }
end
trait :merchant_user do
role { 2 }
end
end
end
spec file
// a spec file
RSpec.describe 'User logging in' do
let(:user) { create(:user, :default_user) }
let(:admin) { create(:user, :admin_user) }
[...]
end
I want create a factory for a Relationship model which contains two attributes followed_id and follower_id but i have no idea how to do this, this is my factories file :
FactoryGirl.define do
factory :user do
sequence(:name) { |n| "Person #{n}" }
sequence(:email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
end
factory :relationship do
# i need something like this
# followed_id a_user.id
# follower_id another_user.id
end
end
update
what i want to do with this relationship factory is to test that if i destroy a user, all his relationships will be destroyed too, this is my test :
describe "relationships associations" do
let!(:relationship) { FactoryGirl.create(:relationship) }
it "should destroy associated relationships" do
relationships = #user.relationships.to_a
#user.destroy
expect(relationships).not_to be_empty
relationships.each do |relationship|
expect(Relationships.where(id: relationship.id)).to be_empty
end
end
end
In my experience such "relationship" factory is rarely needed in test. Instead, "user_with_followers" and "user_following_some_ones" are often used.
factory :user do
sequence(:name) { |n| "Person #{n}" }
sequence(:email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
factory :user_with_followers do
ignore do
followers_count 5
end
after_create do |user, evaluator|
followers = FactoryGirl.create_list(:user, evaluator.followers_count)
followers.each do |follower|
follower.follow(user) # Suppose you have a "follow()" method in User
end
end
factory :user_following_some_ones do
# Do the similar
end
end
# Use
FactoryGirl.create :user_with_followers
use association
factory :relationship do |r| # 'r' is how you call relationship in the block
...
r.association :followed #relationship is associated with followed user
#(i'm not sure how your application is set up,
#so you'll have to do this as best makes sense.
#is followed an attribute of user?
#then it would look like `r.association :user`
f.association :follower #same here
end
In the more recent versions of FactoryGirl, you should be able to do this:
factory :relationship do
association :followed, :factory => :user
association :follower, :factory => :user
end
What each of those two association lines does is set up a user instance (using your :user factory), and then assign to followed or follower of the current relationship instance.
Note that you need to specify the factory unless the association name and factory name are the same.
Update:
When creating the Relationship, specify :followed or :follower (whichever is applicable to you). Otherwise, it creates new user records for each of those and uses them.
FactoryGirl.create(:relationship, :followed => #user)