Kkulikovskis
Kkulikovskis

Reputation: 2088

Partly invoke validation process on some Activerecord object attributes

I have a situation where User has_one :address and Address belongs_to :user. I need to be able to validate the address object in these cases:

After a user has signed up, he has an option to partly fill in the address form. In this state I would like to validate for example validates :phone_number, :postal_code, numericality: true but the user can leave the field blank if he wants to.

When user is making a purchase he has to complete the address form. And all the fields have to be validated by validates presence: true + previous validations.

I understand that one approach would be to attach another parameter to the form (i.e.full_validation) and then add a custom validation method that would check for this parameter and then fully validate all attributes.

I was just wondering is there a more code efficient and easier way to do this.

So far I have only found ways to validate some attributes (seethis blog post) but I have not yet found suggestions on how to invoke part of the validation process for certain attributes.

Any help/suggestions will be appreciated :)

Upvotes: 0

Views: 76

Answers (2)

Richard Peck
Richard Peck

Reputation: 76774

#app/models/user.rb
class User < ActiveRecord::Base
    has_one :address, inverse_of: :user
end

#app/models/address.rb
class Address < ActiveRecord::Base
   belongs_to :user, inverse_of: :address

   validates :phone_number, :postal_code, numericality: true, if: ["phone_number.present?", "postal_code.present?"]
   validates :x, :y, :z, presence: true, unless: "user.new_record?"
end

--

After a user has signed up

Use if to determine if the phone_number or postal_code are present.

This will only validate their numericality if they exist in the submitted data. Whether the User is new doesn't matter.

--

When user is making a purchase

To make a purchase, I presume a User has to have been created (otherwise he cannot purchase). I used the user.new_record? method to determine whether the user is a new record or not.


Ultimately, both my & @odaata's answers allude to the use of conditional evaluation (if / unless) to determine whether certain attributes / credentials warrant validation.

The docs cover the issue in depth; I included inverse_of because it gives you access to the associative objects (allowing you to call user.x in Address).

If you give more context on how you're managing the purchase flow, I'll be able to provide better conditional logic for it.

Upvotes: 1

odaata
odaata

Reputation: 306

For your first use case, you can use the :allow_blank option on validates to allow the field to be blank, i.e. only validate the field if it is not blank?.

http://guides.rubyonrails.org/active_record_validations.html#allow-blank

For both use cases, you can tell Rails exactly when to fire the validations using the :if/:unless options. This is known as Conditional Validation:

http://guides.rubyonrails.org/active_record_validations.html#conditional-validation

For Address, you might try something like this:

class Address
  belongs_to :user
  validates :phone_number, :postal_code, numericality: true, allow_blank: true, if: new_user?

  def new_user?
    user && user.new_record?
  end      
end

This gives you an example for your first use case. As for the second, you'll want to use conditional validation on User to make sure an address is present when the person makes a purchase. How this is handled depends on your situation: You could set a flag on User or have that flag check some aspect of User, e.g. the presence of any purchases for a given user.

class User
  has_one :address
  has_many :purchases
  validates :address, presence: true, if: has_purchases?

  def has_purchases?
    purchases.exists?
  end
end

Upvotes: 1

Related Questions