activerecord-sort 6.1.0.2 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ee5bd719bed4e5e0588390935e7b237f58689dc469eab9a4940ee61117ca567
4
- data.tar.gz: f3f6ea7df7d2d07d8f13acf20cc11bddcd6fa992bbb9572b378695ba6c18d628
3
+ metadata.gz: 50d689663cb6717dab8e034f88d8645cf24a31489e322ca79d9cd52634281dc4
4
+ data.tar.gz: 544297bd2fc5b8322f8a719d3a4f52e235681a1c756dc191ac74fd69a4c556a7
5
5
  SHA512:
6
- metadata.gz: f6e95a89312f348f886a61b723d0f138947520e94cb58248b6eeff25cbfdca65d6bfdb34721ec661c2a9a8c6dfb9cc1e7be1cb1f195d7e57850a2a77d1475e60
7
- data.tar.gz: 7ffe6b076093a14bdfd1b75f6863d5ac17e4bcdcaebf60c8233137637d4f100cb8e5dbaabd2176701db10f9262d2f52bcf7e8865f60c01e0e2be057515f85988
6
+ metadata.gz: b0caa63a30b528ed30f2fa2ff5c29fe779fd633e9d30ec4404ede2c8b08f9a24a1832b3bd4bd10c39914bebe07cfd8fe23b10ce93496b254d6bb81d2450a3c9e
7
+ data.tar.gz: afc2f2911285ed3e681a39013013ad1b9ffbce9161a4ac5bb2da6fda40ddd03421942f3b42eaf0c0a3865878fe667759d4c77df0b8962b431937267be40a9d70
data/CHANGELOG.md ADDED
@@ -0,0 +1,99 @@
1
+ # Changelog
2
+
3
+ ## [8.0.0] - 2026-08-27
4
+
5
+ ### Breaking changes
6
+
7
+ - Relation sorts are unified across association types. Sorting by any
8
+ relation (`has_many`, `has_and_belongs_to_many`, `has_one`,
9
+ `belongs_to`) now `LEFT OUTER JOIN`s the association, groups by the
10
+ sorted table's primary key, and orders by an aggregate of the requested
11
+ column. For `has_many` sorts this changes behavior:
12
+ - each record is returned once, instead of once per associated row
13
+ (the join no longer fans out into duplicates)
14
+ - records with no associated rows are included (previously dropped by
15
+ the `INNER JOIN`)
16
+ - Descending relation sorts key each record by its largest member
17
+ (`MAX`), ascending by its smallest (`MIN`) — the member you'd expect
18
+ to see first in that direction. For records with multiple associated
19
+ rows, descending is therefore not the reverse of ascending: a record
20
+ holding both extremes sorts first in both directions.
21
+ - Sort columns are no longer added to the `SELECT`:
22
+ - loaded records keep their own attributes — a joined sort column can
23
+ no longer overwrite a same-named attribute on the base record
24
+ (previously even `id` could be clobbered)
25
+ - `pluck` and `ids` keep the sort (previously raised on
26
+ `has_and_belongs_to_many` sorts)
27
+ - a caller's `select` is left untouched
28
+ - chaining `.distinct` after a relation sort now raises: PostgreSQL
29
+ requires `ORDER BY` expressions to appear in the select list for
30
+ `SELECT DISTINCT`. It previously appeared to work while silently
31
+ deduplicating over the wrong tuple.
32
+ - A bare `belongs_to`/`has_one` sort with no direction (e.g.
33
+ `Address.sort(property: :name)`) now defaults to ascending, matching
34
+ every other sort form (previously descending).
35
+ - An unknown sort direction on a relation sort (e.g. `:dsc`) now raises
36
+ `ActiveRecord::Sort::InvalidSort` (previously sorted ascending
37
+ silently), matching column sorts.
38
+
39
+ ### Added
40
+
41
+ - Sorting by `has_and_belongs_to_many` relations.
42
+ - Unrecognized sort columns, associations, and directions raise
43
+ `ActiveRecord::Sort::InvalidSort`, a subclass of
44
+ `ActiveRecord::StatementInvalid` — so existing
45
+ `rescue ActiveRecord::StatementInvalid` handlers still catch bad sort
46
+ parameters, while callers can rescue the narrower class. Relation sort
47
+ columns are now validated up front (against the associated model's
48
+ columns) instead of only failing once the query reaches the database.
49
+ - Sorts of different types compose: they can be combined in one call
50
+ (`Property.sort(:name, tags: :name, addresses: :id)`) or chained
51
+ (`.sort(...).sort(...)`), sharing a single `GROUP BY`.
52
+ - Aggregates on a sorted relation (`count`, `sum`, `average`, `minimum`,
53
+ `maximum`) are computed over the records themselves rather than the
54
+ sort's grouped, joined rows — `count` returns the record count instead
55
+ of a per-group `Hash`, and multi-member records aren't weighted once
56
+ per member. A caller-supplied `group` still gets standard grouped
57
+ results.
58
+ - A blank direction (`''`, as query parameters often produce) is
59
+ accepted as ascending on all sort forms.
60
+ - `ActionController::Parameters` are accepted for relation sorts, and
61
+ their nulls option (`nulls_first`/`nulls_last`) is honored.
62
+
63
+ ### Fixed
64
+
65
+ - `has_and_belongs_to_many` sorts crashed with `NoMethodError` on
66
+ ActiveRecord <= 8.0 (`Function#as` mutates and returns the receiver
67
+ there).
68
+ - Combining a `has_and_belongs_to_many` sort with another relation sort
69
+ raised `PG::GroupingError`.
70
+
71
+ ## [7.0.0] - 2026-08-27
72
+
73
+ The gem's version is now independent of the Rails version it targets.
74
+
75
+ ### Breaking Changes
76
+
77
+ - Removed the public `random_sort` method. Use `sort(:random)` instead.
78
+
79
+ ### Changed
80
+
81
+ - Require `activerecord >= 8.0.0, < 9.0`; support for Rails 7.1 and 7.2 has
82
+ been dropped.
83
+ - Require `arel-extensions >= 9.0.0`.
84
+ - `#sort` called with no arguments now falls back to Ruby's `Enumerable#sort`
85
+ (loads the records and sorts them by `<=>`) instead of returning the
86
+ relation. Pass arguments to use the ordering DSL; `sort(nil)` and `sort([])`
87
+ still return the relation for chaining.
88
+
89
+ ### Internal
90
+
91
+ - The `sort`, `sort_for_column`, and `sort_for_relation` methods are now
92
+ defined in an `ActiveRecord::Sort` module prepended onto
93
+ `ActiveRecord::QueryMethods`, rather than reopening `QueryMethods` directly.
94
+ Relations still respond to them, but they no longer appear in
95
+ `ActiveRecord::QueryMethods.public_instance_methods(false)` — which keeps
96
+ Rails' internal delegation invariants intact.
97
+
98
+ [8.0.0]: https://github.com/malomalo/activerecord-sort/releases/tag/v8.0.0
99
+ [7.0.0]: https://github.com/malomalo/activerecord-sort/releases/tag/v7.0.0
data/README.md CHANGED
@@ -1,11 +1,19 @@
1
1
  # ActiveRecord::Sort
