activerecord-sort 6.1.0.1 → 10.0.0.rc1
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 +4 -4
- data/CHANGELOG.md +68 -0
- data/README.md +24 -8
- data/ext/active_record/base.rb +173 -0
- data/lib/active_record/sort/version.rb +1 -1
- metadata +20 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55d032ac200c1f9c9bc9a06f988d54744427dcb2afbb2d0f82e89b7f7e6f32de
|
|
4
|
+
data.tar.gz: 5e10e73498fb36d17d579f9610a79d54ff254b3e36eb5940f9971e5538f15300
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91149455ebd6e9bf3f44b78a4e249c11335ca900923898dc4b90a10113b00276ab44dbe6412f58829f41aa40a04b3d664238e261633c8d3efcfee9087480d78f
|
|
7
|
+
data.tar.gz: 6e8028be2e9a156bd5a36ff0fc566121b2313aaa4aa48a51b48e41d344f7f59f10837f7c4cdee2fc8a98c763e8962b6fe9a406d560c77f482b26dc4c03651248
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 10.0.0.rc1 (July 15, 2026)
|
|
4
|
+
|
|
5
|
+
Starting with this release the gem is versioned independently of Rails,
|
|
6
|
+
following [semantic versioning](https://semver.org). Earlier releases
|
|
7
|
+
(6.x) tracked the minimum supported Rails version; the jump to 10 makes
|
|
8
|
+
the break explicit so the version can't be misread as a Rails version.
|
|
9
|
+
|
|
10
|
+
### Breaking changes
|
|
11
|
+
|
|
12
|
+
- Relation sorts are unified across association types. Sorting by any
|
|
13
|
+
relation (`has_many`, `has_and_belongs_to_many`, `has_one`,
|
|
14
|
+
`belongs_to`) now `LEFT OUTER JOIN`s the association, groups by the
|
|
15
|
+
sorted table's primary key, and orders by an aggregate of the requested
|
|
16
|
+
column. For `has_many` sorts this changes behavior:
|
|
17
|
+
- each record is returned once, instead of once per associated row
|
|
18
|
+
(the join no longer fans out into duplicates)
|
|
19
|
+
- records with no associated rows are included (previously dropped by
|
|
20
|
+
the `INNER JOIN`)
|
|
21
|
+
- Descending relation sorts key each record by its largest member
|
|
22
|
+
(`MAX`), ascending by its smallest (`MIN`) — the member you'd expect
|
|
23
|
+
to see first in that direction. For records with multiple associated
|
|
24
|
+
rows, descending is therefore not the reverse of ascending: a record
|
|
25
|
+
holding both extremes sorts first in both directions.
|
|
26
|
+
- Sort columns are no longer added to the `SELECT`:
|
|
27
|
+
- loaded records keep their own attributes — a joined sort column can
|
|
28
|
+
no longer overwrite a same-named attribute on the base record
|
|
29
|
+
(previously even `id` could be clobbered)
|
|
30
|
+
- `pluck` and `ids` keep the sort (previously raised on
|
|
31
|
+
`has_and_belongs_to_many` sorts)
|
|
32
|
+
- a caller's `select` is left untouched
|
|
33
|
+
- chaining `.distinct` after a relation sort now raises: PostgreSQL
|
|
34
|
+
requires `ORDER BY` expressions to appear in the select list for
|
|
35
|
+
`SELECT DISTINCT`. It previously appeared to work while silently
|
|
36
|
+
deduplicating over the wrong tuple.
|
|
37
|
+
- A bare `belongs_to`/`has_one` sort with no direction (e.g.
|
|
38
|
+
`Address.sort(property: :name)`) now defaults to ascending, matching
|
|
39
|
+
every other sort form (previously descending).
|
|
40
|
+
- An unknown sort direction on a relation sort (e.g. `:dsc`) now raises
|
|
41
|
+
`ActiveRecord::StatementInvalid` (previously sorted ascending
|
|
42
|
+
silently), matching column sorts.
|
|
43
|
+
- ActiveRecord 7.1 or newer is required (previously 6.1).
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- Sorting by `has_and_belongs_to_many` relations.
|
|
48
|
+
- Sorts of different types compose: they can be combined in one call
|
|
49
|
+
(`Property.sort(:name, tags: :name, addresses: :id)`) or chained
|
|
50
|
+
(`.sort(...).sort(...)`), sharing a single `GROUP BY`.
|
|
51
|
+
- Aggregates on a sorted relation (`count`, `sum`, `average`, `minimum`,
|
|
52
|
+
`maximum`) are computed over the records themselves rather than the
|
|
53
|
+
sort's grouped, joined rows — `count` returns the record count instead
|
|
54
|
+
of a per-group `Hash`, and multi-member records aren't weighted once
|
|
55
|
+
per member. A caller-supplied `group` still gets standard grouped
|
|
56
|
+
results.
|
|
57
|
+
- A blank direction (`''`, as query parameters often produce) is
|
|
58
|
+
accepted as ascending on all sort forms.
|
|
59
|
+
- `ActionController::Parameters` are accepted for relation sorts, and
|
|
60
|
+
their nulls option (`nulls_first`/`nulls_last`) is honored.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- `has_and_belongs_to_many` sorts crashed with `NoMethodError` on
|
|
65
|
+
ActiveRecord <= 8.0 (`Function#as` mutates and returns the receiver
|
|
66
|
+
there).
|
|
67
|
+
- Combining a `has_and_belongs_to_many` sort with another relation sort
|
|
68
|
+
raised `PG::GroupingError`.
|
data/README.md
CHANGED
|
@@ -40,18 +40,34 @@ Property.sort(id: {asc: :nulls_last})
|
|
|
40
40
|
# => "...ORDER BY properties.id ASC NULLS LAST"
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
It can also sort on relations
|
|
43
|
+
It can also sort on relations. A relation sort groups by the sorted table's
|
|
44
|
+
primary key — so each record appears once and records with no associated
|
|
45
|
+
rows are still included — and orders by an aggregate of the requested
|
|
46
|
+
column: `MIN` ascending or `MAX` descending, keying each record by the
|
|
47
|
+
member you'd expect to see first in that direction:
|
|
44
48
|
|
|
45
49
|
```ruby
|
|
46
50
|
Property.sort(addresses: :id).to_sql
|
|
47
|
-
# => "
|
|
48
|
-
# => "
|
|
51
|
+
# => "SELECT properties.* FROM properties
|
|
52
|
+
# => " LEFT OUTER JOIN addresses ON addresses.property_id = properties.id
|
|
53
|
+
# => " GROUP BY properties.id
|
|
54
|
+
# => " ORDER BY MIN(addresses.id) ASC"
|
|
49
55
|
|
|
50
56
|
Property.sort(addresses: {id: :desc}).to_sql
|
|
51
|
-
# => "...
|
|
52
|
-
# => " ORDER BY addresses.id DESC"
|
|
57
|
+
# => "...ORDER BY MAX(addresses.id) DESC"
|
|
53
58
|
|
|
54
|
-
Property.sort(addresses: {id: {asc: :
|
|
55
|
-
# => "...
|
|
56
|
-
|
|
59
|
+
Property.sort(addresses: {id: {asc: :nulls_first}}).to_sql
|
|
60
|
+
# => "...ORDER BY MIN(addresses.id) ASC NULLS FIRST"
|
|
61
|
+
|
|
62
|
+
Property.sort(tags: :name).to_sql # has_and_belongs_to_many
|
|
63
|
+
# => "SELECT properties.* FROM properties
|
|
64
|
+
# => " LEFT OUTER JOIN properties_tags ON properties_tags.property_id = properties.id
|
|
65
|
+
# => " LEFT OUTER JOIN tags ON tags.id = properties_tags.tag_id
|
|
66
|
+
# => " GROUP BY properties.id
|
|
67
|
+
# => " ORDER BY MIN(tags.name) ASC"
|
|
57
68
|
```
|
|
69
|
+
|
|
70
|
+
A relation sort is order-only — it never adds or removes records — so
|
|
71
|
+
aggregates on a sorted relation (`count`, `sum`, `average`, `minimum`,
|
|
72
|
+
`maximum`) are computed over the records themselves, not the sort's
|
|
73
|
+
grouped and joined rows.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
require 'active_record/relation'
|
|
3
|
+
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module QueryMethods
|
|
6
|
+
# class << self
|
|
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
|
+
order.each do |column_or_relation, options|
|
|
25
|
+
if column_or_relation.to_sym == :random
|
|
26
|
+
resource = resource.random_sort
|
|
27
|
+
elsif self.column_names.include?(column_or_relation.to_s)
|
|
28
|
+
resource = resource.sort_for_column(self.arel_table[column_or_relation.to_s], options)
|
|
29
|
+
elsif reflect_on_association(column_or_relation.to_sym)
|
|
30
|
+
resource = resource.sort_for_relation(column_or_relation.to_sym, options)
|
|
31
|
+
else
|
|
32
|
+
raise ActiveRecord::StatementInvalid.new("Unkown column #{column_or_relation}")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
resource
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def random_sort
|
|
41
|
+
self.order(Arel::Nodes::RandomOrdering.new)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Normalizes per-column sort options into [direction, nulls]. A blank
|
|
45
|
+
# direction — a bare column, or "" as query params often produce —
|
|
46
|
+
# means :asc.
|
|
47
|
+
# TODO: probably don't need to cast to sym
|
|
48
|
+
def sort_direction_and_nulls(options)
|
|
49
|
+
if options.is_a?(Hash) || options.class.name == "ActionController::Parameters"
|
|
50
|
+
[options.keys.first.to_sym, options.values.first.to_sym]
|
|
51
|
+
elsif options.blank?
|
|
52
|
+
[:asc, nil]
|
|
53
|
+
else
|
|
54
|
+
[options.to_s.downcase.to_sym, nil]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def sort_for_column(column, options)
|
|
59
|
+
direction, nulls = sort_direction_and_nulls(options)
|
|
60
|
+
|
|
61
|
+
if direction == :desc
|
|
62
|
+
self.order(Arel::Nodes::Descending.new(column, nulls))
|
|
63
|
+
elsif direction == :asc
|
|
64
|
+
self.order(Arel::Nodes::Ascending.new(column, nulls))
|
|
65
|
+
else
|
|
66
|
+
raise ActiveRecord::StatementInvalid.new("Unkown ordering #{direction}")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The sort key lives only in the ORDER BY — nothing is added to the
|
|
71
|
+
# select list. That keeps the ORDER BY self-contained (so pluck/ids,
|
|
72
|
+
# which replace the select list, keep the sort), leaves the caller's
|
|
73
|
+
# select untouched, and means loaded records carry exactly their own
|
|
74
|
+
# attributes (a selected join column would overwrite a same-named
|
|
75
|
+
# attribute on the base table).
|
|
76
|
+
def sort_for_relation(relation, options)
|
|
77
|
+
resource = self
|
|
78
|
+
relation = reflect_on_association(relation)
|
|
79
|
+
|
|
80
|
+
if relation.macro == :has_many || relation.macro == :has_and_belongs_to_many
|
|
81
|
+
options = [options] if !options.is_a?(Array)
|
|
82
|
+
|
|
83
|
+
options.each do |order|
|
|
84
|
+
Array(order).each do |column_name, options|
|
|
85
|
+
# LEFT JOIN the collection, group by this table's primary key —
|
|
86
|
+
# so rows don't fan / duplicate and records with an empty
|
|
87
|
+
# collection still appear — and order by an aggregate of the
|
|
88
|
+
# requested column:
|
|
89
|
+
#
|
|
90
|
+
# SELECT properties.*
|
|
91
|
+
# FROM properties
|
|
92
|
+
# LEFT JOIN properties_tags ON properties_tags.property_id = properties.id
|
|
93
|
+
# LEFT JOIN tags ON tags.id = properties_tags.tag_id
|
|
94
|
+
# GROUP BY properties.id
|
|
95
|
+
# ORDER BY MIN(tags.name) ASC
|
|
96
|
+
#
|
|
97
|
+
# Ascending keys each record by its smallest member (MIN),
|
|
98
|
+
# descending by its largest (MAX) — the member you'd expect to
|
|
99
|
+
# see first in that direction. Toggling asc/desc therefore
|
|
100
|
+
# re-keys multi-value records rather than strictly reversing
|
|
101
|
+
# the list.
|
|
102
|
+
column = Arel::Attributes::Relation.new(relation.klass.arel_table[column_name], relation.name)
|
|
103
|
+
direction, nulls = sort_direction_and_nulls(options)
|
|
104
|
+
|
|
105
|
+
order = if direction == :desc
|
|
106
|
+
Arel::Nodes::Descending.new(column.maximum, nulls)
|
|
107
|
+
elsif direction == :asc
|
|
108
|
+
Arel::Nodes::Ascending.new(column.minimum, nulls)
|
|
109
|
+
else
|
|
110
|
+
raise ActiveRecord::StatementInvalid.new("Unkown ordering #{direction}")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
resource = resource.left_outer_joins(relation.name)
|
|
114
|
+
resource = resource.order(order)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
elsif relation.macro == :belongs_to || relation.macro == :has_one
|
|
118
|
+
options = [options] if !options.is_a?(Array)
|
|
119
|
+
|
|
120
|
+
options.each do |order|
|
|
121
|
+
order = Array(order)
|
|
122
|
+
order.each do |column, options|
|
|
123
|
+
column = relation.klass.arel_table[column]
|
|
124
|
+
direction, nulls = sort_direction_and_nulls(options)
|
|
125
|
+
|
|
126
|
+
if direction == :desc
|
|
127
|
+
order = Arel::Nodes::Descending.new(column.maximum, nulls)
|
|
128
|
+
elsif direction == :asc
|
|
129
|
+
order = Arel::Nodes::Ascending.new(column.minimum, nulls)
|
|
130
|
+
else
|
|
131
|
+
raise ActiveRecord::StatementInvalid.new("Unkown ordering #{direction}")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
resource = resource.left_outer_joins(relation.name)
|
|
135
|
+
resource = resource.order(order)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
resource = resource.group(klass.arel_table[klass.primary_key])
|
|
141
|
+
# Tag the relation for the count override below. Chained relations
|
|
142
|
+
# are built with clone, which copies instance variables, so the tag
|
|
143
|
+
# survives further chaining.
|
|
144
|
+
resource.instance_variable_set(:@sorted_by_relation, true)
|
|
145
|
+
resource
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
module Sort
|
|
151
|
+
module Calculations
|
|
152
|
+
# sort_for_relation groups by the primary key and LEFT JOINs the
|
|
153
|
+
# relation, so aggregates would see grouped, fanned-out rows: count
|
|
154
|
+
# returns a per-group Hash, and sum/average weigh a record once per
|
|
155
|
+
# collection member. But a relation sort is order-only — records are
|
|
156
|
+
# never added or removed — so every aggregate has a well-defined
|
|
157
|
+
# answer: compute it over the base table restricted to the sorted
|
|
158
|
+
# relation's (distinct) primary keys. User joins and conditions still
|
|
159
|
+
# apply inside the subquery, and a user-added group falls through to
|
|
160
|
+
# the standard grouped behavior.
|
|
161
|
+
def calculate(operation, column_name)
|
|
162
|
+
if @sorted_by_relation && group_values == [klass.arel_table[klass.primary_key]]
|
|
163
|
+
klass.where(klass.primary_key => unscope(:group, :order, :select).select(klass.primary_key))
|
|
164
|
+
.calculate(operation, column_name)
|
|
165
|
+
else
|
|
166
|
+
super
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
ActiveRecord::Relation.prepend(ActiveRecord::Sort::Calculations)
|
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:
|
|
4
|
+
version: 10.0.0.rc1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jon Bracy
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activerecord
|
|
@@ -16,14 +15,14 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: '7.1'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
25
|
+
version: '7.1'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: arel-extensions
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -112,18 +111,18 @@ dependencies:
|
|
|
112
111
|
name: simplecov
|
|
113
112
|
requirement: !ruby/object:Gem::Requirement
|
|
114
113
|
requirements:
|
|
115
|
-
- -
|
|
114
|
+
- - '='
|
|
116
115
|
- !ruby/object:Gem::Version
|
|
117
|
-
version:
|
|
116
|
+
version: 1.0.0.rc5
|
|
118
117
|
type: :development
|
|
119
118
|
prerelease: false
|
|
120
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
120
|
requirements:
|
|
122
|
-
- -
|
|
121
|
+
- - '='
|
|
123
122
|
- !ruby/object:Gem::Version
|
|
124
|
-
version:
|
|
123
|
+
version: 1.0.0.rc5
|
|
125
124
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
125
|
+
name: faker
|
|
127
126
|
requirement: !ruby/object:Gem::Requirement
|
|
128
127
|
requirements:
|
|
129
128
|
- - ">="
|
|
@@ -137,35 +136,35 @@ dependencies:
|
|
|
137
136
|
- !ruby/object:Gem::Version
|
|
138
137
|
version: '0'
|
|
139
138
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
139
|
+
name: sunstone
|
|
141
140
|
requirement: !ruby/object:Gem::Requirement
|
|
142
141
|
requirements:
|
|
143
142
|
- - ">="
|
|
144
143
|
- !ruby/object:Gem::Version
|
|
145
|
-
version:
|
|
144
|
+
version: 6.1.0.2
|
|
146
145
|
type: :development
|
|
147
146
|
prerelease: false
|
|
148
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
148
|
requirements:
|
|
150
149
|
- - ">="
|
|
151
150
|
- !ruby/object:Gem::Version
|
|
152
|
-
version:
|
|
151
|
+
version: 6.1.0.2
|
|
153
152
|
- !ruby/object:Gem::Dependency
|
|
154
|
-
name:
|
|
153
|
+
name: webmock
|
|
155
154
|
requirement: !ruby/object:Gem::Requirement
|
|
156
155
|
requirements:
|
|
157
156
|
- - ">="
|
|
158
157
|
- !ruby/object:Gem::Version
|
|
159
|
-
version:
|
|
158
|
+
version: '0'
|
|
160
159
|
type: :development
|
|
161
160
|
prerelease: false
|
|
162
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
163
162
|
requirements:
|
|
164
163
|
- - ">="
|
|
165
164
|
- !ruby/object:Gem::Version
|
|
166
|
-
version:
|
|
165
|
+
version: '0'
|
|
167
166
|
- !ruby/object:Gem::Dependency
|
|
168
|
-
name:
|
|
167
|
+
name: debug
|
|
169
168
|
requirement: !ruby/object:Gem::Requirement
|
|
170
169
|
requirements:
|
|
171
170
|
- - ">="
|
|
@@ -187,15 +186,16 @@ extensions: []
|
|
|
187
186
|
extra_rdoc_files:
|
|
188
187
|
- README.md
|
|
189
188
|
files:
|
|
189
|
+
- CHANGELOG.md
|
|
190
190
|
- LICENSE
|
|
191
191
|
- README.md
|
|
192
|
+
- ext/active_record/base.rb
|
|
192
193
|
- lib/active_record/sort.rb
|
|
193
194
|
- lib/active_record/sort/version.rb
|
|
194
195
|
homepage: https://github.com/malomalo/activerecord-sort
|
|
195
196
|
licenses:
|
|
196
197
|
- MIT
|
|
197
198
|
metadata: {}
|
|
198
|
-
post_install_message:
|
|
199
199
|
rdoc_options:
|
|
200
200
|
- "--main"
|
|
201
201
|
- README.md
|
|
@@ -205,15 +205,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
205
205
|
requirements:
|
|
206
206
|
- - ">="
|
|
207
207
|
- !ruby/object:Gem::Version
|
|
208
|
-
version: '
|
|
208
|
+
version: '3.3'
|
|
209
209
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
210
|
requirements:
|
|
211
211
|
- - ">="
|
|
212
212
|
- !ruby/object:Gem::Version
|
|
213
213
|
version: '0'
|
|
214
214
|
requirements: []
|
|
215
|
-
rubygems_version:
|
|
216
|
-
signing_key:
|
|
215
|
+
rubygems_version: 4.0.2
|
|
217
216
|
specification_version: 4
|
|
218
217
|
summary: A safe way to accept user parameters and order against your ActiveRecord
|
|
219
218
|
Models
|