Associated model validations in Rails - and some questions of design
I've got a Task which has many LoggedTimes. I want to put a limit of 8 hours of logged time against a task on any given day. What is the best way to go about this so that I can check whether the person's latest logged time "takes their total over the limit" for a day, so to speak?
Here's where I am beginning (Task.rb):
validate :max_logged_daily_time
def max_logged_daily_time
if (params[:session_time] + (logged_times.where(:created_at => Date.today).to_a.sum(&:session_time)/60)) > 8
errors.add_to_base("Can't have more than 8 hours logged a day")
logged_time.errors.add('session_time', 'Logged times exceeded today')
end
end
Currently this validation is not working (adding another LoggedTime once 8 hours of previous logged times have been registered simply adds it to the rest, instead of throwing an error. Since no errors are being thrown, I'm struggling to pick my way through the problem. Is it something to do with the handling of params?
Which brings me to the question of design: in theory I could revise the view so that the user is only able to submit 8 hours minus the total amount of time they've l开发者_高级运维ogged that day; however this seems like a clunky solution and against the principle of keeping validations in the model. (And it certainly doesn't help me solve this model validations problem).
Any advice here?
TIA
class Task < ActiveRecord::Base
has_many :logged_times
def hours_today
LoggedTime.daily_hours_by_task(self).to_a.sum(&:session_time)
end
end
class LoggedTime < ActiveRecord::Base
belongs_to :task
scope :daily_hours_by_task, lambda { |task| task.\
logged_times.\
where('logged_times.created_at >= ? AND logged_times.created_at < ?',
Date.today, Date.today + 1) }
validate :max_logged_daily_time
private
def max_logged_daily_time
if task && ((task.hours_today + session_time) / 60.0) > 8
errors.add('session_time', 'Logged times exceeded today')
end
end
end
Some notes:
created_at
is a DateTime, so you'll need to test both the start and end of the dayThe validation also prevents the addition of a single LoggedTime which by itself exceeds the maximum.
Dividing by an integer truncates and will give the wrong result -- add the
.0
to convert to a float.This only validates the LoggedTime, not the Task, so you might want to add
validates_associated
in the Task model.Validation is bypassed when Task is nil
EDIT
Well, hours_today
should really be called minutes_today
, but you get the idea.
I'd create a separate method for getting the sum of hours for a given day.
def total_hrs_logged_for_date(date)
#some code
end
Test that method to make sure it works.
Possibly also do the same for calculating the current logged time.
then use those those two in your custom validator
so this line
if (params[:session_time] + (logged_times.where(:created_at => Date.today).to_a.sum(&:session_time)/60)) > 8
becomes
if total_hrs_logged_for_date(Date.today) + current_time_being_logged > 8
At the very least it will help you narrow down which of these isn't working.
I also notice that you have "params[:session_time]"
I think this is in Task.rb which sounds like a model. Probably what you want is just "session_time" instead.
I ended up revising Zetetic's response slightly, because I couldn't get it to work as is.
In the end, this worked:
class Task < ActiveRecord::Base
has_many :logged_times
validates_associated :logged_times
def minutes_today
logged_times.where('created_at >= ? AND created_at < ?', Date.today, Date.today + 1)
end
end
and the LoggedTime model:
class LoggedTime < ActiveRecord::Base
belongs_to :task
validate :max_logged_daily_time
private
def max_logged_daily_time
if task && ((task.minutes_today + session_time) / 60.0) > 8
errors.add('session_time', 'Logged times exceeded today')
end
end
end
I'm not sure why the scope method baulked, but it did. Any hints Zetetic?
精彩评论