Rails validates failed with a simple validation - validation

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

Related

Mongoid : The Validation "validates_uniqueness_of" is only triggered when the specific field changes

I have a unique constraint defined using a condition. But the following test does not pass :
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_uniqueness_of :name, if: :status
end
describe "UniquenessValidator" do
let!(:d1) { Dummy.create!(name: 'NAME_1', status: true) }
let!(:d2) { Dummy.create!(name: 'NAME_1', status: false) }
it "should raise an error" do
expect {
d2.status = true
d2.save!
}.to raise_error
end
end
Since name_changed? is false, no validation seems to happen and therefore the uniqueness condition is not checked.
Is it a bug ? Or have I forgotten something ? I guess it's an optimization to avoid to run the validation each time the element is modified.
In that case what is the good way to trigger the validation when the status is changed ?
Thanks!
As you case is a edge case, I would advice you to create your own validator class for this, something like this should work:
class NameUniquenessValidator < Mongoid::Validatable::UniquenessValidator
private
def validation_required?(document, attribute)
return true "name" == attribute.to_s
super
end
end
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_with(NameUniquenessValidator, :name, if: :status)
end
You are updating the status field so, you need to validate this field. You can do something like this:
class Dummy
include Mongoid::Document
field :name, :type => String
field :status, :type => Boolean
validates_uniqueness_of :name, if: :status
validates_uniqueness_of :status, scope: :name, if: :status
end
I don't know if you can force mongoid to validate all fields when you update a single field.

How do I define associated Factories with FactoryBot?

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

Override a Mongoid model's setters and getters

Is there a way to override a setter or getter for a model in Mongoid? Something like:
class Project
include Mongoid::Document
field :name, :type => String
field :num_users, type: Integer, default: 0
key :name
has_and_belongs_to_many :users, class_name: "User", inverse_of: :projects
# This will not work
def name=(projectname)
#name = projectname.capitalize
end
end
where the name method can be overwritten without using virtual fields?
better use
def name=(projectname)
super(projectname.capitalize)
end
the method
self[:name] = projectname.capitalize
can be dangerous, cause overloading with it can cause endless recursion
def name=(projectname)
self[:name] = projectname.capitalize
end
I had a similar issue with needing to override the "user" setter for a belongs_to :user relationship. I came up with this solution for not only this case but for wrapping any method already defined within the same class.
class Class
def wrap_method(name, &block)
existing = self.instance_method(name)
define_method name do |*args|
instance_exec(*args, existing ? existing.bind(self) : nil, &block)
end
end
This allows you to do the following in your model class:
wrap_method :user= do |value, wrapped|
wrapped.call(value)
#additional logic here
end

Rails/Mongoid error messages in nested attributes

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

Rails Model callback: Passing field as parameter to callback classes?

I want to implement before_validaton callback in a separate class so that it can be reused by multiple model classes.
Here in callback i want to strip field passed as parameter but i am not sure how to pass parameter to callback class. Also i want to pass this as reference rather than by value(not sure if this concept is in Ruby Rails). I am following the link http://guides.rubyonrails.org/active_record_validations_callbacks.html#callback-classes
Here is code which is not completely correct, please help for same
class StripFieldsCallback
def self.before_validation(field)
field = field.strip
end
end
class User < ActiveRecord::Base
validates_uniqueness_of :name, :case_sensitive => false
validates_length_of :name, :maximum => 50
before__validation StripFieldsCallback(name)
end
If i define method in model in itself rather than defining in separate callback class code is like this (which works fine)
class User < ActiveRecord::Base
validates_uniqueness_of :name, :case_sensitive => false
validates_length_of :name, :maximum => 50
before__validation :strip_blanks
protected
def strip_blanks
self.name = self.name.strip
end
end
Of course it is not good to replicate methods in all of models so i want to define method in callback classes.
You may do this or use normalize_attributes gem
module StripFieldsCallback
def before_validation_z(field)
write_attribute(field, read_attribute(field).strip) if read_attribute(field)
end
end
class User < ActiveRecord::Base
include StripFieldsCallback
before_validation lambda{|data| data.before_validation_z(:name)}
end

Resources