Ruby on Rails validation - If model is invalid, then? - ruby-on-rails

I have an EmailContact with validation, like so:
class EmailContact < ActiveRecord::Base
validates :email, :presence => true, :email => {:message => I18n.t('validations.errors.models.user.invalid_email')},
:mx => {:message => I18n.t('validations.errors.models.user.invalid_mx')}
end
Here I am validating EmailContact.email.
Then I have a PhoneContact with no validation:
class PhoneContact < ActiveRecord::Base
end
I want to write something like this:
email_contact = EmailContact.create(params)
if email_contact.invalid?
phone_contact = PhoneContact.create(params)
end
Basically, if the email_contact can't be created due to validation errors, I should then create a phone_contact. How is that possible?
This is what I've tried:
contact = EmailContact.create(:email => 'a')
(0.3ms) BEGIN
(0.4ms) ROLLBACK
ArgumentError: cannot interpret as DNS name: nil
contact.invalid?
NoMethodError: undefined method `invalid?' for nil:NilClass
contact just returns nil in this case...
EDIT
It may be that this question needs to go a different direction. Just FYI:
email_contact = EmailContact.new(:email => 'a')
email_contact.valid?
ArgumentError: cannot interpret as DNS name: nil
email_contact.valid? returns an error instead of returning false as I would expect. I am using the valid_email gem to do my validation on the model.

Using invalid? method, you could do something like this :
email_contact = EmailContact.new(params)
if email_contact.invalid?
phone_contact = PhoneContact.create(params)
else
email_contact.save
end
In your case, you used email_contact = EmailContact.create(params).
email_contact would be set to nil if creation fails and you won't be able to call invalid? on a nil object.
Check for invalid? before creating the record in database.

You should use #new and then call #save directly.
email_contact = EmailContact.new(params)
if !email_contact.save
phone_contact = PhoneContact.create(params)
end

Related

Rails Validation numbericality fails on form object

Related/Fixed: Ruby on Rails: Validations on Form Object are not working
I have the below validation..
validates :age, numericality: { greater_than_or_equal_to: 0,
only_integer: true,
:allow_blank => true
}
It is not required, if entered needs to be a number. I have noticed that if someone types in a word instead of a number, the field value changes to 0 after submit and passes validation. I would prefer it to be blank or the entered value.
Update:
Still no solution, but here is more information.
rspec test
it "returns error when age is not a number" do
params[:age] = "string"
profile = Registration::Profile.new(user, params)
expect(profile.valid?).to eql false
expect(profile.errors[:age]).to include("is not a number")
end
Failing Rspec Test:
Registration::Profile Validations when not a number returns error when age is not a number
Failure/Error: expect(profile.errors[:age]).to include("is not a number")
expected [] to include "is not a number"
2.6.5 :011 > p=Registration::Profile.new(User.first,{age:"string"})
2.6.5 :013 > p.profile.attributes_before_type_cast["age"]
=> "string"
2.6.5 :014 > p.age
=> 0
2.6.5 :015 > p.errors[:age]
=> []
2.6.5 :016 > p.valid?
=> true
#Form Object Registration:Profile:
module Registration
class Profile
include ActiveModel::Model
validates :age, numericality: { greater_than_or_equal_to: 0,
only_integer: true,
:allow_blank => true
}
attr_reader :user
delegate :age , :age=, to: :profile
def validate!
raise ArgumentError, "user cant be nil" if #user.blank?
end
def persisted?
false
end
def user
#user ||= User.new
end
def teacher
#teacher ||= user.build_teacher
end
def profile
#profile ||= teacher.build_profile
end
def submit(params)
profile.attributes = params.slice(:age)
if valid?
profile.save!
true
else
false
end
end
def self.model_name
ActiveModel::Name.new(self, nil, "User")
end
def initialize(user=nil, attributes={})
validate!
#user = user
end
end
end
#Profile Model:
class Profile < ApplicationRecord
belongs_to :profileable, polymorphic: true
strip_commas_fields = %i[age]
strip_commas_fields.each do |field|
define_method("#{field}=".intern) do |value|
value = value.gsub(/[\,]/, "") if value.is_a?(String) # remove ,
self[field.intern] = value
end
end
end
The interesting thing is that if move the validation to the profile model and check p.profile.errors, I see the expected result, but not on my form object. I need to keep my validations on my form object.
If the underlying column in the DB is a numeric type, then Rails castes the value. I assume this is done in [ActiveRecord::Type::Integer#cast_value][1]
def cast_value(value)
value.to_i rescue nil
end
Assuming model is a ActiveRecord model where age is a integer column:
irb(main):008:0> model.age = "something"
=> "something"
irb(main):009:0> model.age
=> 0
irb(main):010:0>
This is because submitting a form will always submit key value pairs, where the keys values are strings.
No matter if your DB column is a number, boolean, date, ...
It has nothing to do with the validation itself.
You can access the value before the type cast like so:
irb(main):012:0> model.attributes_before_type_cast["age"]
=> "something"
If your requirements dictate another behaviour you could do something like this:
def age_as_string=(value)
#age_as_string = value
self.age = value
end
def age_as_string
#age_as_string
end
And then use age_as_string in your form (or whatever). You can also add validations for this attribute, e.g.:
validates :age_as_string, format: {with: /\d+/, message: "Only numbers"}
You could also add a custom type:
class StrictIntegerType < ActiveRecord::Type::Integer
def cast(value)
return super(value) if value.kind_of?(Numeric)
return super(value) if value && value.match?(/\d+/)
end
end
And use it in your ActiveRecord class through the "Attributes API":
attribute :age, :strict_integer
This will keep the age attribute nil if the value you are trying to assign is invalid.
ActiveRecord::Type.register(:strict_integer, StrictIntegerType)
[1]: https://github.com/rails/rails/blob/fbe2433be6e052a1acac63c7faf287c52ed3c5ba/activemodel/lib/active_model/type/integer.rb#L34
Why don't you add validations in frontend? You can use <input type="number" /> instead of <input type="text" />, which will only accept number from the user. The way I see you explaining the issue, this is a problem to be resolved in the frontend rather than backend.
You can read more about it here: Number Type Input
Please let me know if this doesn't work for you, I will be glad to help you.

When does validate method get called in Rails?

I have a model StudentProductRelationship. I am adding a custom validator
validate :validate_primary_product , :if => "!primary_product"
The method is
def validate_primary_tag
unless StudentProductRelationship.exists?(:primary_product => true, :student_id => student_id)
errors.add(:base,"There is no primary product associated to product")
else
end
end
primary_product is a boolean field. I want to validate presence of at least one true primary_product for student_id. The problem is if I have an StudentProductRelationship object say spr with primary_product = true. If I do spr.update_attributes(primary_product: false). The validation does not raise an error because StudentProductRelationship.exists?(:primary_product => true, :student_id => student_id) exists beacuse spr still exists in db with primary_product = true. How do i surpass this?
Doesn't validates_presence_of :primary_product, scope: :student_id work for your?

How can I run validations on derived values for new objects?

I have a model that has several attributes that are provided at creation. The model also has some additional attributes that are derived from the provided attributes, which I also want to calculate at creation. More problematically, I want to be able to run validations on these derived values (since there are inputs that are valid on their own that lead to invalid derived values).
The problem is that when I do this:
class MyClass < ActiveRecord::Base
attr_accessible :given1, :given2, :derived
before_validation :derivation
validates_uniqueness_of :derived
def derivation
self.derived = self.given1 + self.given2
end
end
MyClass.new(:given1 => aNumber, :given2 => otherNumber)
I always get errors saying I can't add nil to nil. Apparently self.attribute is nil until farther into the validation & creation process.
Obviously I could set my derived values in a later stage, and add a custom validation that works on the given attributes, but that would entail doing the derivation twice, which wouldn't be very DRY.
Is there some other way to get at assigned but not yet validated attributes in the before_validates stage?
Edit: To clarify, I want to call MyClass.new(:given1 => aNumber, :given2 => otherNumber) and have the derived value calculated before the validations check, so that the validations check as if I had called MyClass.new(:given1 => aNumber, :given2 => otherNumber, :derived => aNumber + otherNumber). The problem is that I can't seem to access the passed-in values for :given1 and :given2 in a before_validations method.
I wrote my own snippet of code that looks like this:
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name
validates :email, uniqueness: true
before_validation :derivation
def derivation
self.email = self.first_name + self.last_name
end
end
Running the following yielded no errors:
» u = User.new first_name: "leo", last_name: "correa"
=> #<User:0x007ff62dd8ace0> {
:id => nil,
:first_name => "leo",
:last_name => "correa",
:email => nil,
:created_at => nil,
:updated_at => nil,
}
» u.valid?
User Exists (0.9ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'leocorrea' LIMIT 1
=> true
Running u.save saved the record successfully and upon repeating the User.new and saving that new record it returned with ROLLBACK because email was already used.
In any case, make sure you are assigning whatever variables you are using to the given1, given2 and whatever the result is make sure is not giving you false either because it will cancel the before_validate callback and the record won't save.

Rails 3.2 Prevent Object from being Saved using Errors

I have an ActiveRecord object and I would like to prevent it from being saved, without having permanent validations on the model. You used to be able to do something like this using errors.add but it doesn't look like it works anymore.
user = User.last
user.errors.add :name, "name doesn't rhyme with orange"
user.valid? # => true
user.save # => true
or
user = User.last
user.errors.add :base, "my unique error"
user.valid? # => true
user.save # => true
How can I prevent the user object from getting saved in Rails 3.2 without modifying it's model?
You can set errors, but do it within a validate method, e.g.:
validate :must_rhyme_with_orange
def must_rhyme_with_orange
unless rhymes_with_orange?
errors.add(:name, "doesn't rhyme with orange")
end
end
If you want to conditionally run the validation, one trick is to use attr_accessor and a guard condition:
attr_accessor :needs_rhyming
validate :must_rhyme_with_orange, :if => Proc.new {|o| o.needs_rhyming}
> u = User.last
> u.needs_rhyming = true
> u.valid? # false
Your issue is running valid? will rerun the validations.. resetting your errors.
pry(main)> u.errors[:base] << "This is some custom error message"
=> ["This is some custom error message"]
pry(main)> u.errors
=> {:base=>["This is some custom error message"]}
pry(main)> u.valid?
=> true
pry(main)> u.errors
=> {}
pry(main)>
Instead, just check if u.errors.blank?
This is a slight deviation from the original question, but I found this post after trying a few things. Rails has built in functionality to reject an object from saving if it has the _destroy attribute assigned as true. Quite helpful if you're handling model creation on the controller level.

Why doesn't validation in a model referenced by a callback cause my original transaction to fail?

I have a model with an after_create callback. This callback causes a new record to be created in another model. However if a validation fails in the child record creation, the original transaction is still being saved.
This doesn't seem right. According to Rails docs the whole thing is wrapped in a transaction. Am I doing something wrong?
class ServiceProvision < ActiveRecord::Base
has_one :cash_receipt
after_create :receive_payment_for_service_provision, :if => Proc.new { |sp| sp.immediate_settlement == true }
private
def receive_payment_for_service_provision
cash_account = CashAccount.find_by_currency_id_and_institution_id( self.currency_id, self.institution_id )
CashReceipt.create( :account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => ( cash_account ? cash_account.id : nil ) )
end
end
class CashReceipt < ActiveRecord::Base
belongs_to :service_provision
validates_presence_of :cash_account_id
end
The CashReceipt does fail and returns an error when its passed nil for the cash_account_id, however my new ServiceProvision object is still being saved.
it "should fail if a cash account doesn't exist for the currency and institution" do
currency = Factory.create( :currency )
institution = Factory.create( :institution )
service_provision = Factory.build( :service_provision, :currency_id => currency.id, :institution_id => institution.id, :immediate_settlement => true )
service_provision.save.should == false
service_provision.should have( 1 ).error
end
'ServiceProvision service provision creation should raise an error if a cash account doesn't exist for the currency and institution' FAILED expected: false,
got: true (using ==)
This seems to contradict this from the docs
Both Base#save and Base#destroy come
wrapped in a transaction that ensures
that whatever you do in validations or
callbacks will happen under the
protected cover of a transaction. So
you can use validations to check for
values that the transaction depends on
or you can raise exceptions in the
callbacks to rollback, including
after_* callbacks.
And if I manually try to cancel the transaction in the callback like so:
cr = CashReceipt.create( :account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => ( cash_account ? cash_account.id : nil ) )
unless cr.errors.empty?
errors.add_to_base("Error while creating CashReciept [#{cr.errors}].")
return false
end
then the new ServiceProvision object is still saved.
Move the CacheReceipt creation to before_validation filter. Since you have a has_one association on
ServiceProvision, the CacheReceipt object will have the correct :service_provision_id after save. Your code will be as follows:
before_validation :receive_payment_for_service_provision, :if => :immediate_settlement?
def receive_payment_for_service_provision
cash_account = CashAccount.find_by_currency_id_and_institution_id( self.currency_id, self.institution_id )
self.cash_receipt.build(:account_id => account.id,
:amount => self.amount,
:currency_id => self.currency.id,
:cash_account_id => ( cash_account ? cash_account.id : nil ) )
end
Now the save on ServiceProvision instance will return false if there are errors while saving the associated CacheReceipt.
Rollbacks only happen automatically with before callbacks:
The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception the execution chain gets halted and a ROLLBACK is issued. After callbacks can only accomplish that by raising an exception.
This makes sense because it allows for AR to prime the model and save it in memory before applying the transaction. Since you've done an after it has no knowledge of what to rollback too. Why not try before_save and see what you get.
You have to check the execution status of CashReceipt.create call in receive_payment_for_service_proviion method.
def receive_payment_for_service_provision
cash_account = CashAccount.find_by_currency_id_and_institution_id( self.currency_id, self.institution_id )
cr = CashReceipt.create( :account_id => account.id, :service_provision_id => self.id, :amount => self.amount, :currency_id => self.currency.id, :cash_account_id => ( cash_account ? cash_account.id : nil ) )
unless cr.errors.empty?
# Make the ServiceProvision instance invalid
errors.add_to_base("Error while creating CashReciept [#{cr.errors}].")
return false # terminate the callback chain and roll back the TX immediately.
end
end
PS: You can simplify your after_create specification as follows:
after_create :receive_payment_for_service_provision, :if => :immediate_settlement?
Thanks to #KandadaBoggu, who led me to the solution...
Turns out the solution is to change the callback to before_create, and then do this:
def receive_payment_for_service_provision
cash_account = CashAccount.find_by_currency_id_and_institution_id( self.currency_id, self.institution_id )
cr = self.create_cash_receipt( :account_id => account.id,
:amount => self.amount,
:currency_id => self.currency.id,
:cash_account_id => ( cash_account ? cash_account.id : nil ) )
unless cr.errors.empty?
errors.add_to_base( "Error while creating CashReciept [#{cr.errors}]." )
return false
end
end
In other words, we still need to manually check for validation errors in the association.

Resources