How to validate start date on update? - ruby-on-rails

How to validate start date on update that it should not be previous date than previously saved date.
eg:- like I have created record with start date as 07/11/2013, on update it should not before the 07/11/2013.
in view:
f.input :start_date, as: :datepicker
f.input :end_date, as: :datepicker
model:
validates :start_date, allow_nil: true, date: { after: Date.today - 1, message: 'must be today or after today' }, on: :create
validates :end_date, allow_nil: true, date: { after: :start_date, message: 'must be after start date' }
Thanks.

I can't test it right now but I think it might work:
validate :previous_start_date
def previous_start_date
old_start_date = Model.find(self.id).start_date
if(old_start_date > self.start_date)
self.errors.add(:start_date, "Can't be previous than the initial date")
end
end
At the moment of the validation, the object hasn't been saved yet, so, I believe that retrieving the object from the database will give you the previous value. With the value in hand you can compare with the current start_date and then add your custom error.
I hope it helps.

You can add attr_accessor :previous_start_date(dont forget also about attr_accessible) plus add hidden field on form. This field must have value equal to start_date from DB.
Then you can use after :previous_start_date.
Note: value previous_start_date must be set from DB, maybe better to do it in model in getter method or set in before_validation callback.

Related

How to validate with comparison against a blank value?

I have a Rails ActiveModel with two fields date_from and date_to and I want the model to be valid when (and only when)
either of these fields or both are blank
date_from < date_to
In other words, the model should be invalid only when both fields are set but they're in the wrong order. In that case I also want both fields to be marked as invalid.
I tried with
validates :date_from, comparison: { less_than_or_equal_to: :date_to }, allow_blank: true
validates :date_to, comparison: { greater_than_or_equal_to: :date_from }, allow_blank: true
But that fails when exactly one of the fields is set with
#<ActiveModel::Error attribute=date_to, type=comparison of Date with nil failed, options={}>
How can I make the comparison validation pass when the referenced field is blank?
It can be done with two separate validates calls by adding a conditional check with if option
validates :date_from,
comparison: { less_than_or_equal_to: :date_to },
allow_blank: true,
if: :date_to # make sure `date_to` is not `nil`
validates :date_to,
comparison: { greater_than_or_equal_to: :date_from },
allow_blank: true,
if: :date_from
This will skip these validations if one of the dates is nil. When both dates are present it runs both validations and adds two separate errors, which may be not quite right, since it is essentially one error.
To make the intent of this validation more obvious, a validate method is a better fit
validate :date_from_is_less_than_date_to
def date_from_is_less_than_date_to
return unless date_from && date_to # valid if date(s) are missing
unless date_from < date_to
errors.add(:base, 'Date range is invalid')
# NOTE: to add errors to show on form fields
# errors.add(:date_from, 'must come before Date to')
# errors.add(:date_to, 'must come after Date from')
# NOTE: to add errors only for date that changed
# requires ActiveModel::Dirty
# errors.add(:date_from, 'must come before Date to') if date_from_changed?
# errors.add(:date_to, 'must come after Date from') if date_to_changed?
end
end

Created_at.1.year.from_now - Rails

I need to make a method that renders a date to a table that is one year past the creation date. I've tried the line as listed in the title, but that didn't work. I have a table right now that lists "date joined" next to it I'd like it to say "date expired". which will be one year from the date joined.
Example:
class Subscriber < ActiveRecord::Base
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true
validates :phone_number, presence: true
def date_joined
created_at.strftime("%-m/%-d/%-y")
end
def expiration_date
created_at.1.year.from_now
end
end
How should I format that expiration_date method. The date_joined works fine.
You should add 1.year to the created_at time object:
def expiration_date
created_at + 1.year
end
Formatted:
def expiration_date
(created_at + 1.year).strftime("%-m/%-d/%-y")
end
rails console:
=> some_object.created_at
=> Wed, 12 Apr 2016 17:37:12 UTC +00:00
=> some_object.created_at + 1.year
=> Wed, 12 Apr 2017 17:37:12 UTC +00:00
=> (some_object.created_at + 1.year).strftime("%-m/%-d/%-y")
=> "4/12/17"
Remember that created_at isn't populated until the model is saved, your calculations won't work until then. This could be a problem.
The date_joined method you have shouldn't exist. Any formatting concerns should be the responsibility of your view, so push that logic in there like this:
<%= model.created_at.strftime("%-m/%-d/%-y") %>
You can even define a format for your dates using the locales as demonstrated in this question where you can add this to config/locales/en.yml:
en:
date:
formats:
default: "%-m/%-d/%-y"
time:
formats:
default: "%-m/%-d/%-y %H:%M"
Then you can use this in your view as the default without any special handling required:
<%= model.created_at %>
That will format all times the way you want instead of you having to go out of your way to define special formatter methods for each model and then remember to call them.
When it comes to computing dates in the future you can do math on dates:
def expiration_date
(self.created_at || DateTime.now) + 1.year
end
That will work even if the model hasn't been saved.
I feel like you're actually asking the wrong question here. Rather than doing this feature in Rails, you're asking a Ruby question which is "how do I work with interaction between Ruby datetime objects." I suggest you take at Ruby/Rails datetime objects and see how they work first.
That said, I'm pretty sure someone else is gonna post the answer you want to see.

