The Troublesome Active Record Pattern
calpaterson.com
calpaterson.com
The Ruby example he uses as the "The Troublesome Active Record Pattern" simply bizarre. You'd have to deliberately avoid reading the documentation for ActiveRecord to produce that code.
In other words, if you write terrible code, you'll get terrible results.
In actual fact, when you read the docs and actually use ActiveRecord as it was intended you can do exactly what he says it doesn't. Here's what that looks like:
Book.joins(:author).where(authors: {nationality: "Italian", state: "deceased"})
That produces a SQL query that's nearly verbatim what he claims ActiveRecord can't do.You are not the only person to get the wrong end of the stick re:"Active Record". I'm talking about the Active Record the design pattern, not about the Ruby library that took the pattern as it's name but now is apparently a querybuilder library. I probably need to go back and make it clearer somehow.
EDIT: I've done this now. Back to stockpiling tinned food and bullets!
I see this much less now than I did 10-20 years ago, so perhaps it has gone out of style, or perhaps it is a disease of large companies (which I haven't worked at in a decade.)
For example I'd love to move more application logic into postgresql functions, but it's such a pita to run and test I end up procrastinating.
I've been playing with postgREST for a while now and have yet to implement it outside of toy projects. Not because of postgREST itself (which is really cool) but because writing business logic in postgresql is so painful.
Annoying because most CRUD apps could live almost completely in the database exposing views and functions via REST to the end user with w/e flavor of the month UI you like. Oh and they'd run orders of magnitude faster.
I'm not even complaining about SQL syntax, which IMO is actually pretty clean and functional. Just the lack of decent tooling to get shit done.
Personally the conclusion I'm coming to is any interface that presents a table as an class is just the wrong affordance and likely to lead to problems down the line. If programmers modeled tables as sets instead of objects we wouldn't have all these problem apps that page the database into program memory while serving a web request.
This is a bit like acting as if you're the first person to realize that null pointer deferences are a problem and happily telling the entire world to "just switch to pass by value."
Book.joins(:author).where(author: { deceased: true }).map &:isbn`
> first class transactions / decrement race condition Book.find(isbn: 1).decrement :copies_in_stock
> Row-locking Book.transaction do
b = Book.lock("FOR UPDATE NOWAIT").find_(1)
# logic around handling out of stock
b.copies_in_stock -= 1
b.save!
end
Those are all of your examples and the Ruby code that implements the solution you want. (Disclaimer: there are probably small syntax errors here, you get the gist)Honestly this article sounds like it was written by someone who read a 10 year old Martin Fowler article and decided to critique it without even a cursory understanding of modern ORMs. Even more strangely, when everyone starts pointing out the obvious fact that ORMs do solve these problems in practice, you've decided to move the goalposts to say "oh, well the straw man abstract pattern doesn't solve for these real-world problems."
In the future I'd recommend you familiarize yourself with a problem space before trying to publish on it.
I spent a number of years working on a Rails application that used ActiveRecord and which accrued quite a large set of data. In that job, I fixed a whole bunch of N+1 problems, and wrote sql queries with complex joins before the nicer query-builder arel syntax was implemented. I was a pretty vocal critic of AR at my company because of these sorts of headaches (though I was much happier post-arel). But these were all very surmountable problems, the libraries had the capabilities necessary to fix the problems, and as our team matured in our intuition for our data model and size, we ran into these things less and less. In other words, I see this as very much a #1 type of problem rather than a #2 type of problem.
A good analogy seems to be n^2 algorithms. In much the same way, you can write one of those and it works fine for awhile, but then one day you may find that your data has grown enough that it no longer works, and it may be very mysterious what happened. But with some experience and work, you can figure it out and probably fix it. And as you gain experience, you can more wisely decide whether to avoid the n^2 algorithm from the start, or whether it will work fine for your data size.
I agree very much with the idea that the problem is fundamentally caused by wanting to present objects to apps, and that dealing with queries instead is better and the real solution. Of course, the queries can't be SQL -- we can't have that! So we end up with N+1 query languages: SQL and N more non-SQLs. But N+1 query languages is a lot better than no query languages as in earlier ORMs.
> in real life there exist real applications using ORMs that have or cause these problems even where the ORMs in question provide modern query facilities that don't suck
And surely you have also seen plenty of equivalent examples of bad code written using SQL?
Cal is attacking an imaginary ActiveRecord pattern, while ignoring the features of the most famous implementation (RoR), and ignoring the benefits of ActiveRecord. Dumb.
Most SQL RDBMSes have proper parametrized queries, and there's no excuse not to use them. This is no excuse to not use SQL. It's like saying that no one should use ORMs because you can use them poorly.
> And surely you have also seen plenty of equivalent examples of bad code written using SQL?
I've seen bad Java, C, C++, Python, Perl, bash, etc... The SQL I work with is no worse than the Java, C, ... that I work with.
> Cal is attacking an imaginary ActiveRecord pattern, while ignoring the features of the most famous implementation (RoR), and ignoring the benefits of ActiveRecord. Dumb.
He was talking about a particular pattern, not Ruby on Rails.
This is what I would use
Book.objects.filter(author__nationality="Italian", author__state="deceased").values("isbn")
This is- Is one single DB query.
- Completely obvious way to describe what I want to do.
- Only pulls the fields which are needed from the DB.
If you are claiming "the troublesome acriverecord pattern" you need to provide specifics of what can't be done, rather than claim "Don't look too deeply into the code samples for specifics though"
I never read a proper specification of ActiveRecord, but it could be that a "pure" implementation doesn't allow for joins? Hence the weird looking example with the nested for loop, which variations of I have seen plenty in real world code. Mostly by people that don't sufficiently understand the ORM or SQL, not because they were sticking to a pattern though.
In your example it is obvious what happens in this simple example, but as soon the joins become more complex, it becomes in my opinion quite unwieldy and non obvious what the resulting SQL will be. I had multiple performance problems in the past because of the queries the ORM generated or with memory/cpu usage on the app side.
The last time I used Django a couple of years ago that was one of the bigger pains for me: It was either the Django ORM capabilities or raw SQL, not much in between. Or maybe I was just unaware of how to do it better.
Your article here seems to avoid falling into this particular trap, but I consider this a problematic view for authors to hold. In software writing, code listings have equivalent weight as figures and tables, and are often what readers will look at first to get an overview of the article. If they don't adequately demonstrate the argument of the text or don't appear to be plausible, the entire article will fall flat.
book = Books.joins(:authors).where(author_name: 'James S. A. Corey').first
book.title = "Tiamat's Wrath"
book.save!
It's not that they stopped supporting the Active Record pattern, it's that they rebuilt the whole thing a few years ago with a real relational query builder under the hood.https://lobste.rs/s/valayp/troublesome_active_record_pattern...
I agree there are places the pattern falls short at times, but are there actually any implementations of the pattern in widespread use that would make you use separate queries in loops rather than do a basic join?
It's actually harder to go straight to the database if you're using a more powerful ORM, because with a more powerful ORM, you're hiding implementation details. You tend to obfuscate the difference between a calculated and a stored column, a denormalized vs normalized attribute, composite vs entity, etc. That makes it harder to write SQL to get what you want, not easier, and when you do write the SQL, you're slicing through a thicker business layer and are at more risk of violating invariants.
If you've got a monolingual, homogeneous tech stack, the whole database might be abstracted away and you might use an OO interface which uses distributed caches and locks etc. You're much more likely then to write your queries as loops and manual filters and joins.
> Likely they will require increased understanding of the specific ORM being used
So your diagnostic of "deliberately avoid reading the documentation" is spot on.
If the goal of the author is to not read the docs, I completely agree that most ORMs are an absolute hell to use and going back to almost pure SQL would be the holy grail.
But then, I am personnally in the camp of spending the time to understand the tools. There is a tradeoff between the time spent to on learning a specific tool and the ROI of that learning curve, I think this article's position on wanting mostly returns with no time invested is way too unrealistic.
But it's... not very good. He constructed his own interface in order to complain about it, an interface which is so subptimal that it lets him complain about how bad it is as if the pattern was somehow to blame.
In most Active Record implementations I've seen, a Record is a live tuple ("Active" "Record") and you typically have some form of Repository ("Books" maybe in this example?) alongside of it which is responsible for carefully and optimally building the record objects.
The pattern itself says nothing about how to query things, it talks about a kind of DAO that has convenience methods to access and manipulate a tuple.
Also, what's with the REST rant? I don't think I understand how that's linked.
Yes, it is contrived, but it faithfully represents many examples I've seen in the real world.
> Also, what's with the REST rant? I don't think I understand how that's linked.
Why not? It's clearly explained. The typical REST API (as opposed to the well designed REST API) is just Active Record over HTTP: /something/:id called in N+1 loops. And this emerges for the same reason as it does in the Active Record pattern; the 'central abstraction' offered by REST is mapping every resource to a URL, as opposed to mapping every row to an object.
The active record pattern from the article isn’t the active record pattern from Fowler and not really how it’s generally practiced. Fowler’s active record is about having data access methods on the data objects. The article says whole object access and access by id, so something different. Probably would have been a lot better not to reuse the term.
Whole-object access is very typically part of ORMs. The article raises an issue this way: “Retrieving whole rows is hugely wasteful when only part of the row is required to resolve a user request.”
But note that you can control what’s included in the object. In other words, if you are running into problems due to getting too many columns because your object has too many properties... then change your object. An ORM giving you what you asked for isn’t a problem with the ORM. I think when people run into this kind of thing, I think they are usually stuck in a situation where they can’t express queries in a reasonable way. (Could be due to complexities or limitations of a specific ORM, or could be silos between front end and back end development.)
Getting objects by id is very typically part of ORMs as well. But ORMs very typically include a mechanism for queries as well, so I don’t even know why that came up. Maybe again the problem the author is trying to get at is lack of reasonable flexibility to express queries.
Now, a general issue with ORMs is that they tend to bury queries under an extra layer of abstraction. I think there’s an article to be written about that.
The article does go on to say a data access mechanism should have first class queries and first class transactions. Which are completely true, but dont really have anything to do with active record. (Those are just a start, though. Some think an integrated query language is “first class“, which is backwards.)
The part about restful apis is probably not worth breaking down. it conflates restful, the idea, rest apis as commonly implemented, http, json, and so forth.
However it's not a magic bullet by any means: EF will still sometimes generate weird join queries that require serious head scratching and second guessing to get into a better form, and huge nested .Include / .ThenInclude trees are extremely common when your data is highly normalized. This can lead to the same kind of inefficiencies seen with the ActiveRecord pattern.
FWIW what really slows things down isn't typically selecting every column, it's those really giant joins or joins on things that aren't indexed. I think there's some room for improvement in the DX of EF itself here -- it could at least tell you if you write a join on a non-indexed column. (It has all the information there, as the indices are defined in EntityMap source files).
Overall, when building applications on a RDBMS <-> OOP <-> REST API, there are indeed different fundamental abstractions, and no framework or library will save you from needing to understand how those abstractions map between each other.
And believe me, GraphQL is also no panacea! It has its own similar but different issues.
book = Book.get(isbn="978-0099466031")
book.copies_in_stock -= 1
book.save() # WRONG! irreparable data loss here
is that in SQL you should write it like this: update books set
copies_in_stock = copies_in_stock - 1
where isbn = '978-0099466031';
No needs of transactions.I don't remember how to do it with Django's ORM but in Rail's ActiveRecord I'd use directly the connection object
update_query = sanitize_sql(['update books set copies_in_stock = copies_in_stock - 1 where isbn = ?', isbn])
ActiveRecord::Base.connection.execute(update_query)
This saves a lot of programming pain, possibly skip some unnecessary bugs and get better performances.sanitize_sql is from https://api.rubyonrails.org/classes/ActiveRecord/Sanitizatio...
I think that a lot of your argument just boils down to this. The Active Record pattern allows less experienced/knowledgeable developers to write CRUD code that works. These are the same devs which would often create unnecessary quadratic (or even worse) solutions in regular imperative code too. Their code would often be far from pretty or optimized, but using modern high level languages and libraries, they can relatively easily get the job done and provide business value.
If given a dichotomous choice between libraries that are make it easier to write and to reason about out of the box, or libraries that perform much better but only "when you know what you're doing", most would choose the former. And I think they'd be right, there's enough complexity out there in the actual domain already.
But it's actually even better, it's not actually a dichotomous choice, and it's generally quite straightforward to bring in a more experienced dev to help refactor and optimize such code later, when the need arises.
book = Book.objects.get(isbn="978-0099466031")
book.copies_in_stock = F('copies_in_stock ') -1
book.save()
F expressions perform operations at the DB level directly using column name without ever pulling that data into Python code. select isbn, copies_in_stock
from books
where isbn = "12345678"
and copies_in_stock >= 1;
isbn: 12345678
copies_in_stock: 1
update books
set copies_in_stock = copies_in_stock - 1
where isbn = "12345678"
If you have two of the above requests happen at the same time such that the execution order is SELECT SELECT UPDATE UPDATE, you'll end up with -1 copies_in_stock. You can try adding an "and copies_in_stock >= 1" to the UPDATE and then look at the number of affected rows, and perform recovery logic if the number of affected rows is 0, but at that point you've basically implemented a transaction, probably badly if it isn't what you set out to do.https://docs.microsoft.com/en-us/aspnet/mvc/overview/getting...
This also has the added benefit of allowing you to issue only 1 query(just the update), instead of select + update.
The issue here is that you (apparently) want to guarantee that whoever sees the result of the select (probably a customer) can safely buy the last available book with the update.
This can't be solved in a simple way because minutes can pass between that select and that update. This means that you can't put those queries in a transaction. If you could you should use select for update
https://www.postgresql.org/docs/current/sql-select.html
I used code like update table set counter = counter + 1 / - 1 many times without transactions when all I have to do is to update a counter and nothing else in the system really cares about the value of the counter. If this is not the case, the queries accessing the counter should use some form of locking. A transaction is not enough.
This is the first good guide I found googling for examples https://vladmihalcea.com/a-beginners-guide-to-database-locki...
Transactions are not omnipotent in that regard and they fail in several well-researched ways, but it is in their purview.
Book.where({isbn: "978-0099466031"}).update_all("copies_in_stock = copies_in_stock - 1")
without breaking? It's been a long time since I've used it and I can't remember whether you have to use .execute or not. > Unit.where(ccp_id: 1).update_all("position = position + 1")
Unit Update All (9.4ms) UPDATE "units" SET position = position + 1 WHERE "units"."ccp_id" = $1
=> 5 book = Book.get(isbn="978-0099466031")
book.copies_in_stock = Book.copies_in_stock + 1
book.save()There's no difference between `Author.get(id)` and `session.query(Author).get(id)` in terms of code design, data integrity or whatever. Maybe you could make the argument that by forcing you to interact with the `session` object you are encouraged to call `session.save()` which makes the transaction explicit, but I find that a thin argument.
People are going to make O(n^2) mistakes like the one used as an example here regardless of whether they use an ORM. In fact, people make those sort of mistakes without even touching the database.
Yes, the ability to expose query objects is super important, but there are active record libraries that do this, for example Laravel's Eloquent or Django's ORM.
Seems like this article is conflating a lot of issues that aren't really related. I would re-write the whole thing as:
1. The importance of native query objects in your ORM 2. The importance of transactions 3. Traps of O(n^2) when using ORMs
Don't even bring up active record.
What's thin about it? The most important aspect of code is being able to read it and understand what's going wrong; a seemingly innocuous function call that actually interacts in a complex way with some hidden global state is pretty much the worst kind of code you can write.
Same thing is true for SQL.
I'm currently working on migrating a 10 year old asp dot net c# system of applications. The amount of nasty stored procedures being called from other stored procedures that call on multiple views and total disregard for data modeling best practices is atrocious. I've had to just throw away shit code and get the business requirements in order to completely redo the SQL queries because they were impossible to read and fix.
We had a single query with over 2000 execution plans due to parameter explosions! It confuses the shit out of the database query planner and caused intermittent performance issues based on who ran the query with what parameters and when they did it.
His code example with m*n run-time is a total misunderstanding of how to use an ORM and is common among people who are used to thinking of databases as dummy stores. In reality, a database run-time can provide great performance when used correctly. Data partitioning, query optimization, materialized views, are all great skills to have in your toolbox. Most developers don't know jack about databases, data-modeling, and performance optimizations.
Active Record is fantastic for CRUD operations, it removes boilerplate code, and still provides good ways to join relational objects. Always select only the fields you need in ORM queries to reduce overhead and the data retrieved. Tune your database (rebuild indices, and partition your data properly). There's a general disregard for understanding technology and hacking of business logic in the service layer which results in poor performing applications. Perhaps that's what makes people hate on Rails and Active Record. It makes it easy to write bad, non-performant code. What works today on your small dataset, will not work 6 months or 5 years from now.
A tool is only as good as the person using it.
Don't be a fanboy hating on specific technologies, when you don't know when to use what for a particular use-case.
The "pattern" (pun intended) always goes like this:
- Gotta get 1.0 out really fast
- Agile Dev really coming on strong last 15 years
- Architect/early dev lead is not a DB guy
- ORM + Active Record looks attractive and fast due to less code
- Junior devs don't even have to learn SQL
- Simple Active Record architectures let you layer on new stuff to get the all important 2-week sprint demo. Since Agile overvalues the sprint demo & showing stuff in the UI is what matters to PMs performance doesn't matter and gets ignored here. Infrastructure work for future scaling doesn't count as points and/or is considered poor use of time.
- Tons of entities & repositories based around Active record start getting built, it saves tons and tons of code over hand written/optimized SQL code
- Project launches and starts getting customers, getting bigger, needs to scale
- Active Record & Naive ORM generated queries start breaking down due to locking too many rows in DB
- Database deadlocks eventually start happening
- DB Connection pools eventually start running out of connections
- Customers are furious
- New Upper management comes in and wants to rein in PM to try and get developers to work on the massive tech debt. Agile starts to not even work since tech debt makes it look like no work is happening since it doesn't demo.
- Developers start having to piecemeal replace some ORM generated queries with native queries/query builder type patterns
- Eventually the team starts to have to rip the ORM out completely to continue scaling.
The alternate pattern that happens is that the team gets hot under the collar to drop the SQL database for a column-store/NoSql database. The problem wasn't the SQL database here but bad usage of patterns that the NoSQL DB probably doesn't support if it is not an ACID DB.
My feeling is most of these ORM based/Active Record type systems are often 3-4 orders of magnitude slower. Fixing some of this is somewhat one of my specialties.. I have rewritten many stacks that were taking minutes to finish and had them working in < 1 second once the ORM was taken out of the picture.
99% of articles like the above one come down to: "developers wanted to pretend there isn't some underlying data model underneath, and abstracted it away".
The problem isn't that much in ORMs / query builds etc. It's with "Objects" itself, and the idea that one can abstract away the data model and pretend the business code can be written as a bunch of OOP classes and such. It seems like a genius move at the beginning but hits a really steep cliff once the load and complexity of problems incrementally increases. When it starts to happen it's actually funny how not many people can actually realize what happens, and they usually struggle with band-aid solutions and blaming irrelevant pieces.
I decided to post on twitter about it: https://twitter.com/dpc_pw/status/1240719977071575040 in case anyone is interested.
I think it would be more correct to just say that GraphQL encourages queries as first class object as it exposes the entire schema and allows for granular querying.
Implementing it by composing REST APIs is, well, an implementation detail. There are other ways to do it too.
http://www.hydrogen18.com/blog/golang-orms-and-why-im-still-...
The phrase "object-relational impedance mismatch" resonates pretty strongly with me still. That being said, I still use lots of ORMs in my day job.
Retrieving a whole row rather than just a single column from a database that's likely on a local network definitely doesn't seem "hugely wasteful", especially for smaller projects.
Database performing a table read because additional columns were requested shouldn't change the complexity class if the query was otherwise successfully satisfied using the index.
I searched the article for "complexity class" and, while I found the phrase, it didn't explain much.
But yes, you're right: just going to the heap is not a change in complexity class.
(Created by one of the former maintainers of ActiveRecord in Rails. It's much more of a query builder than an AR implementation.)
A similar pattern could be done for on-demand joins, though you might need macro magic (or a dynamic language that lets you, say, subtype 'string') to derive joins from attribute values. For instance, if you have a sufficiently advanced typesystem, you may return instead of a string something like a `SqlSourcedType(string, "tablename", "attributename", originalCallSiteId)` that acts interchangeable with a string. Then later on when passing such a type to a load on another table, the framework would know that you were using a join idiom, and could load the joined tables directly in the first query the next time around. Basically JIT SQL.
2. REST does not insist you fetch whole rows. You can have field masks with REST, it's inherently untyped, there is literally nothing preventing you adding that performance optimization if you need it, in a backwards compatible way (add the field mask query param later when you need it). https://developers.google.com/slides/how-tos/field-masks
https://en.wikipedia.org/wiki/Object-relational_impedance_mi...
Thanks you Cal for the insightful and concise article!
The biggest pain point nearly always also seems to involve time conversions that might or might not use default assumptions and environment variables which language examples almost never cover. Toss in specifying an encoding to handle the result as (or to disable) and I think that's what might make up a good DB interface specification for drivers to implement.
That doesn't mean that you can't have a RESTful API for a relational database that doesn't have these problems. For example, PostgREST does exactly that.
Sometimes developers will argue that they end up reusing variants of the same query in too many places... That is a poor argument; if that's the case, it means that your code is poorly designed. If you organize your code in such a way that components have clear and distinct responsibilities, you're not going to end up with duplicated queries.
Also, the perceived 'impedence mismatch' between OOP languages and relational databases is only apparent when you try to design a data layer in OOP code which exposes a general purpose interface to execute specific queries. Impedence mismatch can be solved simply by dealing with each database operation on a case-by-case basis using raw SQL or Language Integrated Queries (E.g. LINQ). It adds a little bit of boilerplate, but it's absolutely worth it. You don't need general purpose entities just to access data.
The article talks about projection, joins and aggregations, but many projects in Rails that I've seen just don't need those: it's often solved in the application. Yes, it's SUPER SLOW, it loads data just to throw it away again and can break ACID properties, but it generally works -- these applications aren't handling transaction on a stock exchange or controlling a pacemaker. And in the case of Rails' ORM, it's been very easy to do real joins, projections and aggregations when needed, meaning that the Active Record pattern itself hasn't excluded that possibility.
The paragraph about transactions is also a bit weird as it has nothing to do about the Active Record pattern afaics.
I actually find that even though these applications aren't running pacemakers that they still don't work. It's really easy to write what is in fact bad algorithm for accessing (eg) shoe size data when you are working on rows only as objects.
I cannot speak (and am not) for the Rails ORM but the problem here is less about some library people use but about the primary affordances being "get this object by id, edit it, and save it back". This is just not right IMO and is the underlying cause of the famous "object-relational mismatch". I suppose I come down on the relational side of that in that I think object access is the problem.
The last time I tried to write code with just raw SQL, the query patterns got tedious really quickly, and I found myself using the most bizarre patterns to normalize query fragments and avoid typos in rarely used queries. It all made sense to me as I did it but others thought I was nuts. They were probably not wrong.
But every time I used an ORM or worked with people who did, things got sloppy, quickly.
Simple typos are caught of course, but also more subtle problems. If for instance you drop a NOT NULL constraint on a column in your database, your application will not compile until you add in the appropriate NULL handling.
Like I said it is a bit radical, but to me it clearly feels like the future and I miss it whenver I work with a traditional DB mapping.
[1] https://github.com/jawj/mostly-ormless/blob/lateral/README.m... (note: I’ve updated the code and write-up here, but not yet the instructions for setting it up, so YMMV on that)
But then it constructs this giant crazy strawman of ways that you could do a bunch as ridiculous silly things, and how if you did those crazy things it would completely wreck your performance.
Certainly it would. But nobody would ever do any of those crazy things.
My version of this thing, for instance, makes a point of wrapping all your Stored Procedures for you, and provides methods to construct single items or lists of them from a dataset that you hand it. That solves roughly 100% of the panicky disaster scenario that the author predicts. (The other 0% is the giant (also usually 0%) performance hit you get from pulling all six columns of data from a table when you only need two of them.)
So yeah, don't recursively enumerate the entire database three times when you can write a query with a Join in it. But don't make up silly query languages that aren't SQL either.
Just do what sensible people do with this data pattern and you'll find that it works nicely.
Not sure the blame belongs to the ORM but Django rest framework has been the source of N+1 queries as well
Active Record is a powerful pattern - for it's usecase. You use it to build applications to find, create, update and delete objects. It's not useful for complex queries, aggregations, graph applications, NLP, mathematical analysis, or any other use case it's not meant to solve. No surprise there.
The ruby package of the same name includes quite a few things that support complex queries and aggregation, and that way it goes beyond the pattern which it is named for (including all of the examples that are presented as the better alternative solution). For requirements that are best solved by dropping to SQL you can bypass the records machinery entirely, in marked difference to some other ORMs which try to completely hide and monopolise DB access.
Disclaimer: I work at Hasura.
Example contains a piece of code that uses nested loops and each loop queries the db - producing many queries.
There's no problem with ActiveRecord. There's a big problem with programmers not learning relational databases.
Here's an excerpt from the post: "To get all ISBNs of all deceased Italian authors in the Active Record pattern"
Now look at this black magic: you create an SQL view with all the joins you need in order to cross-reference data and extract what you want, then you have your ORM use your view as if it were a table. Job done. Your code is now 1 liner and the database won't receive too many queries.
I know that the text references SQL views but this is what's said about them: "SQL has views too of course, but most ORMs don't have any support for them"
A view is, for all intents and purposes, a table to the ORM. There's no special support needed for it, if you use the view to read from it.
I'm sorry for being one of those guys who shit on someone's hard work, but the mister Cal Peterson missed the whole football field here.