Rails 3 validations problem - ruby-on-rails

In my form I would like users to type a date in DD/MM/YYYY format. I have validation that checks that.
The problem is that in the database it is stored in YYYY-MM-DD format, so if try just to update the is_money_paid field:
job = Job.find(params[:id])
job.update_attributes(:is_money_paid => ...)
the validation fails saying that job's date is in wrong format (YYYY-MM-DD rather than DD/MM/YYYY).
What would be an appropriate way to solve this ?
Here is the relevant code:
class Job < ActiveRecord::Base
validate :job_date_validator
end
DATE_REGEX = /\A\d{2}\/\d{2}\/\d{4}\z/
def job_date_validator
if job_date_before_type_cast.blank?
errors.add(:job_date, "^Date must be selected")
elsif job_date_before_type_cast !~ DATE_REGEX
errors.add(:job_date, "^Date must be in DD/MM/YYYY format")
end
end
is_money_paid is a boolean field.

I would change the validator to say:
validate_presence_of :job_date, :message => "must be selected"
validate :job_date_validator, :if => :job_date_changed?
Or something along those lines.
You can take out the .blank? check too.

Format it in before_validation
http://guides.rubyonrails.org/active_record_validations_callbacks.html#updating-an-object

I would suggest you to see gem validates_timeliness which is rails 3 compatible.

Related

Regex on validates_format_of

I have a field in a form, and I need it to be registered only if its format equals one of the two regex I am using.
I have tried to use methods, the helper structure validates_format_of with Proc.new, but may not be using the correct syntax or the correct data type
private def format_field
errors.add(
:field, "The field does not have a valid format."
)unless[
/[0-9]{3}\.?[0-9]{3}\.?[0-9]{3}\-?[0-9]{2}/,
/[0-9]{2}\.?[0-9]{3}\.?[0-9]{3}\/?[0-9]{4}\-?[0-9]{2}/
].include?(field.format)
end
A simple approach to this is the following:
FORMAT_REGEXES = [
/[0-9]{3}\.?[0-9]{3}\.?[0-9]{3}\-?[0-9]{2}/,
/[0-9]{2}\.?[0-9]{3}\.?[0-9]{3}\/?[0-9]{4}\-?[0-9]{2}/
].freeze
def validate_format
return unless FORMAT_REGEXES.find { |regex| field.format =~ regex }
errors.add(:field, :invalid, message: 'The field does not have a valid format')
end

Understanding Rails date in form_for

