Factory will create one object with a role, but not two - ruby-on-rails

I am using rolify with a User model and a Task model (Rails 4). One of the roles a user can have is "owner." I want to use Factory Girl to create a user object and assign it a role. Here is my factory:
FactoryGirl.define do
factory :task do
owner "Steve"
agency "an agency"
facility "a facility"
description "This task must absolutely be done"
due_date "2013-12-22 03:57:37"
completed_date "2013-12-22 03:57:37"
factory :task_with_owner do
ignore do
user_id nil
end
after(:create) do |task, user_id|
User.find(user_id).add_role :owner, task
end
end
end
end
This spec passes:
it 'is capable of creating a valid object with owner' do
#user = create(:user)
task = create(:task_with_owner, user_id: #user.id)
expect(#user.has_role? :owner, task).to be_true
end
This spec fails:
it 'is capable of creating two valid objects with an owner' do
#user = create(:user, username: 'janeblow')
task = create(:task_with_owner, user_id: #user.id)
expect(#user.has_role? :owner, task).to be_true
task = create(:task_with_owner, user_id: #user.id)
expect(#user.has_role? :owner, task).to be_true
end
The error is:
Failure/Error: task = create(:task_with_owner, user_id: #user.id)
ActiveRecord::RecordNotFound:
Couldn't find User with id=#<#<Class:0x000000050f5e10>:0x00000004c9ed08>
# ./spec/factories/tasks.rb:19:in `block (4 levels) in <top (required)>'
# ./spec/models/role_spec.rb:15:in `block (2 levels) in <top (required)>'
Why?

Your after(:create) block looks a little wrong. Try changing it to the following:
after(:create) do |task, vars|
User.find(vars.user_id).add_role :owner, task
end
Then re-run your failing spec.
Because you told the factory to ignore the user_id being passed in and to use nil instead, in your after(:create) block you have to access it from the passed in attributes (in the second block argument, vars in this case). You were almost there, but were passing the object factory_girl uses to hold the attributes rather than the attribute itself from within that object.
See the Transient Attributes section here for another example - https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md

Related

FactoryGirl undefined method `create='

I'm basically getting an error when I'm trying to use a factory I created for a Post model in Ruby on Rails.
Here's the full error:
Failure/Error: post = create(:post)
NoMethodError:
undefined method `create=' for #<Post:0x007fbf1be6e510>
Did you mean? created_at=
# ./spec/models/post_spec.rb:6:in `block (2 levels) in <top (required)>'
Here's the file for the factories:
spec/factories/post.rb
FactoryGirl.define do
factory :post do
title "Hello"
content "Hello, my name is Jacob."
user create(:user)
user_id 1
end
end
spec/models/post_spec.rb
require 'rails_helper'
require 'spec_helper'
describe Post do
it "has a valid factory" do
post = create(:post)
expect(post).to(be_valid)
end
end
I do have a spec/support/factory_girl.rb file which includes the FactoryGirl::Syntax::Methods. This file is loaded by spec/rails_helper.rb.
Also, the create(:user) line works and I'm able to use the user factory in the rails console, but not the post factory.
Any help would be fantastic. Thank you!
In your post factory, you have the syntax wrong for defining an associated record. Try defining it like this:
FactoryGirl.define do
factory :post do
title "Hello"
content "Hello, my name is Jacob."
user
end
end
You just need to state that the post has a user, and you certainly shouldn't be setting them all to have a specific user_id ... since each post will create a new user unless told otherwise and you have no idea what user_id will be generated.

rspec testing subclass sti methods rails 4

I am trying to build an rspec test for a method on an sti subclass and the test only reads the parent model's method. The method works in the app, just not in the rspec test. I can't figure out what I'm missing
models/animals/animal.rb
class Animal < ActiveRecord::Base
def favorite
"unicorn"
end
end
models/animals/mammal_animal.rb
class MammalAnimal < Animal
def favorite
"whale"
end
end
models/animals/cat_mammal_animal.rb
class CatMammalAnimal < MammalAnimal
def favorite
"tabby"
end
end
mammal_animal_spec.rb
require 'rails_helper'
RSpec.describe MammalAnimal, type: :model do
let(:cat_mammal_animal) {FactoryGirl.create(:cat_factory)}
subject(:model) { cat_mammal_animal }
let(:described_class){"MammalAnimal"}
describe "a Cat" do
it "should initialize successfully as an instance of the described class" do
expect(subject).to be_a_kind_of described_class
end
it "should have attribute type" do
expect(subject).to have_attribute :type
end
it "has a valid factory" do
expect(cat_mammal_animal).to be_valid
end
describe ".favorite " do
it 'shows the favorite Cat' do
expect(cat_mammal_animal.type).to eq("CatMammalAnimal")
expect(cat_mammal_animal.favorite).to include("tabby")
expect(cat_mammal_animal.favorite).not_to include("whale")
expect(cat_mammal_animal.favorite).not_to include("unicorn")
print cat_mammal_animal.favorite
end
end
end
end
error
Failures:
1) MammalAnimal.favorite and .favorite shows the favorite Cat
Failure/Error: expect(cat_mammal_animal.type).to include("tabby")
expected "unicorn" to include "tabby"
# ./spec/models/mammal_animal_spec.rb:82:in `block (3 levels) in <top (required)>'
UPDATE
animals.rb
FactoryGirl.define do
factory :animal do
type 'Animal'
name "dragon"
trait :mammal do
type 'MammalAnimal'
name "zebra"
end
trait :cat do
type 'CatMammalAnimal'
name "calico"
end
factory :mammal_factory, traits: [:mammal]
factory :cat_factory, traits: [:cat]
end
end
as per a suggestion, I added the below line to the test
expect(cat_mammal_animal.class.constantize).to eq(CatMammalAnimal)
and got this error
1) MammalAnimal.favorite and .favorite shows the favorite Cat
Failure/Error: expect(cat_animal_mammal.class.constantize).to eq(CatMammalAnimal)
NoMethodError:
undefined method `constantize' for #<Class:0x007f8ed4b8b0e0>
Did you mean? constants
I think instead of using trait to create objects of subclasses, you should have separate factories for those too.
factory :animal do
name 'dragon'
end
factory :mammal, class: MammalAnimal do
name 'zebra'
end
factory :cat, class: CatMammalAnimal do
name 'calico'
end
All of these can be defined in animals.rb
Then you can create your objects like
create(:animal)
create(:mammal)
create(:cat)

Factory Girl - how to include one association's ID while defining another

A Task belongs to an Employee, who belongs to a User (polymorphic).
Task also has user_id (the same user).
Now how to create this situation using FactoryGirl?
factory :employee do
after(:create) { |employee|
user = FactoryGirl.build :user
user.roleable_type = "Employee"
user.roleable_id = employee.id
user.save!
}
end
factory :task do
employee
user employee.user.id
end
Running a test that creates a task, I get:
/Users/ss/Documents/app/spec/factories.rb:41:in `block (2 levels) in <top (required)>': undefined method `user' for #<FactoryGirl::Declaration::Implicit:0x007fbd67cd2c70> (NoMethodError)

testing relationships in rspec, rails

I am trying to write a test to check that an associated object exists and the relevant id is stored in the database.
A panel belongs to a page. A page has many panels.
A sample of the current errors I have is this
CorporatePanel
Failure/Error: #instance = corporate_panel
ActiveRecord::RecordInvalid:
Validation failed: Corporate page does not exist, Corporate page can't be blank
# ./spec/support/project_helpers.rb:11:in `block (2 levels) in setup_factories'
# ./spec/models/corporate_panel_spec.rb:8:in `block (2 levels) in <top (required)>'
# -e:1:in `<main>'
Which I believe is caused because my test isnt creating an instance of page before a panel is created.
In my panel model I use this
validate :check_associates
private
def check_associates
associated_object_exists CorporatePage, :corporate_page_id
end
The method 'associated_model_exists' is a helper
def associated_object_exists(klass,fn,required=true)
id=self.send(fn)
id_setter=(fn.to_s+'=').to_sym
unless klass.find_by_id(id)
self.send(id_setter,nil)
id=nil
end
if required==true and id.nil?
errors.add(fn,"does not exist")
return false
end
end
The Panel test looks like this,
setup_factories
before do
#instance = corporate_panel
end
it { should belong_to(:corporate_page) }
mandatory_integer :section
mandatory_belongs_to CorporatePage
The madatory belongs_to looks like this
def mandatory_belongs_to(model)
context "#{model}" do
context "where the id is incorrect" do
before do
allow(model).to receive(:find_by_id).and_return(nil)
end
it "should be invalid" do
expect(#instance).to_not be_valid
end
end
context "where the id is correct" do
before do
allow(model).to receive(:find_by_id).and_return(true)
end
it "should be valid" do
expect(#instance).to be_valid
end
end
end
end
Update
FactoryGirl.define do
factory :corporate_panel do
corporate_page_id 1
section 1
# position 1
panel_type "MyString"
title "MyString"
headline "MyString"
body "MyText"
workflow_state "MyString"
end
end
FactoryGirl.define do
factory :corporate_page do
title "MyString"
static_descriptor "Home"
# workflow_state "MyString"
end
end
How would I ensure that a page is created before the model, if that is even the problem?
The problem is the way you've defined your factory. It should create the appropriate association while creating the corporate panel. It should be something like this:
FactoryGirl.define do
factory :corporate_page do
title "MyString"
static_descriptor "Home"
# workflow_state "MyString"
end
end
FactoryGirl.define do
factory :corporate_panel do
corporate_page # this refers to the factory defined above!! and will create appropriate association.
section 1
# position 1
panel_type "MyString"
title "MyString"
headline "MyString"
body "MyText"
workflow_state "MyString"
end
end
Here are some links for further reading on factories: http://www.hiringthing.com/2012/08/17/rails-testing-factory-girl.html and https://github.com/brennovich/cheat-ruby-sheets/blob/master/factory_bot.md
You can use an association as recommended in Surya's answer. If you need more control over the creation of factories, you can also explicitly pass in the Corporate page when creating the CorporatePanel:
let(:corporate_page) { FactoryGirl.create :corporate_page }
let(:corporate_panel) { FactoryGirl.build :corporate_panel, corporate_page: corporate_page }
This might be preferable if you want to use the :corporate_panel factory in other examples without an associated corporate_page.

Using factory girl to create HABTM association

I've been trying now for hours to get factorygirl to create two factories - one for users, one for organizations.
But I don't seem to understand how I can reflect a 'has_and_belongs_to_many' relationship in factories, as soon as I try to create an organization and associate it with an admin user, I run into various error messages (depending on the approach I use).
My model seems to be working fine, my seed file populates the dev DB and all the associations are created.
Right now my files look like this:
user factory
FactoryGirl.define do
factory :user do
email 'example#example.com'
password 'password'
password_confirmation 'password'
after(:create) {|user| user.add_role(:user)}
factory :owner do
after(:create) {|user| user.add_role(:owner)}
end
factory :admin do
after(:create) {|user| user.add_role(:admin)}
end
factory :superadmin do
after(:create) {|user| user.add_role(:superadmin)}
end
end
end
Organization factory
FactoryGirl.define do
factory :organization do |f|
f.name "example"
f.website "www.aquarterit.com"
f.association :users, :factory => :admin
end
end
in my specs I test this:
describe Organization do
it "has a valid factory" do
FactoryGirl.create(:organization).should be_valid
end
it "is invalid without a name" do
FactoryGirl.build(:organization, name: nil).should_not be_valid
end
it "is associated with at least one admin user" do
FactoryGirl.create(:organization)
it { should have_and_belong_to_many(:users)}
end
end
all three tests are failing, here are the error message:
1) Organization has a valid factory
Failure/Error: FactoryGirl.create(:organization).should be_valid
NoMethodError:
undefined method `each' for #<User:0x007fadbefda688>
# ./spec/models/organization_spec.rb:7:in `block (2 levels) in <top (required)>'
2) Organization is invalid without a name
Failure/Error: FactoryGirl.build(:organization, name: nil).should_not be_valid
NoMethodError:
undefined method `each' for #<User:0x007fadc29406c0>
# ./spec/models/organization_spec.rb:11:in `block (2 levels) in <top (required)>'
3) Organization is associated with at least one admin user
Failure/Error: organization = FactoryGirl.create(:organization)
NoMethodError:
undefined method `each' for #<User:0x007fadc2a3bf20>
# ./spec/models/organization_spec.rb:15:in `block (2 levels) in <top (required)>'
Any help is as always very much appreciated!
Update
In theory the same thing that works for assigning roles to the user should work for assigning an admin to the organization. But if I change organizations.rb to
FactoryGirl.define do
factory :organization do
name "example"
website "www.aquarterit.com"
after(:create) {|organization| organization.add_user(:admin)}
end
end
I get following error (I do have gem shoulda installed):
1) Organization is associated with at least one admin user
Failure/Error: it { should have_and_belong_to_many(:users)}
NoMethodError:
undefined method `it' for #<RSpec::Core::ExampleGroup::Nested_1:0x007ff2395f9000>
# ./spec/models/organization_spec.rb:16:in `block (2 levels) in <top (required)>'
Looks like you are not assigning users correctly and not creating the :admin user properly. For this to work, you need to assign an array of users to organization.users. And, you need to populate that array with a User instance (this assumes you have a User factory named :admin).
factory :organization do
name "example"
website "www.aquarterit.com"
after(:create) {|organization| organization.users = [create(:admin)]}
end
I do it this way, questions and tests have a HABTM relationship so:
FactoryGirl.define do
factory :question do
question 'Some stupid question'
user nil
factory :question_with_test do
# factory_girl's dynamic attributes, ignore it and pass it to evaluator
transient do
test nil
end
after(:create) do |question, evaluator|
create(:question_tests, question: question, test: evaluator.test)
end
end
end
end
Now I can create a question with HABTM to the Test model:
let(:test) { FactoryGirl.create :test, user: user }
let(:question_test_1) { FactoryGirl.create :question_with_test, user: user, test: test }
The question_tests factory is very basic:
FactoryGirl.define do
factory :question_tests, class: 'QuestionTest' do
question
test
end
end

Resources