2
2
 
3
- `ActiveRecord::Sort` provides and easy way to accept user input and order a query by the input.
3
+ `ActiveRecord::Sort` provides an easy, safe way to accept user input and order a
4
+ query by it. Only recognized columns and associations produce SQL — anything
5
+ else raises `ActiveRecord::StatementInvalid`, so it's safe to pass request
6
+ parameters straight through.
7
+
8
+ Requirements
9
+ ------------
10
+
11
+ - Rails / ActiveRecord >= 8.0
4
12
 
5
13
  Installation
6
14
  ------------
7
15
 
8
- Add `sunstone` to your Gemfile and run `bundle`:
16
+ Add `activerecord-sort` to your Gemfile and run `bundle`:
9
17
 
10
18
  ```ruby
11
19
  gem 'activerecord-sort', require: 'active_record/sort'
@@ -15,8 +23,10 @@ Or install the gem and require it:
15
23
 
16
24
  ```sh
17
25
  gem install activerecord-sort
18
- irb
19
- # => require('active_record/sort')
26
+ ```
27
+
28
+ ```ruby
29
+ require 'active_record/sort'
20
30
  ```
21
31
 
22
32
  Examples
@@ -33,25 +43,70 @@ Property.sort(:id, :name).to_sql
33
43
  Property.sort(id: :desc).to_sql
34
44
  # => "...ORDER BY properties.id DESC"
35
45
 
36
- Property.sort(id: {asc: :nulls_first})
46
+ Property.sort(id: {asc: :nulls_first}).to_sql
37
47
  # => "...ORDER BY properties.id ASC NULLS FIRST"
38
48
 
39
- Property.sort(id: {asc: :nulls_last})
49
+ Property.sort(id: {asc: :nulls_last}).to_sql
40
50
  # => "...ORDER BY properties.id ASC NULLS LAST"
41
51
  ```
