I am using the timeliness gem to do time and date validation.
My event model has a time field storing a string like "11:15", a start_date and end_date storing a Date object.
I want the following validation behaviour:
If a user tries to create an event on the current day of the year (so start_date == Date.today), and the time is in the past (so if it was 15:30 when the event was created and the user entered 11:30), then the validation should fail. I.e we only want events created where the date is today or is in the future, and the time, if the date is today, is in the future.
I am trying the following validation for this:
validates :time, :presence => true,
:timeliness => { :type => :time, :on_or_after => lambda { if(:start_date == Date.today && :day_of_week.downcase == Date.today.strftime("%A").downcase) then return Time.new(:time.to_s).in_time_zone(:timezone)>Time.now.in_time_zone(:timezone) else return true end } }
However it is not behaving correctly. I.e I am able to create an event with the start_date of 03/08/2015 and a time of 09:00 despite it being 15:31 on 03/08/2015! Please help!
Your lambda doesn't make a lot of sense, because you're using symbols everywhere - :start_date == Date.today will always be false. The value of lambdas in validations is that they're passed your Event instance, so you can reference event.start_date. I agree with #arthur.karganyan in the comments that if you need something this complicated, it'll be easier to work with as a method.
But, you're also making this much more complicated than you need to. It looks like you have start_date and time as separate attributes, which is difficult to work with because they'll tend to rely on each other. I'd recommend making those a single start_time attribute instead, and then you can use validates_timeliness like so:
validates_datetime :start_time, on_or_after: -> { Time.now }
If you must have separate attributes, your validations might look like:
validates_date :start_date, on_or_after: -> { Date.today }
validates_time :time, on_or_after: lambda { |event| Time.now if event.start_date.today? }
This validates that the date is today or in the future, and if it's today, also checks the time. validates_timeliness appears to accept any time when on_or_after lambda evaluates to nil.
Related
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.
I'm trying to validate a date of birth field that has to be in a certain range:
validates :year_of_birth, :inclusion => { :in => 1900..Date.today.year - 5 }
Although for this case it wouldn't pose such a big problem, I realized that this is only valid in development where models are reloaded every time and thus the current year is calculated again.
When in production, how would I avoid that, say, at the turn of the year, the right end of the range remains the same as it was the year before?
in cases like these, I suggest you go for a custom validation.
validate :validates_year_of_birth
def validates_year_of_birth
five_years_ago = 5.years.ago.year
if year_or_birth && (year_of_birth < 1990 || year_of_birth > five_years_ago)
errors.add :year_of_birth, "should be between 1990 and #{five_years_ago}"
end
end
or you can pass the validation in a proc
validates :year_of_birth, :inclusion => { :in => proc { 1900..5.years.ago.year } }
Ok, I'm slowly getting a grasp on this, but I need some more help.
I'm using a time_select in my view, therefore I'm dealing with a multiparameter assignment. Check.
<%= pt.time_select :time, :twelve_hour => true, :minute_step => 5 %>
BUT I'm doing the naughty thing and using it with an attribute that isn't in the database:
attr_accessor time
Therefore since it can't look at the db, it can't piece together what the multiparameter assignment is supposed to be and therefore I get the following error:
1 error(s) on assignment of multiparameter attributes
Thus I am using information I found here:
composed_of :time,
:class_name => 'DateTime',
:mapping => [%w(DateTime to_s)],
:constructor => Proc.new{ |item| item },
:converter => Proc.new{ |item| item }
Other helpful links: rubyonrails.org | apidock.com
This remove the error, but now the issue is that the plugin I'm using doesn't function properly, I think. I am using http://code.google.com/p/rails-twelve-hour-time-plugin/. The goal being, I'm trying to get the time select to have 3 drop downs: hh:mm am/pm.
So the question is: how do I need to adjust my composed_of method in order for it to be properly converted by the plugin? OR is there a better method to this madness?
I'm not sure what mappers / constructors / converters I need. Right now the object keeps the hour and minute, except the hour isn't converted to 24 hour (which the plugin is supposed to take care of, I thought).
The issue was that I didn't follow the article properly. I should have been using the Time model.
composed_of :time,
:class_name => 'Time',
:mapping => [%w(Time to_s)],
:constructor => Proc.new{ |item| item },
:converter => Proc.new{ |item| item }
Looks like this is a common problem. For more information about it check out this link. The suggestion given there is to do:
class Whatever < ActiveRecord::Base
...
attr_accessor :arrival_time
columns_hash["arrival_time"] = ActiveRecord::ConnectionAdapters::Column.new("arrival_time", nil, "time")
end
I'm still working out bugs, but I'll update this when I get something working.
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.
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