Validate uniqueness of email against another model's email address's - ruby-on-rails

I have a User model that has a "has_many" :emails association to an Email model. The user's primary email is just a column "email" for the user. However, I have this association that allows for multiple email addresses for a user.
The problem is that when a new user is created and "validates_uniqueness_of :email" is checked, it only checks other user's email column instead of possible secondary emails stored in the Email model.
I have tried to write a custom validate method in my User model that tries to find a user by the email address given and it works, but then it screws up other things in my code, like being able to add secondary email addresses or assign primary ones.
Is there a way to say something like: "validates_uniqueness_of :email through Email.all.each do {|e| errors.add :email, "this is invalid" if e.email == :email}"
This looks really sloppy, but all I'm trying to do is validate the uniqueness of the email address of a user upon creation against another model's instance's email address.
P.S. In case you are confused about the e.email, Email is the model and it has a column email that stores the email address.

You'll want to write a custom validator. Something along these lines:
class User
validates_uniqueness_of :email
validate :email_in_use
private
def email_in_use
if Email.where("lower(email) = ?", self.email.downcase).first
errors.add(:email, "There is already a user with this email")
end
end
end
I agree with #CWitty, though – you may want to consider moving the primary email address into the 'emails' table, rather than storing it straight on the User model.

It sounds like you need to think about your schema a bit. I would remove the email column from your User model and instead have all of the emails stored using the association. You could add a default flag to the Email model to signify the users default email. This would allow you to move the validates_uniqueness_of :email to the Email model and not try to check it in multiple places. Plus it cleans up your data so that it isn't in multiple places.

Related

Rails 4 - method to check matching attributes

