rails 7.0.4 Unknown validator: DateValidator - ruby-on-rails

This line of code in my rails 7 model causes the error "Unknown validator: DateValidator" It was working in rails 6
validates :showdate, :date => { :after => Time.now + 2.hour }, :if => Proc.new { |e| e.user_id.present? }

In rails 7 there is ComparisonValidator and you can use it like so:
validates :showdate, comparison: { greater_than: Time.now + 2.hour }
or
validates_comparison_of :showdate, greater_than: Time.now + 2.hour

Related

set custom message in each different error validation in ruby on rails

let say that I have set in model for validation like this
validates :tel, presence: true , length: { minimum: 10, maximum: 11 }, numericality: { only_integer: true }
how do I can display a custom message in view for each validate.
when I set this in views page.
<% if #diary.errors.include?(:tel) %>
<div class="err"><p><%= #diary.errors.full_messages_for(:tel).join("") %></p></div>
<% end %>
it directly displays all error message. I want to make a display in view like this
if(error_require)
echo "tel is needed"
else if(error_length)
echo "tel is to long"
else
echo "tel must numeric"
end
can I make like that?
You can pass message in separate hashes for each validator:
validates :tel,
presence: { message: 'is needed' },
length: { minimum: 10, maximum: 11, too_long: 'is too long' },
numericality: { only_integer: true, message: 'must be numeric' }
Read more about presence, length, and numericality validators.
One way to do this is to define methods for each type of validation (in your model) like this:
validate :chech_length
def chech_length
if tel.length < 10 || tel.length > 11
errors.add(:base, "tel is too long!")
end
end
validate :check_if_present
def check_if_present
if tel.blank?
errors.add(:base, "tel must be present!")
end
end
etc...
Hope this helps.

Validate graduation date in Rails 5 model

I am trying to make sure that users have a valid graduation date within my application. Not sure if my variable for the current_year will be updated every time I want to validate a user's graduation date. Here is what I have (I am making this field optional for users):
current_year = Time.now.year
if !(:grad_year.nil?)
validates :grad_year, numericality: { greater_than_or_equal_to: current_year, less_than_or_equal_to: current_year + 5 }
end
validates :grad_year, numericality: { greater_than_or_equal_to: Date.today.year, less_than_or_equal_to: Date.today.year + 5 }, allow_nil: true
Actually, it is not correct to use Date.today.year for validation because the current year will be hardcoded on Rails start and won't update next year (until you restart Rails).
Use Lambda instead:
validates :grad_year, numericality: { greater_than_or_equal_to: -> { Date.today.year }, less_than_or_equal_to: -> { Date.today.year + 5 } }, allow_nil: true

rails 4 refactor factorygirl creates inaccurate data

