Skip to content

Commit

Permalink
Add support for sorting by scopes
Browse files Browse the repository at this point in the history
For when we want to sort by something complex such as the result of a SQL function.
  • Loading branch information
drborges authored and benlangfeld committed Oct 23, 2018
1 parent 7145471 commit d4f66e5
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

* Add support for sorting by scopes
PR [XXX](https://github.com/activerecord-hackery/ransack/pull/XXX)

*Diego Borges*

## Version 2.0.1 - 2018-08-18

* Don't return association if table is nil
Expand Down
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ created by [Ernie Miller](http://twitter.com/erniemiller)
and developed/maintained for years by
[Jon Atack](http://twitter.com/jonatack) and
[Ryan Bigg](http://twitter.com/ryanbigg) with the help of a great group of
[contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors). Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic).
[contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors). Ransack's logo is designed by [Anıl Kılıç](https://github.com/anilkilic).
While it supports many of the same features as MetaSearch, its underlying
implementation differs greatly from MetaSearch,
and backwards compatibility is not a design goal.
Expand Down Expand Up @@ -133,8 +133,8 @@ which are defined in
```

The argument of `f.search_field` has to be in this form:
`attribute_name[_or_attribute_name]..._predicate`
`attribute_name[_or_attribute_name]..._predicate`

where `[_or_another_attribute_name]...` means any repetition of `_or_` plus the name of the attribute.

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

In the case that you wish to sort by some complex value, such as the result
of a SQL function, you may do so using scopes. In your model, define scopes
whose names line up with the name of the virtual field you wish to sort by,
as so:

```ruby
class Person < ActiveRecord::Base
scope :sort_by_reverse_name_asc, lambda { order("REVERSE(name) ASC") }
scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
...
```

and you can then sort by this virtual field:

```erb
<%= sort_link(@q, :reverse_name) %>
```

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

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

`ransackable_scopes_skip_sanitize_args`
Expand Down
22 changes: 21 additions & 1 deletion lib/ransack/adapters/active_record/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,29 @@ def type_for(attr)
def evaluate(search, opts = {})
viz = Visitor.new
relation = @object.where(viz.accept(search.base))

if search.sorts.any?
relation = relation.except(:order).reorder(viz.accept(search.sorts))
relation = relation.except(:order)
# Rather than applying all of the search's sorts in one fell swoop,
# as the original implementation does, we apply one at a time.
#
# If the sort (returned by the Visitor above) is a symbol, we know
# that it represents a scope on the model and we can apply that
# scope.
#
# Otherwise, we fall back to the applying the sort with the "order"
# method as the original implementation did. Actually the original
# implementation used "reorder," which was overkill since we already
# have a clean slate after "relation.except(:order)" above.
viz.accept(search.sorts).each do |scope_or_sort|
if scope_or_sort.is_a?(Symbol)
relation = relation.send(scope_or_sort)
else
relation = relation.order(scope_or_sort)
end
end
end

opts[:distinct] ? relation.distinct : relation
end

Expand Down
12 changes: 8 additions & 4 deletions lib/ransack/adapters/active_record/ransack/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ def quoted?(object)
end

def visit_Ransack_Nodes_Sort(object)
return unless object.valid?
if object.attr.is_a?(Arel::Attributes::Attribute)
object.attr.send(object.dir)
if object.valid?
if object.attr.is_a?(Arel::Attributes::Attribute)
object.attr.send(object.dir)
else
ordered(object)
end
else
ordered(object)
scope_name = :"sort_by_#{object.name}_#{object.dir}"
scope_name if object.context.object.respond_to?(scope_name)
end
end

Expand Down
7 changes: 7 additions & 0 deletions spec/ransack/adapters/active_record/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,13 @@ def self.simple_escaping?
expect(search.result.to_sql).to match /ORDER BY .* ASC/
end
end

context 'sorting by a scope' do
it 'applies the correct scope' do
search = Person.search(sorts: ['reverse_name asc'])
expect(search.result.to_sql).to include("ORDER BY REVERSE(name) ASC")
end
end
end

describe '#ransackable_attributes' do
Expand Down
3 changes: 3 additions & 0 deletions spec/support/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class Person < ActiveRecord::Base
of_age ? where("age >= ?", 18) : where("age < ?", 18)
}

scope :sort_by_reverse_name_asc, lambda { order("REVERSE(name) ASC") }
scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }

alias_attribute :full_name, :name

ransack_alias :term, :name_or_email
Expand Down

0 comments on commit d4f66e5

Please sign in to comment.