I am trying to make an app in Rails 4.
I am using devise and want to write an after sign up redirect path based on the following logic.
I have a user model that includes an :email attribute. I have an organisation model that includes an :email_format attribute (email attribute holds the part of the email address that comes after '#'. I have a profile model (which contains the user's information that can be altered by the user itself).
The associations are:
User - has_one: profile
Profile - belongs_to :user, belongs_to: organisation
Organisation - has_many :profiles
If the user (registering) inputs an email address that includes an email format that is saved in the db, then I want to associate that new user's profile with the organisation that has the matching email format.
Example:
New user's :email = bob#cat.com
I then check the organisation table to see if any :email_attributes stored are 'cat.com'.
If they are, then the user profile for the new user, is associated with the organisation that has the matching email_format (so the organisation_id in the relevant profile table is set to match that organisation id).
Here's my best attempt at writing this:
registrations controller:
def after_sign_up_path_for(resource)
# check if the email address the user used to register includes the email format of an organisation
if #user.email.includes? #organisation(:email_format)
# if it does match, then update the user profile so that the org id equals the org id for the org that has the matching email format
#user.profile.organisation_id.update_attribute(organisation.id == (#organisation.email includes? :email_format))
else
# set up a mandrill email sender to send this message
set_flash_message('We are just getting started and will get in touch with
you as soon as we have onboarded your organisation')
end
end
I'm new to this and don't understand how to query things well. Any pointers would be very much appreciated.
NEXT ATTEMPT
Taking the suggestion below, I've tried to add an after_create callback to my profile model as follows:
after_create :update_organisation
def update_organisation
Profile.update_attribute(organisation_id: :matching_org_id) # Associations must be defined correctly for this syntax, avoids using ID's directly.
# Profile.save
end
def matching_org_id
if #self.user.email.includes? #organisation(:email_format)
# how do you ask for the organisation with the matching email format id
profile.organisation_id: organisation.id
else
TBC
end
end
This isn't working. I'm not sure how to express this method correctly. I'm currently getting an error that says:
syntax error, unexpected '(', expecting keyword_then or ';' or '\n' if #self.user.email.includes? #organisation(:email_format)
First problem:
This part is fairly dangerous: you could have bobcat.com#gmail.com return a false positive.
if #user.email.includes? #organisation(:email_format)
Perhaps you can use store a regex format and check it via
/#cat.com$/ =~ #user.email
Try out the formats at rubular.com
Second problem:
The update method should be something similar to this.
#user.profile.update(organisation_id: #matching_organisation.id)
You will need to loop through all organisations to find the matching organisation.
Third Problem
I think this is a bad place to do this sort of manipulation. You should perhaps add an after_create hook to the user model and put these logic in that method.

Rails: One User Model but need different info on registration

I have one user model and within that model I have roles (admin and general). I am using Devise https://github.com/plataformatec/devise and CanCan https://github.com/ryanb/cancan.
An admin creates the master account during their registration and then can register other users as well as edit their accounts. This works.
Finally, I have changed it so that the users can log in using their username and not email because email is not necessary for the general user.
My current (main) registration form (used only by the admin) requires the following information: First Name; Last Name: Email; Username and Password. I know that it is necessary for me to both validate certain fields and also make it so that usernames and email are unique. Both I know how to do.
USER MODEL
validates :username, :email, :password, presence: true
DB MIGRATE
add_index :users, :email, :unique => true
My problem is that when the admin creates the other users email is not applicable and also I want to include additional fields. Because email is not necessary and because I am using a single user model, validating that field or making it unique will not work. Or I do not think it will work?
If I do not want to separate out the model is their another direction I can approach this to solve the problem. I am thinking two registration forms -- but still the issue of validation and uniqueness will be present.
Any guidance would be appreciated.
Have you considered a method in your application controller to create users like so:
def create_new_user
u = User.create(:email => "guest_#{Time.now.to_i}#{rand(99)}#example.com", :password => params([:password]), :username => params([:username]))
u.save!
end
You could create a form that admin could use to get username and any other parameters you need to this method.
Alternatively, you can generate the devise views and customize them to suit your needs.
rails generate devise:views
Then you can move :email into a hidden field (it will still be present and so pass validation) in the form and set it randomly like above. This will handle the unique issue. You can also add other attributes like you wanted.
Keep in mind though that if you want to create a Devise resource while signed in as a devise resource, you'll have to customize a few things in the registrations controller.
Devise has some good info on custom views. https://github.com/plataformatec/devise

Interchange name of db column in rails logically

I have a table admin. In which there are two columns 'username' and 'email'. Since earlier app was in php, mistakenly the email is being used for storing username and username for emails.
Now we have migrated smaller part of application in rails, what I want is to do some changes in rails model so that username points to email and email points to username. I cannot rename column in db since it will break php side of app.
Is this possible in rails?
You can create virtual attributes to set\get username and and email fields and define getter and setter methods. Some examples can be found here:
http://railscasts.com/episodes/16-virtual-attributes?view=asciicast
UPD:
Some code
class User < ActiveRecord::Base
def username
self.email
end
def username=(name)
self.email=name
name
end
end

Difference between self.attribute_name and only attribute_name

Suppose There is a model User with email attribute.
I have check in some of the tutorials that we can use self.email and email alone. So what is the difference in both ?
If you are in an instance method within the User model then either will work, but email on its own is an implicit scope definition - meaning that the application will look for a local email variable, then an email method/attribute. self.email explicitly skips the search for a local variable.
You can access email from different ways when you are on the User class.
self.email when you are in the User scope
a_user.email when you have specified a user
email when you are in the User class. This is valid for every method in the User class.
#email, the variable returned by the email function
attributes[:email] the ActiveRecord attribute.
All of this methods are automatically generated by the ActiveRecord model, you can see the doc for more details.

Rails ActiveRecord Update question

I have a user model which has multiple addresses. Now for my application in rails, address is not mandatory. So, if someone wants to create a user and enter the address after the user has been created, my application should allow that. My problem is, for Address model I have validations for Address Line 1, City and Postal Code. These fields cannot be blank. When, editing a user, the following code fails:
user.addresses << Address.new
Rails tries to create a new Address and fires an Insert command. This is going to fail because of the validations that is required in the model. The above code doesn't fail if the user is not present in the database. One solution to this problem is to create a separate form_for binding for the edit partial for user. I don't want to do that solution. Is there any solution that allows me to bind an empty Address object for an already existing User object in the database ?
Why attempt to add an empty Address object to the user.addresses collection? I think you could simply do something like:
user.addresses << Address.new unless (conditions)
I unfortunately don't know what your conditions are here, so it could be something like
user.addresses << Address.new unless params[:address].nil?
...although my guess is that you have a real Address object instead of just passing in a blank Address.new...
user.addresses << Address.new
This code isn't going to work anyway if your Address model requires its fields to be set, because you're not supplying a hash to Address.new
If you want to add the address conditionally, you probably want something like this:
if !params[:address].blank?
user.addresses.create(params[:address])
end
or
user.addresses << Address.new(params[:address]) unless params[:address].blank
If you really want to create an "empty" address object for each user (instead of just having users without addresses), you can change your validations so they only fire if the fields are filled out.
Something like this:
class Address < ActiveRecord::Base
validates_presence_of :address1, :if => :non_empty_address?
# etc
private
def non_empty_address?
!address1.blank? || !address2.blank || !city.blank? # etc
end
end
The restful_authentication plugin uses a similar approach to determine if the user's password is required.

Resources