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
Related
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
My code require a user to have a sponsor id except the first user. My code is
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save {email.downcase!}
validates :first_name, :presence =>true, length: {maximum: 50}
validates :last_name, :presence => true, length: {maximum: 50}
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, :presence =>true, length: {maximum: 255}, format: {with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false }
has_secure_password
validates :password, presence: true, length: {minimum: 6}, allow_nil:true
.
.
.
class User < ActiveRecord::Base
belongs_to :sponsor, class_name: "User"
validates :sponsor, presence:true
validate :sponsor_id_valid
def sponsor_id_valid
errors.add(:sponsor_id, "is not exist") if User.exists? sponsor_id: sponsor_id
end
#Validate that the user isV exist in the data
def validate_sponsor_id
errors.add(:sponsor_id,"is not exist") if User.find(self.sponsor_id).blank?
end
end
I require that the User need to have sponsor_id for all user exept the User.first. I tried unless method but it didn't give me a desired outcome. The way I test the code is that I create a test code which give the first user to have a sponsor_id to be nil and the test failed. Can anyone help me please?
Thanks
def sponsor_id_valid
if self.sponsor_id.blank? && self != User.first
errors.add(:sponsor_id, "is not exist")
end
end
EDIT: to take account of the case where there isn't an existing first user yet, (which we want to pass) the logic could be changed to:
if self.sponsor_id.blank? && (first_user = User.first) && (self != first_user)
#add a validation error
I have model with some validation rules:
class Order < ActiveRecord::Base
validates :zip_code, presence: true, length: {is: 5}, numericality: {only_integer: true, :greater_than => 0}
end
And when zip_code is blank i don't need to perform other zip_code validations (it is redundant and all other validation messages on user page look very strange if zip_code is blank)
How can i implement this logic? i need to validate length, is_integer and greater_than only if zip_code is not blank? and i need to show only zip_code can't be blank message on user page
You can do something like
validates :zip_code, presence: true
validates :zip_code, length: {is: 5}, numericality: {only_integer: true, :greater_than => 0}, :if => :zip_code?
Hope it helps!
I got this model:
rails g model Absence user:references company:references from:date to:date date:date category:integer hours:decimal remarks
This also generates:
FactoryGirl.define do
factory :absence do
user nil
company nil
from nil
to nil
date nil
category 0
hours "8.00"
remarks "MyString"
end
end
I set from, to and date to nil because it's either: from and to OR a certain date.
When I try this in my spec:
#absence = create(:absence, user: #company.owner, from: "2015-09-10", to: "2015-09-10", hours: 4)
I receive this error message:
NoMethodError:
undefined method `from=' for #<Absence:0x007f81f5494b88>
What could be wrong?
Edit:
When I remove the
from nil
from the factories/absences.rb I'm getting it on the next field (to) and after removing that I'm seeing the error message on category.
Edit2:
Model:
class Absence < ActiveRecord::Base
belongs_to :user
belongs_to :company
enum type: {holiday: 0, sick: 1}
validates :from, presence: true, if: '!user_id.nil?'
validates :to, presence: true, if: '!user_id.nil?'
validates :date, presence: true, if: '!company_id.nil?'
validates :hours, presence: true, if: '!user_id.nil?'
validates :hours, :numericality => { :greater_than_or_equal_to => 0 }, if: '!user_id.nil?'
validates :category, presence: true, if: '!user_id.nil?'
validates_numericality_of :company_id, allow_nil: true
validates_numericality_of :user_id, allow_nil: true
validate :company_xor_user
validate :to_date_after_from_date
validate :hours_smaller_than_workday
validate :non_overlapping
after_save :calculate_time_checks
after_destroy :calculate_time_checks_delete
DB:
https://www.evernote.com/shard/s29/sh/e8c1429d-9fa7-475b-87e8-3dc11a3f3978/08a7e7d6dfd80c6f407339cab97734c2
FINALLY found the real cause.
At first I had the Absence model created with an attribute named 'type'. This was migrated to both the development and test database. Afterwards I changed it to category and added 'from' and 'to' as well and did a rollback and migrated again (but not on test!).
By using pry
require 'pry'; binding.pry
in the test I did Absence.columns and noticed the difference.
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 == "/" }