trailblazer-finder 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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