validating uniqueness of has_many association with a string "checksum" - ruby-on-rails

I had an idea to validate the uniqueness of a has_many association: what if we generate a string based on the ids of the associated records?
For example:
class Exam
has_many :problems #problems are unique and can be in multiple exams
validate :checksum, uniqueness: true #string
before_validate :check
def check
checksum = problems.map {|p| p.id}.join
end
end
The edge case we want to solve is:
Given distinct problems 3x4, sqrt(4), 5+5, etc.., we don't want all of them to be in more than one exam.
Does anyone have thoughts on this approach? Is there a better way to validate uniqueness of has_many?
(P.S. I'm not sure if "checksum" is the right term.)

Base on the following:
You have an Exam model and a Problem model
Each Exam has_many Problems
Each problem must be unique per Exam
I think it makes more sense to place a uniqueness validation on an attribute of Problem but scope the validation to its Exam so that that multiple Exams can have the same Problems but each Exam has a unique set of Problems.
So for example if there was an attribute named value, we place the uniqueness validation on it and scope it to exam_id.
class Exam < ActiveRecord::Base
has_many :problems
end
class Problem < ActiveRecord::Base
belongs_to :exam
validates :value, :uniqueness => { :scope => :exam_id }
end

Related

Rails uniqueness validation with nested has_many

I am building a Polls website and I want to achieve that a user (ip address) can vote once per poll. I have the following models:
class Poll < ApplicationRecord
has_many :answers, :dependent => :destroy
accepts_nested_attributes_for :answers, allow_destroy: true
end
class Answer < ApplicationRecord
belongs_to :poll
has_many :votes
end
class Vote < ApplicationRecord
validates_uniqueness_of :ip_address
belongs_to :answer
end
With my current solution a user can vote once on one poll and then he can't vote on another.
What should I use to validates_uniqueness_of :ip_address per poll?
I tried to use scope by poll but it didn't work.
PS: I know IP address is not the best solution for validating unique votes.
You need scoped uniqueness
validates :ip_address, uniqueness: { scope: :answer_id }
It will not allow a duplicate ip_address per answer
https://guides.rubyonrails.org/active_record_validations.html#uniqueness
You can also enforce it at database level by adding this to migration, this is optional though
add_index :votes, [:ip_address, :answer_id], unique: true
But the above solution will only keep user from not voting for an answer more than once. If you want uniqueness with poll, one option is to save poll_id in answers table as well and then define uniqueness on that,
or, second option is to define custom validation in Vote model
validate :uniqueness_with_poll
def uniqueness_with_poll
errors.add(:answer_id, 'already voted for poll') if answer_for_poll_exists?
end
def answer_for_poll_exists?
answer_voted = Vote.where(ip_address: ip_address).pluck(:answer_id)
return false if answer_voted.blank?
answer.poll.answers.where(id: answer_voted).exists?
end
Then defining a method answer_for_poll_exists? to check whether poll is already voted, but this could be costly though.
Hope that helps!

Rails Validates uniqueness of a column based on a relation

I have a product table which belongs to market & has many variants, every variants has SKU column, i want to validates uniqueness of sku but for the same market. i've tried
validates :sku, uniqueness: { scope: :market_id }
but since variant doesn't have market_id column it doesn't work!!, how can i overcome this issue ?!
Although you can access variants from market,
if you set you relations with has many through, but
my opinion it's better to save market_id inside variants table and set index based two column
class Market < ApplicationRecord
has_many :products
has_many :variants , :through => :products
end
class Product < ApplicationRecord
belongs_to :market
has_many :variants
end
class Variant < ApplicationRecord
belongs_to :Product
end
You can generate migration to add market_id in variants table
with index based two column
here is the reference
and here is the code to generate index based two columnn(market_id and sku)
add_index(:variants, [:market_id, :sku], unique: true)
A variant belongs to a product in a certain market, therefore, in order to avoid an extra join (which is a costly operation) when retrieving a variant of a product in a market, and in order to implement this validation, the schema should be designed in a way that Variant should belong to both Market and Product.
If a Variant belongs both to Product and Market, the validation would be as follows
validates_uniqueness_of :sku, scope: [:market_id, :product_id]

mongoid-4 how to validate uniqueness of belongs_to in 1 to 1 association

I have a 1-to-1 association between 2 mongoid models and I keep getting duplicates, that is having more than one child record(card) with same parent_id(that is user). I have tried validating uniqueness of the belongs_to association has shown below, but it doesn't work.
class User
include Mongoid::Document
field :name, type: String
has_one :card
end
The second model:
class Card
include Mongoid::Document
field :name, type: String
belongs_to :user
validates :user, :uniqueness => {:scope => :user_has_child}
def user_has_child
q = Segment.where(drop_id: {'$ne' => nil})
s = q.map(&:drop_id)
errors.add(:drop_id, "this user already has a card") if s.include?(:drop_id)
end
end
The syntax is more simple. You just want to make sure there are no 2 documents with the same user_id
class Card
belongs_to :user
validates_uniqueness_of :user
You need to use scope if you want the uniqueness of a tuple of n fields. For example, if a User can have at most one card per year, you can write
class Card
field :year
belongs_to :user
validates_uniqueness_of :user, scope: [:year] # A user can have one card per year
Note that validations apply when you save the model ie. you try to persist the changes. Calling .save will return true or false if some validations fail, but the object in memory is always modified! This is so, for example, you can display previous values in the HTML input fields, so the user knew what he wrote and can fix it (otherwise he'd have to re-write all his information in case of a single mistake)
Also, Mongoid by default handles dirty tracking (this is now the doc for v5.0 but it was the same for Mongoid 4). That is to say, you can call .changed? .changes, etc on the object in memory to see what are the changes compared to the object in the DB.

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