Rails: Ensure phone number is unique checking several columns - ruby-on-rails

I have two columns, phone_1 and phone_2.
I want to ensure that once a phone number is entered in any of these fields, the same number cannot be saved on the same record or on any other future record in either one of these two columns.
How to achieve this on a database level and on a model level?
class Brand < ApplicationRecord
before_save :normalize_phones
validates :phone_1, phone: true, allow_blank: true
validates :phone_2, phone: true, allow_blank: true
private
# https://justincypret.com/blog/validating-normalizing-and-formatting-phone-numbers-in-rails
def normalize_phones
self.phone_1 = normalize_phone(self.phone_1)
self.phone_2 = normalize_phone(self.phone_2)
end
# https://justincypret.com/blog/validating-normalizing-and-formatting-phone-numbers-in-rails
def normalize_phone(phone)
self.phone_1 = Phonelib.parse(phone_1).full_e164.presence
self.phone_2 = Phonelib.parse(phone_2).full_e164.presence
end
end

I would just go a completly different direction:
class Brand < ApplicationRecord
has_many :phone_numbers
accepts_nested_attributes_for :phone_numbers
end
# rails g model phone_number number:uniq description
class PhoneNumber < ApplicationRecord
belongs_to :brand
validates :number,
presence: true,
uniqueness: true
end
This ensures the uniqueness both on the db and application level while allowing you to attach any arbitrary number of phone numbers.

Related

Ruby on Rails - ActiveRecord validates presence if 'self.preference.present?' doesnt work

I'd like to write a validation for preference. It should validate the presence of :city (which is associated with belongs_to) in the case if a preference record for this user exists.
user.rb
# attributes
# :city, :string
has_one :preference
preference.rb
# attributes
# preferred_car_brand
belongs_to :user
I tried this, but records get saved without an error.
user.rb
validates :city, presence: true, if: :user_preference_exists
def user_preference_exists
self.preference.present?
end
You can use this to validate the presence of a field.
class User < ActiveRecord::Base
validates :city, presence: true
end
It won't let active record save user model with empty value for :city.

Validates uniqueness from foreign key of foreign key

I have 3 models in my rails application, User, Course, and CourseTemplate.
A Course belongs to a User and a CourseTemplate belongs to a Course.
What I want to do is to validate the uniqueness between the CourseTemplate name and the User id.
Is this possible?
Without denormalization of data
class CourseTemplate < ActiveRecord::Base
belongs_to :course
has_one :user, through: :course
validate :unique_course_template_for_user
private
def unique_course_template_for_user
errors.add(:name, 'Must be unique') if CourseTemplate.find_by(user: user.id, name: self.name).count > 0
end
end
With denormalization of data
If you're ok with some denormalization of your data, you could add user_id to CourseTemplate, and then simply use the scope feature of validates uniqueness.
Below I show how to use callbacks to maintain the user_id in the CourseTemplate. Note that it assumes a course cannot be moved to a different user.
class CourseTemplate < ActiveRecord::Base
before_create :copy_user_id_from_course
validates :name, uniqueness: { scope: :user_id, message: 'Must be unique for the same user'}
private
def copy_user_id_from_course
self.user_id = course.user_id
true
end
end
If the course can be moved to a different user, you should add a callback on Course:
class Course < ActiveRecord::Base
after_save :set_course_templates_user, if: :user_id_changed?
private
def set_course_templates_user
course_templates.update_all user_id: self.user_id
end
end

Rails - new User builds dependent Email and validates both?

