Rails 6 uniqueness validations - ruby-on-rails

Currently having trouble with the validate_uniqueness_of method on my model with a given scope.
Right now I have 3 tables:
User, College, UsersColleges (join between user and college)
User has_many :users_colleges
College has_many :users_colleges
UsersColleges belongs_to :user, :college
Right now, I am validating the uniqueness of the user_colleges, so that a user can only have 1 entry of the specified college.
validates_uniqueness_of :college_id, scope: :user_id
However, when updating my user with specific colleges, the validation fails -- College has been taken.
This behavior only happens for specific colleges, and I can't seem to figure out why.

the syntax for Rails6 would be:
validates :college_id, uniqueness: {scope: :user_id}
See here
I don't know if the older syntax is supported.

Related

Rails has many association validation

I need to check presence validation for the associated attributes every time when the parent model gets updated.
In my user.rb
accepts_nested_attributes_for :histories
has_many :histories
I need to add validation for the histories when the user model gets updated, I know accepts_nested_attributes will take care of the validations while adding the user through forms, I need to check for the validation every time the user model gets updated even in the console,
If I add
validates :histories, presence: true
It will check for record in the histories table, If any record available for the user It will skip the validation for histories, I need to validate every time the object gets updated. Is there any way to validate whether the new record is being created when updating the parent model?
From your description, I think what you may be looking for is validates_associated:
class User < ApplicationRecord
has_many :histories
# validates that the association exists
validates :histories, presence: true
# validates that the objects in the associated collection are themselves valid
validates_associated :histories
end
the validates :attribute, presence: true validator is meant to validate first-class attributes on the model, not relationships. Such things like on a User class validates :email, presence: true, is where it works best.
Is your goal to validate the has_many relationship or test the relationship? If it's a test, you should make a spec that runs a test along the lines of it { should have_many(:histories) }.... obviously depending on your testing framework.
If you're goal is to validate the has many relationship, you may need to write a custom validate method. However, can you share a little more about what exactly your trying to accomplish/what about the has_many relationship you are trying to validate?

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 validate only one enter per pair of db_columns

How can I validate that there can only be one enter with the same doctor_id and patient_id? (a patient can only recommend a doctor once)
class DoctorRecommendation < ActiveRecord::Base
belongs_to :patient, :class_name => "User"
belongs_to :doctor, :class_name => "User"
validates :patient, presence: true
validates :doctor, presence: true
# does not work
validates_uniqueness_of :recommend, scope: [:doctor_id, :patient_id]
end
Rails makes this kind of validations fairly easy.
The first step is to define the validation on your model.
The uniqueness validation supports a scope option, that should contain the name of the other column you want to limit the uniqueness to (or an Array of other columns, if it's a >=3 column scoped uniqueness).
Your mistake is to declare it with a different name (:recommend).
This is what you want:
class DoctorRecommendation < ActiveRecord::Base
belongs_to :patient, class_name: "User"
belongs_to :doctor, class_name: "User"
validates :patient, presence: true
validates :doctor, presence: true
validates :patient_id, uniqueness: { scope: :doctor_id }
end
Considering that you already have :presence validations for the associated models, the :uniqueness validation can be limited to the IDs.
This will enable the validation on the application layer, that is, it will be verified in your Ruby process.
Unfortunately this is not enough in a real world scenario, where you can have multiple processes/threads modify the same table at the same time.
Imagine, for example that two requests reach your servers at the same time, both to create the same DoctorRecommendation. If the requests are served by two server processes/threads in parallel (or close enough), there is a chance that the Rails validation will pass in both cases.
In details:
both servers instantiate a new unsaved model in memory, and populate its fields
both read from the DB, to see if the uniqueness validation passes
there is no record yet with that patient_id and doctor_id pair, the validation passes in both processes
both servers save the record, and the data is written to the the DB
bang. your uniqueness constraint has been violated.
For this reason you need to enforce uniqueness on the DB layer as well, with a unique multi-column index.
More precisely, with:
class AddMultiIndexToDoctorRecommendations < ActiveRecord::Migration
def change
# using a custom name to make sure it's below the length limit
add_index :doctor_recommendations,
[:patient_id, :doctor_id],
unique: true,
name: 'index_docrecomm_on_patient_id_and_doctor_id'
end
end
This will define a [:patient_id, :doctor_id] index, with a unique constraint.
If you read the docs on multi column indexes (e.g. for postgres or mysql), you'll find that the order of the columns matters. The Migration I wrote uses the right order for the validation I defined above, which means that the validation queries will be correctly optimized. Make sure to modify the index if you want to invert the validation scope.
Now, back to the example above, on point 4 both server processes will try to save the record at the same time, but one will finish a few milliseconds before the other. The second will raise a ActiveRecord::RecordNotUnique exception, that you can rescue in your code.
For example:
begin
DoctorRecommendation.create(attributes)
rescue ActiveRecord::RecordNotUnique
# ops, let's retry again, to see if we get a validation error
end
I would use a migration and add a unique index for that combination.
add_index :doctor_recommenations, [:doctor_id, :patient_id], :unique => true

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

Resources