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
Related
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
I am using CarrierWave to upload the images.
i have 2 models
class Book < ActiveRecord::Base
has_many :book_attachments, dependent: :destroy
accepts_nested_attributes_for :book_attachments, allow_destroy: true, reject_if: proc { |attributes| attributes['image'].blank?}
end
class BookAttachment < ActiveRecord::Base
belongs_to :book
has_many :images
validates :image,
:file_size => {
:maximum => 3.megabytes.to_i
}
mount_uploader :image, ImageUploader
end
i need to localize the validation message for image size validation.
i give like the following in the en.yml:
en:
activerecord:
errors:
models:
book_attachment:
attributes:
image:
too_big: The image is too big. The maximum size is 3 MB
The following message will get by default if image size is more:
"Book attachments image is too big (should be at most 3 MB)".
But. i need to get the message shown in the en.yml file.
Please help.
Assuming you've implemented the custom validator from the wiki (https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Validate-attachment-file-size).
Try this:
en:
activemodel:
errors:
models:
book_attachment:
attributes:
image:
size_too_big: "The image is too big. The maximum size is %{file_size} MB"
Alternatively you could also try a validation on the model directly:
class BookAttachment < ActiveRecord::Base
belongs_to :book
has_many :images
mount_uploader :image, ImageUploader
validate :image_size
def image_size
if image.file.size.to_i > 3.megabytes.to_i
errors.add(:image, :size_too_big)
end
end
end
en:
activerecord:
errors:
models:
book_attachment:
attributes:
image:
size_too_big: "is too big (should be at most 3 MB)"
The above worked for me . Thanks #Youri for the help. i used activerecord instead of activemodel.
I need validate the 'name' attribute of B class only when the attribute 'need_name' of A class is true. But I have a trouble with this validations.
My code:
class A
validates :need_name, presence: true
end
class B
validates :name, :presence => :need_name?
belongs_to :a
def need_name?
A.find(a).need_name
end
end
And my tests:
describe A do
context "validations" do
it { should validate_presence_of :need_name }
end
end
describe B do
context "validations" do
it { should validate_presence_of :name }
end
end
The test of class A works fine but I received this erro when I run the test of B class:
ActiveRecord::RecordInvalid:Validation failed: Need name can't be blank
The error disappears if I set true to the 'need_name' and I can't understand why this happens.
I really appreciate any help. Thank you guys.
Don't validate class A from within class B.
I'd validate the presence of the relation from class B, and then use validates_associated (described here) to trigger validations on A.
class A
validates :need_name, presence: true
end
class B
belongs_to :a
validates_presence_of :a
validates_associated :a
end
In the above code instances of class B will only validate if there's an association with a class A instance present and if the associated instance passes validation (i.e. has need_name set).
The solution was to change the way the validation is done to:
class A
validates :need_name, inclusion: { in: [true, false] }
end
class B
validates :name, :presence => { if: :need_name? }
belongs_to :a
def need_name?
a.need_name unless a.blank?
end
end
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
I have a contact info class defined like this:
class ContactInfo
include Mongoid::Document
validates_presence_of :name, :message => ' cannot be blank'
field :name, :type => String
field :address, :type => String
field :city, :type => String
field :state, :type => String
field :zip, :type => String
field :country, :type => String
embedded_in :user
end
This contact info class is embedd as a nested attribute inside my user class:
class PortalUser
include Mongoid::Document
accepts_nested_attributes_for :contact_info
end
When I attempt to save a user without a name, I get an error message like this:
Contact info is invalid
However, this is not very useful to the end user, because he or she doesn't know what contact info is invalid. The REAL message should be 'Name cannot be blank'. However, this error is not being propagated upwards. Is there a way to get the 'Name cannot be blank' message inside the user.errors instead of the 'Contact info is invalid' error message?
Thanks
Here's the solution I eventually came up with:
Added these lines to the user class
after_validation :handle_post_validation
def handle_post_validation
if not self.errors[:contact_info].nil?
self.contact_info.errors.each{ |attr,msg| self.errors.add(attr, msg)}
self.errors.delete(:contact_info)
end
end
Instead of returning the user.errors.full_messages create a specific error message method for your user model where you handle all your embedded document errors.
class PortalUser
include Mongoid::Document
accepts_nested_attributes_for :contact_info
def associated_errors
contact_info.errors.full_messages unless contact_infos.errors.empty?
end
end
and in your controller
flash[:error] = user.associated_errors
My solution that covers each embedded document validation error is the following:
after_validation :handle_post_validation
def handle_post_validation
sub_errors = ActiveModel::Errors.new(self)
errors.each do |e|
public_send(e).errors.each { |attr,msg| sub_errors.add(attr, msg)}
end
errors.merge!(sub_errors)
end
There might be a solution in the controller...
in the create action you can add something like
params[:portal_user][:contact_info_attributes] = {} if params[:portal_user] && params[:portal_user][:contact_info_attributes].nil?
This will force contact_info creation, and will trigger an error on the right field
If you don't add this, contact_info is not created