Problem with validates_uniqueness_of - ruby-on-rails

I'm using rails 2.3.5 and Authlogic in our website, and I have been getting errors on the database through hoptoad of users with duplicated emails trying to be saved. The problem is, obviously I have the validates_uniqueness_of :email on the model.
On my tests here in development I get the expected validation error and the user is not saved, but in production, I keep getting this errors on the DB layer.
I've tested with case sensitive emails and it also validated correctly.
I've checked and the class and there is no attr_accessor or any other attribute override, and I don't think Authlogic would do it in a wrong way...
What could be happening in production? Are there any cases where rails validates doesn't work?

Locate the SQL running validates_uniqueness_of in your development log, and if you see something like WHERE (email = BINARY 'foo#example.com'), try creating a user with FOO#EXAMPLE.COM and now you can reproduce the DB-level duplicate exception.
To fix this, put the following code in config/initializers/patches.rb:
class ActiveRecord::ConnectionAdapters::Mysql2Adapter
def case_sensitive_equality_operator
"="
end
end
Note that Mysql2Adapter should be MysqlAdapter if you're on Rails 2.
On a side note, it is a longstanding bug in Rails IMO - handling case-sensitivity in Ruby level doesn't make sense at all. If you need case-sensitive lookup, you should have the column collation of utf8_bin. If you need case-insensitive lookup, you should have the column collation of utf8_general_ci. Applying BINARY function in the where clause will disable the use of index, and validates_uniqueness_of causes full table scan every time you try to create/update a record. If you have millions of records, you're totally screwed. The patch above will fix that, too - in fact, it was my original motivation to create that patch.
If you agree, please +1 to https://github.com/rails/rails/issues/1399 :)

Have you tried recreating the scenario. Why should throw errors that warrant Hoptoad Notification. I mean, basically if you have a it should not save the user and not throw an error for hoptoad to notify you about.
Also with authlogic, i don't think you are required to specify the validate_uniqueness_of for email. Usually authlogic will take care of that for you.
So I guess, its time for you to deep dive.
Look at the logs, and try recreating this error locally. Its always best to retrace the steps leading to error.
More details, error stack , code would definitely be helpful.

Just a guess but could it be that your email column allows null, validates_uniqueness_of is ignoring nil (or blank) values and that your users are trying to register without specifying their email addresses?

Related

Delete record with nil id in rails console heroku ruby postgres

I used destroy to delete a record remotely in heroku rails console and now it does not show up if I write
MyModel.find_by(email: 'some#email.com')
but it does show up if I write
MyModel.find_by_or_create_by(email: 'some#email.com')
...except the id is nil. I can't figure out how to get rid of this record. I am using postgres and rails 4
When I try to create a new record with the same email via the web ui, it triggers the uniqueness validation for this ghost record...yet I can't remove the ghost record.
When find_or_create_by returns a record with a nil id, that suggests the find part is failing, and then the create part fails too with validation errors. What do you get from MyModel.find_or_create_by(email: 'some#email.com').errors.full_messages? I'm guessing you see the same uniqueness validation error as you're seeing in the web console.
Is your app using a soft-delete approach, e.g. with a gem like acts_as_paranoid or permanent_records? Those gems change the behavior of destroy so that it does not issue a SQL DELETE command but instead sets a deleted_at column. They also hide soft-deleted records, so that may be why find_by isn't giving you anything. If this is what you're doing, you should make sure your uniqueness validation knows to ignore soft-deleted records. How to do that depends on your soft-delete implementation, but you might find some tips here.
You might want to try straight SQL to see what's really in your database, e.g. using the Heroku psql prompt or this Ruby code: MyModel.unscoped.where(email: "some#email.com")
find_or_create_by will look for the record by the given parameters and create it if it can't be found. If there isn't an id field that means it isn't being saved and your problem is already solved.

Model named "Type" in Rails