I have been battling a major refactor to slim down a payments controller and could use a hand. Step one I am trying to fix my factories. Right now all of the factories work great on their own, but when I try to build associations the FactoryGirl.create(:job, :purchased_with_coupon) it will setup the association correctly on the coupon but not the payment. This means that the price paid is always is always 1. I just noticed this which you can see the other section commented out. Before I start tackling the bloated controller I need to figure this out for my tests. Thoughts?
Factories
FactoryGirl.define do
factory :job do
category
company
title { FFaker::Company.position }
location { "#{FFaker::Address.city}, #{FFaker::AddressUS.state}" }
language_list { [FFaker::Lorem.word] }
short_description { FFaker::Lorem.sentence }
description { FFaker::HTMLIpsum.body }
application_process { "Please email #{FFaker::Internet.email} about the position." }
trait :featured do |job|
job.is_featured true
end
trait :reviewed do |job|
job.reviewed_at { Time.now }
end
trait :purchased do |job|
job.reviewed_at { Time.now }
job.start_at { Time.now }
job.end_at { AppConfig.product['settings']['job_active_for_day_num'].day.from_now }
job.paid_at { Time.now }
payments { |j| [j.association(:payment)] }
end
trait :purchased_with_coupon do |job|
job.reviewed_at { Time.now }
job.start_at { Time.now }
job.end_at { AppConfig.product['settings']['job_active_for_day_num'].day.from_now }
job.paid_at { Time.now }
association :coupon, factory: :coupon
payments { |j| [j.association(:payment)] }
end
trait :expired do |job|
start_at = (200..500).to_a.sample.days.ago
job.reviewed_at { start_at }
job.start_at { start_at }
job.end_at { |j| j.start_at + AppConfig.product['settings']['job_active_for_day_num'].days }
job.paid_at { start_at }
payments { |j| [j.association(:payment)] }
end
end
end
FactoryGirl.define do
factory :payment do
job
# price_paid { rand(100..150) }
price_paid { 1 }
stripe_customer_token { (0...50).map { (65 + rand(26)).chr }.join }
end
end
FactoryGirl.define do
factory :coupon do
code { rand(25**10) }
percent_discount { rand(100**1) }
start_at { 2.days.ago }
end_at { 30.day.from_now }
trait :executed do |c|
association :job, factory: [:job, :purchased]
c.executed_at { Time.now }
end
end
end
Models
class Job < ActiveRecord::Base
acts_as_paranoid
strip_attributes
acts_as_taggable
acts_as_taggable_on :languages
belongs_to :company
before_validation :find_company
belongs_to :category
has_one :coupon
has_many :payments
before_create :create_slug, :set_price
after_create :update_vanity_url
accepts_attachments_for :company
accepts_nested_attributes_for :company
accepts_nested_attributes_for :coupon
accepts_nested_attributes_for :payments
validates :title,
:location,
:short_description,
presence: true,
format: { with: /\A[\w\d .,:-#]+\z/, message: :bad_format }
validates :application_process,
presence: true,
format: { with: %r{\A[\w\d .,:/#&=?-]+\z}, message: :bad_format }
validates :title, length: { minimum: 10, maximum: 45 }
validates :location, length: { minimum: 10, maximum: 95 }
validates :short_description, length: { minimum: 10, maximum: 245 }
validates :application_process, length: { minimum: 10, maximum: 95 }
validates :description,
:category_id,
:language_list,
presence: true
validates :reviewed_at,
:start_at,
:end_at,
:paid_at,
date: { allow_blank: true }
validates :start_at, date: { before: :end_at, message: :start_at_before_end_at }, if: proc { start_at? }
validates :end_at, date: { after: :start_at, message: :end_at_after_start_at }, if: proc { end_at? }
scope :active, -> { where.not(reviewed_at: nil, paid_at: nil).where('end_at >= ?', Date.today) }
def expired?
end_at.present? && end_at < Date.today
end
def reviewed?
reviewed_at.present?
end
def paid_for?
reviewed? && paid_at.present?
end
def active?
reviewed? && paid_at.present? && end_at <= Date.today
end
private
def set_price
self.price = AppConfig.product['settings']['job_base_price']
end
def create_slug
self.slug = title.downcase.parameterize
end
def update_vanity_url
self.vanity_url = '/jobs/' + company.slug + '/' + slug + '/' + id.to_s + '/'
save
end
def find_company
existing_company = Company.where(email: company.email) if company
self.company = existing_company.first if existing_company.count > 0
end
end
class Coupon < ActiveRecord::Base
acts_as_paranoid
strip_attributes
belongs_to :job
validates :start_at, date: { before: :end_at }
validates :executed_at, date: { allow_blank: true }
validates_presence_of :job, if: proc { executed_at? }
validates_presence_of :executed_at, if: :job
validates :code,
presence: true,
length: { minimum: 10, maximum: 19 },
uniqueness: { case_sensitive: false },
numericality: { only_integer: true }
validates :percent_discount,
inclusion: { in: 1..100 },
length: { minimum: 1, maximum: 3 },
numericality: { only_integer: true },
presence: true
scope :active, -> { where('start_at < ? AND end_at > ? AND executed_at IS ?', Date.today, Date.today, nil) }
def active?
start_at < Date.today && end_at > Date.today && executed_at.nil?
end
def executed?
job_id.present?
end
end
class Payment < ActiveRecord::Base
belongs_to :job
belongs_to :coupon
validates_presence_of :job
validate :coupon_must_be_active
before_create :net_price
Numeric.include CoreExtensions::Numeric::Percentage
attr_accessor :coupon_code
def coupon_code=(code)
#coupon = Coupon.find_by_code(code)
end
def net_price
return job.price unless #coupon
job.price = #coupon.percent_discount.percent_of(job.price)
self.coupon = #coupon
end
private
def coupon_must_be_active
if #coupon
errors[:coupon] << I18n.t('flash_messages.coupons.id.inactive') unless #coupon.active?
elsif #coupon_code.present?
errors[:coupon_code] << I18n.t('flash_messages.coupons.id.not_found')
end
end
end
It looks like the problem is that there is logic outside of your models that is updating the price_paid column on your Payment, and possibly setting the coupon_id on it as well.
So I would recommend duplicating any extra logic that might be coming from your controllers, service classes, etc. into an after(:create) callback on your factory.
trait :purchased_with_coupon do
# ...other attributes...
association :coupon
after(:create) do |job, evaulator|
discount_value = 100 - job.coupon.percent_discount) / 100.0
calculated_price_paid = job.price * discount_value
create(:payment, price_paid: price_paid, job: job, coupon: coupon)
end
end
Now ultimately, that code belongs in some kind of abstraction, such as a service class that can easily be tested (and used in other tests). However, you mentioned you are getting started on a refactor and want passing tests. I think this is a reasonable compromise until you're ready to abstract it. Ultimately, I would do something like this:
class CreatePaymentWithCoupon
attr_reader :job
def initialize(job)
#job = job
end
def call
job.payments.create(coupon: job.coupon, price_paid: discounted_price)
end
private
def discounted_price
discount_value = (100 - job.coupon.percent_discount) / 100.0
job.price * discount_value
end
end
Then, in your specs:
it "calculates discounted price" do
coupon = create(:coupon, percent_discount: 25)
job = create(:job, :purchased_with_coupon, price: 100)
CreatePaymentWithCoupon.new(job).call
expect(job.payments.first.price_paid).to eq(75.0)
end

Validating min and max length of a phone number according to select of country code

I have phone number field with country code in drop-down, Now I want to validate Max length validation according to selection of country code in drop-down.
profile.rb
validates_length_of :phone, :minimum => 10, :maximum => 10 if country_code = 91
You can't do that; if would evaluate at class definition, not at validation time. You either need to use :if option:
validates_length_of :phone, :minimum => 10, :maximum => 10,
:if => Proc.new { |x| x.country_code == 91 }
or you need to use a custom validator, something like:
PHONE_LENGTH_LIMITS_BY_COUNTRY_CODE = {
91 => [10, 10]
}
def phone_number_is_correct_according_to_country_code
min, max = *PHONE_LENGTH_LIMITS_BY_COUNTRY_CODE[country_code]
if phone.length < min || phone.length > max
errors.add(:phone, "must be between #{min} and #{max} characters")
end
end
validate :phone_number_is_correct_according_to_country_code
(Disclaimer: untested code)

Hide password_digest error message in JSON output

I'm building a simple JSON API using the rails-api gem.
models/user.rb:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email, :password, :password_confirmation
validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i }
validates :password, presence: { on: :create }, length: { minimum: 6 }
end
When I try to sign up without a password this is the JSON output I get:
{
"errors": {
"password_digest": [
"can't be blank"
],
"password": [
"can't be blank",
"is too short (minimum is 6 characters)"
]
}
}
Is it possible to hide the error message for password_digest? I'm returning the #user object with respond_with in the controller.
I've tried the following but no luck (it just duplicates the error "can't be blank"):
validates :password_digest, presence: false
#freemanoid: I tried your code, and it didn't work. But it gave me some hints. Thanks! This is what worked for me:
models/user.rb
after_validation { self.errors.messages.delete(:password_digest) }
You can manually delete this message in json handler in User model. Smth like:
class User < ActiveRecord::Base
def as_json(options = {})
self.errors.messages.delete('password_digest')
super(options)
end
end

Resources