42
52
 
43
- It can also sort on relations:
53
+ It can also sort on relations. A relation sort groups by the sorted table's
54
+ primary key — so each record appears once and records with no associated
55
+ rows are still included — and orders by an aggregate of the requested
56
+ column: `MIN` ascending or `MAX` descending, keying each record by the
57
+ member you'd expect to see first in that direction. A record with no
58
+ associated rows has a `NULL` sort key; where `NULL`s land is
59
+ database-dependent, so pass `nulls_first`/`nulls_last` to place those
60
+ records explicitly:
44
61
 
45
62
  ```ruby
46
63
  Property.sort(addresses: :id).to_sql
47
- # => "...INNER JOIN addresses ON addresses.property_id = properties.id
48
- # => " ORDER BY addresses.id ASC"
64
+ # => "SELECT properties.* FROM properties
65
+ # => " LEFT OUTER JOIN addresses ON addresses.property_id = properties.id
66
+ # => " GROUP BY properties.id
67
+ # => " ORDER BY MIN(addresses.id) ASC"
49
68
 
50
69
  Property.sort(addresses: {id: :desc}).to_sql
51
- # => "...INNER JOIN addresses ON addresses.property_id = properties.id
52
- # => " ORDER BY addresses.id DESC"
70
+ # => "...ORDER BY MAX(addresses.id) DESC"
71
+
72
+ Property.sort(addresses: {id: {asc: :nulls_first}}).to_sql
73
+ # => "...ORDER BY MIN(addresses.id) ASC NULLS FIRST"
74
+
75
+ Property.sort(tags: :name).to_sql # has_and_belongs_to_many
76
+ # => "SELECT properties.* FROM properties
77
+ # => " LEFT OUTER JOIN properties_tags ON properties_tags.property_id = properties.id
78
+ # => " LEFT OUTER JOIN tags ON tags.id = properties_tags.tag_id
79
+ # => " GROUP BY properties.id
80
+ # => " ORDER BY MIN(tags.name) ASC"
81
+ ```
82
+
83
+ A relation sort is order-only — it never adds or removes records — so
84
+ aggregates on a sorted relation (`count`, `sum`, `average`, `minimum`,
85
+ `maximum`) are computed over the records themselves, not the sort's
86
+ grouped and joined rows.
87
+
88
+ Order randomly:
53
89
 
54
- Property.sort(addresses: {id: {asc: :nulls_frist}}).to_sql
55
- # => "...INNER JOIN addresses ON addresses.property_id = properties.id
56
- # => " ORDER BY addresses.id ASC NULLS FIRST"
90
+ ```ruby
91
+ Property.sort(:random).to_sql
92
+ # => "...ORDER BY RANDOM()"
93
+ ```
94
+
95
+ Unrecognized columns raise, so unfiltered params can't inject SQL:
96
+
97
+ ```ruby
98
+ Property.sort(:name_or_something_unexpected)
99
+ # => raises ActiveRecord::Sort::InvalidSort
100
+ ```
101
+
102
+ `ActiveRecord::Sort::InvalidSort` subclasses `ActiveRecord::StatementInvalid`,
103
+ so existing `rescue ActiveRecord::StatementInvalid` handlers keep catching bad
104
+ sort parameters, while callers that want to can rescue the narrower class.
105
+
106
+ Called with no arguments, `#sort` behaves like Ruby's `Enumerable#sort` —
107
+ it loads the records and sorts them by `<=>` — rather than building a query:
108
+
109
+ ```ruby
110
+ Property.all.sort # => Array of Property, sorted by <=>
111
+ Property.sort(nil) # => relation (unchanged), for chaining
57
112
  ```
