Merge pull request #973 from powerhome/sort-by-scopes

Add support for sorting by scopes
This commit is contained in:
Sean 2018-10-26 17:05:52 +02:00 committed by GitHub
commit 2c16a5a954
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 65 additions and 9 deletions

View File

@ -2,6 +2,11 @@
## Unreleased
* Add support for sorting by scopes
PR [973](https://github.com/activerecord-hackery/ransack/pull/973)
*Diego Borges*
## Version 2.0.1 - 2018-08-18
* Don't return association if table is nil

View File

@ -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.
@ -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
@ -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
@ -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`

View File

@ -31,9 +31,29 @@ module Ransack
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

View File

@ -21,11 +21,15 @@ module Ransack
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

View File

@ -605,6 +605,13 @@ module Ransack
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

View File

@ -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