Merge pull request #1006 from IndependentIP/1-8-sort-scope

[1.8] Backport support for sorting by scopes
This commit is contained in:
Greg Molnar 2019-02-24 07:08:43 +01:00 committed by GitHub
commit 159d7f01cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 5 deletions

View File

@ -1,5 +1,10 @@
# Change Log
* Add support for sorting by scopes
PR [973](https://github.com/activerecord-hackery/ransack/pull/973)
*Diego Borges*
## Version 1.8.9 - 2018-08-09
* Locked Active Record compatibiity at 5.1.0

View File

@ -218,6 +218,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

View File

@ -37,9 +37,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

@ -576,6 +576,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

@ -57,6 +57,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