@@ -0,0 +1,183 @@
1
+ require 'active_record'
2
+ require 'active_record/relation'
3
+
4
+ module ActiveRecord
5
+ # Prepended onto ActiveRecord::QueryMethods (see lib/active_record/sort.rb).
6
+ # Living in a separate module keeps these methods out of
7
+ # QueryMethods.public_instance_methods(false) — which Rails' own
8
+ # delegation tests assert against — while relations still respond to them.
9
+ module Sort
10
+
11
+ # Raised when a sort references an unrecognized column or association,
12
+ # or an unknown direction. Subclasses StatementInvalid so existing
13
+ # `rescue ActiveRecord::StatementInvalid` handlers — the safety
14
+ # contract that makes it OK to pass request params straight through —
15
+ # keep working, while callers can rescue this narrower class to tell a
16
+ # bad sort parameter apart from a genuine database error.
17
+ class InvalidSort < ActiveRecord::StatementInvalid
18
+ end
19
+
20
+ # ordering:
21
+ # :id
22
+ # :name, :id
23
+ # :id => :desc
24
+ # :id => {:desc => :nulls_last}
25
+ # :listings => :id
26
+ # :listings => {:id => {:asc => :nulls_first}}
27
+ # :random
28
+ def sort(*ordering, &block)
29
+ # With no arguments, fall back to Ruby's Enumerable#sort so code (and
30
+ # Rails' own test suite) that calls `relation.sort` expecting Ruby
31
+ # semantics keeps working. The ordering DSL only applies when given
32
+ # arguments; `sort(nil)`/`sort([])` still return the relation for
33
+ # chaining.
34
+ return super(&block) if ordering.empty?
35
+
36
+ resource = all
37
+ ordering.compact!
38
+ ordering.flatten!
39
+ return resource if ordering.size == 0
40
+
41
+ ordering.each do |order|
42
+ order = Array(order)
43
+ order.each do |column_or_relation, options|
44
+ if column_or_relation.to_sym == :random
45
+ resource = resource.order(Arel::Nodes::RandomOrdering.new)
46
+ elsif self.column_names.include?(column_or_relation.to_s)
47
+ resource = resource.sort_for_column(self.arel_table[column_or_relation.to_s], options)
48
+ elsif reflect_on_association(column_or_relation.to_sym)
49
+ resource = resource.sort_for_relation(column_or_relation.to_sym, options)
50
+ else
51
+ raise InvalidSort.new("Unknown column #{column_or_relation}")
52
+ end
53
+ end
54
+ end
55
+
56
+ resource
57
+ end
58
+
59
+ # Normalizes per-column sort options into [direction, nulls]. A blank
60
+ # direction — a bare column, or "" as query params often produce —
61
+ # means :asc.
62
+ # TODO: probably don't need to cast to sym
63
+ def sort_direction_and_nulls(options)
64
+ if options.is_a?(Hash) || options.class.name == "ActionController::Parameters"
65
+ [options.keys.first.to_sym, options.values.first.to_sym]
66
+ elsif options.blank?
67
+ [:asc, nil]
68
+ else
69
+ [options.to_s.downcase.to_sym, nil]
70
+ end
71
+ end
72
+
73
+ def sort_for_column(column, options)
74
+ direction, nulls = sort_direction_and_nulls(options)
75
+
76
+ if direction == :desc
77
+ self.order(Arel::Nodes::Descending.new(column, nulls))
78
+ elsif direction == :asc
79
+ self.order(Arel::Nodes::Ascending.new(column, nulls))
80
+ else
81
+ raise InvalidSort.new("Unknown ordering #{direction}")
82
+ end
83
+ end
84
+
85
+ # The sort key lives only in the ORDER BY — nothing is added to the
86
+ # select list. That keeps the ORDER BY self-contained (so pluck/ids,
87
+ # which replace the select list, keep the sort), leaves the caller's
88
+ # select untouched, and means loaded records carry exactly their own
89
+ # attributes (a selected join column would overwrite a same-named
90
+ # attribute on the base table).
91
+ def sort_for_relation(relation, options)
92
+ resource = self
93
+ relation = reflect_on_association(relation)
94
+ options = [options] if !options.is_a?(Array)
95
+
96
+ # LEFT JOIN the association, group by this table's primary key — so
97
+ # rows don't fan / duplicate and records with an empty collection
98
+ # still appear — and order by an aggregate of the requested column:
99
+ #
100
+ # SELECT properties.*
101
+ # FROM properties
102
+ # LEFT JOIN properties_tags ON properties_tags.property_id = properties.id
103
+ # LEFT JOIN tags ON tags.id = properties_tags.tag_id
104
+ # GROUP BY properties.id
105
+ # ORDER BY MIN(tags.name) ASC
106
+ #
107
+ # Ascending keys each record by its smallest member (MIN), descending
108
+ # by its largest (MAX) — the member you'd expect to see first in that
109
+ # direction. Toggling asc/desc therefore re-keys multi-value records
110
+ # rather than strictly reversing the list.
111
+ options.each do |order|
112
+ Array(order).each do |column_name, column_options|
113
+ if !relation.klass.column_names.include?(column_name.to_s)
114
+ raise InvalidSort.new("Unknown column #{column_name}")
115
+ end
116
+
117
+ # A collection (has_many / has_and_belongs_to_many) wraps the
118
+ # attribute in an Arel::Attributes::Relation so the aggregate
119
+ # references the joined table correctly; a singular association
120
+ # (belongs_to / has_one) uses the plain attribute.
121
+ column = if relation.collection?
122
+ Arel::Attributes::Relation.new(relation.klass.arel_table[column_name], relation.name)
123
+ else
124
+ relation.klass.arel_table[column_name]
125
+ end
126
+
127
+ direction, nulls = sort_direction_and_nulls(column_options)
128
+
129
+ order = if direction == :desc
130
+ Arel::Nodes::Descending.new(column.maximum, nulls)
131
+ elsif direction == :asc
132
+ Arel::Nodes::Ascending.new(column.minimum, nulls)
133
+ else
134
+ raise InvalidSort.new("Unknown ordering #{direction}")
135
+ end
136
+
137
+ resource = resource.left_outer_joins(relation.name)
138
+ resource = resource.order(order)
139
+ end
140
+ end
141
+
142
+ # Group by the primary key so the aggregate collapses the joined,
143
+ # fanned-out rows to one per record. Every relation sort adds this,
144
+ # so only add it once: ActiveRecord < 8.1's group! appends rather
145
+ # than unioning, which would leave a duplicate [pk, pk] that trips
146
+ # the count override's group_values check below.
147
+ # TODO: once Rails <= 8.0 is no longer supported, group! unions on
148
+ # its own — drop the include? guard and just group(primary_key).
149
+ primary_key = klass.arel_table[klass.primary_key]
150
+ resource = resource.group(primary_key) unless resource.group_values.include?(primary_key)
151
+ # Tag the relation for the count override below. Chained relations
152
+ # are built with clone, which copies instance variables, so the tag
153
+ # survives further chaining.
154
+ resource.instance_variable_set(:@sorted_by_relation, true)
155
+ resource
156
+ end
157
+
158
+ end
159
+
160
+ module Sort
161
+ module Calculations
162
+ # sort_for_relation groups by the primary key and LEFT JOINs the
163
+ # relation, so aggregates would see grouped, fanned-out rows: count
164
+ # returns a per-group Hash, and sum/average weigh a record once per
165
+ # collection member. But a relation sort is order-only — records are
166
+ # never added or removed — so every aggregate has a well-defined
167
+ # answer: compute it over the base table restricted to the sorted
168
+ # relation's (distinct) primary keys. User joins and conditions still
169
+ # apply inside the subquery, and a user-added group falls through to
170
+ # the standard grouped behavior.
171
+ def calculate(operation, column_name)
172
+ if @sorted_by_relation && group_values == [klass.arel_table[klass.primary_key]]
173
+ klass.where(klass.primary_key => unscope(:group, :order, :select).select(klass.primary_key))
174
+ .calculate(operation, column_name)
175
+ else
176
+ super
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ ActiveRecord::Relation.prepend(ActiveRecord::Sort::Calculations)
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Sort
3
- VERSION = '6.1.0.2'
3
+ VERSION = '8.0.0'
4
4
  end
