Skip to content

Commit 2c16a5a

Browse files
authored
Merge pull request #973 from powerhome/sort-by-scopes
Add support for sorting by scopes
2 parents 7145471 + bdd1c24 commit 2c16a5a

File tree

6 files changed

+65
-9
lines changed

6 files changed

+65
-9
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
* Add support for sorting by scopes
6+
PR [973](https://github.com/activerecord-hackery/ransack/pull/973)
7+
8+
*Diego Borges*
9+
510
## Version 2.0.1 - 2018-08-18
611

712
* Don't return association if table is nil

README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ created by [Ernie Miller](http://twitter.com/erniemiller)
99
and developed/maintained for years by
1010
[Jon Atack](http://twitter.com/jonatack) and
1111
[Ryan Bigg](http://twitter.com/ryanbigg) with the help of a great group of
12-
[contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors). Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic).
12+
[contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors). Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic).
1313
While it supports many of the same features as MetaSearch, its underlying
1414
implementation differs greatly from MetaSearch,
1515
and backwards compatibility is not a design goal.
@@ -133,8 +133,8 @@ which are defined in
133133
```
134134

135135
The argument of `f.search_field` has to be in this form:
136-
`attribute_name[_or_attribute_name]..._predicate`
137-
136+
`attribute_name[_or_attribute_name]..._predicate`
137+
138138
where `[_or_another_attribute_name]...` means any repetition of `_or_` plus the name of the attribute.
139139

140140
`cont` (contains) and `start` (starts with) are just two of the available
@@ -201,6 +201,23 @@ This example toggles the sort directions of both fields, by default
201201
initially sorting the `last_name` field by ascending order, and the
202202
`first_name` field by descending order.
203203

204+
In the case that you wish to sort by some complex value, such as the result
205+
of a SQL function, you may do so using scopes. In your model, define scopes
206+
whose names line up with the name of the virtual field you wish to sort by,
207+
as so:
208+
209+
```ruby
210+
class Person < ActiveRecord::Base
211+
scope :sort_by_reverse_name_asc, lambda { order("REVERSE(name) ASC") }
212+
scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
213+
...
214+
```
215+
216+
and you can then sort by this virtual field:
217+
218+
```erb
219+
<%= sort_link(@q, :reverse_name) %>
220+
```
204221

205222
The sort link order indicator arrows may be globally customized by setting a
206223
`custom_arrows` option in an initializer file like
@@ -727,7 +744,7 @@ Ransack.configure do |c|
727744
end
728745
```
729746

730-
To turn this off on a per-scope basis Ransack adds the following method to
747+
To turn this off on a per-scope basis Ransack adds the following method to
731748
`ActiveRecord::Base` that you can redefine to selectively override sanitization:
732749

733750
`ransackable_scopes_skip_sanitize_args`

lib/ransack/adapters/active_record/context.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,29 @@ def type_for(attr)
3131
def evaluate(search, opts = {})
3232
viz = Visitor.new
3333
relation = @object.where(viz.accept(search.base))
34+
3435
if search.sorts.any?
35-
relation = relation.except(:order).reorder(viz.accept(search.sorts))
36+
relation = relation.except(:order)
37+
# Rather than applying all of the search's sorts in one fell swoop,
38+
# as the original implementation does, we apply one at a time.
39+
#
40+
# If the sort (returned by the Visitor above) is a symbol, we know
41+
# that it represents a scope on the model and we can apply that
42+
# scope.
43+
#
44+
# Otherwise, we fall back to the applying the sort with the "order"
45+
# method as the original implementation did. Actually the original
46+
# implementation used "reorder," which was overkill since we already
47+
# have a clean slate after "relation.except(:order)" above.
48+
viz.accept(search.sorts).each do |scope_or_sort|
49+
if scope_or_sort.is_a?(Symbol)
50+
relation = relation.send(scope_or_sort)
51+
else
52+
relation = relation.order(scope_or_sort)
53+
end
54+
end
3655
end
56+
3757
opts[:distinct] ? relation.distinct : relation
3858
end
3959

lib/ransack/adapters/active_record/ransack/visitor.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ def quoted?(object)
2121
end
2222

2323
def visit_Ransack_Nodes_Sort(object)
24-
return unless object.valid?
25-
if object.attr.is_a?(Arel::Attributes::Attribute)
26-
object.attr.send(object.dir)
24+
if object.valid?
25+
if object.attr.is_a?(Arel::Attributes::Attribute)
26+
object.attr.send(object.dir)
27+
else
28+
ordered(object)
29+
end
2730
else
28-
ordered(object)
31+
scope_name = :"sort_by_#{object.name}_#{object.dir}"
32+
scope_name if object.context.object.respond_to?(scope_name)
2933
end
3034
end
3135

spec/ransack/adapters/active_record/base_spec.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,13 @@ def self.simple_escaping?
605605
expect(search.result.to_sql).to match /ORDER BY .* ASC/
606606
end
607607
end
608+
609+
context 'sorting by a scope' do
610+
it 'applies the correct scope' do
611+
search = Person.search(sorts: ['reverse_name asc'])
612+
expect(search.result.to_sql).to include("ORDER BY REVERSE(name) ASC")
613+
end
614+
end
608615
end
609616

610617
describe '#ransackable_attributes' do

spec/support/schema.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ class Person < ActiveRecord::Base
4343
of_age ? where("age >= ?", 18) : where("age < ?", 18)
4444
}
4545

46+
scope :sort_by_reverse_name_asc, lambda { order("REVERSE(name) ASC") }
47+
scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
48+
4649
alias_attribute :full_name, :name
4750

4851
ransack_alias :term, :name_or_email

0 commit comments

Comments
 (0)