Skip to content

Conversation

@westonganger
Copy link
Contributor

@westonganger westonganger commented Nov 22, 2024

Motivation / Background

This Pull Request has been created because of a discussion we had in #53695

The reasoning for these new sections is:

  • There no best practices written anywhere official regarding usage of select_all over exec_query over execute
  • There is no documentation on how to use the binds argument of these methods. One would assume they support positional or named binds the same as where or any other method, however the reality is they do not. It only supports positional binds and they must be wrapped in an explicit array.
  • Theres also a weird nil as the 2nd argument to all these methods prior to binds which makes it hard to understand how to properly use them
  • It seems to be easier/better to use Arel.sql rather than attempt use the binds argument given the above

Below I show the current (poor) experience with binds on select_all (and other methods)

# Example 1
MyModel.connection.select_all("select title from posts where id = :id", nil, [1000])
# => Output SQL: "select title from post where id = 1000" 
# NOTE: it seems that the existing behaviour is quite bad as it somehow switches the named binds to positional ?

# Example 2
MyModel.connection.select_all("select title from posts where id = ?", nil, [1000])
# => Output SQL: "select title from post where id = 1000"

# Example 3
MyModel.connection.select_all("select title from posts where id = :id", nil, {id: 1000})
# => TypeError: can't cast Array (TypeError)

# Example 4
MyModel.connection.select_all("select title from posts where id = :id", nil, [[:id, 1000]])
# => TypeError: can't cast Array (TypeError)

Detail

This Pull Request adds a section to the documentation on the "Active Record Query Interface" page

The new sections are

Using Raw SQL
--> Sanitizing Raw SQL input
--> Executing model agnostic SQL queries for reads
--> Executing model agnostic SQL queries for writes

Additional information

After this documentation is merged. I would suggest that it might be wise to deprecate the binds argument from all these methods and instead recommend to use Arel.sql as a replacement. Suggested by @matthewd

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

@rails-bot rails-bot bot added the docs label Nov 22, 2024
@westonganger westonganger changed the title Add documentation for model-less SQL reads and writes Add documentation for raw SQL reads and writes capabilities with ActiveRecord Nov 22, 2024
@westonganger westonganger force-pushed the add_docs_for_raw_sql_read_and_writes branch 2 times, most recently from 133025e to c2df36b Compare November 22, 2024 19:21

```irb
irb> Customer.find_by_sql("SELECT * FROM customers INNER JOIN orders ON customers.id = orders.customer_id ORDER BY customers.created_at desc")
irb> Customer.find_by_sql("SELECT * FROM customers INNER JOIN orders ON customers.id = orders.customer_id WHERE orders.company_id = ? ORDER BY customers.created_at desc", [company.id])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now shows how to use the binds argument on find_by_sql

It might be more desirable to steer users away from using this awkward binds argument and instead use Arel.sql

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the example for find_by_sql to use Arel.sql

There are some small differences with where and find_by_sql binds arguments in that for find_by_sql specifically you must wrap your arguments in an explicit array or hash

@westonganger westonganger force-pushed the add_docs_for_raw_sql_read_and_writes branch 2 times, most recently from 43ecc07 to 49668cb Compare November 22, 2024 19:53
@westonganger westonganger force-pushed the add_docs_for_raw_sql_read_and_writes branch 3 times, most recently from dd2e97b to 29ed0a4 Compare November 23, 2024 18:50
@westonganger westonganger force-pushed the add_docs_for_raw_sql_read_and_writes branch from 29ed0a4 to 0ed23a8 Compare November 23, 2024 22:59

[`find_or_initialize_by`]: https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_initialize_by

Finding by SQL
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im tempted to say that we should not have a section for find_by_sql in this document at all.

Its absolutely bizarre when people start selecting columns outside of the model you are working with, which is what commonly happens here.

In place of I would suggest a section in select_all where it recommends that any usage of find_by_sql might be better served by the select_all method instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant