The expectation is that "validates :field, uniqueness: true" would validate that the value of the indicated field is unique among all rows in the table. However, this abstraction breaks spectacularly. Any web application, even a Rails app, is usually run over multiple processes, often on multiple different hosts. Uniqueness validation does a non-atomic check and set, which is a known race condition in this kind of environment. The guide does say:
> ...it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on both columns in your database.
But what actually happens when we do this? The race condition where you would otherwise get non-unique values inserted into a column of unique values is instead a race condition where a database adapter throws a generic exception for "the DB complained" with an error message about a failed constraint, and your site throws a 500. Your only recourse is to capture an overly generic class of exception that's thrown by e.g. the MySQL adapter, pattern-match the exception text for a magic string like "uniqueness constraint failed" depending upon which DB you're using, and write your own custom code to recover from that.
That's right: Rails has adapters for MySQL, PostgreSQL, etc, but "adapting" different SQL variants to ActiveRecord doesn't go as far as turning constraint failures, lock wait timeouts, etc. into generic, application-intelligible exception classes. The entire point of ActiveRecord is that your application code should be portable between SQL variants--hell, it shouldn't even have to know what SQL variant it runs on!--but between having to write your own SQL for any use case other than "let's deal with a result set as an array of ActiveRecord objects" and this nonsense, no real-world Rails application achieves this.
In other words, something that Rails pretends happens in one line of code does not actually happen at all, and to make it actually happen, you have to write a whole bunch of code that has to resort to string-matching a bucket exception class for "the DB complained" for the specific exception text that your particular DB engine throws. This is probably the worst example, but it's illustrative. Rails provides the illusion of simple interfaces and enjoyable programming. After working in Rails for just a few years, though, the illusion has vanished for me and I spend far too much time asking questions like "how the fuck did THAT get set to nil?" and "what does does it take to turn this multiline string of raw SQL into objects I can actually use?".
Honestly, I don't want to come across as too harsh. The sad truth is that nothing is perfect, and if you focus on the imperfections, all software pretty much just sucks. But Rails actually tries to convince you that it doesn't suck, that it abstracts away the complexity for you, and that you don't have to worry about it yourself, leaving you ill-prepared for the reality that it's still there and you do have to worry about it.
Ah gotcha - yes this is a flaw in Rails - from the first version, Rails aimed to be "database-agnostic," which isn't actually a valuable property in practice. And DHH disregards when he feels like it, eg. nonsense like https://github.com/rails/rails/commit/9f6e82ee4783e491c20f52... .
There are some ORM's where you define the schema in the application code and it alters the DB schema on the fly, but that seems dangerous, too.
What I would do, given Rails' existing framework of "programmatically discover the table schema and magically generate logic from it", is set it up so that it observes uniqueness constraints and programmatically adds the validation when found. Also, the adapters should interpret server error messages, throw a custom exception class for "uniqueness constraint failed", and ActiveRecord should catch this exception class and turn it into a failed validation when you attempt to save a record.
If that's too much work, just be fucking honest, remove the uniqueness validation (because it's completely useless), and be upfront with us that we have to roll our own solution for it.
http://guides.rubyonrails.org/active_record_validations.html...
The expectation is that "validates :field, uniqueness: true" would validate that the value of the indicated field is unique among all rows in the table. However, this abstraction breaks spectacularly. Any web application, even a Rails app, is usually run over multiple processes, often on multiple different hosts. Uniqueness validation does a non-atomic check and set, which is a known race condition in this kind of environment. The guide does say:
> ...it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on both columns in your database.
But what actually happens when we do this? The race condition where you would otherwise get non-unique values inserted into a column of unique values is instead a race condition where a database adapter throws a generic exception for "the DB complained" with an error message about a failed constraint, and your site throws a 500. Your only recourse is to capture an overly generic class of exception that's thrown by e.g. the MySQL adapter, pattern-match the exception text for a magic string like "uniqueness constraint failed" depending upon which DB you're using, and write your own custom code to recover from that.
That's right: Rails has adapters for MySQL, PostgreSQL, etc, but "adapting" different SQL variants to ActiveRecord doesn't go as far as turning constraint failures, lock wait timeouts, etc. into generic, application-intelligible exception classes. The entire point of ActiveRecord is that your application code should be portable between SQL variants--hell, it shouldn't even have to know what SQL variant it runs on!--but between having to write your own SQL for any use case other than "let's deal with a result set as an array of ActiveRecord objects" and this nonsense, no real-world Rails application achieves this.
In other words, something that Rails pretends happens in one line of code does not actually happen at all, and to make it actually happen, you have to write a whole bunch of code that has to resort to string-matching a bucket exception class for "the DB complained" for the specific exception text that your particular DB engine throws. This is probably the worst example, but it's illustrative. Rails provides the illusion of simple interfaces and enjoyable programming. After working in Rails for just a few years, though, the illusion has vanished for me and I spend far too much time asking questions like "how the fuck did THAT get set to nil?" and "what does does it take to turn this multiline string of raw SQL into objects I can actually use?".
Honestly, I don't want to come across as too harsh. The sad truth is that nothing is perfect, and if you focus on the imperfections, all software pretty much just sucks. But Rails actually tries to convince you that it doesn't suck, that it abstracts away the complexity for you, and that you don't have to worry about it yourself, leaving you ill-prepared for the reality that it's still there and you do have to worry about it.