5
5
  end
@@ -1,126 +1,7 @@
1
1
  require 'active_record'
2
- require 'active_record/relation'
3
2
  require 'arel/extensions'
4
3
 
5
- module ActiveRecord
6
- module Sort
7
-
8
- # ordering:
9
- # :id
10
- # :name, :id
11
- # :id => :desc
12
- # :id => {:desc => :nulls_last}
13
- # :listings => :id
14
- # :listings => {:id => {:asc => :nulls_first}}
15
- # :random
16
- def sort(*ordering)
17
- resource = all
18
- ordering.compact!
19
- ordering.flatten!
20
- return resource if ordering.size == 0
21
-
22
- ordering.each do |order|
23
- order = Array(order)
24
-
25
- order.each do |column_or_relation, options|
26
- if column_or_relation.to_sym == :random
27
- resource = resource.random_sort
28
- elsif self.column_names.include?(column_or_relation.to_s)
29
- resource = resource.sort_for_column(self.arel_table[column_or_relation.to_s], options)
30
- elsif reflect_on_association(column_or_relation.to_sym)
31
- resource = resource.select(resource.klass.arel_table[Arel::Nodes::SqlLiteral.new('*')])
32
- resource = resource.sort_for_relation(column_or_relation.to_sym, options)
33
- else
34
- raise ActiveRecord::StatementInvalid.new("Unkown column #{column_or_relation}")
35
- end
36
- end
37
- end
38
-
39
- resource
40
- end
41
-
42
- def random_sort
43
- self.order(Arel::Nodes::RandomOrdering.new)
44
- end
45
-
46
- # TODO: probably don't need to cast to sym
47
- def sort_for_column(column, options)
48
- direction = (options.is_a?(Hash) || options.class.name == "ActionController::Parameters" ? options.keys.first.to_sym : options.to_s.downcase.to_sym)
49
-
50
- nulls = (options.is_a?(Hash) ? options.values.first.to_sym : nil)
51
- if direction == :desc
52
- self.order(Arel::Nodes::Descending.new(column, nulls))
53
- elsif direction == :asc || direction == :''
54
- self.order(Arel::Nodes::Ascending.new(column, nulls))
55
- else
56
- raise ActiveRecord::StatementInvalid.new("Unkown ordering #{direction}")
57
- end
58
- end
59
-
60
- def sort_for_relation(relation, options)
61
- resource = self
62
- relation = reflect_on_association(relation)
63
-
64
- if relation.macro == :has_many
65
- options = [options] if !options.is_a?(Array)
66
-
67
- options.each do |order|
68
- order = Array(order)
69
- order.each do |column, options|
70
- column = Arel::Attributes::Relation.new(relation.klass.arel_table[column], relation.name)
71
- direction = (options.is_a?(Hash) ? options.keys.first.to_sym : options.to_s.downcase.to_sym)
72
-
73
- nulls = (options.is_a?(Hash) ? options.values.first.to_sym : nil)
74
- if direction == :desc
75
- # aggregation = Arel::Nodes::Max.new([column], "max_#{relation.name}_#{column.name}")
76
- # order = Arel::Nodes::Descending.new(Arel::Nodes::SqlLiteral.new("max_#{relation.name}_#{column.name}"), nulls)
77
-
78
- if relation.options[:through]
79
- resource = resource.joins(relation.options[:through] => relation.source_reflection_name)
80
- else
81
- resource = resource.joins(relation.name)
82
- end
83
- # resource = resource.select(aggregation)
84
- # resource = resource.order(order)
85
- resource = resource.order(Arel::Nodes::Descending.new(column, nulls))
86
- else
87
- # aggregation = Arel::Nodes::Min.new([column], "min_#{relation.name}_#{column.name}")
88
- order = Arel::Nodes::Ascending.new(Arel::Nodes::SqlLiteral.new("min_#{relation.name}_#{column.name}"), nulls)
89
-
90
- resource = resource.joins(relation.name)
91
- # resource = resource.select(aggregation)
92
- # resource = resource.order(order)
93
- resource = resource.order(Arel::Nodes::Ascending.new(column, nulls))
94
- end
95
- end
96
- end
97
- elsif relation.macro == :belongs_to || relation.macro == :has_one
98
- options = [options] if !options.is_a?(Array)
99
-
100
- options.each do |order|
101
- order = Array(order)
102
- order.each do |column, options|
103
- column = relation.klass.arel_table[column]
104
- direction = (options.is_a?(Hash) ? options.keys.first.to_sym : options.to_s.downcase.to_sym)
105
-
106
- nulls = (options.is_a?(Hash) ? options.values.first.to_sym : nil)
107
- if direction == :asc
108
- order = Arel::Nodes::Ascending.new(column, nulls)
109
- else
110
- order = Arel::Nodes::Descending.new(column, nulls)
111
- end
112
-
113
- resource = resource.left_outer_joins(relation.name)
114
- resource = resource.order(order)
115
- end
116
- end
117
- end
118
-
119
- resource
120
- end
121
-
122
- end
123
- end
4
+ require File.expand_path(File.join(__FILE__, '../../../ext/active_record/base'))
124
5
 
