Reputation: 2088
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
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
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