Nested attribute validations not being called - ruby-on-rails

I've been struggling with this for some time now. I'm just trying to get nested attributes to validate on Rails 3.2 with no luck. It's like it's just completely ignoring validations for the nested attributes. Below is an example validation that's not working:
class Invoice < ActiveRecord::Base
validates :description, :presence => true
belongs_to :client_branch
has_many :invoice_items
accepts_nested_attributes_for :invoice_items, :allow_destroy => true
end
class InvoiceItem < ActiveRecord::Base
belongs_to :invoice
validate :thisisatest
def thisisatest
errors.add(:qty, 'QTY NOT VALIDATING TEST.')
end
end
When saving an Invoice with some InvoiceItems, it saves it successfully, even though the custom validation is clearly adding an error for the :qty attribute. Is there something I should be adding to my models for nested validation to work, or am I perhaps missing something else?

Actually, I'm being daft. I changed the model name, along with all references to it and it took me this long to miss one reference still pointing to the old model in the javascript. Thus, items being added dynamically weren't named correctly, causing the validation not to trigger. :/

Related

Ruby on Rails - save new record with nested association requiring this record

I have an issue creating a new record with nested associations in a clean way. Here's the controller code:
#listing = current_user.listings.build(params[:listing].permit(ATTRIBUTES_FOR_CREATE))
This builds an entity with several nested associations, like this:
class ListingDataField < ActiveRecord::Base
belongs_to :listing
validates_presence_of :listing
end
However, when I do #listing.save in controller, I get validation errors on those nested ListingDataField entities that 'listing can't be blank'. If I understand correctly, AutosaveAssociation first validates and saves nested associations, and eventually saves top-level entity. Thus, it fails validating ListingDataField, because Listing is not yet saved.
But I believe it's right having :listing validation in ListingDataField, so I wouldn't consider removing this. I can see 2 solutions:
in transaction - save Listing record, then build nested associations
one by one
#listing.save(:validate => false) but this is too ugly
Both aren't as much elegant as current_user.listings.build(...), so my question is - what is the proper Rails way for this?
P.S. I searched SO for similar question but I couldn't find any, hopefully this is not a duplicate :)
Have you tried adding:
class ListingDataField < ActiveRecord::Base
belongs_to :listing, inverse_of: :listing_data_fields
validates :listing, presence: true
end
and
class Listing < ActiveRecord::Base
has_many :listing_data_fields, inverse_of: :listing
end
This should make validation of presence work.

Nested attributes not working creating children with new parent

I have two models:
class Shift < ActiveRecord::Base
attr_accessible :ranges_attributes
has_many :ranges
accepts_nested_attributes_for :ranges, allow_destroy: true
end
class Range < ActiveRecord::Base
belongs_to :shift
validates :shift, presence: true
end
When, in my controller, I want to create a shift with ranges I'm getting:
Shift.create! params[:shift]
#ActiveRecord::RecordInvalid Exception: Validation failed: Shift ranges shift can't be blank
If I remove validates :shift, presence: true from Range model this works beautifully. I'm able to create a new shift with his children. ActiveRecord does that for me.
The question is: why do I need to remove that validation to make this work?
The thing with validating presence of parent like this is timing !! actually the Shift is not yet saved so when trying to create nested ranges it won't find parent Shift in database.
I found this workaround here
class Shift < ActiveRecord::Base
attr_accessible :ranges_attributes
has_many :ranges, :inverse_of => :shift
accepts_nested_attributes_for :ranges, allow_destroy: true
end
and i quote (with minor modifications) from the same source:
With this option rails won't try to get parent from database when
child is validated. The parent will be got from memory. If you don't
familiar with this option I strongly recommend you to read an official
rails guide

Validation of HABTM association on delete:

I have a newbie rails question. I'm trying to make sure a model has at least one association via a HABTM relationship. Basically I have created the following validation:
validate :has_tags?
def has_tags?
errors.add(:base, 'Must have at least one tag.') if self.tags.blank?
end
This works fine when I create a new record. The problem is when I take the model and try to remove the association, doing something like this:
tag = Tag.find(params[:tag_id])$
#command.tags.delete(tag)$
It is permitted, i.e. the association will be deleted. Based on my reading on HABTM associations (http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association), I should "use has_many :through if you need validations, callbacks, or extra attributes on the join model."
I guess my question is how to perform validation on the .delete method for an association. Should I do this manually when I call delete (i.e. run a separate join to count the number of associations before executing a delete), or is there a way to use a validation model when deleting? Here is my model:
class Command < ActiveRecord::Base
has_many :tagmapsorters
has_many :tags, through: :tagmapsorters
validates :text, presence: true
validates :description, presence: true
validates :text, uniqueness: true
validate :has_tags?
def has_tags?
errors.add(:base, 'Must have at least one tag.') if self.tags.blank?
end
end
I appreciate you taking the time to help me.
Dan
Any callbacks that you need should be registered as before_destroy (for validations) or after_destroy (for cleanup) on the join model Tagmapsorter, as that is the record that is actually being destroyed.

Rails: Validating existence of an association