125
6
  ActiveRecord::QueryMethods.prepend(ActiveRecord::Sort)
126
7
  ActiveRecord::Querying.delegate :sort, to: :all
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-sort
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.0.2
4
+ version: 8.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-03-23 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -16,28 +15,34 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 6.1.0
18
+ version: 8.0.0
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
23
25
  requirements:
24
26
  - - ">="
25
27
  - !ruby/object:Gem::Version
26
- version: 6.1.0
28
+ version: 8.0.0
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
27
32
  - !ruby/object:Gem::Dependency
28
33
  name: arel-extensions
29
34
  requirement: !ruby/object:Gem::Requirement
30
35
  requirements:
31
36
  - - ">="
32
37
  - !ruby/object:Gem::Version
33
- version: 6.1.0
38
+ version: 9.0.0
34
39
  type: :runtime
35
40
  prerelease: false
36
41
  version_requirements: !ruby/object:Gem::Requirement
37
42
  requirements:
38
43
  - - ">="
39
44
  - !ruby/object:Gem::Version
40
- version: 6.1.0
45
+ version: 9.0.0
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: pg
43
48
  requirement: !ruby/object:Gem::Requirement
@@ -52,20 +57,6 @@ dependencies:
52
57
  - - ">="
53
58
  - !ruby/object:Gem::Version
