Rails accepts_nested_attributes_for with _destroy can skip presence validation - ruby-on-rails

So I have a typical Rails model with accepts_nested_attributes_for with presence validation
(snippets)
class Book < ActiveRecord::Base
...
has_one :cover
accepts_nested_attributes_for :cover, allow_destroy: true
validate :require_cover
def require_cover
errors.add('', 'You must have a cover for the book.') if self.cover.blank?
end
...
end
This works and validates okay on the first step when I'm creating. But when I try to edit it and click delete on the cover (clicking delete adds _destroy true) and save it, it deleted the cover but the validation regarding presence has passed already.
I think what happened was:
tagged destroy cover
validation process happens (thinks there is still cover (but maybe doesn't recognize destroy))
valid and goes to saving
saves book and
passes
No validation again regarding the no cover
Have I done this incorrectly? Is there another way to implement this? Or how do I revalidate this scenario (like, after the save and destroy that has happened, there would be another validation to say that the resulting object is now invalid)?

I found the fix to my question, I was right that the save doesn't take into account those that are tagged as _destroy.
This link describes the problem better and has the answer to it too.
http://homeonrails.com/2012/10/validating-nested-associations-in-rails/
Basically he rejected those that are marked for destruction and counted the remaining ones.

Related

Which approach to use for validating presence of a belongs_to association?

Given
class Post
has_many :comments
end
class Comment
belongs_to :post
end
If I want to validate that a comment always has a post, should I use
class Comment
belongs_to :post
validates :post, presence: true
end
or
class Comment
belongs_to :post
validates :post_id, presence: true
end
?
The Rails guides suggests the validates :post, presence: true approach, saying:
If you want to be sure that an association is present, you'll need to
test whether the associated object itself is present, and not the
foreign key used to map the association.
One difference I can see between the two is that if you do
post = Post.new
comment = Comment.new
comment.post = post
comment.save
then validation fails if it's based on post_id, but succeeds if it's based on post. The latter makes more sense to me.
However, I sometimes see people in real life using the post_id approach. Are they simply using the "wrong" approach, or is there a rationale I'm not aware of?
The Rails Style Guide doesn't have anything related to this topic.
The question Rails ActiveRecord:: Proper way for validating presence on associations? seems to be asking about how to get a project to work using the post_id approach, but doesn't seem to explain why it's using that approach.
Rails 4: Difference between validates presence on id or association is about validation, but merely describes the different effects of the two approaches, rather than saying why post_id should be used.
The question Rails - Validate Presence Of Association? is unrelated - it's talking about validating that a post has at least one comment, which is a different requirement to what I'm asking about.
I don't think a single case would fit all scenarios.
In the case of posts and comments, I would tend to validate on the foreign_key rather than the association. This is partly due to better performance but also testing the Comment model would become independent of the Post model.
For the example of Comment and Post, when creating a comment, I would set association (i.e. comment.post = post or post.comments.create!) rather than the foreign_key (i.e. comment.post_id = post.id), so I know that the post exists (and that's why I am not validating the presence of the association). However in the unlikely case of data corruption, this will not have a significant impact on the application (just one comment wouldn't be associated with an existing post).
In other scenarios, where you need to be absolutely sure that the association is present, you could be extremely safe and validate the association (for instance a CreditCard belongs to a User or an Invoice belonging to an Account).
The comments section of the blog post http://railsguides.net/belongs-to-and-presence-validation-rule1/ , linked to from the answer https://stackoverflow.com/a/25808182/38765 suggests that some people validate on the post_id just because they weren't aware of other ways of doing it:
Thanks for posting this! I've always validated on the field. I didn't
even think about validating the association.
and
Amazing, thank you. That was subtle.
One person said post_id may have better performance:
Keep in mind that validating on :account instead of :account_id
will incur a performance penalty, as the database has to be queried to
check that the association exists.
but I tend to be wary of doing things for performance reasons.

how can i validate the nested attributes field in rails 4?

I have two models
class Information < ActiveRecord::Base
belongs_to :study
validates_presence_of :email
end
and
class Study < ActiveRecord::Base
has_many :informations
accepts_nested_attributes_for :informations
end
I show up a form of study which contains few fields for the informations and i want to validate presence of those fields. Only on validation success i wanted to save the study field values as well and i wanted to show errors if the validation fails. How can i do this? Thanks in advance.
You write validations in the models that you require, as normal. So if you need to validate presence of field foo in the Information class you'd just write validates_presence_of :foo in that class. Likewise validations for Study fields just go in the Study class. With nested attributes, when you update a Study instance from a params hash that contains nested attributes, it'll update the Information instance(s) too, running validations in passing. That's what the accepts_nested_attributes_for call is doing - it's giving "permission" for the appropriate bits of a params hash to be used in this way.
You can use reject_if to only reject new nested records should they fail to meet criteria. So I might let someone create a Study and only create one or more nested Information instances associated with that Study if they'd filled in field(s) in the form, but if they left them blank, the nested stuff wouldn't be created and saved (so you don't get pointless blank associated records). The Study would still be saved. For example:
accepts_nested_attributes_for(
:informations,
reject_if: proc() { | attrs | attrs[ 'title' ] .blank? }
)
This and more is covered in the API documentation here:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Beware that nested fields are intended for existing records only. If you were creating a new Study instance in a new/create action with no Information instances associated, you won't see any nested form fields for your Information class at all - when you might be expecting just one, for a blank new item. This can be very confusing if you aren't ready for it! You'll need to manually add a new Information instance to your Study instance in the controller or similar for the 'new' and 'create' actions, e.g. using before_filter :create_blank_object, only: [ :new, :create ], with, say:
def create_blank_object
#study = Study.new
#study.informations << Information.new
end
you can use validates_presence validation available in rails other wise you can write before_create or before_save callback method. write validation logic inside the before_create or before_save callback method.
Check out the API Doc for validates_associated:
Validates whether the associated object or objects are all valid. Works with any kind of association.
If you call a method on the parent object which runs validations (e.g. save), the validation on the associated objects will be called as well.

Why Rails update_attributes is calling validation before saving

I am having a very strange problem in Rails. I am using update_attributes on a Parent Object to update all the children objects. The children objects have (custom) validation and indeed this works ok, meaning that if I give wrong values the validation trigger and I get an error back.
Now I am in a strange situation where one of the model is invalid in the database (let's not question why, let's just say I can go in the DB and run some SQL to make the model invalid). If I go in my app I can see the invalid values and this is fine. I fix the values and save again and I can see, stepping in the ruby code that the validation is called also BEFORE saving the new values, meaning that I will get an error and Rails will never execute the SQL to actually update the values to the correct ones.
I hope the above makes sense. Do you have any idea or do you think there is something I am overlooking?
SOLUTION:
What was happening was that a many-to-many relationship was validating the existing DB data before being replaced by the new data. Basically the structure was like this:
class User
has_many :user_permissions
has_many :permissions, :through => :model_permissions
class Permission
has_many :user_permissions
has_many :users, :through => :user_permissions
class UserPermission
belongs_to :user
belongs_to :permission
validates_associated :user # THIS was causing the problem
validates_associated :permission # and THIS as well
I simply removed the validates_associated directive, since I am validating the linked records independently anyway.
Well, Rails does run your validation prior to writing the data to the database (please refer to Active Record callback sequence), so having validation errors means that some piece of the model you are trying to save is not valid. It might an associated model containing errors with the validation turned on or just some missing part – in any case, just have a look at what are the errors you are getting.
In case (let's no question why either :) you want to skip validation - you are open to choose from #update_attribute (to update just one attribute), calling #save(false), using +udpate_all method of the model class or even go down to ActiveRecord::Base.connection.execute – none of these will ever bother you with validation errors :)

nested record can't validate because its nested belongs_to doesn't save

I'm trying to create a nested child and grandchild record. The child belongs_to both the parent and the grandchild. The child won't validates_presence_of the grandchild because it hasn't been saved yet.
I'm using Rails 2.3.11, Formtastic, InheritedResources, and Haml, and everything else seems to work correctly - for example, validation errors on the grandchild populate properly in the parent form, and the invalid values are remembered and presented to the user. The parent model doesn't even try to update unless everything is valid, just as it should be.
My code is something like this, though in a different problem domain:
class Project < ActiveRecord::Base
has_many :meetings, :dependent => :destroy
accepts_nested_attributes_for :meetings
end
class Meeting < ActiveRecord::Base
belongs_to :project
belongs_to :task
accepts_nested_attributes_for :task
validates_presence_of :task_id, :project_id
end
class Task < ActiveRecord::Base
has_many :meetings, :dependent => :destroy
end
The Project ALWAYS exists already, and may already have Meetings that we don't want to see. Tasks may belong to other Projects through other Meetings, but in this case, the Task and Meeting are ALWAYS new.
In the controller, I build a blank record only on the new action
#project.meetings.build
and save the data like this:
#project.update_attributes(params[:project])
and in the view
- semantic_form_for #project do |f|
- f.semantic_fields_for :meetings do |m|
- next unless m.object.new_record?
= m.semantic_errors :task_id
- m.object.build_task unless i.object.task
- m.semantic_fields_for :task do |t|
- f.inputs do
= t.input :task_field
= m.input :meeting_field
When I try to save the form, I get a validation error of "Task can't be blank." Well, sure, the Task hasn't been saved yet, I'm trying to validate, and I don't have an ID for it.
Is there a simple and elegant way to make sure that the grandchild record (Task) gets built before the child record?
I've tried something like this in the Meeting model:
before_validation_on_create do |meeting|
meeting.task.save if meeting.task.valid?
end
and that seems to save the Task, but the Meeting still doesn't get the right ID. Same error, but the Task record gets created.
I've also tried this:
before_validation_on_create do |meeting|
new_task = meeting.task.save if meeting.task.valid?
meeting.task = new_task
end
Which has the strange behaviour of raising ActiveRecord::RecordNotFound "Couldn't find Task with ID=XX for Meeting with ID=" - which I sort of get, but seems like a red herring.
I also tried adding :inverse_of to all the relationships and validating :task instead of :task_id. The latter, oddly, fails but seems to give no error message.
My actual goal here is to create more than one Task, each with an initial Meeting on a previously selected Project... so I could take another approach with my problem - I could do something simple and ugly in the controller, or create the first Meeting in an after_create on the Project. But this is so pretty and soooo close to working. The fact that I'm getting proper validation errors on :task_field and :meeting_field implies that I'm on the right track.
I see what the problem is, but not how to solve it: I suspect I'm missing something obvious.
Thank you!
Well, I found a solution, based on one of the similar questions out there, but the short of it is "rails 2.3 doesn't seem to be very good at this." I think I can put the answer in a more succinct way than any of the other answers I've seen.
What you do is you skip the validation of the :task_id, but only if task is valid! Most of the other answers I've seen use a proc, but I think it's more readable using delegate, like this:
delegate :valid?, :to => :task, :prefix => true, :allow_nil => true
validates_presence_of :task_id, :unless => :task_valid?
I also had another problem hidden under the waterline - in the case, the "Project" is actually a special sort of record that I wanted to protect, which has a validation that (intentionally) fails only for this special record, and I also set readonly? to true for the special record.
Even though I'm not actually changing that special record, it still needs to validate and can't be readonly to update children through it. For some reason, I wasn't seeing the error message for that validation. To solve that, I made the validation on the Project only applicable :on => :create, and I took out the readonly? thing.
But the general solution is "don't validate presence of the unbuilt belongs_to object if the object itself is valid." Nil is never valid, therefore the validation still works if you just have an object_id.
(Please don't vote down a sincere question unless you have an answer or a link to one. I'm aware the question has been asked by others in other ways, I read many of those other questions, none seemed to be precisely the same problem, and I had not found a solution.)

Validating a Rails model post-save?

I have a model with a couple of accepts_nested_attributes_for. There is a requirement that I have at least one of each of the nested attributes when saving and moving on.
However, validation occurs pre-save, so when I'm removing an item and moving on, it let's it through.
How can I validate that when I've saved, I have at least one item of each nested type there?
There's a bug with accepts_nested_attributes_for. Meaning you have to be a little more devious when it comes to validations in the parent model.
You could use an :after_save callback in each of your nested models to check if it's the last one. But if there's many nested associations where you want to ensure at least one, this isn't very DRY.
This is however a valid workaround for the linked bug:
class Whatever < ActiveRecord::Base
:has_many => :association_a
:has_many => :association_b
def ensure_minimum_associations
bad_associations = [:association_a, :association_b].
select{|assoc| self.send(assoc).all?{|a| a.marked_for_destruction?}}
unless bad_associations.empty?
bad_associations.each do |association|
errors.add_to_base "Each #{self.class.name.downcase} must retain at least one #{association}"
end
return false
end
end
end
You can always call valid? on a model and it will run the validation.
I believe you're looking for validates_associated

Resources