Make Rails Validation Helpers uniqueness multiple with a group of columns - ruby-on-rails

How i can validate four attributes at once using method uniqueness or if I have how?
I am use MySQL:
class Product < ApplicationRecord
validates :manufacturer,:model, :color,:carrier_plan_type, :quantity, :price, presence: true
validates :quantity, numericality: {greater_than_or_equal_to: 0}
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :manufacturer, confirmation: { case_sensitive: false }
validates :model, format: {with: /\w+\s\w{2}\s\d{2,3}GB/i }
validates :color, format: {with: /[a-zA-Z]+|[a-zA-Z]+\s[a-zA-Z]+/i}
validates :carrier_plan_type, format: {with: /p(re|os)/}
end
This is my model Product, i would like to 4 columns (manufacturer, model, color and carrier_plan_type) with method_uniqueness. So that it is only valid to insert an instance in my database if you do not have these 4 columns repeated.
I found in Rails Guide that:
See the MySQL manual for more details about multiple column indexes or the PostgreSQL manual for examples of unique constraints that refer to a group of columns.
But i didn't understand how i can to group the columns.
To add, It is a good practice to do the validations as I did up there, if you have another opinion I am open to an idea.

Try using:
validates_uniqueness_of :carrier_plan_type, scope: [:manufacturer, :model, :color]
or
validates :carrier_plan_type, uniqueness: { scope: [:manufacturer, :model, :color] }
either should give you what you want.
In reply to the comment below try:
validates_uniqueness_of :carrier_plan_type,
scope: [:manufacturer, :model, :color, :price], if: -> { quantity == 0 }
or even
validates_uniqueness_of :carrier_plan_type,
scope: [:manufacturer, :model, :color, :price], if: :quantity_zero
private
def quantity_zero
quantity == 0
end

Related

Override validation in rails model

Hi I have a spree model that has the following validations:
with_options presence: true do
validates :firstname, :lastname, :address1, :city, :country
validates :zipcode, if: :require_zipcode?
validates :phone, if: :require_phone?
end
I would like to remove the city and/or country from presence validation. In my address_decorator i wrote this
Spree::Address.class_eval do
with_options presence: true do
validates :firstname, :lastname, :address1
validates :zipcode, if: :require_zipcode?
validates :phone, if: :require_phone?
end
......
But this didnt remove city or country. Both are still demanded in order to create new record.
What am i missing? Please help.
When you add "with_options" you are not removing the previous validations, you are just adding more.
So, there is to remove the validations for :city and :country
Not checked myself, but I´ve seen something similar to:
.class_eval do
_validators.reject{ |key, _| key == :field }
_validate_callbacks.reject do |callback|
callback.raw_filter.attributes == [:field]
end
where :field is :city and :country

how to validate presence of element in absence of another in rails

I have this model that validates presence: true for both post_id and user_id but I want it to validate the presence of one if the other is absent.
here is the validation :
validates :user_id, :comment_id, :post_id, presence: true
If you have any questions don't hesitate to ask
validates_presence_of :post_id, unless: :user_id
validates_presence_of :user_id, unless: :post_id
Since ActiveRecord automatically creates question mark methods for each of your model's attributes, you could also do:
validates_presence_of :post_id, unless: :user_id?
validates_presence_of :user_id, unless: :post_id?
:post_id? or user_id? returns false for nil or blank.
Simple Update thanks to rubocop, when i tried to use this logic, rubocop auto correct my line actually
C: [Corrected] Rails/Validation: Prefer the new style validations validates :column, presence: value over validates_presence_of.
validates_presence_of :<model_name>, unless: :
it basically converted this version
validates_presence_of :<model_name>, unless: :<condition>
To this:
validates :<model_name>, presence: { unless: :<condition> }

How to make a enum field to be updatable in rails?

I am trying to create a simple RoR application. I have faced a problem with enums for User categories.
class User < ApplicationRecord
before_save :name_to_sentence_case
extend Enumerize
enumerize :category, in: [:admin, :staff, :customer]
VALID_NAME_REGEX = /\A[a-z]+\z/i
VALID_PHONE_REGEX = /\A[0-9]+\z/
validates :first_name, presence: true, length: {maximum: 20}, format: {with: VALID_NAME_REGEX}
validates :last_name, presence: true, length: {maximum: 30}, format: {with: VALID_NAME_REGEX}
validates :category, presence: true
validates :password, presence: true, length: {minimum: 6}
validates :phone, presence: true, length: {maximum: 15}, uniqueness: true, format: {with: VALID_PHONE_REGEX}
has_secure_password
def name_to_sentence_case
self.first_name = first_name.humanize
self.last_name = last_name.humanize
end
end
I can create a user from rails console and set category based on specified categories and the system won't allow to create a user with another categories. However, I can not update the category of an existing user even if there is a correct category.
Better method is to create a hash. So you just update a numeric value.
For Example
CATEGORY = {
0 => 'admin',
1 => 'staff',
2 => 'customer'
}
You just store value numeric value in database(0,1 or 2) and you will know who it is from your hash.
Rails has build in enum helper. According to Documentation you can use it in next way:
in model:
enum category: [:admin, :staff, :customer]
your category field should be integer type or
enum category: { admin: :admin, staff: :staff, customer: :customer }
if you want to have string type field
than you can update your user in very simple way
#user.admin!
To elaborate on Oleg's answer, you will be able to pass in the enumerable option as string the parameter i.e.
#user.update(phone: "123-234-1234", category: 'admin')
or category
#user.update(phone: "123-234-1234", category: :admin)
See lines 21, 22 at source code on github

How to skip multiple validation ruby on rails