54
59
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
60
  - !ruby/object:Gem::Dependency
70
61
  name: rake
71
62
  requirement: !ruby/object:Gem::Requirement
@@ -112,18 +103,18 @@ dependencies:
112
103
  name: simplecov
113
104
  requirement: !ruby/object:Gem::Requirement
114
105
  requirements:
115
- - - ">="
106
+ - - '='
116
107
  - !ruby/object:Gem::Version
117
- version: '0'
108
+ version: 1.0.0.rc5
118
109
  type: :development
119
110
  prerelease: false
120
111
  version_requirements: !ruby/object:Gem::Requirement
121
112
  requirements:
122
- - - ">="
113
+ - - '='
123
114
  - !ruby/object:Gem::Version
124
- version: '0'
115
+ version: 1.0.0.rc5
125
116
  - !ruby/object:Gem::Dependency
126
- name: factory_bot
117
+ name: faker
127
118
  requirement: !ruby/object:Gem::Requirement
128
119
  requirements:
129
120
  - - ">="
@@ -137,35 +128,35 @@ dependencies:
137
128
  - !ruby/object:Gem::Version
138
129
  version: '0'
139
130
  - !ruby/object:Gem::Dependency
140
- name: faker
131
+ name: sunstone
141
132
  requirement: !ruby/object:Gem::Requirement