Good Day.
I have a problem with my Model named "Type". The error is:
undefined method `all' for ActiveRecord::AttributeMethods::Serialization::Type:Class
In this particular part of validation:
validates :type_id, presence: { message: 'selected is invalid' }, inclusion: { in: 1..Type.all.count }
Maybe it's due to reserved names conflict in Rails. But is there a way that Rails will consider this name before I proceed to refactor? (if nothing else to do).
It's really a bad choice for a name but it's too late.
PS.
When I do 'Type.all.count' in rails console, there are no errors that occured.
Here is a list of reserved words in Rails. As you can see, type has also been reported to cause problems.
Rails is built over convention over configuration agreement, so I would suggest just to pick another model name (which is not mentioned in stated blog post).
Take a look at this thread to get an idea of what should be done to turn your Type model to something else.
In case you are completely sure model name is not the case, you could try to trick around with different validation notations like
inclusion: { in: 1.upto(Type.all.count).to_a}
Ruby's constant lookup is resulting in it finding the "wrong" Type class. Using "::Type" forces ruby to use the top level Type constant.
You should note that this count will be done once only: when the rails instance starts. If new types were to be added, the validation would not take that into account. If a type were ever deleted then it would not allow the last type to be used.
If you have a type association then you could do
validates_presence_of :type
Upon saving it would try and load the corresponding Type object from the database, so saving would fail if there was no such row.
For a really strong guarantee, you could use a foreign key constraint (the foreigner gem adds helpers to rails migrations for this, but you can also just write the sql statement by hand)
Fortunately, I found a workaround. However, it is not recommended but if you don't want to do Migration and ton's of Refractions, here is my solution (credits to twonegatives).
in my validation code:
inclusion: { in: 1.upto(:type_alias) }
then create that method type_alias
def type_alias
Type.all.count
end

How to access translation key for ActiveModel validation error?

I Have a situation, where I wan't to store the translation key for a validation error in my db instead of the error message it self. Imagine the following situation:
class Car < ActiveRecord::Base
validates_presence_of :year, :fuel
end
car = Car.new(:fuel => 'Diesel')
car.save!
#=> ActiveRecord::RecordInvalid
Now if I call:
car.errors
#=> :year=>["can't be blank"]
I get the translated error message.
Instead I would like to extract the translation key that would generate this (in this case I think it would be something like errors.messages.blank), so I can store it in my database in a different model e.g. FailedCar so I can later on generate an I18n customized form for filling in the missing information manually in a view.
UPDATE
I think it's this method that I need to hook into. I want to fetch the key and the options returned, so I can perform the translations again at a later point in time.
Okay I finally got it!
The solution is NOT to patch or do anything on the rails side of things - instead - the answer lies in the I18n gem.
I18n, which rails uses for translations by standard, has the ability to plugin new backends to provide more flexibility to it. In this case, the backend called metadata does exactly what I needed. When adding I18n::Backend::Simple.include(I18n::Backend::Metadata) to an initializer, it gives me the possibility of extracting all translation related information directly from the error message string by adding the method call translation_metadata.
A fantastic simple solution to a complicated problem :-)
Have you looked at (.yml) local files too? You can store it as a translation string. For more Info Rails Internationalization.

Rails Association Validations: The field, or the _id field?

One of the messier practices I have in Rails development is juggling validations of associated fields between validating the actual object (eg: validates_presence_of :related_object) and validating on the id column for that association (eg: validates_presence_of :related_object_id).
I figure I should probably start being a little more consistent with this, and before I commit to anything, I'm wondering if there's any advantage of either method over the other? I can't think of anything, but then I've been known to overlook stuff before. So, does it make any difference? Is there a convention re: what most developers do that I should abide by?
Any suggestions appreciated.
This question comes up every so often.
In most cases you will want to validate the presence of the actual associated object, not just verify that an id (which could well be invalid) has been set.
Validating association_id will also prevent you from creating the object with a new association record and saving both together.
Of course you have to check the presence of :object_id. If you check the presence of :object then this object will be fetched from your DB and then will be checked via simple blank?. I guess you won't be happy with additional DB hit.

Using acts_as_paranoid plugin for soft delete - what about validations?

I am looking on trying to use acts_as_paranoid plugin for soft delete of records. I was earlier managing it using a flag in the db. I know that this plugin will omit a record from searches and finds if the record is soft deleted. What I want to know is if I have a validation in the model like validates_uniqueness_of :email and I deleted(soft deleted) the record having email 'prince#gmail.com'. Now when I try to create a new user having same email, will the validation work and prevents the creation of the new record. Or will it omit the soft deleted record as it does for finds? (I would like this to happen, of course.)
acts_as_paranoid does not reimplement validates_uniqueness_of, so if you have (soft) deleted a record with email 'prince#gmail.com' you cannot create a new record with the same email.
The easy fix for this is to add a scope to validates_uniqueness_of:
validates_uniqueness_of :email, :scope => :deleted_at
This way you can have any number of (soft) deleted records with email 'prince#gmail.com' and still create a new record with the same email.
From our testing, the patching that acts_as_paranoid does affect the deletes, so you would end up with two records. From most of the conversations around the web, this is probably what you expect.
In our case, we didn't want this. When we create another user with the same email, we want to "undelete" the first user, and we'd like the validations to hep us with this. Turns out that we couldn't figure out a way to do what we wanted. We ended up not using acts_as_paranoid in this case, but we are still considering going back.
We did find one patch that allowed passing in a flag to validations (:with_deleted => true), so that you could explicitly control this. This seems like a good idea, but we decided not to pursue it. Unfortunately this issue highlights that this approach is a bit of a "leaky abstraction" and has to be used with care.
if yor are using "rails3_acts_as_paranoid" then have provision for above mentioned issue,
ActiveRecord's built-in uniqueness validation does not account for records deleted by ActsAsParanoid. If you want to check for uniqueness among non-deleted records only, use the macro validates_as_paranoid in your model. Then, instead of using validates_uniqueness_of, use validates_uniqueness_of_without_deleted. This will keep deleted records from counting against the uniqueness check.
Need to specify following way ,
acts_as_paranoid
validates_as_paranoid
validates_uniqueness_of_without_deleted :name

Resources