Factory referring to association in after create - ruby-on-rails

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

Related

FactoryGirl::AttributeDefinitionError: Attribute already defined: user

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.
)

FactoryGirl How to add several objects with different roles

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

Improving factories of associated models

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.

How do I create an admin role with Factory girl?

Ok so i want to test out features that need an admin user and i am trying to login as admin but in order to do that i need to have an admin user
here is my code
let(:user) { FactoryGirl.create(:user) }
let(:admin_role) { FactoryGirl.create(:role) }
FactoryGirl.define do
factory :user do
first_name "John"
last_name "Doe"
email "john#doe.com"
end
factory :role do
name "Admin"
end
end
how do i connect them I tried user.roles << user_role but got this error
/Users/matt/Sites/application/spec/controllers/directory_controller_spec.rb:16:in `block (3 levels) in <top (required)>': undefined local variable or method `user' for #<Class:0x007fa550890d80> (NameError)
Here is my models
class User < ActiveRecord::Base
has_many :roles, :through => :role_users
has_many :role_users
...
class Role < ActiveRecord::Base
has_many :users, :through => :role_users
has_many :role_users
...
class RoleUser < ActiveRecord::Base
belongs_to :role
belongs_to :user
end
spec/factories/user.rb
FactoryGirl.define do
factory :user do
first_name "John"
last_name "Doe"
email "john#doe.com"
end
factory :admin_user, :parent => :user do
roles { [ FactoryGirl.create(:admin_role) ] }
end
factory :role do
name { "Role_#{rand(9999)}" }
end
factory :admin_role, :parent => :role do
name "Admin"
end
end
Solved
Factory.sequence(:email) {|n| "person#{n}#example.com" }
Factory.define :role do |r|
r.name {"Admin"}
end
Factory.define :role_user do |ru|
ru.association(:role)
ru.association(:user)
end
Factory.define :user do |u|
u.first_name {"matt"}
u.last_name {"jones"}
u.email {Factory.next(:email)}
end
and using it like
let(:role_user) { FactoryGirl.create(:role_user) }
before { sign_in(role_user) }
I was having the exact same problem, with code like:
describe "..." do
let(:user) { create :user }
let(:manager) { create :manager_role }
user.roles << manager
it "should ...." do
# etc.
end
end
# run rspec...
# => `block (2 levels) in <top (required)>': undefined local variable or method `user' for #<Class:0x000001016a2dc0> (NameError)
When I moved the user.roles << manager code into a before(:each) block, the errors disappeared. Thus, something like:
describe "..." do
let(:user) { create :user }
let(:manager) { create :manager_role }
before(:each) do
user.roles << manager
end
it "should ...." do
# etc.
end
end
This saves the pain of having to specify the role <-> user association in your factories.
I'm not familiar enough with how rspec and factory_girl work to understand why you can't have code like user.roles << role immediately after calling let(:user) { create :user }, but putting it into a before(:each) block or into an individual it "should ... " do block should do the trick.

Factory Girl: How to associate a record to another record without creating a new record?

I'm using Factory Girl/Rspec2/Rails 3.
In factories.rb, I have:
Factory.define :user do |user|
user.name 'Some guy'
user.email 'some_guy#somewhere.org'
user.password 'password'
end
Factory.define :org_admin, :parent => :user do |user|
user.email 'org_admin#somehwere.org'
end
Factory.define :user_with_membership_request, :parent => :user do |user|
user.email 'user_with_membership_request#somehwere.org'
end
Factory.define :organization do |org|
org.name 'MEC'
org.description 'Mountain Equipment Co-op'
end
Factory.define :membership do |membership|
membership.user { Factory(:user) }
membership.organization { Factory(:organization) }
end
Factory.define :admin_membership, :parent => :membership do |membership|
membership.user { Factory(:org_admin) }
membership.is_admin true
membership.status 'active'
end
Factory.define :membership_request, :parent => :membership do |membership|
membership.user { Factory(:user_with_membership_request) }
membership.status 'requested'
end
and then in my rspec test I have:
it 'should accept the membership request' do
#org_admin = Factory(:org_admin)
test_sign_in(#org_admin)
#organization = Factory(:organization)
#membership_request = Factory(:membership_request)
put :update, :organization_id => #organization.id, :id => #membership_request.id, :acceptance => 'approve'
...
end
When I run the test, I get:
Failure/Error: #membership_request = Factory(:membership_request)
Validation failed: Name has already been taken
I understand the reason for the failure is because FactoryGirl is creating another organization (with the same name).
But what I'd like to do is create several memberships all associated with the same organization. How do I do that?
Thanks.
Sean
You could check for an existing organization and use it, or create one if none exists:
Factory.define :membership do |membership|
membership.user { Factory(:user) }
membership.organization { Organization.first || Factory(:organization) }
end
FactoryGirl 4+ update:
Factory.define do
factory :membership do
user { create(:user) }
organization { Organization.first || create(:organization) }
end
end
Another approach is to use unique identifiers (e.g.: names) for each factory that you want to reuse, then use initialize_with to generate it:
factory :organization_1 do
ignore { organization_name 'Sample Organization 1' }
name { organization_name }
initialize_with { Organization.find_or_create_by_name(organization_name) }
end
Now any reference to :organization_1 will always retrieve the same Organization. Obviously, you must use distinct names for this to work.
There are two things.
1. You might still want to create unique names for Factory(:organisation) you can achieve that using Factory.sequence which will generate it uniquely for you.
2. You can pass in a Factory(:membership_request, :organization => #organization) to use the existing object instead of creating a new one.
With mongoid you can take combine the use of the #find_or_create_by method with Factory.attributes_for and do something like this
factory :membership do
organization { Organization.find_or_create_by(Factory.attributes_for(:organization))}
end
I'm sure ActiveRecord has something similar.

Resources