142
133
  requirements:
143
134
  - - ">="
144
135
  - !ruby/object:Gem::Version
145
- version: '0'
136
+ version: 7.0.0
146
137
  type: :development
147
138
  prerelease: false
148
139
  version_requirements: !ruby/object:Gem::Requirement
149
140
  requirements:
150
141
  - - ">="
151
142
  - !ruby/object:Gem::Version
152
- version: '0'
143
+ version: 7.0.0
153
144
  - !ruby/object:Gem::Dependency
154
- name: sunstone
145
+ name: webmock
155
146
  requirement: !ruby/object:Gem::Requirement
156
147
  requirements:
157
148
  - - ">="
158
149
  - !ruby/object:Gem::Version
159
- version: 6.1.0.2
150
+ version: '0'
160
151
  type: :development
161
152
  prerelease: false
162
153
  version_requirements: !ruby/object:Gem::Requirement
163
154
  requirements:
164
155
  - - ">="
165
156
  - !ruby/object:Gem::Version
166
- version: 6.1.0.2
157
+ version: '0'
167
158
  - !ruby/object:Gem::Dependency
168
- name: webmock
159
+ name: debug
169
160
  requirement: !ruby/object:Gem::Requirement
170
161
  requirements:
171
162
  - - ">="
@@ -178,42 +169,43 @@ dependencies:
178
169
  - - ">="
179
170
  - !ruby/object:Gem::Version
180
171
  version: '0'
181
- description: A safe way to accept user parameters and order against your ActiveRecord
182
- Models
172
+ description: 'Adds a #sort query method to ActiveRecord that turns whitelisted user-supplied
173
+ parameters into ORDER BY clauses -- ordering by columns or associations, ascending/descending,
174
+ with NULLS handling and random ordering -- without exposing you to SQL injection.'
183
175
  email:
184
176
  - jonbracy@gmail.com
185
177
  executables: []
186
178
  extensions: []
187
- extra_rdoc_files:
188
- - README.md
179
+ extra_rdoc_files: []
189
180
  files:
181
+ - CHANGELOG.md
190
182
  - LICENSE
191
183
  - README.md
184
+ - ext/active_record/base.rb
192
185
  - lib/active_record/sort.rb
193
186
  - lib/active_record/sort/version.rb
194
187
  homepage: https://github.com/malomalo/activerecord-sort
195
188
  licenses:
196
189
  - MIT
197
- metadata: {}
198
- post_install_message:
199
- rdoc_options:
200
- - "--main"
201
- - README.md
190
+ metadata:
191
+ source_code_uri: https://github.com/malomalo/activerecord-sort
192
+ changelog_uri: https://github.com/malomalo/activerecord-sort/blob/master/CHANGELOG.md
193
+ rubygems_mfa_required: 'true'
194
+ rdoc_options: []
202
195
  require_paths:
203
196
  - lib
204
197
  required_ruby_version: !ruby/object:Gem::Requirement
205
198
  requirements:
206
199
  - - ">="
207
200
  - !ruby/object:Gem::Version
208
- version: '0'
201
+ version: '3.3'
209
202
  required_rubygems_version: !ruby/object:Gem::Requirement
210
203
  requirements:
211
204
  - - ">="
212
205
  - !ruby/object:Gem::Version
213
206
  version: '0'
214
207
  requirements: []
215
- rubygems_version: 3.2.3
216
- signing_key:
208
+ rubygems_version: 4.0.11
217
209
  specification_version: 4
218
210
  summary: A safe way to accept user parameters and order against your ActiveRecord
219
211
  Models