Skip to content

Commit

Permalink
Allow to pass array values to .in_order_of
Browse files Browse the repository at this point in the history
Passing arrays allows to group records and order those groups with another query:

```rb
Posts
  .in_order_of(:state, [[:published, :canceled], :archived])
  .order(created_at: :desc)
```
  • Loading branch information
doits committed Oct 4, 2024
1 parent 0df09dd commit 614c2cd
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 4 deletions.
18 changes: 18 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
* Allow to pass array values to `in_order_of`

Passing arrays allows to group records and order those groups with another query:

```rb
Posts
.in_order_of(:state, [[:published, :canceled], :archived])
.order(created_at: :desc)
# =>
# SELECT "posts".* FROM "posts" WHERE "posts"."state" IN (1, 2, 3)
# ORDER BY CASE
# WHEN "posts"."state" IN (1, 2) THEN 1
# WHEN "posts"."state" = 3 THEN 2
# END ASC, "posts"."created_at" DESC
```

*Markus Doits*

* Make Float distinguish between `float4` and `float8` in PostgreSQL.

Fixes #52742
Expand Down
20 changes: 16 additions & 4 deletions activerecord/lib/active_record/relation/query_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -719,17 +719,25 @@ def in_order_of(column, values, filter: true)
references = column_references([column])
self.references_values |= references unless references.empty?

values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
values = values.map do |value|
if value.is_a?(Array)
value.map do |current_value|
model.type_caster.type_cast_for_database(column, current_value)
end
else
model.type_caster.type_cast_for_database(column, value)
end
end
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)

scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))

if filter
where_clause =
if values.include?(nil)
arel_column.in(values.compact).or(arel_column.eq(nil))
arel_column.in(values.compact.flatten(1)).or(arel_column.eq(nil))
else
arel_column.in(values)
arel_column.in(values.flatten(1))
end

scope = scope.where!(where_clause)
Expand Down Expand Up @@ -2133,7 +2141,11 @@ def order_column(field)
def build_case_for_value_position(column, values, filter: true)
node = Arel::Nodes::Case.new
values.each.with_index(1) do |value, order|
node.when(column.eq(value)).then(order)
if value.is_a?(Array)
node.when(column.in(value)).then(order)
else
node.when(column.eq(value)).then(order)
end
end

node = node.else(values.length + 1) unless filter
Expand Down
7 changes: 7 additions & 0 deletions activerecord/test/cases/relation/field_ordered_values_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,11 @@ def test_in_order_of_with_filter_false
assert_equal(order, posts.limit(3).map(&:id))
assert_equal(11, posts.count)
end

def test_in_order_of_with_array_values
order = [3, [5, 2], [4, 7], 1]
posts = Post.in_order_of(:id, order).order(id: :asc)

assert_equal([3, 2, 5, 4, 7, 1], posts.map(&:id))
end
end

0 comments on commit 614c2cd

Please sign in to comment.