how do i add a rails validation that negates existing rows - ruby-on-rails

We have a standard email validation on a User model. The requirements change and say the new validation checks the format with a regexp, to not allow dashes in the emails.
Problem is, how do i avoid checking for existing users in the system that have dashes (which we want to keep allowing for now).
If I apply the validation on the model it wont let me save any other field in the user till email field validates.
How to i work around this? What's the best practice for a case like this?

I would probably do:
validates :email, format: { with: <new_format_regex> }, if: email_changed?
Adding if: email_changed? will trigger this validation only if email is changed. Hence it will not trigger for the existing users, unless they try to change their password and it will trigger for all new users creating theirs accounts.

Related

Ruby on Rails: How to validate if on specific page?

In my Ruby on Rails application I am trying to add in validations that will ensure the user has entered a value in a text box. In my system I have a table called Account which stores users' email account information, when they go onto the views/accounts/_form.html.erb page to add a new email account I want to validate the presence of a port number. I can do this through the following code:
validates :port, presence: true
This successfully ensures that users enter their port number, but when a user signs up through the views/users/_new.html.erb page they have to enter only an email address (e.g example#example.com) and the users_controller will then create a record in the Account table for this email address. My problem is that on the views/accounts/_form.html.erb page the port number is required but on the views/users/_new.html.erb it is not.
Is there a way of validating that the user enters the port number if they are on the views/accounts/_form.html.erb page or invoking the create method in the accounts_controller?
I am aware that I could do this through the HTML required validation like so: <%= f.text_field :port, :required => true %> but I need to add in further validation as well as presence, so this is not suitable.
You can create an attr_accessor field that determines if the validation should occur...
class Account < ActiveRecord:Base
attr_accessor :port_needs_validation
validates :port, presence: true, if: -> {port_needs_validation}
Then just set the accessor in your create method...
def create
#account = Account.new
#account.assign_attributes(account_params)
#account.port_needs_validation = true
if #account.save
...
Extract that part of the logic into a form object, check out the legendary 2012 blog entry from CodeClimate. Things have changed since then, the author uses Virtus to build form objects, more popular & up-to-date gems these days are:
reform
dry-rb
active type
but really you can make anything behave like an ActiveModel object
if it's a one-off thing, just do what Steve said in the other answer but that is a sure way to hell, safe-hate and divorce (at least from personal experience) in any slightly teeny weeny bigger project (i.e. you mean to spend some hours more working on it, it's not like you just finished everything and want to go home).
Actually, just use form classes everywhere and avoid model validations & other callbacks at all. You don't want things sending account activation mails or validating your password complexity when you're writing tests and just need a "post" that belongs to "user".
My own favorite personal fuckup due to model callbacks is sending 240.000 "your account has been upgraded/downgraded" emails because of an innocent spelling change update in an account_type attribute migration just because account_type_changed? was true.
So.. Form classes for ever, model callbacks never.
I would not recommend you have model aware of views. In #SteveTurczyn 's solution, an abstract field is introduced into model to identified the which page it come from, which is an good solution.
As from Ruby on Rail MVC, both View and Model talk to the controller, another solution will be have controller handle validation of params before passing the value to create account.

Apply validation module to model in certain controllers only

I have a model that can be edited by two different types of users. The first has a login and has special privileges (let's call them a 'user'). The second is just some random user without a login with limited privileges (let's call them a 'guest').
The guest only really interacts with the model through one controller and we want certain validations to only apply in this case. The validations we want to apply exist within a module.
I tried doing something like this in the controller action, but it didn't seem to work:
#object = Model.find(params[:object_id])
#object.extend SpecialValidations
Then we would check for the objects validity (maybe directly or when updating attributes) and then display any errors generated by the validations.
Is there a better way to do this?
Thanks!
One alternative is to include the following in your Model:
attr_accessor :guest
def run_special_validations?
guest
end
validate :special_validation1, if: run_special_validations?
validate :special_validation2, if: run_special_validations?
Then, by having the controller set #object.guest = true, you will tell the object to run the conditional validations.
You could keep the validation without any conditions, and just skip it in the user controller (by using the update_attribute method, for example).

Is there a way in Rails to say "run all the validates EXCEPT :password"?