Rails Thinks Field is Nil When it Shouldn't

I have an Event model which has a start_date and end_date. I have a simple validation to make sure that the end_date is after the start_date, however, the validation keeps failing when a date field is not changed, but another field is updated. In such cases, it interprets the fields are nil, even though in the trace, the record shows the proper field values.
# error
undefined method `<' for nil:NilClass
app/models/event.rb:32:in `end_after_start'
# validation in event.rb
attr_accessible :end_date, :start_date
validate :end_after_start
def end_after_start
if end_date < start_date
errors.add(:end_date, "must be after the start date")
end
end
# request parameters in trace
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"DD6rVimJxAJclO4IKfv69Txn8XkJZ4IpHZhh+cHOpg4=",
"event"=>{"name"=>"Birthday",
"start_date"=>"05/16/2013",
"end_date"=>"05/31/2013",
"commit"=>"Submit",
"id"=>"5"}
# _form
<%= f.text_field :start_date, :value => (#event.start_date.strftime("%m/%d/%Y") if #event.start_date.present?) %>
<%= f.text_field :end_date, :value => (#event.end_date.strftime("%m/%d/%Y") if #event.end_date.present?) %>
Even though I see the end_date and start_date populated in the trace parameters, if I add put start_date (or end_date) in the end_after_start validation, it prints as nil to the console.
The problem is your form is formatting your dates as "mm/dd/yyyy" and the fields are being submitted to your application in that format as strings.
There is no implicit conversion of a string in that format to a DateTime so your start_date and end_date are ending up nil.
Try to set:
attr_accessor :end_date, :start_date
If you do not have the columns in your database, you will need that.
attr_accessible allows parameters in a mass association.
attr_accessor sets a getter and a setter.
If you have those columns you could try prepending self..
I got this to work by installing the american_date gem. It allows for my date field to be displayed as MM/DD/YYYY and validates and saves my date correctly.

Rails : Validates_format_of for float not working

I am new at Ruby on Rails.
I was trying to validate format of one of the attribute to enter only float.
validates :price, :format => { :with => /^[0-9]{1,5}((\.[0-9]{1,5})?)$/, :message => "should be float" }
but when I enter only character in price, it accepts it and show 0.0 value for price.
can anybody tell, what is wrong in this or why this happens?
This is my solution,
validates :price,presence:true, numericality: {only_float: true}
when you fill in for example 7 it automatically transfer the value to 7.0
For rails 3:
validates :price, :format => { :with => /^\d+??(?:\.\d{0,2})?$/ },
:numericality =>{:greater_than => 0}
A float is a number and regular expressions are for strings.
It appears that when you enter a string for the float, it gets converted as 0.0 automatically by Rails.
Do you have a default (0.0) on the column? If yes, then you may try removing it and use validates_presence_of :price only.
Something to try: instead of putting the string directly into the price column, put it into a price_string attr and use a before_save callback to try to convert the string to price. Something like that:
attr_accessor :price_string
before_save :convert_price_string
protected
def convert_price_string
if price_string
begin
self.price = Kernel.Float(price_string)
rescue ArgumentError, TypeError
errors.add(ActiveRecord::Errors.default_error_messages[:not_a_number])
end
end
And in your form, change the name of the text_field to :price_string.

How to validate a rails model against the original value but store the processed value

I am interacting with a time duration in a rails form, currently it is a text box and the format requires MM:SS
I have the validator:
validates_format_of :time, :with => /^[0-9]?[0-9]{1}[:][0-9]{2}$/, :allow_nil => true, :allow_blank => true, :message => 'format must be MM:SS'
though I want to store this in the database as an integer(seconds) to make it easier to do reporting on that field.
I overwrote the accessors as:
def time=(new_time)
parts = new_time.split(':')
write_attribute(:time, (parts[0].to_i * 60) + parts[1].to_i)
end
def time
Time.at(read_attribute(:time).to_i).gmtime.strftime('%R:%S')
end
but it ends up sending a validation error since the time attribute is just an integer after it gets set by the time= method.
How do store a duration value in the database in seconds but still enforce validation in a different format (MM:SS)?
I don't know if this is the best solution, but I believe you could use after_validation like the following:
after_validation :convert_time_to_integer
def convert_time_to_integer
parts = self.split(':')
write_attribute(:time, (parts[0].to_i * 60) + parts[1].to_i)
end

Resources