1. 程式人生 > >Ask HN: Is Ruby on Rails still worth learning?

Ask HN: Is Ruby on Rails still worth learning?

> After reading the rest of your post I just think it's that you haven't used Rails since 2006.

I used Rails on a full-time basis between approximately 2011 and 2015.

> Like what?

Did you actually read the rest of my post? I explicitly covered that:

> ...manually add all the DB constraints the ActiveRecord documentation tries to talk you out of, write my own error handling code for when these constraints threw exceptions that ActiveRecord had no idea how to handle, liberally call 'connection.execute' or 'find_by_sql' when nothing I wanted to do cleanly fit into the ActiveRecord query interface...

And while I haven't used Rails since 2015, a quick check of the documentation reveals that the same basic limitations still exist, which I've written about before:

> OK, what if you want to generate the SQL dynamically with attributes? connection.execute doesn't do sanitization for you by itself (though you can call a private method to do that). If the generation is more complicated than that, you also have to have code to concatenate together strings of SQL code. Not because Rails doesn't have a SQL generation library, just because it's tightly coupled to ActiveRecord and fuck you and the horse you rode in on if that isn't good enough for you.

> I generally advocate for find_by_sql and select_all, but think about what that actually does. It instantiates a bunch of ActiveRecord objects that have to be of a particular model (what if the query joins tables together and returns a result set that doesn't actually neatly contain the intended columns of a given model? I can't even use ActiveRecord::Base.find_by_sql, I have to choose an actual ActiveRecord model arbitrarily), has the columns of the result set dynamically bound to it as methods during runtime (slow), also has the methods of the model itself bound to it, which may have unexpected behavior based on the query (especially if we just choose an arbitrary model), and in exchange we get to treat the result set as an array of structs rather than an array of hashes (which admittedly has its performance advantages, but not if the fields have to be dynamically bound to each object as methods!). Oh, and if you're writing an INSERT statement you have to use connection.execute after all. Have fun!

> 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.

> ...

> 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?".

> 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.