Consider a User model with the following fields:
First name (required)
Last name (required)
Email (required)
Password (required)
Phone (required, size: 10 digits)
Address (required)
And a multi-step signup form with the following steps:
1st step with fields First Name, Last Name, and Email
2nd step with Password, Phone and Address.
How would you create a solution to validate the input in each step?
The standard ActiveRecord's way doesn't works, since it validates all fields at once.
I've created a solution for this problem but it turned out in a complicated code, so I'm looking for alternatives.
Personally I would let the model manage it's own validation and move the logic for managing validations for each step to that.
Now, this is probably not the most elegant way to do it but its effective and doesn't clutter the code too much.
Add a step transient attribute to the model.
attr_accessor :step
Add the following method to the model to check if on right step:
def on_step(step_number)
step == step_number or step.nil?
end
The reason for step.nil? - this has the advantage that if you want to use validation on this model without using steps simply don't assign a value for step on your model and the method will allows return true enabling the validation to always be carried out.
Change validations to process only if on right step or to bypass if not using steps
validates :first_name, if: "on_step 1", presence: true
validates :last_name, if: "on_step 1", presence:true
validates :email, if: "on_step 1", presence:true
validates :password, if: "on_step 2", presence:true
validates :phone, if: "on_step 2", format:{ with: TEL_REGEX }, allow_blank: false
validates :address, if: "on_step 2", presence:true
Of course don't forget to set the current step for the model, for example by hard-coding it in a hidden field of the form (if rendering separate forms for each step) and change your params to receive it.
Related
Trying to create a validation on a model. The model has two attributes private_key and public_key. If the user provides either one I want the validation to make sure that the other is provided. So if they provide the public_key they must provide the private_key and vice versa. Right now I have the following:
validates_presence_of :public_key if :private_key?
validates_presence_of :private_key if :public_public?
For some reason if I don't provide either I am getting an error.
Thanks in advance :)
use if like attribute, not like condition:
validates_presence_of :public_key, if: :private_key?
validates_presence_of :private_key, if: :public_public?
This might work:
validates_presence_of :public_key, if: 'private_key?'
validates_presence_of :private_key, if: 'public_public?'
You could also use validates shortcut method, which allows you to tuck in multiple validations later on the same attribute if needed:
validates :public_key, presence: true, if: Proc.new { |r| r.private_key.present? }
validates :private_key, presence: true, if: Proc.new { |r| r.public_key.present? }
I would prefer to test the presence of an attribute explicitly, for example using public_key.present? rather than public_key? as it is more readable and AFAIK public_key? method is only available for boolean fields, unless you defined.
I have this validation for my user:
validates :password,
presence: true,
confirmation: true,
length: { minimum: 6 },
:on => :create
This is obvious. When I'm creating (registering) a new user, I want to fill up their password hence that's why presence: true.
Case 1) When the user wants to update his account (lets say change his username), the form has only the email and username fields. That's ok and the validation is ok.
Case 2) He forgot his password and I send him his "forgotten password link" and he is on the page where he is creating his new password. The form has these two fields: password and password confirmation. However, he leaves both of these fields empty and submits the form. The validation passes because it's only :on => create! because of case 1)
I can not add :on => update because the case 1) wouldn't pass, because there is no password field.
What should I do in this situation? What is the best practice or what is the real word solution to this "problem"?
What I have done for this situation is instead of using on: :create, I use a virtual attribute that I set only when setting/changing the password. Something like this:
validates :password, if: :changing_password?
attr_accessor :password_scenario
def changing_password?
self.password_scenario.present?
end
Then in your controller, you would simply set password_scenario to true whenever you are requiring password to be present.
I am working on a project and need some help on where to begin. I have three pages
Update User
Create User
Admin User Password Change (like a Hard Reset Password for but only the admin can reset the user's password)
Change Password
On Create User first name, last name, username, password, and password confirmation are mandatory.
On Update User just first name, last name and username are mandatory.
On Admin User Password Change and Change Password, just password and password confirmation are mandatory.
How would you go about doing this? I don't think this is possible through models using validates_presence_of with an if because there are too many scenarios. Any help or guidance would be appreciated. Also, I am pretty new to Rails if you can't already tell.
You can pass conditionals to your validations:
validates :password, :confirmation => true, :presence => true
validates :first_name, :last_name, :username, :presence => true
validate :admin_user_password_change?
Of course you'd have to define what the admin_user_password_change? method would be to determine if it is an admin user changing a password.
UPDATE
The admin_user_password_change? method might be something like:
def admin_user_password_change?
unless self.admin? && self.password.present? && self.password_confirmation.present?
self.errors.add(:admin_password_change, "password and password_confirmation are required.")
end
end
As for How would it communicate with the controller?, it wouldn't directly. But if any of the conditions in the method are false (e.g. self.admin? && self.password.present? && self.password_confirmation.present?), an error will be added to the instance of User and the instance won't save in the controller.
Setting some fields to new values doesn't unset other fields; just because you're only updating some fields in one action doesn't mean the other fields will be unset, so long as they start in a consistent state.
Just add your validations. It will work fine.
You can tell to your validation work only on certain cenarios only using:
The create:
validates :first_name, :last_name, :username, presence: true, on: :create
The update:
validates :password, presence: true, on: :update
Take a look at on.
For validation based on context take a look at Context Validations
I'm learning rails with the book Agile Web development with Rails 4e. It uses the following so far as our product model (adapted from a scaffold):
class Product < ActiveRecord::Base
attr_accessible :description, :image_url, :price, :title
validates :description, :title, :image_url, presence: true
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)$}i,
message: 'Must be a valid URL for a gif, png, or jpg..'
}
end
I'm wondering why it tests first for the presence of :image_url, but then in the tertiary validation to make sure the image url is valid, it allows for blank responses which contradicts the first validation. I don't understand why this is supposed to work as is.
As an additional question, if the image_url is empty, how can I test if it is empty in my code? (e.g. in the product view to display a default image.)
Model validations are tested in isolation. A model is valid if and only if it passes validation for each validates statement independently.
It's probably bad-form, and evidently confusing for that allow_blank: true to be in the 4th validation, but that only applies to that single statement. The model must pass all validations to be considered valid, so the 1st statement merely imposes a tighter restriction than the 4th.
A final point, note that presence tests for non-nilness, whereas blank is defined as nil or the empty string. It is therefore possible to be both present and blank; e.g. image_url = ''. However, it remains the case that validations are tested separately in isolation.
I think maybe you are confused about the validation code? I'm a noob, and this is probably not entirely accurate: the validates keyword doesn't test for presence, it starts a block that you use to specify your validations.
As is, your code will validate the :image_url according to your specifications if it exists. If you took out allow_blank: true, then a nonexistent or blank :image_url would fail the validations.
I'm new to Rails as well and am using the same book. My understanding is that in order to stop the validation returning two errors immediately against validation (i.e. one if the field is blank and one if it doesn't have a correct file extension) it must allow_blank for the file format.
The best way I can explain it is to suggest removing the allow_blank: true code and trying to submit the description form again.
You should then see that you get both the validation errors for the field being blank and the file format being wrong.
The allow_blank therefore tells the validation to only error on the file format once the field is no longer blank.
Confused me as well which is why I ended up here!
I have an User model. It has next fields:
attr_accessible :user_name, :first_name, :last_name, :email ....
There is a profile view for the User with 6 blocks. Each of them associated with the various fields. Box 1 - first_name and last_name, Box 2 - user_name and email, etc.
I need to validate all the fields (presence, format, etc). But validators must trigger only for those fields, that has came from a particular block (Box 1 or Box 2, for example).
If I write something like next:
validates :user_name, :presence => true
and I will not edit the block with the *user_name*, I will see the error "user Name can't be blank". I can't use *:allow_blank => true* or nil because it can't(!) be blank!
In two words: I must validate only those fields, that was past from the resquest.
What I can do to solve my problem? Thx
You can add if or unless option to skip of particular condition.
validates :user_name, :presence => true, :if => "first_name.blank? and last_name.blank?"
You can pull the specific fields out of your model and create a model for each block, then you add one_to_one relationships to your User model.