I am using Devise for my authentication. If a hashed_password isn't set, Rails/Devise's validations will require a password to be set, as well as the password_confirmation.
When I invite new users, I obviously don't want to set their password, so when I create the invitation in my system, it fails because user.password is blank.
I can set a temporary hashed_password on the user, but when they enter their own password, the validation checks for :password and :password_confirmation will not happen because hashed_password is set, which is a real problem.
Is there any way to tell Rails that I want to run all the validations except for the ones associated with :password?
I know Rails has :if conditions, which might fix my problem, but Devise declares the :password validation on my behalf, so that essentially is hidden.
How can I get the desired result here?, hopefully in a way that is not a hack.
My current hypothetical solution that is somewhat messy: The only thing I can think of is to create a new Invitation model that is not the User model, and use the Invitation model for the form. When the invitation is submitted I can validate that Invitation and copy over all the values to the new User model. I can save that User without any validations at all.
That's the best solution I dreamed up.
It seems like my solution will be a lot more work than saying something simple like:
user.save(validations => {:except => :password})
EDIT: I have found one part of the solution, but I am still having problems. In our user model, we can override a Devise method to prevent the validation of the password for invitations with this bit of code:
#protected
def password_required?
!is_invited && super
end
The is_invited attribute is just a column I added to the users table/model.
However, there is one gotcha here. When a user accepts an invitation and they arrive to the form where they need to set their password/password_confirmation, valid? will always return true.
This one has me deeply perplexed. I don't see how requires_password? and valid? can be true at the same time. If it requires the password, it should do a validation check and cause the validations to fail.
I'm starting to hate Devise - or just the idea of using gems to build parts of your application in a blackbox. I think the real solution probably is to rip out Devise and just do it all from scratch. That way your app has total control of how all of this works :(
I recently started using this great devise add-on: devise_invitable
It's commonly used so users (or any model) can invite other users to join.
But I adapt it for manually (via an admin panel) invite new potential users to my app.
Hope this helps!

Set Authlogic validate_login_field to false on user update

We have a current database of users who can have any symbol in their username. We have started using authlogic for authentication. So, any current users updating any of their information fail validations because their login has unaccepted characters.
We want to prevent new users signing up from using symbols not accepted by authlogic, but those who have them already in their login to continue using them.
I know that I need to use something like this:
acts_as_authentic do |c|
c.validate_login_field = false
end
My questions is how do I set validate_login_field to false for already existing users but leave it to default value of true for new users signing up? Thanks.
I would suggest forgetting about that particular validation feature in Authlogic for your particular case (i.e. keep it as c.validate_login_field = false) and use validates_format_of setting a :with clause to a regex to ensure Authlogic valid user names and an :if clause to some proc or method private method which returns true if the validation should be done at all (using some application specific logic of your own design)
Or you can not to turn of validate_login_field and add :if option to validates_format_of_login_field_options
UPDATE:
Actually forget, it's a bad idea, because in that case you have to provide :if option for every possible validation.
Not only for validates_format_of_login_field_options, but for validates_length_of_login_field_options and validates_uniqueness_of_login_field_options

Validate password on change of certain fields in RoR

I am building a RoR 3 app, a community. It has a User model and some fields.
So when a user is updating a certain field, like his/her birthday, I want to validate that the User typed in the password that is the same in the database. This way I know that it is the right user trying to change the birthday.
So I ask you how i can create such a validator.
Also I would like to be able to specify an array of which fields the user has to validate the password to change.
This is actually pretty easy to do once you are familiar with the Rails framework.
models/User.rb
class User < ActiveRecord::Base
validate :correct_password?, :if => :check_password?
def check_password?
[birthday_changed?, other_field_changed?].any?
end
def correct_password?
# without knowing more about how you store the password
# this probably won't work with your code directly
errors.add_to_base("Must provide password") unless password?
errors.add_to_base("Incorrect password") unless password == User.find_by_id(id).password
end
end
Even though building user authentication and authorization is not hard - I would advise to use something like "AuthLogic" or "Devise" gems/plugins which will most likely cover 90% of the functionality that you need. You alsways can customize/add new functionality if needed.
Such plugins will do most of the grunt work for you: generate MVC, create database, do proper security checks, even email password recovery and such.

Resources