Reputation: 7068
Original title: before_validation on update - not handling error properly
I'm trying to figure out how to handle the validation of a nested object, especially on the update.
My parent class that handles the nested attributes.
meeting.rb
has_many :proposed_times, :dependent => :destroy, :inverse_of => :meeting
validates_associated :proposed_times
accepts_nested_attributes_for :proposed_times
...
def proposed_times_attributes=(attributes)
attributes.each do |key,value|
value[:timezone] = timezone
if value[:id]
p = ProposedTime.find(value[:id])
value.delete(:id)
p.update_attributes(value)
else
self.proposed_times << ProposedTime.new(value)
end
end
end
The class I'm trying to validate is below. Basically I need to validate that the date is in the future. And I'm constructing my starting_at with a before_validation
method. So I'm not sure how to handle the case that (A) it can't properly construct the date (in the case where the user deletes the date I need to tell the user to fill it in) (B) If I do construct it, then it fails the save, I don't know how to get it to go back to the view to correct the information.
proposed_times.rb
belongs_to :meeting, :inverse_of => :proposed_times
validate :future_enough
before_validation :construct_starting_at
...
def future_enough
unless starting_at && starting_at >= (Time.now + 15.minutes)
errors.add(:not_far_enough_out, "msg not used but: starting_at isn't far enough out")
end
end
def construct_starting_at
if date.present? && time.present? && timezone.present?
begin
d = Time.parse(date)
self.starting_at = Time.zone.parse("#{d.year}-#{d.month}-#{d.day} #{time.hour}:#{time.min}:00 #{timezone}")
rescue
self.starting_at = nil
end
end
end
Here is the view I'm using. I know this isn't the best way to handle this functionality, but I'm working with what I have… Everything I do redirects to thank_you
. I'm not sure why it isn't failing on the save.
meeting_controller.rb
def propose_times
@consultation = Consultation.find(params[:id])
@user = current_user
# If this is coming second time around with data to save
if request.put?
@consultation.attributes = params[:consultation]
if @consultation.save
redirect_to thank_you_path
else
render :layout => "simple"
end
else
render :layout => "simple"
end
end
What is the proper way for me to validate a nested object and handle the errors? Do I need to be using new_record?
for the before_validation
method?
Update: So using @Aditya's suggestion of validates_associated, it still doesn't validate just the updated data. The updated times I put in get validated… but since they aren't valid, they aren't kept, and then it gets validated again (with the old data) and then that passes/fails validation and the warnings for those dates are shown.
What am I missing?
Update 2: I added a bunch of p statements to see the order of events and if I leave out the validated_associated
, then it only gets validated once, but for some reason the meeting itself saves fine and therefore it redirects to thank you. But if I leave it in, it validates twice, hence the issues. So why is it validating the first (or second) time and how do I stop that?
Update 3: Ok, so I took out my proposed_times_attributes=
function and in the view added the timezone there. This fixed the issue (along with @Aditya's method). I wish I knew how to get it to work with a defined method like that. I tried various other combinations of having/not having validates_associated
, accepts_nested_attributes_for
, and having that function defined.
SO if you come across this, and happen to know how to write proposed_times_attributes=
so the validation would still work, please let me know.I need to be able to put timezone in proposed_times_attributes=
because on view I would like to put one timezone select box for all of the proposed_times. I wish there was a way to call super() inside the *_attributes=()
method.
Upvotes: 1
Views: 1101
Reputation: 7068
Finally figured it out. So you can do a super()
-type method by calling assign_nested_attributes_for_collection_association
or assign_nested_attributes_for_one_to_one_association
. But you need to also call accepts_nested_attributes_for
in order to set some of the options for those methods (click on the function's name to see the documentation for each).
accepts_nested_attributes_for :advisor, :client, :proposed_times
...
def proposed_times_attributes=(attributes)
attributes.each do |key,value|
value[:timezone] = client.timezone
end
assign_nested_attributes_for_collection_association(:proposed_times, attributes)
end
Upvotes: 1
Reputation: 13433
@RyanJM
You don't need to manually validate the child associate entities if you use the following
meeting.rb
validates_associated :proposed_times
assuming you already have the following association in your meeting.rb
has_many :proposed_times
Upvotes: 0