I'm building a quick Rails project that allows users to manage their email addresses. Users can have many emails, but one (and only one) of those emails has to be marked as 'primary' (for login), and a user cannot exist without a primary email.
I've been struggling to get this to work right - it seems so circular to me. I need to build a User, and then the Email, but I don't want to save the User into the database unless the Email is valid, which it won't be until the User is saved (because of the validates :user, presence: true constraint).
Accepts nested resources for doesn't seem to work with .new (works fine with .create), and if my Email fails its validations, the User still shows as valid.
Been having a difficult time trying to find good resources (or SO questions) for building/validating multiple/dependent models from a single form.
What's the most Rails way to do this?
User
has_many :emails
has_one :primary_email, -> { where(primary: true) }, class_name: "Email"
accepts_nested_attributes_for :primary_email
validates :first_name, presence: true
validates :last_name, presence: true
validates :birthday, presence: true
validates :password_digest, presence: true
Email
belongs_to :user
validates :user, presence: true
validates :address, presence: true, uniqueness: {
case_sensitive: false
}
UsersController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
# do something
else
# show #user.errors
end
end
private
def user_params
params.require(:user).permit(
:first_name,
:last_name,
:birthday,
:password,
:password_confirmation,
:primary_email_attributes => [:address]
)
end
EDIT
The Email model also contains the following fields:
label = string, eg. 'Personal', 'Work', etc
primary = boolean, whether it's marked as primary email or not
confirmation_code = autogenerated on creation, used to confirm ownership
confirmed = boolean, whether it's been confirmed or not
class User
user has_many :emails
user has_one :primary_email, -> { where(primary: true) }, class_name: "Email", autosave: true
after_initialize {
build_primary_email if new_record?
}
end
class Email
# use gem https://github.com/balexand/email_validator
validates :my_email_attribute, :email => true
end
So after a user initialized its building a primary_email so that record is already associated, or at least it will be if it can be saved. the autosave is working pretty cool - if the primary-email can't be saved due validation error, the user can't neither. should work out of the box, im in a bus right now, can't check it. cheers
futher information: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
If validations for any of the associations fail, their error messages will be applied to the parent. That means, the Parent Model (in your case User) is having errors, and thats why the saving is not possible! that's what you are looking for.
I would store a primary email as a common field and additional emails some another way. I would prefer to store additional emails in another field too that is Array rather than in an associated table. You shouldn't store a primary email in another table. Just imagine, every time you need authorize user or just get his email you will perform an extra request to db.
Meant to post this months ago.
The solution, keeping users and emails normalized across different models without storing a primary email as an attribute on the user, is to use inverse_of:
User.rb
class User < ActiveRecord::Base
has_many :emails, inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :emails
validates :emails, presence: true
end
Email.rb
class Email < ActiveRecord::Base
belongs_to :user, inverse_of: :emails
validates :user, presence: true
end
This allows validations to be performed using in-memory objects, rather than via database calls (ie the object associations are being validated, rather than the presence of an id/record in the database). Therefore they both pass validation and can both be saved in the same transaction.
See: https://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations

How to validate that a dependent row belongs to a user

I have a model called Category;
class Category < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :name, presence: true
validates :user_id, presence: true
end
And, I have a model called Group:
class Group < ActiveRecord::Base
belongs_to :user
has_many :categories
validates :name, presence: true
validates :user_id, presence: true
end
As you can see, a group can have many categories. When a user adds a category or updates it's group_id value, I want to check that the group belongs to that user. I don't want users adding to and update categories to another user's group. What is best practise to validate this prior to saving? Thanks
validate :ownership_of_group
def ownership_of_group
ids = []
ids << self.group_id
ids << self.group_id_was if self.group_id_was.present?
if(Group.find(ids).reject {|group| group.user_id == self.user_id}.present?)
# if all of them is owned by the user the array will return an empty array
errors.add(:user_id, 'You cant edit a category that is not yours')
end
end
If we say group_id we get the current value that is being set by the use.
If we say group_id_was it get the old value before the update.
In the update we need to handle both in the create we have no previous value.

Rails: Looking up foreign key id using virtual attribute failing

My app allows users to add words from a master words table into their own custom list. So, a word list contains multiple custom words each of which link to a master word.
In my view, I have a field called word_text (virtual attribute) where I let users enter a word, and in my model I am trying to look up the master_word_id and set it on the custom word table. I am unable to access the #word_text value in the model. I always seem to get an error that the master word is a required field (because the look up is failing).
class CustomWord < ActiveRecord::Base
attr_accessible :master_word_id, :word_list_id, :word_text
attr_accessor :word_text
belongs_to :word_list
belongs_to :master_word
validates :word_list, presence: true
validates :master_word, presence: true
before_validation :set_master_word
private
def set_master_word
logger.debug "Received word text #{#word_text}"
_mw_id = nil
if !#word_text.nil?
master_word = MasterWord.find_word(#word_text)
if master_word.nil?
errors.add("#{#word_text} is not a valid word")
else
_mw_id = master_word.id
end
end
self.master_word_id = _mw_id
end
end
I sincerely appreciate any suggestions as to how I can set the value of the master_word_id correctly.
There are several things to fix:
class CustomWord < ActiveRecord::Base
attr_accessible :master_word_id, :word_list_id, :word_text
attr_accessor :word_text
belongs_to :word_list
belongs_to :master_word
validates :word_list, presence: true
#validates :master_word, presence: true <= This will always throw error
validates :word_text, presence: true
validates :master_word_id, presence: true
before_validation :set_master_word
private
def set_master_word
logger.debug "Received word text #{self.word_text}"
self.master_word_id = MasterWord.find_by_word_text(self.word_text).id
end
end
Not sure if it will work because I don't know the rest of your app but I hope it points you in the right direction.

Resources