When does validate method get called in Rails? - ruby-on-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?

Related

How do I validate_uniqueness_of when scoped to two attributes only when one attribute on matching existing records does not equal a specific value?

I have an Invitation model that represents an invitation to join a subscription. There should only be one Invitation at any given time with a specific email / subscription_id combination unless the other records with the matching email / subscription_id also have a state of 'declined'.
I can currently validate for uniqueness given that the email and subscription_id combination is unique:
My Invitation model:
validates :email, :uniqueness => { :scope => :subscription_id }
Rspec (passes):
it { should validate_uniqueness_of(:email).scoped_to(:subscription_id) }
However, I want to skip the uniqueness check if the matching model(s) in the database have a state that is equal to 'declined'.
If the existing model's state is 'declined', the validation should pass.
The first thing that comes to mind is:
validates :email, :uniqueness => { :scope => :subscription_id },
:unless => lambda { |asset| asset.state == 'declined' }
But this is wrong because it checks if the newly created model has a state of 'declined', I want to check if the previously existing records have a state of 'declined'.
I also tried this:
validates :email, :uniqueness => { :scope => :subscription_id, :message => 'subscriptionery do' },
:if => lambda { |asset| asset.state == 'declined' }
But that fails for what I assume is the same reason.
How would I write a validation that checks an additional scope?
I feel like writing something like the following, but this is just made up syntax to help explain my idea:
it { should validate_uniqueness_of(:email).scoped_to(:subscription_id) }
unless MyModel.where(:email == new_object.email,
:subscription_id == new_object.subscription_id,
:state == 'declined')
Update:
I did this and it worked:
validates :email, uniqueness: { scope: :subscription_id, message: 'The email address %{value} is already associated with this subscription.' }, if: :state_of_others_are_not_declined?, on: :create
def state_of_others_are_not_declined?
Invitation.where(email: email).where(subscription_id: subscription_id).where.not(state: 'declined').any?
end
How does this work for you;
validate :unique_email_with_subscription_and_state
def unique_email_with_subscription_and_state
errors.add(:email,"YOUR MESSAGE") if Invitation.where(email: self.email, subscription_id: self.subscription_id).where.not(state: 'declined').any?
end
This will select all Invitiations where the the email matches, subscription_id matches and the state is not declined. If it finds any it will add an error to :email. Something like this
"SELECT invitations.* FROM invitatations WHERE email = 'me#example.com' AND subscription_id = 2 AND state <> 'declined'"
Is that the desired result?

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

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

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.

using scope in Rails custom validations

I want to apply scope limiter in my custom validation
I have this Product Model
which has make,model,serial_number, vin as a attributes
Now I have a custom validation to check against vin if vin is not present to check for combination of make+model+serial_number uniqueness in database something like this
validate :combination_vin,:if => "vin.nil?"
def combination_vin
if Product.exists?(:make => make,:model => model,:serial_number => serial_number)
errors.add(:base,"The Combination of 'make+model+serial_number' already present")
end
end
I want to introduce a scope in this validator against user_id
Now I know I could easily write this to achieve same using
def combination_vin
if Product.exists?(:make => make,:model => model,:serial_number => serial_number,:user_id => user_id)
errors.add(:base,"The Combination of 'make+model+serial_number' already present")
end
end
But out of curiosity I was thinking is there a scope validator (something like {:scope => :user_id}) on custom validation
so that I dont have to pass that extra user_id in the exists? hash
Thanks
Try :
validate :combination_vin , :uniqueness => { :scope => :user_id } , :if => "vin.nil?"

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