activerecord-sort 6.1.0.2 → 7.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: ec46a72d471b80abc582726404812b56fb9a89176349d5609b9734a6227c4c61
4
+ data.tar.gz: dd93e9fd01692c8113898c9c34aac125af3288a8b4d92d0be047660f396ace24
5
5
  SHA512:
6
- metadata.gz: f6e95a89312f348f886a61b723d0f138947520e94cb58248b6eeff25cbfdca65d6bfdb34721ec661c2a9a8c6dfb9cc1e7be1cb1f195d7e57850a2a77d1475e60
7
- data.tar.gz: 7ffe6b076093a14bdfd1b75f6863d5ac17e4bcdcaebf60c8233137637d4f100cb8e5dbaabd2176701db10f9262d2f52bcf7e8865f60c01e0e2be057515f85988
6
+ metadata.gz: 56688dd4bc023b5098de0792f27309452dd85d46bf56f905ae771b9ff3154d5c0ef5c9348a52e363b3a4ed20987805eb7f1e66384faf4eca40429ac126eb15ec
7
+ data.tar.gz: 8950bc656939f09fb886d5c917fb12a295a3da80d6d79a59ffde629a909c82a335aea17ce3cb5fbaceb4c1e5925261e11e536853321c89e58896dca9c66e2cff
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ ## [7.0.0]
2
+
3
+ The gem's version is now independent of the Rails version it targets.
4
+
5
+ ### Breaking Changes
6
+
7
+ - Removed the public `random_sort` method. Use `sort(:random)` instead.
8
+
9
+ ### Changed
10
+
11
+ - Require `activerecord >= 8.0.0, < 9.0`; support for Rails 7.1 and 7.2 has
12
+ been dropped.
13
+ - Require `arel-extensions >= 9.0.0`.
14
+ - `#sort` called with no arguments now falls back to Ruby's `Enumerable#sort`
15
+ (loads the records and sorts them by `<=>`) instead of returning the
16
+ relation. Pass arguments to use the ordering DSL; `sort(nil)` and `sort([])`
17
+ still return the relation for chaining.
18
+
19
+ ### Internal
20
+
21
+ - The `sort`, `sort_for_column`, and `sort_for_relation` methods are now
22
+ defined in an `ActiveRecord::Sort` module prepended onto
23
+ `ActiveRecord::QueryMethods`, rather than reopening `QueryMethods` directly.
24
+ Relations still respond to them, but they no longer appear in
25
+ `ActiveRecord::QueryMethods.public_instance_methods(false)` — which keeps
26
+ Rails' internal delegation invariants intact.
27
+
28
+ [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,14 +43,14 @@ 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 associations:
44
54
 
45
55
  ```ruby
46
56
  Property.sort(addresses: :id).to_sql
@@ -51,7 +61,29 @@ Property.sort(addresses: {id: :desc}).to_sql
51
61
  # => "...INNER JOIN addresses ON addresses.property_id = properties.id
52
62
  # => " ORDER BY addresses.id DESC"
53
63
 
54
- Property.sort(addresses: {id: {asc: :nulls_frist}}).to_sql
64
+ Property.sort(addresses: {id: {asc: :nulls_first}}).to_sql
55
65
  # => "...INNER JOIN addresses ON addresses.property_id = properties.id
56
66
  # => " ORDER BY addresses.id ASC NULLS FIRST"
57
67
  ```
68
+
69
+ Order randomly:
70
+
71
+ ```ruby
72
+ Property.sort(:random).to_sql
73
+ # => "...ORDER BY RANDOM()"
74
+ ```
75
+
76
+ Unrecognized columns raise, so unfiltered params can't inject SQL:
77
+
78
+ ```ruby
79
+ Property.sort(:name_or_something_unexpected)
80
+ # => raises ActiveRecord::StatementInvalid
81
+ ```
82
+
83
+ Called with no arguments, `#sort` behaves like Ruby's `Enumerable#sort` —
84
+ it loads the records and sorts them by `<=>` — rather than building a query:
85
+
86
+ ```ruby
87
+ Property.all.sort # => Array of Property, sorted by <=>
88
+ Property.sort(nil) # => relation (unchanged), for chaining
89
+ ```
@@ -0,0 +1,134 @@
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
+ # ordering:
12
+ # :id
13
+ # :name, :id
14
+ # :id => :desc
15
+ # :id => {:desc => :nulls_last}
16
+ # :listings => :id
17
+ # :listings => {:id => {:asc => :nulls_first}}
18
+ # :random
19
+ def sort(*ordering, &block)
20
+ # With no arguments, fall back to Ruby's Enumerable#sort so code (and
21
+ # Rails' own test suite) that calls `relation.sort` expecting Ruby
22
+ # semantics keeps working. The ordering DSL only applies when given
23
+ # arguments; `sort(nil)`/`sort([])` still return the relation for
24
+ # chaining.
25
+ return super(&block) if ordering.empty?
26
+
27
+ resource = all
28
+ ordering.compact!
29
+ ordering.flatten!
30
+ return resource if ordering.size == 0
31
+
32
+ order_columns = []
33
+ ordering.each do |order|
34
+ order = Array(order)
35
+ order.each do |column_or_relation, options|
36
+ if column_or_relation.to_sym == :random
37
+ resource = resource.order(Arel::Nodes::RandomOrdering.new)
38
+ elsif self.column_names.include?(column_or_relation.to_s)
39
+ resource = resource.sort_for_column(self.arel_table[column_or_relation.to_s], options)
40
+ elsif reflect_on_association(column_or_relation.to_sym)
41
+ resource = resource.sort_for_relation(column_or_relation.to_sym, options, order_columns)
42
+ else
43
+ raise ActiveRecord::StatementInvalid.new("Unkown column #{column_or_relation}")
44
+ end
45
+ end
46
+ end
47
+
48
+ if order_columns.present?
49
+ resource = resource.select(resource.klass.arel_table[Arel::Nodes::SqlLiteral.new('*')], *order_columns)
50
+ end
51
+
52
+ resource
53
+ end
54
+
55
+ # TODO: probably don't need to cast to sym
56
+ def sort_for_column(column, options)
57
+ direction = (options.is_a?(Hash) || options.class.name == "ActionController::Parameters" ? options.keys.first.to_sym : options.to_s.downcase.to_sym)
58
+
59
+ nulls = (options.is_a?(Hash) ? options.values.first.to_sym : nil)
60
+ if direction == :desc
61
+ self.order(Arel::Nodes::Descending.new(column, nulls))
62
+ elsif direction == :asc || direction == :''
63
+ self.order(Arel::Nodes::Ascending.new(column, nulls))
64
+ else
65
+ raise ActiveRecord::StatementInvalid.new("Unkown ordering #{direction}")
66
+ end
67
+ end
68
+
69
+ def sort_for_relation(relation, options, order_columns)
70
+ resource = self
71
+ relation = reflect_on_association(relation)
72
+
73
+ if relation.macro == :has_many
74
+ options = [options] if !options.is_a?(Array)
75
+
76
+ options.each do |order|
77
+ order = Array(order)
78
+ order.each do |column, options|
79
+ column = Arel::Attributes::Relation.new(relation.klass.arel_table[column], relation.name)
80
+ order_columns.push(column)
81
+ direction = (options.is_a?(Hash) ? options.keys.first.to_sym : options.to_s.downcase.to_sym)
82
+
83
+ nulls = (options.is_a?(Hash) ? options.values.first.to_sym : nil)
84
+ if direction == :desc
85
+ # aggregation = Arel::Nodes::Max.new([column], "max_#{relation.name}_#{column.name}")
86
+ # order = Arel::Nodes::Descending.new(Arel::Nodes::SqlLiteral.new("max_#{relation.name}_#{column.name}"), nulls)
87
+
88
+ if relation.options[:through]
89
+ resource = resource.joins(relation.options[:through] => relation.source_reflection_name)
90
+ else
91
+ resource = resource.joins(relation.name)
92
+ end
93
+ # resource = resource.select(aggregation)
94
+ # resource = resource.order(order)
95
+ resource = resource.order(Arel::Nodes::Descending.new(column, nulls))
96
+ else
97
+ # aggregation = Arel::Nodes::Min.new([column], "min_#{relation.name}_#{column.name}")
98
+ order = Arel::Nodes::Ascending.new(Arel::Nodes::SqlLiteral.new("min_#{relation.name}_#{column.name}"), nulls)
99
+
100
+ resource = resource.joins(relation.name)
101
+ # resource = resource.select(aggregation)
102
+ # resource = resource.order(order)
103
+ resource = resource.order(Arel::Nodes::Ascending.new(column, nulls))
104
+ end
105
+ end
106
+ end
107
+ elsif relation.macro == :belongs_to || relation.macro == :has_one
108
+ options = [options] if !options.is_a?(Array)
109
+
110
+ options.each do |order|
111
+ order = Array(order)
112
+ order.each do |column, options|
113
+ column = relation.klass.arel_table[column]
114
+ order_columns.push(column)
115
+ direction = (options.is_a?(Hash) ? options.keys.first.to_sym : options.to_s.downcase.to_sym)
116
+
117
+ nulls = (options.is_a?(Hash) ? options.values.first.to_sym : nil)
118
+ if direction == :asc
119
+ order = Arel::Nodes::Ascending.new(column, nulls)
120
+ else
121
+ order = Arel::Nodes::Descending.new(column, nulls)
122
+ end
123
+
124
+ resource = resource.left_outer_joins(relation.name)
125
+ resource = resource.order(order)
126
+ end
127
+ end
128
+ end
129
+
130
+ resource
131
+ end
132
+
133
+ end
134
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Sort
3
- VERSION = '6.1.0.2'
3
+ VERSION = '7.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: 7.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
@@ -156,14 +147,14 @@ dependencies:
156
147
  requirements:
157
148
  - - ">="
158
149
  - !ruby/object:Gem::Version
159
- version: 6.1.0.2
150
+ version: 7.0.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: 7.0.0
167
158
  - !ruby/object:Gem::Dependency
168
159
  name: webmock
169
160
  requirement: !ruby/object:Gem::Requirement
@@ -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