I have a Category and a Post model, with each Post belonging to a Category. Before creating or updating a post, I need to check that the category selected exists. What's the best way to validate this information?
At the moment, I'm doing a find in the controller to ensure that the category exists. Is it possible to put these kinds of validations in the model?
http://blog.hasmanythrough.com/2007/7/14/validate-your-existence
class Post < ActiveRecord::Base
belongs_to :category
validates_presence_of :category
end
-OR-
class Post < ActiveRecord::Base
belongs_to :category
validates :category, presence: true
end
Rails versions prior to 3.2:
class Post < ActiveRecord::Base
belongs_to :category
validates_existence_of :category
end
In Rails 3.2, validates_existence_of is replaced by validates_presence_of.
I've put this in my model:
validate :ensure_category_exists
def ensure_category_exists
errors.add('Category') unless self.blog.categories.find_by_id(self.category_id)
end
Which prints "Category is invalid" if the category does not exist for the parent blog.
It's definitely worth mentioning my experiences. This is for Rails 4 (potentially other versions as well).
Given an entity has_many or has_one of a model.
Validation that will ensure the entered association (association ID) exists, even if there is an ID given in the submission.
validates_presence_of :model
IS NOT THE SAME as a validation that will ensure there is something entered (not blank) in the input.
validates_presence_of :model_id
You may be able to get by with just the former, but I have both to have more specific error messages.
In my way of thinking a better choice is this gem: https://github.com/perfectline/validates_existence
It validates the related model's existence in the database. Imagine you have a dropdown field that gives back some garbage data even when you do not select anything (default non selected first field label as value). Validating presence won't work, as it will pass for existing data. But we want some kind of a constraint and this DB side check is what solves the problem.
In rails 5 and above, belongs_to automatically validates for presence.
But if you use belongs_to :category, optional: true it does not validate presence, and you can then do post.update!(category: -1) which is not great. To fix that:
validates :category, presence: true, if: :category_id
Just to be clear, the above is useful only when the association is optional.
In Rails 3, validates_associated is probably what you're looking for?
http://guides.rubyonrails.org/active_record_validations_callbacks.html#validates_associated

accepts_nested_attributes_for child association validation failing

I'm using accepts_nested_attributes_for in one of my Rails models, and I want to save the children after creating the parent.
The form works perfectly, but the validation is failing. For simplicity's sake imagine the following:
class Project < ActiveRecord::Base
has_many :tasks
accepts_nested_attributes_for :tasks
end
class Task < ActiveRecord::Base
belongs_to :project
validates_presence_of :project_id
validates_associated :project
end
And I am running:
Project.create!(
:name => 'Something',
:task_attributes => [ { :name => '123' }, { :name => '456' } ]
)
Upon saving the project model, the validation is failing on the tasks because they don't have a project_id (since the project hasn't been saved).
It seems like Rails is following the pattern below:
Validate Project
Validate Tasks
Save Project
Save Tasks
The pattern should be:
Validate Project
On Pass: Save Project and continue...
Validate Tasks
On Pass: Save Tasks
On Fail: Delete Project (rollback maybe?)
So my question boils down to: How can I get Rails to run the project_id= (or project=) method and validation on the children (tasks) AFTER the parent (project) has been saved, but NOT save the parent (project) model if any child (task) is invalid?
Any ideas?
Use :inverse_of and validates_presence_of :parent. This should fix your validation problem.
class Dungeon < ActiveRecord::Base
has_many :traps, :inverse_of => :dungeon
end
class Trap < ActiveRecord::Base
belongs_to :dungeon, :inverse_of => :traps
validates_presence_of :dungeon
end
http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_presence_of
https://github.com/rails/rails/blob/73f2d37505025a446bb5314a090f412d0fceb8ca/activerecord/test/cases/nested_attributes_test.rb
Use this answer for Rails 2, otherwise see below for the :inverse_of answer
You can work around this by not checking for the project_id if the associated project is valid.
class Task < ActiveRecord::Base
belongs_to :project
validates_presence_of :project_id, :unless => lambda {|task| task.project.try(:valid?)}
validates_associated :project
end
Only validate the relationship, not the ID:
class Task < ActiveRecord::Base
belongs_to :project
validates_presence_of :project
end
As soon as the association is populated, ActiveRecord will consider the validation to have succeeded, whether or not the model is saved. You might want to investigate autosaving as well, to ensure the task's project is always saved:
class Task < ActiveRecord::Base
belongs_to :project, :autosave => true
validates_presence_of :project
end
Unfortunately none of the above suggestions work for me with Rails 2.3.5.
In my case, the project in a task is always nil if both are created using nested attributes. Only if I remove the validates_presence_of, the create goes through successfully. The unit test and the log show that all is created correctly.
So I'd now tend to add constraints to the DB instead of Rails as that seems to be more reliable in the first place.
You could just create the project and only add the projects if it passes validation:
tasks = params.delete(:task_attributes)
if Project.create(params)
Project.update_attributes(:task_attributes => tasks)
end
Ciao
Contrary to what bigo suggests, it's not always acceptable to save the parent object first and then the children. Usually you want to make sure all objects validate before you start saving them. That gives the user the chance to re-edit the input form and correct any errors.
The problem you describe will be fixed in Rails 3.0. I would have posted a link to the Lighthouse ticket, but stackoverflow.com does not allow this because I'm a new user (#fail). But for the time being, you can use the plugin "parental_control", which will fix your "bug".

Resources