diff --git a/README.md b/README.md index ea8076f..99ab3c9 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/rails/prefer-select-all-over-execute-for-read-queries.md b/rails/prefer-select-all-over-execute-for-read-queries.md new file mode 100644 index 0000000..8822083 --- /dev/null +++ b/rails/prefer-select-all-over-execute-for-read-queries.md @@ -0,0 +1,54 @@ +# 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. + +> Note: 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. + +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 +```