3

I need to find a way to get rails (3.2.8) to save nested attributes before performing validation on the parent object. I've searched a long time for an answer to my problem and, while I've found similar questions, I haven't yet seen an elegant solution.

Here's the situation:

I have an activity_log where each log_entry has multiple activities. The log_entry has a field called 'hours', which represents the total time worked that day. Each activity also has an 'hours' field, representing time spent on that activity. I need to make sure that log_entry.activities.sum(:hours) is not greater than log_entry.hours.

Here's the code:

class LogEntry < ActiveRecord::Base
  attr_accessible :date, :hours, :activities_attributes

  has_many :activities
  accepts_nested_attributes_for :activities

  validate :sum_hours_not_more_than_total_hours

  private
  def sum_hours_not_more_than_total_hours
    unless activities.sum(:hours) <= hours
      errors.add(:hours, "Hours cannot exceed total hours")
    end
  end
end

The problem is that it checks against the db when calculating activities.sum(:hours) instead of checking against the new data in params. So if I set a log_entry's hours to 4 and create an activity with hours set to 5, it will save with no errors (because the db says that activities.sum(:hours) == 0). But if I edit that same record and set the activity's hours to 4 it will give an error (because the db says activities.sum(:hours) == 5), when it should pass.

I found a workaround here: http://railsforum.com/viewtopic.php?id=41562, but I was hoping there was a more elegant way of solving this problem. (Actually, this solution doesn't work for me as described because it assumes that the only problem is that records are passing validation that shouldn't. I also have the problem that records fail validation when they should pass. I imagine I could fix it by skipping the validation step and then doing a check like sum_hours_not_more_than_total_hours in the controller (after save), but I really like the sense of security I get from validation.)

4
  • The solution I linked to is less useful than I had originally thought. It saves the record then double-checks the validation and redirects back to :edit if the record doesn't pass validation. At that point, the user could just navigate to another page and the invalid record would still be saved to the database. I guess what I would need to do is save the record, check validation, and then delete it if it didn't pass, at which point it would be hard to give the user a meaningful error message. Commented Aug 1, 2013 at 23:59
  • instead of hitting the database with a sum-query, you could calculate the stuff based of the activities collection like this self.activities.map(&:hours).sum Commented Aug 7, 2013 at 20:22
  • Thanks! That's exactly what I was looking for. Why not post an answer? Commented Aug 9, 2013 at 22:34
  • If you really care about security, I strongly recommend you to upgrade your rails to 3.2.14 github.com/rails/rails/releases/tag/v3.2.14 Commented Aug 10, 2013 at 19:50

1 Answer 1

2

instead of hitting the database with a sum-query, you could calculate the stuff based of the activities collection like this

self.activities.map(&:hours).sum
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.