How to validate unique pairs while being case insensitive - ruby-on-rails

I have a model for Guilds that have a "Name" and "Realm" attribute
I want to make it so that the "Name" and "Realm" pairings are unique regardless of case. Enforcing the case sensitivity is the only issue I have at them moment.
here's what I've got so far:
validates_uniqueness_of :name, scope: :realm, case_sensitive: false
the [case_sensitive: false] part works but only for the first attribute(Name)
I'm currently using Rails 4.1.8, SQLite for developemnt and PG for Production
I really appreciate any help. Thanks!

It's my understanding that this isn't supported. The case_sensitive option applies to the column being validated and the scope option applies only to columns in the same table.
You could try using PG's lower like so scope: "lower(realm)" but I suspect it'll fail when trying to access the column table_name.lower(realm)
What you need to do is replace that validator with a custom one.
class Model
validate :your_validator
private
def your_validator
if Model.
where("LOWER(name) = ? AND LOWER(realm) = ?",
name.downcase, realm.downcase).
exists?
# add errors here
end
end
end

Related

Rails validate uniqueness for the ASCII approximation

I hope the title is not too unclear.
I am making arails app and I have a question about rails validation. Consider this code in the User,rb model file:
validates :name,
presence: true,
length: { maximum: 50 },
uniqueness: { case_sensitive: false }
I am using the friendly_id gem to generate slugs for the users. I wont allow users to change their name. What I now need is to ensure that Names are unique in such a way that there will be no UUID's appended to slugs if two people have the same name converted in ascii approximation.
Current behaviour is:
User 1 signs up with a name and gets a slug like this:
name: "Jaiel" slug: "jaiel"
User 2 now does the same name but a bit different:
name: "Jàìèl" slug: "jaiel-6558c3f1-e6a1-4199-a53e-4ccc565657d4"
The problem here as you see I want such a uniqueness validation that User 2 would have been rejected because both names would generate the slug "jaiel" for their friendly_id's
I would appreciate your help on that matter
Thanks
Take a look into ActiveSupport::Inflector.transliterate:
ActiveSupport::Inflector.transliterate('Ærøskøbing')
#=> "AEroskobing"
Also, with this type of validation you might want to go with custom validation (alongside the one you already have):
class User
validate :unique_slug
private
def unique_slug
names = self.class.all.map(&:asci_name)
raise ActiveRecord::RecordInvalid.new(self) if names.include?(asci_name)
end
def asci_name
ActiveSupport::Inflector.transliterate(name)
end
end
Obviously, this is super inefficient to query whole table on each validation, but this is just to point you into one of the possible directions.
Another option would be going for a callback. Transliterating the name upon creation:
before_validation: :transliterate_name
def transliterate_name
self.name = ActiveSupport::Inflector.transliterate(name)
end
It will first transliterate the name, then validate uniqueness of already transliterated name with the validation you have. Looks like a solution, definitely not as heavy as initial one.

Custom Uniqueness validator for hstore Postgres

I would like to perform a uniqueness validation on a hstore field.
When I set is as:
class User
store_accessor :attributes, :foo_attr
validates :foo_attr, uniqueness: true
end
I get undefined method 'limit' for nil:NilClass
In a Rails issue store_accessor and uniqueness validation? user al2o3cr explains:
validates_uniqueness_of is not going to work in this case - it's
expecting a database column named stripe_id. With an Hstore column
it's technically possible to perform the required query, but the
resulting SQL is only applicable to that storage format and only works
on Postgres.
In your case, a custom subclass of
ActiveRecord::Validations::UniquenessValidator with an overridden
build_relation would probably be a better choice.
How would you go around creating that custom validator?
I already have a database level uniqueness set up as explained in Race condition using Postgres hstore all I need now is to make valid? return false on the same foo_attr.
If you're going to the trouble of setting up a custom hstore index and writing a custom validation, my first instinct is that maybe you want foo_attr to be a column of its own.
As for a custom validation, it's pretty straightforward:
validate :foo_attr_uniqueness
def foo_attr_uniqueness
if self.class.where(foo_attr: foo_attr) # Same foo_attr
.where.not(id: id) # On a different record
.exists?
errors.add(:foo_attr, 'must be unique')
end
end

How would I save a username in a database and keep is capitalisation whilst have an un-case sensitive validation?

I am using Ruby on Rails. I want to store usernames into a database and when I extract them I want them to have the same capitalisation as it had when entered, but I want to add some validation that is not case sensitive to ensure the same username cannot be taken, no matter the capitalisation.
I say this because not all database adapters use case-sensitive indices so I would need to down-case the username before it was saved into the database.
So the validation in the model would be this:
uniqueness: { case_sensitive: false }
How would I go about doing this?
A couple of approaches come to mind.
easy
You can have a case-sensitive model level validation, as you are doing and simply not validate uniqueness in the database.
ugly
Or, if you insist on a database level validation, a dirty database-agnostic way to do it could be to have another field, just used for validations.
add_column :users, :uppercase_username, :string, unique: true
class User
def username=
#username = value
uppercase_username = value.upper
end
end
awesome
You can define your own indexing function (may be database specific).
The validation that you mentioned (uniqueness: { case_sensitive: false }) will not change the case of the username when it is stored but will instead simply check that no emails exist that are the same. In essence, you do not have to worry about this problem given your current validation.

Rails: Validate unique combination of 3 columns

