Rails: Validating existence of an association - ruby-on-rails

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

Related

It is necessary uniqueness validation in associations has_one

I create uniqueness validation in my model to garantice that user_id in table was unique, but I am not sure if association has_one do that.
User model
class User < ActiveRecord::Base
#association macros
has_one :balance
end
Balance Model
class Balance < ActiveRecord::Base
#association macros
belongs_to :user
#validation macros
validates :user_id, presence: true, uniqueness: true #uniqueness is necessary?
end
It is not necessary to have a validates_presence_of for that since it is handled in your database. However, to not have to handle a database error, it is better to do it in your model like you have. Rails built in error handlers for validation will then work.
If your table data shows that it cannot be null/nil, then the validation is on the database itself and will return an error which is much harder to handle. You will get a system error and the Rails 'better errors' message. Which basically is breaking your app.
If you do the model validation as you have in your Model using the...
validates :user_id, presence: true, uniqueness: true
then Rails will allow you to control these error messages within your app. You can choose to ignore them (bad) and have data entry almost silently fail. Or, you can turn on label_errors along with flash messages in your controller to allow users to see what is wrong with the data they are trying to enter on a form.

Rails 4 Validation: Do I ensure that an association is present with the foreign key or the association variable itself?

In Rails 4, what is the best way to ensure that an association is present?
It looks like you can test with the foreign key or the association variable itself.
I am still fairly new to Rails and so this could be a stupid question.
I am also thinking that both methods below might work.
# method 1:
class Region < ActiveRecord::Base
# In order to validate associated records whose presence is required,
# you must specify the :inverse_of option for the association:
has_many :flags, inverse_of: :region
end
class Flag < ActiveRecord::Base
# 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.
belongs_to :region
validates :region, :presence => true
end
# method 2:
class Region < ActiveRecord::Base
has_many :flags
end
class Flag < ActiveRecord::Base
# Rails 4 Way book: "when you're trying to ensure that an association is present,
# pass its foreign key attribute, not the association variable itself"
validates :region_id, :presence => true
validate :region_exists
def region_exists
errors.add(:region_id, "does not exist") unless Region.exists?(region_id)
end
end
I took a look at validates_associated, thinking that would be what would work best here.
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_associated
In the notes for validates_associated is this tidbit:
NOTE: This validation will not fail if the association hasn't been
assigned. If you want to ensure that the association is both present
and guaranteed to be valid, you also need to use
validates_presence_of.
So it looks like if you're just after verifying the association is assigned, validates_presence_of should work. If you also want to verify the association is valid, validates_presence_of and validates_associated could be used together.

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.

More than one belongs_to

I’m new to Rails and I’ve been reading Michael Hartl’s tutorial and I am not sure if I’m doing this correctly. I have Users, Posts, and Categories:
Users can create Posts and Categories
A Post can be assigned to only one Category.
Currently, when the User creates a post they type in the Category (let’s just say the Category will always exist in the database) and from there it looks up the ID of the Category and passes that along to the post creation. This is what I’m running to create the post and assign it to a Category in my Post_Controller:
category_id = Category.find_by_name(post_params[:category])
#post = current_user.posts.build(title: post_params[:title], content: post_params[:content], category_id: category.id)
My question is: Is this the proper way to enter data with two belong_to’s? I’ve dug around and I can’t find a simple answer to this. To me it seems that passing a category ID like this is not secure but I don’t know of another way to do this. Here’s my basic Model information (just the belong_to’s, has_many, etc). Please let me know if you need more:
class User < ActiveRecord::Base
attr_accessible :username, :email, :password, :password_confirmation
has_secure_password
has_many :posts
has_many :categories
class Post < ActiveRecord::Base
attr_accessible :title, :content, :category_id
belongs_to :user
belongs_to :category
class Category < ActiveRecord::Base
attr_accessible :name
belongs_to :users
has_many :posts
validates :name, presence: true, uniqueness: true, length: {maximum:30}
validates :user_id, presence: true
Is this the proper way to enter data with two belong_to’s?
It's fine. Does it work? If it works, it's fine. Maybe there are things you could do to tighten it up later, if you find you're making the Category.find... call a lot, but, you're also just starting, so don't worry about stuff like that too much.
To me it seems that passing a category ID like this is not secure but I don’t know of another way to do this.
Again, don't worry about that too much at the moment. If you want to read up on Rails security, though, take a look at this.
If all the information you need to create a new post is in post_params, it seems you're doing that Category.find_by_name unnecessarily. You should be collecting the category_id in your params rather than the category's name.
And then in your PostsController:
#post = current_user.posts.build(post_params)
Just remember to allow :category_id along with the other regular attributes in your post_params and you'll be golden.

Nested attribute validations not being called

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. :/

Resources