trailblazer-finder 0.1.2 → 0.1.3

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.
@@ -2,8 +2,6 @@ module Trailblazer
2
2
  class Finder
3
3
  # Base module
4
4
  module Base
5
- PREDICATES = %w[eq not_eq].freeze
6
-
7
5
  def self.included(base)
8
6
  base.extend ClassMethods
9
7
  base.instance_eval do
@@ -2,6 +2,8 @@ module Trailblazer
2
2
  class Finder
3
3
  module Features
4
4
  module Predicate
5
+ PREDICATES = %w[eq not_eq blank not_blank lt lte gt gte].freeze
6
+
5
7
  def self.included(base)
6
8
  base.extend ClassMethods
7
9
  base.instance_eval do
@@ -10,7 +12,7 @@ module Trailblazer
10
12
  end
11
13
 
12
14
  module ClassMethods
13
- def do_filters(attribute, predicate)
15
+ def predicate_filter(attribute, predicate)
14
16
  filter_by "#{attribute}_#{predicate}" do |entity_type, value|
15
17
  splitter = Utils::Splitter.new "#{attribute}_#{predicate}", value
16
18
  splitter.split_key predicate.to_sym
@@ -20,14 +22,7 @@ module Trailblazer
20
22
 
21
23
  def predicates_for(*attributes)
22
24
  attributes.each do |attribute|
23
- do_filters(attribute, "eq")
24
- do_filters(attribute, "not_eq")
25
- do_filters(attribute, "blank")
26
- do_filters(attribute, "not_blank")
27
- do_filters(attribute, "gt")
28
- do_filters(attribute, "gte")
29
- do_filters(attribute, "lt")
30
- do_filters(attribute, "lte")
25
+ PREDICATES.each { |predicate| predicate_filter(attribute, predicate) }
31
26
  end
32
27
  end
33
28
  end
@@ -5,55 +5,97 @@ module Trailblazer
5
5
  def self.included(base)
6
6
  base.extend ClassMethods
7
7
  base.instance_eval do
8
- filter_by :sort do |entity_type, _|
9
- sort_it(entity_type, sort_attribute, sort_direction)
8
+ filter_by :sort do |entity_type, value|
9
+ next sort_it(entity_type, [sort_orders(sort_attribute(value), sort_direction(value))]) if value.nil?
10
+ sort_attributes = value.split(',')
11
+ sorters = []
12
+ sort_attributes.each do |sort_attr|
13
+ sorters << sort_orders(sort_attribute(sort_attr), sort_direction(sort_attr))
14
+ end
15
+ sort_it(entity_type, sorters)
10
16
  end
11
17
  end
12
18
  end
13
19
 
14
- # Adapters will overwite this method
15
- def sort_it(entity_type, sort_attribute, sort_direction)
16
- case sort_direction
17
- when 'asc', 'ascending'
18
- entity_type.sort_by { |a| a[sort_attribute.to_sym] }
19
- when 'desc', 'descending'
20
- entity_type.sort_by { |a| a[sort_attribute.to_sym] }.reverse
21
- end
22
- end
23
-
24
20
  def sort?(attribute)
25
- attribute == sort || sort.to_s.start_with?("#{attribute} ")
21
+ sort.include?(attribute.to_s)
26
22
  end
27
23
 
28
- def sort_attribute
29
- @sort_attribute ||= Utils::Extra.ensure_included sort.to_s.split(' ', 2).first, self.class.sort_attributes
24
+ def sort_direction_for(attribute)
25
+ return 'asc' if !sort.nil? && sort.include?("#{attribute} asc")
26
+ 'desc'
30
27
  end
31
28
 
32
- def sort_direction
33
- @sort_direction ||= Utils::Extra.ensure_included sort.to_s.split(' ', 2).last, %w[desc asc]
29
+ def reverse_sort_direction_for(attribute)
30
+ return 'desc' if sort_direction_for(attribute) == 'asc'
31
+ 'asc'
34
32
  end
35
33
 
36
- def sort_direction_for(attribute)
37
- if sort_attribute == attribute.to_s
38
- reverted_sort_direction
39
- else
40
- 'desc'
41
- end
34
+ def sort_params_for(attribute, options = {})
35
+ options['sort'] = if sort.nil?
36
+ "#{attribute} #{sort_direction_for(attribute)}"
37
+ elsif sort.include?(attribute.to_s)
38
+ sort
39
+ else
40
+ "#{attribute} #{sort_direction_for(attribute)}"
41
+ end
42
+ params options
42
43
  end