I am trying to get a date from a user and send it inside an email as plain text in the following format: "07/30/2015".
In order to do that, if the output I am getting is a string, I could just do:
Date.parse("2015-07-30").strftime("%m/%d/%Y")
The problem is, I am getting a FixNum.
The issues are many:
If I try to convert to a string to parse it with Date.parse, it becomes "2001".
If I apply the code I just wrote, Date.parse... it will throw 'invalid date'.
For instance:
(2016-02-13).to_s #=> "2001"
(2016-02-13).to_date #=> NoMethodError: undefined method `to_date' for 2001:Fixnum
Date.parse("2001").strftime("%m/%d/%Y") #=> invalid date
So if I can convert 2015-07-30 into "2015-07-30", it would work:
Date.parse("2015-07-30").strftime("%m/%d/%Y") #=> "07/30/2015"
Then I tried using date_select instead of date_field, but now the message arrives with those fields empty.
Any suggestions?
Here is my form:
= form_for #contact do |f|
= f.text_field :product_name
= f.date_field :purchase_date
= f.submit
Here is my code:
<%= message.subject %>
<% #resource.mail_form_attributes.each do |attribute, value|
if attribute == "mail_subject"
next
end
%>
<%= "#{#resource.class.human_attribute_name(attribute)}: #{Date.parse(value).class == Date ? Date.parse(value).strftime("%m/%d/%Y") : value}" %>
<% end %>
My controller:
class ContactsController < ApplicationController
before_action :send_email, except: [:create]
def create
#contact = Contact.new(params[:contact])
#contact.request = request
if #contact.deliver
#thank = "Thank you for your message!"
#message = "We have received your inquiry and we'll be in touch shortly."
else
#error = "Cannot send message. Please, try again."
end
end
def contact_page
end
def product_complaint
#the_subject = "Product Complaint Form"
end
private
def send_email
#contact = Contact.new
end
end
My model:
class Contact < MailForm::Base
# all forms
attribute :mail_subject
attribute :first_name, validate: true
attribute :last_name, validate: true
# product-complaint
attribute :best_by, validate: true, allow_blank: true # date
attribute :bag_code, validate: true, allow_blank: true
attribute :purchase_date, validate: true, allow_blank: true # date
attribute :bag_opened, validate: true, allow_blank: true # date
attribute :problem_noticed, validate: true, allow_blank: true # date
# all forms
attribute :message, validate: true
attribute :nickname, captcha: true
def headers
{
content_type: "text/plain",
subject: %(#{mail_subject}),
to: "xxxxx#xxxxxxx.com",
# from: %("#{first_name.capitalize} #{last_name.capitalize}" <#{email.downcase}>)
from: "xxx#xxxxx.com"
}
end
end
(2016-02-13).to_date #=> NoMethodError: undefined method `to_date' for 2001:Fixnum
youre getting this error because you dont have quotes around the value. i.e. its not a string, its a number that is having subtraction applied to it. this is being interpreted as
2016 - 2
2014 - 13
2001.to_date
it needs to be ('2016-02-13').to_date
if youre unable to get it as a string, can you post how you're getting it from the user to begin with? (a date field ought to be sending you a string to your controller, not a series of numbers)
You're not understanding something about receiving values from forms: You can NOT receive an integer, a fixnum or anything else other than strings. So, you can't have received 2016-02-13. Instead you got "2016-02-13" or "2016", "02" or "2" and "13" depending on the form. If you're running under Rails, then it got the strings, and through its meta-data understands you want an integer (which really should probably be defined as a string), and it converts it to an integer for you.
Either way, when you write:
(2016-02-13).to_s
(2016-02-13).to_date
you're propagating that misunderstanding into your testing. This is how it MUST be written because you need to be working with strings:
require 'active_support/core_ext/string/conversions'
("2016-02-13").to_s # => "2016-02-13"
("2016-02-13").to_date # => #<Date: 2016-02-13 ((2457432j,0s,0n),+0s,2299161j)>
You can create dates without them being strings though: Ruby's Date initializer allows us to pass the year, month and day value and receive a new Date object:
year, month, day = 2001, 1, 2
date = Date.new(year, month, day) # => #<Date: 2001-01-02 ((2451912j,0s,0n),+0s,2299161j)>
date.year # => 2001
date.month # => 1
date.day # => 2
Moving on...
Parsing dates in Ruby quickly demonstrates it's not a U.S.-centric language. Americans suppose all dates of 01/01/2001 are in "MM/DD/YYYY" but that's a poor assumption because much of the rest of the world uses "DD/MM/YYYY". Not knowing that means that code written under that assumption is doing the wrong thing. Consider this:
require 'date'
date = Date.parse('01/02/2001')
date.month # => 2
date.day # => 1
Obviously something "wrong" is happening, at least for 'mericans. This is very apparent with:
date = Date.parse('01/31/2001')
# ~> -:3:in `parse': invalid date (ArgumentError)
This occurs because there is no month "31". In the previous example of '01/02/2001', that misunderstanding means the programmer thinks it should be "January 2" but the code thinks it's "February 1", and work with that. That can cause major havoc in an enterprise system, or anything dealing with financial calculations, product scheduling, shipping or anything else that works with dates.
Because the code is assuming DD/MM/YYYY format for that sort of string, the sensible things to do are:
KNOW what format your users are going to send dates in. Don't assume, ever. ASK them and make your code capable of dealing with alternates, or tell them what they MUST use and vet out their data prior to actually committing it to your system. Or, provide a GUI that forces them to pick their dates from popups and never allows them to enter it by hand.
Force the date parser to use explicit formats of dates so it can always do the right thing:
Date.strptime('01/31/2001', '%m/%d/%Y') # => #<Date: 2001-01-31 ((2451941j,0s,0n),+0s,2299161j)>
Date.strptime('31/01/2001', '%d/%m/%Y') # => #<Date: 2001-01-31 ((2451941j,0s,0n),+0s,2299161j)>
The last point is the crux of writing code: We're telling the language what to do, not subjecting ourselves, and our employers, to code that's guessing. Give code half a chance and it'll do the wrong thing, so you control it. That's why programming is hard.

Best date validation in Rails 4?

What is the best way to validate a date in Ruby on Rails? I need to make sure the "birthday" is a date, is less than 125 years ago and is not in the future (today is ok).
I have tried three methods:
1) date_validator gem
I used the following code (after installing the gem):
validates :birthday,
date: {
after: Proc.new {Time.now - 125.years}, message: :after,
before: Proc.new {Time.now}, message: :cannot_be_in_the_future
}
This worked except that I could set the date to the number 12 and pass validation.
2) Checking if the date is in a range of dates, in a custom validation method, like so:
from = 125.years.ago.to_date
to = Time.now.to_date
unless (from..to).include? birthday
errors.add(:birthday, :custom_error_msg)
end
This worked well and passed all my tests, but the drawback is that you only get one error message. I would have liked separate error messages for the case when the date is in the future, when it is too long ago and when the input is not a date.
3) Multiple checks in a custom validation method, like so:
begin
birthday.to_date
rescue
errors.add(:birthday, "must be a date")
else
if birthday > Time.now
errors.add(:birtday, "cannot be in the future")
elsif birthday < Time.now - 125.years
errors.add(:birthday, "cannot be over 125 years ago")
end
end
This also passes all my test, and I get different messages as explained above.
So I am wondering, is this the best method? Can it be improved at all (except that the error message text needs work)?
Thanks
For this simple validation, I think following ruby code is enough!
Please check :
validate :is_valid_dob?
private
def is_valid_dob?
if((birthday.is_a?(Date) rescue ArgumentError) == ArgumentError)
errors.add(:birthday, 'Sorry, Invalid Date of Birth Entered.')
end
end
Just use gem 'validates_timeliness'
In your case, using this gem
validates_date :birthday, on_or_after: lambda { 125.years.ago }
You can easily use validates_each method
Just put there 2 validations:
1) For birthday in the past
validates_each :birthday do |record, attr, value|
record.errors.add(attr, 'must be in the past') if value >= Time.now.to_date
end
2) For birthday not more than 150 years ago
validates_each :birthday do |record, attr, value|
record.errors.add(attr, 'must be less than 150 years in the past')
if value <= (Time.now.to_date - 125.years)
end

