I have a FactoryGirl Object that creates a Category in my case (it is associated with an image)
class Image < ActiveRecord::Base
has_many :image_categories, dependent: :destroy
has_many :categories, through: :image_categories
validates :categories, presence: { message: 'Choose At Least 1 Category' }
end
class Category < ActiveRecord::Base
has_many :image_categories
has_many :images, through: :image_categories
validates :name, presence: { message: "Don't forget to add a Category" }
validates_uniqueness_of :name, message: 'Category name %{value} already exists'
end
FactoryGirl.define do
factory :category do
name 'My Category'
end
end
FactoryGirl.define do
factory :image do
title 'Test Title'
description 'Test Description'
transient do
categories_count 1
end
categories { build_list(:category, categories_count) }
end
end
When creating an image with 1 category everything is fine, but if i try and save with 2 categories the second entry gets saved as nil, I guess thats because of my validation of unique names.
So my question is how can I use transients to create a list of 2 unique categories
Hope this makes sense
Thanks
Direct from Factory Girl Documentation: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Transient_Attributes
FactoryGirl.define do
# post factory with a `belongs_to` association for the user
factory :post do
title "Through the Looking Glass"
user
end
# user factory without associated posts
factory :user do
name "John Doe"
# user_with_posts will create post data after the user has been created
factory :user_with_posts do
# posts_count is declared as a transient attribute and available in
# attributes on the factory, as well as the callback via the evaluator
transient do
posts_count 2
end
# the after(:create) yields two values; the user instance itself and the
# evaluator, which stores all values from the factory, including transient
# attributes; `create_list`'s second argument is the number of records
# to create and we make sure the user is associated properly to the post
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
end
end
Related
I have the following factory setup
FactoryGirl.define do
factory :image do
title 'Test Title'
description 'Test Description'
photo File.new("#{Rails.root}/spec/fixtures/louvre_under_2mb.jpg")
after(:build) do
FactoryGirl.build_list(:category, 1)
end
end
end
Within my model i have these validations
class Image < ActiveRecord::Base
has_many :categories
validates :title, presence: { message: "Don't forget to add a title" }
validates :description, presence: { message: "Don't forget to add a description" }
validates :categories, presence: { message: 'Choose At Least 1 Category' }
end
When I run this test it fails
RSpec.describe Image, type: :model do
it 'should have a valid Factory' do
expect(FactoryGirl.build(:image)).to be_valid
end
end
Failure/Error: expect(FactoryGirl.build(:image)).to be_valid
expected #<Image id: nil, title: "Test Title", description: "Test Description", photo_file_name: "louvre_under_2mb.jpg", photo_content_type: "image/jpeg", photo_file_size: 65618, photo_updated_at: "2015-12-15 08:01:07", created_at: nil, updated_at: nil> to be valid, but got errors: Categories Choose At Least 1 Category
Am i approaching this wrong as i was thinking that the validations would not kick in until the whole object was created? or am i thinking about this incorrectly ?
Thanks
the problem is in this part.
after(:build) do
FactoryGirl.build_list(:category, 1)
end
this will create a list of categories of size 1, but those categories are not associated to the image object. The proper way is as follows:
transient do
categories_count 1
end
after(:build) do |image, evaluator|
image.categories = build_list(:category, evaluator.categories_count)
end
or
transient do
categories_count 1
end
categories { build_list(:category, categories_count) }
personally, I would choose this last option.
the photo attribute as well is problematique. FactoryGirl is all about flexibility of creating records. But the way you're using it will not present any flexibility, so the photo attribute will be shared between all the records that you'll create using this factory. And sooner or later you'll face some headaches.
so the proper way of creating the photo attribute is as follows.
transient do
photo_name 'default_photo.jpg'
end
photo { File.new(File.join(Rail.root, "spec/fixtures", photo_name) }
than you can use it this way
FactoryGirl.build(:image, photo_name: 'new_photo_name.jpg')
I would recommend don't use after method in image factory. You should create a correct association. Using this you'll resolve validation error and will't have others problems in the future.
class Image
accepts_nested_attributes_for :categories
end
FactoryGirl.define do
factory :image do
categories_attributes { [FactoryGirl.attributes_for(:category)] }
end
end
So I have a factory to create an item. Which can contain one or more line_items.
describe Item do
let(:user) { create :user }
let(:currency) { create :currency }
subject(:item) { create :item, currency: currency, create_user: user, update_user: user }
It fails when creating the item with an active record validation:
ActiveRecord::RecordInvalid: Validation failed: You have not entered any data and cannot save a blank form., You have not entered any data and cannot save a blank form.
Is this because a line_item is not being created when an item is created?
In my factory, whenever I create a line_item, I create an item:
FactoryGirl.define do
factory :line_item do
item
create_user factory: :user
update_user factory: :user
end
end
Make Sure that your models are validated by your model validations in your Factory. Please can you paste your models. Try the code below. It is not tested so you have to doctor it based on your model.
FactoryGirl.define do
factory :user do
sequence(:username) { |n| "foo#{n}"}
password "password"
email {"#{username}#example.com"}
end
factory :item do
name "item1"
user
end
factory :line_item do
name "line_item1"
item
end
end
I'd like to test that a non signed in user can not access a page normally reserved for specific customers (one of their deal:only them can see the draft page the deal). It should redirect him to customer sign in page.
A customer has many deals
Models/customer.rb
class Customer < ActiveRecord::Base
rolify
has_many :customer_deals
has_many :deals, through: :customer_deals
models/deal.rb
class Deal < ActiveRecord::Base
belongs_to :admin_user, :foreign_key => 'admin_user_id'
belongs_to :partner, :foreign_key => 'partner_id', :counter_cache => true
has_many :customer_deals
has_many :customers, through: :customer_deals
accepts_nested_attributes_for :customers #:reject_if # needed for Active admin
Models/customer_deal.rb
belongs_to :customer, :foreign_key => 'customer_id'
belongs_to :deal, :foreign_key => 'deal_id'
My tests below are failing with this error:
1) Customer Interface pages As NON SIGNED-IN visitor does not have access to prepare deal page
Failure/Error: let(:deal) { FactoryGirl.create(:deal, :customer => customer) }
NoMethodError:
undefined method `customer=' for #<Deal:0x007f4f34b3a788>
# ./spec/requests/client_interface_pages_spec.rb:15:in `block (2 levels) in <top (required)>'
# ./spec/requests/client_interface_pages_spec.rb:50:in `block (4 levels) in <top (required)>'
Here is the rspec test page: client_interface_pages_spec.rb
require 'spec_helper'
require 'cancan/matchers'
describe "Customer pages" do
subject { page }
let(:user) { FactoryGirl.create(:user) }
let(:customer) { FactoryGirl.create(:customer) }
let(:prospect) { FactoryGirl.create(:prospect) }
let(:wrong_customer){ FactoryGirl.create(:customer, email: "wrong#example.com") }
let(:non_admin) { FactoryGirl.create(:customer) }
let(:deal) { FactoryGirl.create(:deal, :customer => customer) }
context "As NON SIGNED-IN visitor" do
let(:customer) {FactoryGirl.create(:customer)}
describe "does not have access to prepare deal page" do
it "cannot access the deal preparation page" do
get :draft_deal_page, :deal_id => deal.id, :customer_id => customer.id
response.should redirect_to(new_customer_session_path)
flash[:alert].should eql("You need to login or sign up before continuing.")
end
end
end
Routes
match '/prepare/deal_:id/draft-deal-page',
to: 'deals#draft_deal_page',
via: 'get',
as: :draft_deal_page
I don't know if it's related but here are the main factories I use:
customer_deals.rb
FactoryGirl.define do
factory :customer_deal do
customer
deal
end
end
customers.rb
FactoryGirl.define do
factory :customer do
sequence(:email) { |n| "person_#{n}#example.com"}
password "abcde"
password_confirmation "abcde"
# required if the Devise Confirmable module is used
confirmed_at Time.now
confirmation_token nil
partner_id 3
# give customer role to customers
factory :customer_with_customer_status do
after(:create) {|customer| customer.add_role(:customer)}
end
end
end
deals.rb
FactoryGirl.define do
factory :deal do
title "lorem ipsum"
featured true
admin_user_id 1
partner_id 3
customer_id 3
end
end
A deal has_many :customers. Your deal factory is attempting to assign the "customer_id" attribute. There is no such attribute on deal because a deal has many customers. The customer_deal factory might be more appropriate to use here:
let(:customer) { FactoryGirl.create(:customer_deal).customer }
This would require you to get rid of the customer_id in your deal factory. In general, I think it's poor form to pick an arbitrary integer to stand in for a foreign key in your factories. If a factory requires a relationship, that relationship should just be created by the factory. If the relationship isn't required for object validity then I'd leave it off of the factory altogether, perhaps adding it behind a trait if it's something you need often.
I'd suggest reading the GETTING STARTED guide -- the whole thing, but particularly the parts about has_many associations, traits, and associations.
I have this model:
class CompetitionEntry < ActiveRecord::Base
has_many :participants
has_one :address
has_many :music_programs
accepts_nested_attributes_for :address
accepts_nested_attributes_for :participants, :music_programs,
:allow_destroy => true,
:reject_if => :all_blank
end
and this one:
class Participant < ActiveRecord::Base
belongs_to :competition_entry
has_one :birthplace
validates :name, :surname, :instrument, presence: true
end
Now the problem is that, if I create a new competition entry, it goes through.
But if I fill ONE field, i.e name, then it comes up with an error!
Why is this happening? It should fail when all are empty!
When you use accepts_nested_attributes_for, you are able to create the participants records at the same time that the competition_entry record, considering that the hash passed to competition_entry.create contains participants_attributes. When you pass only name, it validates the participant to be created and fail because it has no surname and instrument. When you leave all field empty, the behaviour should be the same, but it isn't because you explicitly set :reject_if => :all_blank.
:reject_if => :all_blank states that the participant_attributes hash should be ignored if it is blank?, which happens when you don't fill any field. What is happening then is that a competition_entry is being created without trying to create a participant because the accepts_nested_attributes_for is just ignored.
Given two models, Alert and Zipcode, where one Alert must have 1 or more Zipcodes:
class Alert < ActiveRecord::Base
attr_accessible :descr, :zipcode
has_many :zipcode
validates :zipcode, :length => { :minimum => 1 }
end
class Zipcode < ActiveRecord::Base
attr_accessible :zip
belongs_to :alert
end
How do I write my FactoryBot factories so that:
Zipcode factories are defined in their own file
Alert factories are defined in their own file
Alert can rely on the factory defined by Zipcode?
All of the documentation and examples I read expect you to define the contained class inside the parent factory file, blob them all together, or make some other compromise or work-around. Isn't there a clean way to keep the spec factories separate?
The trick is to make sure the container class, that is, the one with a has_many statement in its definition, creates the contained class as an array in FactoryBot. For example:
In your spec/factories/zipcodes.rb:
FactoryBot.define do
factory :zipcode do
zip { 78701 + rand(99) }
end
end
And in spec/factories/alerts.rb:
FactoryBot.define do
factory :alert do
zipcode { Array.new(3) { FactoryBot.build(:zipcode) } }
end
end