1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-18 14:38:01 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
jbranchaud
6df0693804 Add a few more notes to the latest TIL 2024-11-02 13:47:16 -05:00
jbranchaud
507602ef0c Add Prefer select_all Over execute For Read Queries as a Rails TIL 2024-11-02 13:29:54 -05:00
2 changed files with 65 additions and 1 deletions

View File

@@ -10,7 +10,7 @@ pairing with smart people at Hashrocket.
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
_1493 TILs and counting..._
_1494 TILs and counting..._
---
@@ -980,6 +980,7 @@ _1493 TILs and counting..._
- [Parse Request Params In Rack::Attack Block](rails/parse-request-params-in-rack-attack-block.md)
- [Perform SQL Explain With ActiveRecord](rails/perform-sql-explain-with-activerecord.md)
- [Polymorphic Path Helpers](rails/polymorphic-path-helpers.md)
- [Prefer select_all Over execute For Read Queries](rails/prefer-select-all-over-execute-for-read-queries.md)
- [Pretend Generations](rails/pretend-generations.md)
- [Prevent Writes With A Sandboxed Rails Console](rails/prevent-writes-with-a-sandboxed-rails-console.md)
- [Query A Single Value From The Database](rails/query-a-single-value-from-the-database.md)

View File

@@ -0,0 +1,63 @@
# Prefer select_all Over execute For Read Queries
Though the `#execute` function provided by ActiveRecord technically works as a
general-purpose query runner for strings of raw SQL, it has some downsides.
First, let's say we have a large semi-complex (better in SQL than ActiveRecord
DSL) SQL query defined in a heredoc.
```ruby
books_by_status_query = <<-SQL
select
books.*,
latest_statuses.status as current_status,
array_to_json(array_agg(...)) as reading_statuses
from books
-- plus several left joins
-- where clause, group by, and order by
SQL
```
I reflexively reach for
[`#execute`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-execute)
in a situation like that:
```ruby
result = ActiveRecord::Base.connection.execute(books_by_status_query)
```
However, if we're doing a read-only query and we are expecting multiple rows in
the result, then we are better off reaching for
[`#select_all`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-select_all).
```ruby
result = ActiveRecord::Base.connection.select_all(books_by_status_query)
```
It has the advantage of semantically communicating that it's just a read and
won't have any side-effects. It also avoids an unnecessary clear of the query
cache.
> Note: [when execute is used] the query is assumed to have side effects and
> the query cache will be cleared. If the query is read-only, consider using
> select_all instead.
The `#execute` method also has been known to leak memory with some database
connectors.
> Note: depending on your database connector, the result returned by this
> method may be manually memory managed. Consider using exec_query wrapper
> instead.
We can then iterate through and transform the results just as we would have
done with `#execute`.
```ruby
result.map do |row|
row.tap do |hash|
hash["reading_statuses"] = JSON.parse(hash["reading_statuses"])
end
OpenStruct.new(row)
end
```