I have 2 forms.
Form 1 I have 10 fields which I am validating.
Form 2 but it only contains 2 fields.
The model is same.
What I need to do is:
To validate fields when they are submitted by forms. If I am not posting any fields it should not validate in model. If I post 5 fields it should validate 5 fields. If I post 2 fields it should validate only 2 not all of them.
So form 1 all 10 should be validated, form 2 only 2 should validate not rest of 8.
Here is my code:
validates :teacher_number, :title, :name, :gender, :location, :dob,
:contact_mobile, :contact_home, :street, :city, :state, :zip_code, :country,
presence: true
validates :teacher_number, uniqueness: {scope: :school_id}
validate :teacher_number_existance, :on => :create
validate :school_existance, :on => :create
Below is my attempt which successfully works fine but its bulk of code that somewhat a bad practice.
validates :teacher_number, presence: true, if: "teacher_number && teacher_number.blank?"
validates :title, presence: true, if: "title && title.blank?"
validates :name, presence: true, if: "name && name.blank?"
validates :gender, presence: true, if: "gender && gender.blank?"
validates :location, presence: true, if: "location && location.blank?"
validates :dob, presence: true, if: "dob && dob.blank?"
validates :contact_mobile, presence: true, if: "contact_mobile && contact_mobile.blank?"
validates :contact_home, presence: true, if: "contact_home && contact_home.blank?"
validates :street, presence: true, if: "street && street.blank?"
validates :city, presence: true, if: "city && city.blank?"
validates :state, presence: true, if: "state && state.blank?"
validates :zip_code, presence: true, if: "zip_code && zip_code.blank?"
validates :country, presence: true, if: "country && country.blank?"
validates :teacher_number, uniqueness: {scope: :school_id}, if: "teacher_number && teacher_number.blank?"
validate :teacher_number_existance, :on => :create, if: "self.teacher_number && self.teacher_number.blank?"
validate :school_existance, :on => :create, if: "self.teacher_number && self.teacher_number.blank?"
EDIT
UPDATED MY QUESTION.
I see two ways for this:
Some hidden param in form and attr_accesor in model to turn off validation.
Use save(validate: false) for save from that second form.
Next, you can do it like this
if validate_object?
validates :email, presence: true
validates :variant, presence: true
end
You can use some patterns like form object.
But you have to remember that this object will be invalid in future too.
If you want different validations of data in different circumstances, you should not be validating on the model. You should validate elsewhere: either in the view using HTML form validation, or in the controller using Rails' Strong Params.
I think you should use HTML form validation to manage this, because you're worried about the record missing attributes. Strong Params is more useful in case you are worried about people supplying forbidden values for record attributes.
Here is how you would use HTML form validation to manage this (example using HAML):
= form_for #model do |f|
= f.text_input(:teacher_number, required: true)
...all your other inputs...
= f.submit 'Submit Form'
Here is how you would use Strong Params to constrain the number of things you can get:
class ModelsController < ApplicationController
def form_action_1
Model.create(form_1_params)
end
def form_action_2
Model.create(form_2_params)
end
private
def form_1_params
# let's permit all the things you want for the first form
params.require(:model).permit(:teacher_number, :title, ...)
end
def form_2_params
# let's permit only the few things you want in the second form
params.require(:model).permit(:only, :a, :few, :things)
end
end
I'm not sure exactly what you're asking, but perhaps this approach would work. Write your validations like this:
validates :teacher_number,
:title,
:name,
:gender,
:location,
:dob,
:contact_mobile,
:contact_home,
:street,
:city,
:state,
:zip_code,
:country,
presence: true,
on: :form2
validates :teacher_number, uniqueness: {scope: :school_id}
validate :teacher_number_existance, :on => :create
validate :school_existance, :on => :create
The on: :form2 near the bottom of the first validator means the validations will only run if you explicitly ask them to run.
So in your controller action for saving form2, you would have to do this:
if valid?(:form2) && #model.save
The other validators will run when you save, as normal. Using the on: parameter of validates for your own symbols (as opposed to the built-in ones) is covered in the Rails Guide for validations, but it's easy to miss. I didn't notice it myself until recently. I hope this helps.
Finally, after so going back and forth below solution worked well for me.
before_validation :strip_validations
def strip_validations
['teacher_number', 'title', 'name', 'gender', 'location', 'dob', 'contact_mobile', 'contact_home', 'street', 'city', 'state', 'zip_code', 'country'].each do |attr|
errors.add("Teacher", {attr => " #{attr} can't be blank"}) if send(attr.to_sym) && send(attr.to_sym).blank?
end
end

Default value along with uniqueness validation

I have a Path model with name attribute as unique. I want to set default value as '/'
to the same.
I have done in the following manner.
class Path < ActiveRecord::Base
attr_accessible :name
validates :name, presence: true, uniqueness: true
before_validation :set_default_path
private
def set_default_path
self.name = name.presence || '/'
end
end
Domain model is designed as:
class Domain < ActiveRecord::Base
attr_accessible :name, path_id
validates :name, :path_id, presence: true
validates :name, uniqueness: {scope: :path_id}
end
But this doesn't work for consecutive inserts with a blank name for path.
path = Path.find_or_create_by_name('')
domain = Domain.new(name: 'stackoverflow.com')
domain.path = path
domain.save! # Fails with validation error
ActiveRecord::RecordInvalid:
Validation failed: Path can't be blank
Is there a robust way to achieve this ?
You should remove following callback
before_validation :set_default_path
and use validation for name as following:--
validates :name, presence: true, uniqueness: true, :if => 'name.present?'
and write a migration file to add default value to name attribute of paths table as either of followings:--
change_column :paths, :name, :string, :default => '/'
or
change_column_default :paths, :name, '/'
add condition on validation:
validates :name, presence: true
validates :name, uniqueness: true, unless: proc { |e| e.name == "/" }

Resources