These things are so basic (and not even specific to Ruby) it makes me want to cry :)
These are mistakes we were shamed for, in a previously PHP-based career, literally 15 years ago.
I’m with you. :/ But yeah, the user_id was one I just corrected on StackOverflow yesterday. I’ve seen super-senior people completely botch scoping their queries and still string interpolating parts of queries.
The ease with which Active Record allows SQL injecting yourself is pretty stunning to me. https://rails-sqli.org/ highlights the various methods that take arguments that look like they’re safe, but can actually be arbitrary SQL.
Granted I have a bias (I’m an emeritus Django core developer), but to me there’s a sharp contrast with the Django ORM, which doesn’t take strings-that-can-be-SQL anywhere in the ORM API, except methods that take exclusively SQL, like raw() or extra(). In my experience this is a much more fool-proof API, I’ve reviewed a lot of Django applications, and I can count the number of SQL injection vulnerabilities on one hand.
The scopes one is interesting. I agree that you need to authorize resources, but there are some nuances at times I’ve found non-scoped finding to be useful for.
Given the application, you may want an authorization failure to return a 404 status, to prevent information leaking to unauthenticated or unauthorized users. For instance, this is what GitHub does, so that I can’t poke around a private project and look for config/initializers/ files and tell if a file exists by comparing 403 vs 404 responses.
In such instances though, you may also wish to have audit logs that distinguish between not-found situations, and forbidden situations. In that case, an unscoped find, with a separate authorization layer that effectively enforces the scope, can yield the same effect but give you distinction. This may also prove useful for plain old debugging because you get information faster as to the cause of the failure (not found vs. authorization failure).
Additionally, if you have multiple controllers acting on the same resources, if you spread finders scoping through associations across all those controllers, rather than centralizing it in an authorization layer, it can turn out to be not-very DRY and spread what is essentially authorization logic across multiple places. Furthermore, if your authorization layer grows in complexity, you effectively end up sharing authorization logic between your authorization policy layer, and your controllers.
I tend to find this plays out more for #find style calls, and not so much for where queries as in your examples. For those, I do tend to still rely on scoping more.
At the end of the day, I think what we’re saying the accomplishes the same thing, just in different ways.
One question regarding SQL-injection. I was under the impression that ActiveRecord creates a prepared statement for each query. Is it not so?
It does, but you can still include raw components, with string interpolation that will not be quoted correctly. Also, if you pass in dynamic sort or filter keys those are an interesting avenue of injection that many people are not aware of.