mirror of https://github.com/rails/rails
`ActiveRecord::Relation#order` supports hash like `ActiveRecord::Relation#where` (#50000)
* relation#order supports hash like relation#where This allows for an ActiveRecord::Relation to take a hash such as `Topic.includes(:posts).order(posts: { created_at: :desc })` * use is_a? to support subclasses of each Co-authored-by: Rafael Mendonça França <rafael@rubyonrails.org>
This commit is contained in:
parent
db4c6db59d
commit
278d6574cf
|
@ -505,4 +505,12 @@
|
|||
|
||||
*Tristan Fellows*
|
||||
|
||||
* Allow for more complex hash arguments for `order` which mimics `where` in `ActiveRecord::Relation`.
|
||||
|
||||
```ruby
|
||||
Topic.includes(:posts).order(posts: { created_at: :desc })
|
||||
```
|
||||
|
||||
*Myles Boone*
|
||||
|
||||
Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activerecord/CHANGELOG.md) for previous changes.
|
||||
|
|
|
@ -1918,7 +1918,9 @@ module ActiveRecord
|
|||
args.each do |arg|
|
||||
next unless arg.is_a?(Hash)
|
||||
arg.each do |_key, value|
|
||||
unless VALID_DIRECTIONS.include?(value)
|
||||
if value.is_a?(Hash)
|
||||
validate_order_args([value])
|
||||
elsif VALID_DIRECTIONS.exclude?(value)
|
||||
raise ArgumentError,
|
||||
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
||||
end
|
||||
|
@ -1926,9 +1928,13 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def flattened_args(order_args)
|
||||
order_args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
||||
end
|
||||
|
||||
def preprocess_order_args(order_args)
|
||||
@klass.disallow_raw_sql!(
|
||||
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
||||
flattened_args(order_args),
|
||||
permit: connection.column_name_with_order_matcher
|
||||
)
|
||||
|
||||
|
@ -1943,14 +1949,20 @@ module ActiveRecord
|
|||
when Symbol
|
||||
order_column(arg.to_s).asc
|
||||
when Hash
|
||||
arg.map { |field, dir|
|
||||
case field
|
||||
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
||||
field.public_send(dir.downcase)
|
||||
arg.map do |key, value|
|
||||
if value.is_a?(Hash)
|
||||
value.map do |field, dir|
|
||||
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
||||
end
|
||||
else
|
||||
order_column(field.to_s).public_send(dir.downcase)
|
||||
case key
|
||||
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
||||
key.public_send(value.downcase)
|
||||
else
|
||||
order_column(key.to_s).public_send(value.downcase)
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
else
|
||||
arg
|
||||
end
|
||||
|
@ -1964,18 +1976,20 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def column_references(order_args)
|
||||
references = order_args.flat_map do |arg|
|
||||
order_args.flat_map do |arg|
|
||||
case arg
|
||||
when String, Symbol
|
||||
arg
|
||||
when Hash
|
||||
arg.keys.map do |key|
|
||||
key if key.is_a?(String) || key.is_a?(Symbol)
|
||||
end
|
||||
arg.keys.select { |e| e.is_a?(String) || e.is_a?(Symbol) }
|
||||
end
|
||||
end
|
||||
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
||||
references
|
||||
end.filter_map do |arg|
|
||||
arg =~ /^\W?(\w+)\W?\./ && $1
|
||||
end +
|
||||
order_args
|
||||
.select { |e| e.is_a?(Hash) }
|
||||
.flat_map { |e| e.map { |k, v| k if v.is_a?(Hash) } }
|
||||
.compact
|
||||
end
|
||||
|
||||
def order_column(field)
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cases/helper"
|
||||
require "models/author"
|
||||
require "models/book"
|
||||
|
||||
class OrderTest < ActiveRecord::TestCase
|
||||
fixtures :authors
|
||||
|
||||
def test_order_asc
|
||||
Book.destroy_all
|
||||
z = Book.create!(name: "Zulu", author: authors(:david))
|
||||
y = Book.create!(name: "Yankee", author: authors(:mary))
|
||||
x = Book.create!(name: "X-Ray", author: authors(:david))
|
||||
|
||||
alphabetical = [x, y, z]
|
||||
|
||||
assert_equal(alphabetical, Book.order(name: :asc))
|
||||
assert_equal(alphabetical, Book.order(name: :ASC))
|
||||
assert_equal(alphabetical, Book.order(name: "asc"))
|
||||
assert_equal(alphabetical, Book.order(:name))
|
||||
assert_equal(alphabetical, Book.order("name"))
|
||||
assert_equal(alphabetical, Book.order("books.name"))
|
||||
assert_equal(alphabetical, Book.order(Book.arel_table["name"]))
|
||||
assert_equal(alphabetical, Book.order(books: { name: :asc }))
|
||||
end
|
||||
|
||||
def test_order_desc
|
||||
Book.destroy_all
|
||||
z = Book.create!(name: "Zulu", author: authors(:david))
|
||||
y = Book.create!(name: "Yankee", author: authors(:mary))
|
||||
x = Book.create!(name: "X-Ray", author: authors(:david))
|
||||
|
||||
reverse_alphabetical = [z, y, x]
|
||||
|
||||
assert_equal(reverse_alphabetical, Book.order(name: :desc))
|
||||
assert_equal(reverse_alphabetical, Book.order(name: :DESC))
|
||||
assert_equal(reverse_alphabetical, Book.order(name: "desc"))
|
||||
assert_equal(reverse_alphabetical, Book.order(:name).reverse_order)
|
||||
assert_equal(reverse_alphabetical, Book.order("name desc"))
|
||||
assert_equal(reverse_alphabetical, Book.order("books.name desc"))
|
||||
assert_equal(reverse_alphabetical, Book.order(Book.arel_table["name"].desc))
|
||||
assert_equal(reverse_alphabetical, Book.order(books: { name: :desc }))
|
||||
end
|
||||
|
||||
def test_order_with_association
|
||||
Book.destroy_all
|
||||
z = Book.create!(name: "Zulu", author: authors(:david))
|
||||
y = Book.create!(name: "Yankee", author: authors(:mary))
|
||||
x = Book.create!(name: "X-Ray", author: authors(:david))
|
||||
|
||||
author_then_book_name = [x, z, y]
|
||||
|
||||
assert_equal(author_then_book_name, Book.includes(:author).order(authors: { name: :asc }, books: { name: :asc }))
|
||||
assert_equal(author_then_book_name, Book.includes(:author).order("authors.name", books: { name: :asc }))
|
||||
assert_equal(author_then_book_name, Book.includes(:author).order("authors.name", "books.name"))
|
||||
assert_equal(author_then_book_name, Book.includes(:author).order({ authors: { name: :asc } }, Book.arel_table[:name]))
|
||||
|
||||
author_desc_then_book_name = [y, x, z]
|
||||
|
||||
assert_equal(author_desc_then_book_name, Book.includes(:author).order(authors: { name: :desc }, books: { name: :asc }))
|
||||
assert_equal(author_desc_then_book_name, Book.includes(:author).order("authors.name desc", books: { name: :asc }))
|
||||
assert_equal(author_desc_then_book_name, Book.includes(:author).order({ authors: { name: :desc } }, :name))
|
||||
end
|
||||
end
|
|
@ -912,6 +912,14 @@ irb> Book.order("title ASC").order("created_at DESC")
|
|||
SELECT * FROM books ORDER BY title ASC, created_at DESC
|
||||
```
|
||||
|
||||
You can also order from a joined table
|
||||
|
||||
```ruby
|
||||
Book.includes(:author).order(books: { print_year: :desc }, authors: { name: :asc })
|
||||
# OR
|
||||
Book.includes(:author).order('books.print_year desc', 'authors.name asc')
|
||||
```
|
||||
|
||||
WARNING: In most database systems, on selecting fields with `distinct` from a result set using methods like `select`, `pluck` and `ids`; the `order` method will raise an `ActiveRecord::StatementInvalid` exception unless the field(s) used in `order` clause are included in the select list. See the next section for selecting fields from the result set.
|
||||
|
||||
Selecting Specific Fields
|
||||
|
|
Loading…
Reference in New Issue