43
44
 
44
- def sort_params_for(attribute, options = {})
45
+ def new_sort_params_for(attribute, options = {})
45
46
  options['sort'] = "#{attribute} #{sort_direction_for(attribute)}"
46
47
  params options
47
48
  end
48
49
 
49
- def reverted_sort_direction
50
- sort_direction == 'desc' ? 'asc' : 'desc'
50
+ def add_sort_params_for(attribute, options = {})
51
+ options['sort'] = if sort.nil?
52
+ "#{attribute} #{sort_direction_for(attribute)}"
53
+ elsif sort.include?(attribute.to_s)
54
+ sort.gsub(/#{attribute} #{sort_direction_for(attribute)}/, "#{attribute} #{reverse_sort_direction_for(attribute)}")
55
+ else
56
+ "#{sort}, #{attribute} #{sort_direction_for(attribute)}"
57
+ end
58
+ params options
59
+ end
60
+
61
+ private
62
+
63
+ # ORM Adapters will overwite this method
64
+ def sort_it(entity_type, sort_attributes)
65
+ entity_type.sort do |this, that|
66
+ sort_attributes.reduce(0) do |diff, order|
67
+ next diff if diff != 0 # this and that have differed at an earlier order entry
68
+ key, direction = order
69
+ # deal with nil cases
70
+ next 0 if this[key].nil? && that[key].nil?
71
+ next 1 if this[key].nil?
72
+ next -1 if that[key].nil?
73
+ # do the actual comparison
74
+ comparison = this[key] <=> that[key]
75
+ comparison * direction
76
+ end
77
+ end
78
+ end
79
+
80
+ # ORM Adapters will overwite this method
81
+ def sort_orders(sort_attr, sort_dir)
82
+ [sort_attr.to_sym, (sort_dir.to_sym == :asc ? 1 : -1)]
83
+ end
84
+
85
+ def sort_attribute(attribute)
86
+ result = Utils::Extra.ensure_included attribute.to_s.split(' ', 2).first, self.class.sort_attributes
87
+ result
88
+ end
89
+
90
+ def sort_direction(attribute)
91
+ return Utils::Extra.ensure_included attribute.to_s.split(' ', 2).last, %w[desc asc] unless attribute.nil?
92
+ 'desc'
51
93
  end
52
94
 
53
95
  module ClassMethods
96
+ attr_accessor :sorted_attributes
54
97
  def sortable_by(*attributes)
55
- config[:sort_attributes] = attributes.map(&:to_s)
56
- config[:defaults]['sort'] = "#{config[:sort_attributes].first} desc"
98
+ config[:sort_attributes] = attributes.map(&:to_s)
57
99
  end
58
100
 
59
101
  def sort_attributes
@@ -2,35 +2,20 @@ module Trailblazer
2
2
  class Finder
3
3
  module Utils
4
4
  module DeepLocate
5
- def self.deep_locate(comparator, object)
6
- comparator = _construct_key_comparator(comparator, object) unless comparator.respond_to?(:call)
7
-
8
- _deep_locate(comparator, object)
9
- end
10
-
11
- def self._construct_key_comparator(search_key, object)
12
- search_key = search_key.to_s if defined?(::ActiveSupport::HashWithIndifferentAccess) && object.is_a?(::ActiveSupport::HashWithIndifferentAccess)
13
- search_key = search_key.to_s if object.respond_to?(:indifferent_access?) && object.indifferent_access?
14
-
15
- lambda do |non_callable_object|
16
- ->(key, _, _) { key == non_callable_object }
17
- end.call(search_key)
18
- end
19
-
20
- def self._deep_locate(comparator, object, result = [])
5
+ def self.deep_locate(comparator, object, result = [])
21
6
  if object.is_a?(::Enumerable)
22
- if object.any? { |value| _match_comparator?(value, comparator, object) }
7
+ if object.any? { |value| match_comparator?(value, comparator, object) }
23
8
  result.push object
24
9
  end
25
10
  (object.respond_to?(:values) ? object.values : object.entries).each do |value|
26
- _deep_locate(comparator, value, result)
11
+ deep_locate(comparator, value, result)
27
12
  end
28
13
  end
29
14
 
30
15
  result
31
16
  end
32
17
 
33
- def self._match_comparator?(value, comparator, object)
18
+ def self.match_comparator?(value, comparator, object)
34
19
  if object.is_a?(::Hash)
35
20
  key, value = value
36
21
  else
@@ -7,13 +7,13 @@ module Trailblazer
7
7
  class Parse
8
8
  # Need a replacement for this
9
9
  def self.date(value)
10
- return if value.nil?
11
- return if [true, false].include? value
12
- return if value.is_a? Integer
13
- return if value =~ /[[:alpha:]]/
10
+ return unless valid_date(value)
14
11
  Date.parse(value).strftime('%Y-%m-%d')
15
- rescue ArgumentError
16
- nil
12
+ end
13
+
14
+ def self.valid_date(date)
15
+ date_hash = Date._parse(date.to_s)
16
+ Date.valid_date?(date_hash[:year].to_i, date_hash[:mon].to_i, date_hash[:mday].to_i)
17
17
  end
18
18
 
19
19
  def self.term(value)
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
2
  class Finder
3
- VERSION = '0.1.2'.freeze
3
+ VERSION = '0.1.3'.freeze
4
4
  end
5
5
  end
@@ -12,4 +12,5 @@ end
12
12
  require 'coveralls'
13
13
  Coveralls.wear!
14
14
 
15
+ require_relative 'support/sorting_shared_example'
15
16
  require 'trailblazer/finder'
@@ -1,6 +1,5 @@
1
1
  require_relative 'spec_helper'
2
2
  require_relative 'support/paging_shared_example'
3
- require_relative 'support/sorting_shared_example'
4
3
  require 'active_record'
5
4
 
6
5
  ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
@@ -1,71 +1,90 @@
1
1
  shared_examples_for 'a sorting feature' do
2
2
  describe '#sort?' do
3
- it 'matches the sort option' do
3
+ it 'says if the requested item is sorted on, for single sort' do
4
4
  finder = finder_with_sort 'price desc'
5
5
 
6
6
  expect(finder).to be_sort :price
7
7
  expect(finder).not_to be_sort :name
8
8
  end
9
9
 
10
- it 'matches string also' do
10
+ it 'says if the requested item is sorted on for multiple sorts' do
11
+ finder = finder_with_sort 'price desc, name asc, id desc'
12
+
13
+ expect(finder).to be_sort :price
14
+ expect(finder).to be_sort :name
15
+ expect(finder).to be_sort :id
16
+ expect(finder).not_to be_sort :created_at
17
+ end
18
+
19
+ it 'matches string also, for single sort' do
11
20
  finder = finder_with_sort 'price desc'
12
21
 
13
22
  expect(finder).to be_sort 'price'
14
23
  expect(finder).not_to be_sort 'name'
15
24
  end
16
25
 
17
- it 'matches exact strings' do
18
- finder = finder_with_sort 'price desc'
26
+ it 'matches string also, for multiple sorts' do
27
+ finder = finder_with_sort 'price desc, name asc, id desc'
19
28
 
20
- expect(finder).to be_sort 'price desc'
21
- expect(finder).not_to be_sort 'price asc'
29
+ expect(finder).to be_sort 'price'
30
+ expect(finder).to be_sort 'name'
31
+ expect(finder).to be_sort 'id'
32
+ expect(finder).not_to be_sort 'created_at'
22
33
  end
23
- end
24
34
 
25
- describe '#sort_attribute' do
26
- it 'returns sort option attribute' do
35
+ it 'matches exact strings, for single sort' do
27
36
  finder = finder_with_sort 'price desc'
28
- expect(finder.sort_attribute).to eq 'price'
29
- end
30
37
 
31
- it 'defaults to the first sort by option' do
32
- finder = finder_with_sort
33
- expect(finder.sort_attribute).to eq 'name'
38
+ expect(finder).to be_sort 'price desc'
39
+ expect(finder).not_to be_sort 'price asc'
34
40
  end
35
41
 
36
- it 'rejects invalid sort options, uses defaults' do
37
- finder = finder_with_sort 'invalid'
38
- expect(finder.sort_attribute).to eq 'name'
42
+ it 'matches exact strings, for multiple sort' do
43
+ finder = finder_with_sort 'price desc, name asc'
44
+
45
+ expect(finder).to be_sort 'price desc'
46
+ expect(finder).to be_sort 'name asc'
47
+ expect(finder).not_to be_sort 'name desc'
39
48
  end
40
49
  end
41
50
 
42
- describe '#sort_direction' do
43
- it 'returns asc or desc' do
44
- expect(finder_with_sort('price desc').sort_direction).to eq 'desc'
45
- expect(finder_with_sort('price asc').sort_direction).to eq 'asc'
51
+ describe '#sort_direction_for' do
52
+ it 'returns desc if current sort attribute is not the given attribute' do
53
+ expect(finder_with_sort('price desc').sort_direction_for('name')).to eq 'desc'
46
54
  end
47
55
 
48
- it 'defaults to desc' do
49
- expect(finder_with_sort.sort_direction).to eq 'desc'
50
- expect(finder_with_sort('price').sort_direction).to eq 'desc'
56
+ it 'returns asc if current sort attribute is the given attribute' do
57
+ expect(finder_with_sort('name desc').sort_direction_for('name')).to eq 'desc'
51
58
  end
52
59
 
53
- it 'rejects invalid sort options, uses desc' do
54
- expect(finder_with_sort('price foo').sort_direction).to eq 'desc'
60
+ it 'returns desc if current sort attribute is the given attribute, but asc with direction' do
61
+ expect(finder_with_sort('name asc').sort_direction_for('name')).to eq 'asc'
55
62
  end
56
63
  end
57
64
 
58
- describe '#sort_direction_for' do
65
+ describe '#reverse_sort_direction_for' do
59
66
  it 'returns desc if current sort attribute is not the given attribute' do
60
- expect(finder_with_sort('price desc').sort_direction_for('name')).to eq 'desc'
67
+ expect(finder_with_sort('price desc').reverse_sort_direction_for('name')).to eq 'asc'
61
68
  end
62
69
 
63
70
  it 'returns asc if current sort attribute is the given attribute' do
64
- expect(finder_with_sort('name desc').sort_direction_for('name')).to eq 'asc'
71
+ expect(finder_with_sort('name desc').reverse_sort_direction_for('name')).to eq 'asc'
65
72
  end
66
73
 
67
74
  it 'returns desc if current sort attribute is the given attribute, but asc with direction' do
68
- expect(finder_with_sort('name asc').sort_direction_for('name')).to eq 'desc'
75
+ expect(finder_with_sort('name asc').reverse_sort_direction_for('name')).to eq 'desc'
76
+ end
77
+ end
78
+
79
+ describe '#new_sort_params_for' do
80
+ it 'adds new sort parmas when none are present' do
81
+ finder = finder_with_sort
82
+ expect(finder.new_sort_params_for(:price)).to eq 'sort' => 'price desc'
83
+ end
84
+
85
+ it 'adds new sort params when multiple ones exists (replaces them all)' do
86
+ finder = finder_with_sort 'name desc, price desc, id asc', name: 'test'
87
+ expect(finder.new_sort_params_for(:name)).to eq 'sort' => 'name desc', 'name' => 'test'
69
88
  end
70
89
  end
71
90
 
@@ -75,9 +94,10 @@ shared_examples_for 'a sorting feature' do
75
94
  expect(finder.sort_params_for(:price)).to eq 'sort' => 'price desc', 'name' => 'test'
76
95
  end
77
96
 
78
- it 'reverses sort direction if this is the current sort attribute' do
79
- finder = finder_with_sort 'name desc', name: 'test'
80
- expect(finder.sort_params_for(:name)).to eq 'sort' => 'name asc', 'name' => 'test'
97
+ it 'finds params for multiple sorts' do
98
+ finder = finder_with_sort 'name desc, price desc, id asc', name: 'test'
99
+ expect(finder.sort_params_for(:name)).to eq 'sort' => 'name desc, price desc, id asc', 'name' => 'test'
100
+ expect(finder.sort_params_for(:price)).to eq 'sort' => 'name desc, price desc, id asc', 'name' => 'test'
81
101
  end
82
102
 
83
103
  it 'accepts additional options' do
@@ -86,10 +106,20 @@ shared_examples_for 'a sorting feature' do
86
106
  end
87
107
  end
88
108
 
89
- describe '#reverted_sort_direction' do
90
- it 'reverts sorting direction' do
91
- expect(finder_with_sort('price desc').reverted_sort_direction).to eq 'asc'
92
- expect(finder_with_sort('price asc').reverted_sort_direction).to eq 'desc'
109
+ describe '#add_sort_params_for' do
110
+ it 'adds sort params when none exists' do
111
+ finder = finder_with_sort
112
+ expect(finder.add_sort_params_for(:price)).to eq 'sort' => 'price desc'
113
+ end
114
+
115
+ it 'adds sort params when one already exists' do
116
+ finder = finder_with_sort 'name', name: 'test'
117
+ expect(finder.add_sort_params_for(:price)).to eq 'sort' => 'name, price desc', 'name' => 'test'
118
+ end
119
+
120
+ it 'adds sort params when current attribute already exists, but change it' do
121
+ finder = finder_with_sort 'name asc, price asc', name: 'test'
122
+ expect(finder.add_sort_params_for(:price)).to eq 'sort' => 'name asc, price desc', 'name' => 'test'
93
123
  end
94
124
  end
95
125
  end
@@ -6,6 +6,11 @@ module Trailblazer
6
6
  module Adapters
7
7
  module ActiveRecord
8
8
  describe Predicates do
9
+ after do
10
+ Product.delete_all
11
+ Product.reset_pk_sequence
12
+ end
13
+
9
14
  class TestFinder < Trailblazer::Finder
10
15
  features Predicate
11
16
  adapters ActiveRecord
@@ -23,8 +28,6 @@ module Trailblazer
23
28
 
24
29
  describe 'equals' do
25
30
  before do
26
- Product.delete_all
27
- Product.reset_pk_sequence
28
31
  10.times do |i|
29
32
  next Product.create name: "", price: "1#{i}" if i == 7
30
33
  next Product.create name: nil, price: "1#{i}" if i == 8
@@ -1,86 +1,99 @@
1
1
  require 'spec_helper_active_record'
2
2
 
3
- module Trailblazer
4
- class Finder
5
- module Adapters
6
- module ActiveRecord
7
- describe Sorting do
8
- def finder_class
9
- Class.new do
10
- include Trailblazer::Finder::Base
11
- include Trailblazer::Finder::Features::Sorting
12
- include Trailblazer::Finder::Adapters::ActiveRecord
13
-
14
- entity_type { Product.all }
15
-
16
- sortable_by :name, :price, :created_at
17
-
18
- filter_by :name
19
- filter_by :price
20
- filter_by(:category) { |entity_type, _| entity_type.joins(:category) }
21
- end
22
- end
23
-
24
- def finder_with_sort(sort = nil, filters = {})
25
- finder_class.new filter: { sort: sort }.merge(filters)
26
- end
27
-
28
- it 'can be inherited' do
29
- child_class = Class.new(finder_class)
30
- expect(child_class.new.sort_attribute).to eq 'name'
31
- end
32
-
33
- describe 'sorting' do
34
- before do
35
- Product.delete_all
36
- end
37
-
38
- after do
39
- Product.delete_all
40
- end
41
-
42
- it 'sorts results based on the sort option desc' do
43
- 5.times { |i| Product.create! price: i }
44
-
45
- finder = finder_with_sort 'price desc'
46
- expect(finder.results.map(&:price)).to eq [4, 3, 2, 1, 0]
47
- end
48
-
49
- it 'sorts results based on the sort option asc' do
50
- 5.times { |i| Product.create! price: i }
51
-
52
- finder = finder_with_sort 'price asc'
53
- expect(finder.results.map(&:price)).to eq [0, 1, 2, 3, 4]
54
- end
55
-
56
- it 'defaults to first sort by option' do
57
- 5.times { |i| Product.create! name: "Name#{i}" }
58
-
59
- finder = finder_with_sort
60
- expect(finder.results.map(&:name)).to eq %w[Name4 Name3 Name2 Name1 Name0]
61
- end
62
-
63
- it 'ignores invalid sort values' do
64
- finder = finder_with_sort 'invalid attribute'
65
- expect { finder.results.to_a }.not_to raise_error
66
- end
67
-
68
- it 'can handle renames of sorting in joins' do
69
- older_category = Category.create! title: 'older'
70
- newer_category = Category.create! title: 'newer'
71
-
72
- product_of_newer_category = Product.create! name: 'older product', category: newer_category
73
- product_of_older_category = Product.create! name: 'newer product', category: older_category
74
-
75
- finder = finder_with_sort 'created_at desc', category: ''
76
-
77
- expect(finder.results.map(&:name)).to eq [product_of_older_category.name, product_of_newer_category.name]
78
- end
79
- end
80
-
81
- it_behaves_like 'a sorting feature'
82
- end
3
+ describe "Trailblazer::Finder::Adapters::ActiveRecord::Sorting", :sorting do
4
+ after do
5
+ Product.delete_all
6
+ Product.reset_pk_sequence
7
+ end
8
+
9
+ before do
10
+ Product.delete_all
11
+ Product.reset_pk_sequence
12
+ end
13
+
14
+ class TestARFinder < Trailblazer::Finder
15
+ features Sorting
16
+ adapters ActiveRecord
17
+
18
+ entity_type { Product.all }
19
+
20
+ sortable_by :name, :price, :created_at
21
+
22
+ filter_by :name
23
+ filter_by :price
24
+ end
25
+
26
+ def finder_with_sort(sort = nil, filters = {})
27
+ TestARFinder.new filter: (sort.nil? ? {} : { sort: sort }).merge(filters)
28
+ end
29
+
30
+ describe 'sorting' do
31
+
32
+ it 'sorts results based on the sort option desc' do
33
+ 5.times { |i| Product.create! price: i }
34
+
35
+ finder = finder_with_sort 'price desc'
36
+ expect(finder.results.map(&:price)).to eq [4, 3, 2, 1, 0]
37
+ end
38
+
39
+ it 'sorts results based on the sort option asc' do
40
+ 5.times { |i| Product.create! price: i }
41
+
42
+ finder = finder_with_sort 'price asc'
43
+ expect(finder.results.map(&:price)).to eq [0, 1, 2, 3, 4]
44
+ end
45
+
46
+ it 'defaults to defaulted database sort if none is set' do
47
+ 5.times { |i| Product.create! name: "Name#{i}" }
48
+
49
+ finder = finder_with_sort
50
+ expect(finder.results.map(&:name)).to eq %w[Name0 Name1 Name2 Name3 Name4]
51
+ end
52
+
53
+ it 'ignores invalid sort values' do
54
+ finder = finder_with_sort 'invalid attribute'
55
+ expect { finder.results.to_a }.not_to raise_error
56
+ end
57
+
58
+ it 'can handle renames of sorting in joins' do
59
+ older_category = Category.create! title: 'older'
60
+ newer_category = Category.create! title: 'newer'
61
+
62
+ product_of_newer_category = Product.create! name: 'older product', category: newer_category
63
+ product_of_older_category = Product.create! name: 'newer product', category: older_category
64
+
65
+ finder = finder_with_sort 'created_at desc', category: ''
66
+
67
+ expect(finder.results.map(&:name)).to eq [product_of_older_category.name, product_of_newer_category.name]
68
+ end
69
+ end
70
+
71
+ describe 'sorting by multiple' do
72
+ before do
73
+ 5.times do |i|
74
+ Product.create name: "Name#{i}", price: "1#{i}"
83
75
  end
76
+ Product.create name: 'Name3', price: '8'
77
+ end
78
+
79
+ it 'sorts by multiple columns name asc and price asc' do
80
+ finder = finder_with_sort 'name asc, price asc'
81
+ expect(finder.results.map(&:name)).to eq %w[Name0 Name1 Name2 Name3 Name3 Name4]
82
+ expect(finder.results.map(&:price)).to eq [10, 11, 12, 8, 13, 14]
83
+ end
84
+
85
+ it 'sorts by multiple columns name asc and price desc' do
86
+ finder = finder_with_sort 'name asc, price desc'
87
+ expect(finder.results.map(&:name)).to eq %w[Name0 Name1 Name2 Name3 Name3 Name4]
88
+ expect(finder.results.map(&:price)).to eq [10, 11, 12, 13, 8, 14]
89
+ end
90
+
91
+ it 'sorts by multiple columns name desc and price desc' do
92
+ finder = finder_with_sort 'name desc, price desc'
93
+ expect(finder.results.map(&:name)).to eq %w[Name4 Name3 Name3 Name2 Name1 Name0]
94
+ expect(finder.results.map(&:price)).to eq [14, 13, 8, 12, 11, 10]
84
95
  end
85
96
  end
97
+
98
+ it_behaves_like 'a sorting feature'
86
99
  end