Hi I wan't to validate the unique combination of 3 columns in my table.
Let's say I have a table called cars with the values :brand, :model_name and :fuel_type.
What I then want is to validate if a record is unique based on the combination of those 3. An example:
brand model_name fuel_type
Audi A4 Gas
Audi A4 Diesel
Audi A6 Gas
Should all be valid. But another record with 'Audi, A6, Gas' should NOT be valid.
I know of this validation, but I doubt that it actually does what I want.
validates_uniqueness_of :brand, :scope => {:model_name, :fuel_type}
There is a syntax error in your code snippet. The correct validation is :
validates_uniqueness_of :car_model_name, :scope => [:brand_id, :fuel_type_id]
or even shorter in ruby 1.9.x:
validates_uniqueness_of :car_model_name, scope: [:brand_id, :fuel_type_id]
with rails 4 you can use:
validates :car_model_name, uniqueness: { scope: [:brand_id, :fuel_type_id] }
with rails 5 you can use
validates_uniqueness_of :car_model_name, scope: %i[brand_id fuel_type_id]
Depends on your needs you could also to add a constraint (as a part of table creation migration or as a separate one) instead of model validation:
add_index :the_table_name, [:brand, :model_name, :fuel_type], :unique => true
Adding the unique constraint on the database level makes sense, in case multiple database connections are performing write operations at the same time.
To Rails 4 the correct code with new hash pattern
validates :column_name, uniqueness: {scope: [:brand_id, :fuel_type_id]}
I would make it this way:
validates_uniqueness_of :model_name, :scope => {:brand_id, :fuel_type_id}
because it makes more sense for me:
there should not be duplicated "model names" for combination of "brand" and "fuel type", vs
there should not be duplicated "brands" for combination of "model name" and "fuel type"
but it's subjective opinion.
Of course if brand and fuel_type are relationships to other models (if not, then just drop "_id" part). With uniqueness validation you can't check non-db columns, so you have to validate foreign keys in model.
You need to define which attribute is validated - you don't validate all at once, if you want, you need to create separate validation for every attribute, so when user make mistake and tries to create duplicated record, then you show him errors in form near invalid field.
Using this validation method in conjunction with ActiveRecord::Validations#save does not guarantee the absence of duplicate record insertions, because uniqueness checks on the application level are inherently prone to race conditions.
This could even happen if you use transactions with the 'serializable' isolation level. The best way to work around this problem is to add a unique index to the database table using ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the rare case that a race condition occurs, the database will guarantee the field's uniqueness.
Piecing together the other answers and trying it myself, this is the syntax you're looking for:
validates :brand, uniqueness: { scope: [:model_name, :fuel_type] }
I'm not sure why the other answers are adding _id to the fields in the scope. That would only be needed if these fields are representing other models, but I didn't see an indication of that in the question. Additionally, these fields can be in any order. This will accomplish the same thing, only the error will be on the :model_name attribute instead of :brand:
validates :model_name, uniqueness: { scope: [:fuel_type, :brand] }

In Rails, How do I validates_uniqueness_of :field with a scope of last 6 months

First Item
I Want to validate a field to make sure it is unique (in the last 6 months) before saving it to the database.
I am thinking I should use validates_uniqueness_of :field, case_sensitive => false, Scope => ...
For my application it only has to be unique if, it was used <6 months ago.
Thinking to compare it to created_at, but don't really know how to go about it.
Second Item
I think I should somehow use .strip to remove any spaces before or after the text that the use may have put in accidentally (I know that these extra spaces are used by default in rails and if they are there can make a filed unique.)
If anyone has any hints on how this should be done correctly I really would appreciate it.
validates_uniqueness_of works by checking if a record already exists with the same value of the given field within the given scope. :scope lets you define the scope (obviously) of the uniqueness; for instance, if I was creating blog software and wanted to only allow a post title to be used once per blog, I could say validates_uniqueness_of :title, :scope => :blog_id -- without the scope, I'd only be allowing each title to be used once across the entire system. :scope won't let you do a complex check, like that which you desire.
What you're probably need to do is create your own validation function to check the uniqueness of the field in question within the given timeframe (code goes within the model):
validate :field_must_be_unique_within_six_months
def field_must_be_unique_within_six_months
return if field.blank?
num_duplicates = self.class.count(:conditions => ["field = ? AND created_at < ?", self.field, 6.months.ago])
if num_duplicates > 0
errors.add(:field, :taken)
end
end
The field_must_be_unique_within_six_months method will work similarly to validates_uniqueness_of, in that it will add an error message if there is already a record with the same given field, but with the added condition that it will also check the date. The validate :field_must_be_unique_within_six_months will add the method to the validation process when a record is saved.
To validate multiple fields at the same time without violating DRY, you could use validates_each to do something like the following:
validates_each :field1, :field2 do |record, attr, value|
if record.class.exists?(["#{attr.to_s} = ? AND created_at < ?", value, 6.months.ago])
errors.add(attr, :taken)
end
end
In the above block, record is the record being validated, attr is the attribute (so field1, field2, etc.) and value is the value of that attribute.
You can probably do something like this:
def validate
errors.add(:field, 'blah blah') if is_used_recently && !has_unique_field?
end
def has_unique_field?
Model.exists?(['field = ? and created_at > ?', self.field, 6.months.ago])
end
def is_used_recently
self.created_at < 6.months.ago || self.new? # i don't know if created_at would be set by this point
end
Alternatively you might want to create a new validation handler, or extend the existing one to pass in a :within option if that's something you're going to be doing often.
To get rid of leading and trailing white space the method you want is 'strip'. You can run this on all your fields by doing something like:
before_validation :clean_up_whitespace
def clean_up_whitespace
self.some_field.strip! # this does the strip in place
end
I hope this helps, let me know if I've made any mistakes!

Resources