Ruby validation based off of different field

I am new to rails validation.
I have two fields:
field :feed_entitlements, :type => Array
...
field :alert_news, :type => Boolean, :default => false
I want to put a validation on "alert_news" that requires a count > 0 in "feed_entitlements". Is this possible using a rails validation? It seems like all examples of validations that I can find are simple "can't be blank" type problems.
Thanks.
Add to your model this code
def validate
errors.add_to_base "count should be more then 0" if feed_entitlements.count < 0
end

Rails 3 - custom validation to check date type

I want to validate my date (which actually have DATE type) in model. So, i try to write for that simle method and run it via validation.
Teacher
class Teacher < ActiveRecord::Base
attr_accessible :teacher_birthday # DATE type!
belongs_to :user
validates :teacher_birthday, :presence => true,
:unless => :date_is_correct?
########
def date_is_correct?
parsed_data = Date._parse(:teacher_birthday)
input_day = parsed_data[:mday]
input_month = parsed_data[:mon]
input_year = parsed_data[:year]
correct_days = 1..31
correct_months = 1..12
correct_year = 1900..2000
if ( correct_days.member? input_day ) and ( correct_months.member? input_month) and
( correct_year.member? input_year)
true
else
errors.add(:teacher_birthday, 'date is invalid')
false
end
end
When i run rspec a lot of tests fail.
TypeError: can't convert Symbol into String
# ./app/models/teacher.rb:56:in `_parse'
# ./app/models/teacher.rb:56:in `date_is_correct?'
I suppose i do something wrong. Can someone tell me what is wrong?
This isn't necessary at all. If Date.parse(:teacher_birthday) returns a date and doesn't raise an exception, you have a valid date.
Date._parse expects a string-value containing a date and will in your code always try to parse 'teacher_birthday'. You need to get the value of the field first and pass the value to Date._parse. ActiveRecord creates methods with the same name as the field to get the value.
Any of the following will work:
Short way
parsed_data = Date._parse(teacher_birthday)
Identically to the first (the self. is added for you during parsing)
parsed_data = Date._parse(self.teacher_birthday)
Explicit way
parsed_data = Date._parse(self[:teacher_birthday])
A new gem has been created to help validate types in rails and an explanatory blog post exists to answer more of the "why" it was created in the first place.
With this library your code would simple be:
class Post < ActiveRecord::Base
validates_type :teacher_birthday, :date
end
This will throw an exception when anything except a a Date is assigned to :teacher_birthday.

Resources