stub_solr 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +23 -0
- data/.rubocop.yml +4 -0
- data/README.md +50 -12
- data/lib/stub_solr/extra_dsl.rb +144 -0
- data/lib/stub_solr/search.rb +107 -0
- data/lib/stub_solr/search_helper.rb +168 -0
- data/lib/stub_solr/stub_session_proxy.rb +27 -0
- data/lib/stub_solr/version.rb +1 -1
- data/lib/stub_solr.rb +7 -2
- data/stub_solr.gemspec +1 -3
- metadata +9 -33
- data/lib/stub_solr/sunspot_rails_stub_session_proxy.rb +0 -363
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d7ccf1ca8364cbe78d549c7adee20fc57e6657b
|
4
|
+
data.tar.gz: e7373b1aad31088dd937d28cf6c7559acc1bc1fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c63a06b47829d41038bb6f581742ba546ea37aa3a0e12d5c9accadd8a371abae23b04c8c5daf860eb753ad30397b2c95d204404227d5c9e066bdeef5e60c6ab2
|
7
|
+
data.tar.gz: cd6bfc624908d13a46bdbc8d53c3f350f4823eed7c3a3e4ca5535e21a4d600a931fd66c30bf81fc189518e17612822fdaf79c239bf7e25493bf0ff87e9813536
|
data/.codeclimate.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
engines:
|
2
|
+
duplication:
|
3
|
+
enabled: true
|
4
|
+
config:
|
5
|
+
languages:
|
6
|
+
- ruby
|
7
|
+
fixme:
|
8
|
+
enabled: true
|
9
|
+
rubocop:
|
10
|
+
enabled: true
|
11
|
+
ratings:
|
12
|
+
paths:
|
13
|
+
- "**.js"
|
14
|
+
- "**.jsx"
|
15
|
+
- "**.module"
|
16
|
+
- "**.rb"
|
17
|
+
exclude_paths:
|
18
|
+
- test/
|
19
|
+
- Rakefile
|
20
|
+
- gemfiles/
|
21
|
+
- dev_tasks/
|
22
|
+
- bin/
|
23
|
+
- tmp/
|
data/.rubocop.yml
CHANGED
@@ -6,6 +6,7 @@ AllCops:
|
|
6
6
|
- gemfiels/*
|
7
7
|
- test/**/*
|
8
8
|
- tmp/**/*
|
9
|
+
- Rakefile
|
9
10
|
TargetRubyVersion: 2.3
|
10
11
|
|
11
12
|
Rails:
|
@@ -25,3 +26,6 @@ Style/EmptyLinesAroundClassBody:
|
|
25
26
|
|
26
27
|
Style/EmptyLinesAroundMethodBody:
|
27
28
|
Enabled: false
|
29
|
+
|
30
|
+
Style/FrozenStringLiteralComment:
|
31
|
+
Enabled: false
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
`Sunspot.session = Sunspot::Rails::StubSessionProxy.new(Sunspot.session)` is enough for most of cases but if you want more than `allow_any_instance_of(Sunspot::Rails::StubSessionProxy::Search).to receive(:results).and_return(myExpectedResults)` this gem can be helpful. `kaminari` for pagination and plain AR to mimic resutls.
|
7
7
|
|
8
|
-
this gem depends on `2.2.
|
8
|
+
this gem depends on `2.2.x` version of sunspot, sunspot_rails.
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
@@ -15,17 +15,49 @@ Add this line to your application's Gemfile:
|
|
15
15
|
gem 'stub_solr'
|
16
16
|
```
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
$ bundle
|
21
|
-
|
22
|
-
Or install it yourself as:
|
18
|
+
## Usage
|
23
19
|
|
24
|
-
|
20
|
+
`test_helper` file should have `require 'stub_solr'`. Given a sunspot search block as following codes,
|
25
21
|
|
26
|
-
|
22
|
+
```ruby
|
23
|
+
class ActivitiesController < ApplicationController
|
24
|
+
def search
|
25
|
+
...
|
26
|
+
search = Sunspot.search Excercise, Activity do
|
27
|
+
any_of do
|
28
|
+
with :blocked, false
|
29
|
+
without :exhausted, true
|
30
|
+
end
|
31
|
+
|
32
|
+
if min && max
|
33
|
+
any_of do
|
34
|
+
all_of do
|
35
|
+
with :start_limit_number, nil
|
36
|
+
with(:end_limit_number).less_than_or_equal_to max
|
37
|
+
end
|
38
|
+
all_of do
|
39
|
+
with :end_limit_number, nil
|
40
|
+
with(:start_limit_number).greater_than_or_equal_to min
|
41
|
+
end
|
42
|
+
all_of do
|
43
|
+
without(:start_limit_number, nil)
|
44
|
+
without(:end_limit_number, nil)
|
45
|
+
with(:start_limit_number).greater_than_or_equal_to min
|
46
|
+
with(:end_limit_number).less_than_or_equal_to max
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
## search by text
|
51
|
+
fulltext params[:search] if params[:search].present?
|
52
|
+
order_by :created_at, :asc
|
53
|
+
paginate page: page, per_page: per_page
|
54
|
+
end
|
55
|
+
...
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
27
59
|
|
28
|
-
|
60
|
+
You can write expected results like this. use `concat` for `any_of` block and `array_1 & array_2` for `all_of` block.
|
29
61
|
|
30
62
|
```ruby
|
31
63
|
class ActivitiesControllerTest < ActionDispatch::IntegrationTest
|
@@ -41,9 +73,15 @@ class ActivitiesControllerTest < ActionDispatch::IntegrationTest
|
|
41
73
|
super
|
42
74
|
end
|
43
75
|
|
44
|
-
test '
|
45
|
-
|
46
|
-
|
76
|
+
test 'filter with number range' do
|
77
|
+
pool_1 = Activity.where(blocked: false).to_a.concat(Activity.where(exhausted: false).to_a).uniq
|
78
|
+
target_1 = Activity.where("end_limit_number <= ? AND start_limit_number = ?", 18, nil).to_a
|
79
|
+
target_2 = Activity.where("start_limit_number >= ? AND end_limit_number = ?", 3, nil).to_a
|
80
|
+
target_3 = Activity.where("start_limit_number >= ?", 3).where("end_limit_number <= ?", 18).to_a
|
81
|
+
pool_2 = target_1.concat(target_2).concat(target_3)
|
82
|
+
expected = pool_1 & pool_2
|
83
|
+
|
84
|
+
get search_activities_url(params: { limits: [3, 18] })
|
47
85
|
res = ActiveSupport::JSON.decode(response.body)["activities"]
|
48
86
|
res.size.must_equal expected.size
|
49
87
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
class Sunspot::Rails::StubSessionProxy
|
2
|
+
# with and fulltext can be called multiple times
|
3
|
+
# based on the block. that's why i am mutating @results
|
4
|
+
#
|
5
|
+
# for all and any block, we need to consult
|
6
|
+
# sunspot/lib/sunspot/dsl/standard_query.rb
|
7
|
+
#
|
8
|
+
class Search
|
9
|
+
def hits
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
# Added to pass .group_by
|
14
|
+
# Idea is save grouped hash result seperately and then
|
15
|
+
# access it with [] method.
|
16
|
+
#
|
17
|
+
# initial_grouped_hits = initial.hits.group_by(&:class_name)
|
18
|
+
# initial_grouped_hits["Activity"]
|
19
|
+
def [](key)
|
20
|
+
return if @grouped_results.empty? || @grouped_results[key].nil?
|
21
|
+
@group_key = key
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# possible situation
|
26
|
+
#
|
27
|
+
# initial_grouped_hits = initial.hits.group_by(&:class_name)
|
28
|
+
# initial_grouped_hits["Activity"].map(&:primary_key)
|
29
|
+
def map(&_pr)
|
30
|
+
return unless @grouped_results[@group_key]
|
31
|
+
@grouped_results[@group_key].map(&:id)
|
32
|
+
end
|
33
|
+
|
34
|
+
# possible situation
|
35
|
+
#
|
36
|
+
# initial_grouped_hits = initial.hits.group_by(&:class_name)
|
37
|
+
def group_by(_ = nil)
|
38
|
+
return self if final_results.empty?
|
39
|
+
@types.each do |type|
|
40
|
+
@grouped_results[type.name] = sorted_temp_result.group_by do |i|
|
41
|
+
i.class.name == type.name
|
42
|
+
end[true]
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def phrase_fields(arg)
|
48
|
+
@operation_context.first[:textsearch_priorities] = arg
|
49
|
+
return self if @operation_context.first[:search_term].empty?
|
50
|
+
fulltext(@operation_context.first[:search_term])
|
51
|
+
end
|
52
|
+
|
53
|
+
def query_phrase_slop(_)
|
54
|
+
fulltext(@operation_context.first[:search_term])
|
55
|
+
end
|
56
|
+
|
57
|
+
# with default `StubSessionProxy`, the results is blank whatever
|
58
|
+
# argument or options you put in the search block
|
59
|
+
def fulltext(term, _opt = {}, &bl)
|
60
|
+
matches = []
|
61
|
+
@operation_context.first[:search_term] = term
|
62
|
+
Sunspot::Util::ContextBoundDelegate
|
63
|
+
.instance_eval_with_context(self, &bl) unless bl.nil?
|
64
|
+
# if there is phrase_fields option then we only search for the field
|
65
|
+
# to mimic the priority search. #string_text_fields
|
66
|
+
@types.each do |type|
|
67
|
+
string_text_fields(type).each do |field|
|
68
|
+
if type.has_attribute?(field.to_sym)
|
69
|
+
matches << type.where(type.arel_table[field.to_sym].
|
70
|
+
matches("%#{term}%")).to_a
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
calculated = (result_for_current_context & matches.flatten.uniq)
|
75
|
+
operation_context_result(calculated)
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def order_by(attribute, direction)
|
80
|
+
@operation_context.first[:order_key] = attribute
|
81
|
+
@operation_context.first[:order_direction] = direction
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def in_radius(*args)
|
86
|
+
calculated = result_for_current_context.select do |r|
|
87
|
+
distance = r&.location&.distance_from([args[0], args[1]])
|
88
|
+
distance && distance < args[2].to_i
|
89
|
+
end
|
90
|
+
operation_context_result(calculated)
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
def greater_than(time)
|
95
|
+
calculated = filter(result_for_current_context) do |x|
|
96
|
+
x.read_attribute(@range_search_field) > time
|
97
|
+
end
|
98
|
+
operation_context_result(calculated)
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
def greater_than_or_equal_to(time)
|
103
|
+
calculated = filter(result_for_current_context) do |x|
|
104
|
+
x.read_attribute(@range_search_field) >= time
|
105
|
+
end
|
106
|
+
operation_context_result(calculated)
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def less_than(time)
|
111
|
+
calculated = filter(result_for_current_context) do |x|
|
112
|
+
x.read_attribute(@range_search_field) < time
|
113
|
+
end
|
114
|
+
operation_context_result(calculated)
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
def less_than_or_equal_to(time)
|
119
|
+
calculated = filter(result_for_current_context) do |x|
|
120
|
+
x.read_attribute(@range_search_field) <= time
|
121
|
+
end
|
122
|
+
operation_context_result(calculated)
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
def paginate(args = {})
|
127
|
+
@operation_context.first[:page] = args[:page]
|
128
|
+
@operation_context.first[:per_page] = args[:page]
|
129
|
+
self
|
130
|
+
end
|
131
|
+
|
132
|
+
def total
|
133
|
+
@operation_context.last[:result].size
|
134
|
+
end
|
135
|
+
|
136
|
+
def filter(array)
|
137
|
+
array.select do |i|
|
138
|
+
i.read_attribute(@range_search_field) &&
|
139
|
+
!i.read_attribute(@range_search_field).nil? &&
|
140
|
+
yield(i)
|
141
|
+
end.uniq
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require File.expand_path('search_helper', File.dirname(__FILE__))
|
2
|
+
require File.expand_path('extra_dsl', File.dirname(__FILE__))
|
3
|
+
class Sunspot::Rails::StubSessionProxy
|
4
|
+
# with and fulltext can be called multiple times
|
5
|
+
# based on the block. that's why i am mutating @results
|
6
|
+
#
|
7
|
+
# for all and any block, we need to consult
|
8
|
+
# sunspot/lib/sunspot/dsl/standard_query.rb
|
9
|
+
#
|
10
|
+
class Search
|
11
|
+
attr_reader :block, :types, :page, :per_page, :grouped_results
|
12
|
+
|
13
|
+
def initialize(results = [], types = [], &block)
|
14
|
+
@types = types
|
15
|
+
@grouped_results= {}
|
16
|
+
@group_key = ''
|
17
|
+
@range_search_field = nil
|
18
|
+
@operation_context = [{
|
19
|
+
operation: 'all', result: results, order_key: nil, order_direction: nil,
|
20
|
+
page: 1, per_page: 30, search_term: '', textsearch_priorities: {}
|
21
|
+
}]
|
22
|
+
@current_index = 0
|
23
|
+
run_proc_block(&block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def final_results
|
27
|
+
@operation_context.last[:result]
|
28
|
+
end
|
29
|
+
|
30
|
+
def results
|
31
|
+
return PaginatedCollection.new if sorted_temp_result.empty?
|
32
|
+
if final_results.is_a? Kaminari::PaginatableArray
|
33
|
+
return sorted_temp_result
|
34
|
+
end
|
35
|
+
Kaminari.paginate_array(sorted_temp_result).
|
36
|
+
page(@operation_context.first[:page]).
|
37
|
+
per(@operation_context.first[:per_page])
|
38
|
+
end
|
39
|
+
|
40
|
+
def any_of(&bl)
|
41
|
+
previous_result = result_for_current_context
|
42
|
+
push(previous_result, :any)
|
43
|
+
Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl)
|
44
|
+
after_results = temp_result_for_current_context
|
45
|
+
pop unless @current_index == 0
|
46
|
+
# parent is now current context
|
47
|
+
if parent_temp_result
|
48
|
+
@operation_context[@current_index][:temp_result] =
|
49
|
+
parent_temp_result.concat(after_results)
|
50
|
+
else
|
51
|
+
@operation_context[@current_index][:result] =
|
52
|
+
after_results
|
53
|
+
end
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_of(&bl)
|
58
|
+
previous_result = result_for_current_context
|
59
|
+
push(previous_result, :all)
|
60
|
+
Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl)
|
61
|
+
after_results = result_for_current_context
|
62
|
+
parent_temp_result =
|
63
|
+
@operation_context[@current_index - 1][:temp_result]
|
64
|
+
pop unless @current_index == 0
|
65
|
+
# parent is now current context
|
66
|
+
if parent_temp_result
|
67
|
+
@operation_context[@current_index][:temp_result] =
|
68
|
+
parent_temp_result.concat(after_results)
|
69
|
+
else
|
70
|
+
@operation_context[@current_index][:result] =
|
71
|
+
(previous_result & after_results).uniq
|
72
|
+
end
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def with(att, val = 'no_value')
|
77
|
+
# return_early_for_with_blocks(att, val, :with)
|
78
|
+
return self if range_no_value?(att, val) && skip_it(att, val)
|
79
|
+
return self if range_value_nil?(val, att) && deal_with_nil(att, :with)
|
80
|
+
return self if val == 'no_value' || att == :location
|
81
|
+
return self if (att == :search_class) && filter_class
|
82
|
+
matches = if val.class.name == 'Array'
|
83
|
+
select_result_for_array_val(att)
|
84
|
+
elsif @types.map{ |x| x.has_attribute?(att)}.include?(true)
|
85
|
+
select_result_attr_is_val(att, val)
|
86
|
+
else
|
87
|
+
result_for_current_context.select { |x| x.send(att) == val }
|
88
|
+
end
|
89
|
+
operation_context_result(matches)
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
def without(att, val = 'no_value')
|
94
|
+
# return_early_for_with_blocks(att, val, :without)
|
95
|
+
return self if range_no_value?(att, val) && skip_it(att, val)
|
96
|
+
return self if range_value_nil?(val, att) && deal_with_nil(att, :without)
|
97
|
+
return self if val == 'no_value' || att == :location
|
98
|
+
matches = if val.class.name == 'Array'
|
99
|
+
select_result_attr_cant_send_val(att, val)
|
100
|
+
else
|
101
|
+
select_result_attr_is_not_value(att, val)
|
102
|
+
end
|
103
|
+
operation_context_result(matches)
|
104
|
+
self
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# serach helper
|
2
|
+
|
3
|
+
class Sunspot::Rails::StubSessionProxy
|
4
|
+
# with and fulltext can be called multiple times
|
5
|
+
# based on the block. that's why i am mutating @results
|
6
|
+
#
|
7
|
+
# for all and any block, we need to consult
|
8
|
+
# sunspot/lib/sunspot/dsl/standard_query.rb
|
9
|
+
#
|
10
|
+
class Search
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def instance_eval_with_context(&block)
|
15
|
+
Sunspot::Util::ContextBoundDelegate.
|
16
|
+
instance_eval_with_context(self, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
# iterate the given block and change @operation_context
|
20
|
+
# block may cotain with, without, fulltext, paginate, in_raius, order_by
|
21
|
+
def run_proc_block(&block)
|
22
|
+
result = @operation_context.last[:result]
|
23
|
+
return PaginatedCollection.new if result.empty?
|
24
|
+
instance_eval_with_context(&block)
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def current_operation
|
29
|
+
@operation_context[@current_index][:operation]
|
30
|
+
end
|
31
|
+
|
32
|
+
def result_for_current_context
|
33
|
+
@operation_context[@current_index][:result]
|
34
|
+
end
|
35
|
+
|
36
|
+
def temp_result_for_current_context
|
37
|
+
@operation_context[@current_index][:temp_result]
|
38
|
+
end
|
39
|
+
|
40
|
+
def parent_temp_result
|
41
|
+
@operation_context[@current_index - 1][:temp_result]
|
42
|
+
end
|
43
|
+
|
44
|
+
def operation_context_result(after_results)
|
45
|
+
previous_result = result_for_current_context
|
46
|
+
unless current_operation == 'any'
|
47
|
+
return @operation_context[@current_index][:result] =
|
48
|
+
(previous_result & after_results).uniq
|
49
|
+
end
|
50
|
+
@operation_context[@current_index][:result] =
|
51
|
+
previous_result.concat(after_results).uniq
|
52
|
+
if temp_result_for_current_context
|
53
|
+
@operation_context[@current_index][:temp_result] =
|
54
|
+
temp_result_for_current_context.concat(after_results)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def pop
|
59
|
+
@current_index -= 1
|
60
|
+
@operation_context.pop
|
61
|
+
end
|
62
|
+
|
63
|
+
def push(data, type)
|
64
|
+
if type == :any
|
65
|
+
@operation_context.push(operation: 'any', result: data, temp_result: [])
|
66
|
+
else
|
67
|
+
@operation_context.push(operation: 'all', result: data)
|
68
|
+
end
|
69
|
+
@current_index += 1
|
70
|
+
end
|
71
|
+
|
72
|
+
def filter_class
|
73
|
+
matches = result_for_current_context.select { |x| x.class.name == value }
|
74
|
+
operation_context_result(matches)
|
75
|
+
end
|
76
|
+
|
77
|
+
def range_value_nil?(value, attribute)
|
78
|
+
value.nil? && attribute_is_about_range?(attribute)
|
79
|
+
end
|
80
|
+
|
81
|
+
def range_no_value?(attribute, value)
|
82
|
+
(value == 'no_value') && attribute_is_about_range?(attribute)
|
83
|
+
end
|
84
|
+
|
85
|
+
def skip_it(attribute, value)
|
86
|
+
matches = result_for_current_context.select do |x|
|
87
|
+
x.has_attribute?(attribute)
|
88
|
+
end
|
89
|
+
operation_context_result(matches)
|
90
|
+
end
|
91
|
+
|
92
|
+
def deal_with_nil(attribute, operation)
|
93
|
+
matches = if operation == :with
|
94
|
+
selelct_result_for_nil(attribute)
|
95
|
+
else
|
96
|
+
select_result_for_not_nil(attribute)
|
97
|
+
end
|
98
|
+
operation_context_result(matches)
|
99
|
+
end
|
100
|
+
|
101
|
+
def string_text_fields(type)
|
102
|
+
pr = @operation_context.first[:textsearch_priorities]
|
103
|
+
return pr.keys unless pr.keys.empty?
|
104
|
+
type.columns_hash.select { |k,v| [:string, :text].include? v.type }.keys
|
105
|
+
end
|
106
|
+
|
107
|
+
def attribute_is_about_range?(attribute)
|
108
|
+
matched = nil
|
109
|
+
Sunspot.session.range_fields.each do |x|
|
110
|
+
if attribute.to_s.include? x
|
111
|
+
matched = true
|
112
|
+
@range_search_field = attribute
|
113
|
+
next
|
114
|
+
end
|
115
|
+
end
|
116
|
+
matched
|
117
|
+
end
|
118
|
+
|
119
|
+
def sorted_temp_result
|
120
|
+
key = @operation_context.first[:order_key]
|
121
|
+
direction = @operation_context.first[:order_direction]
|
122
|
+
return final_results unless direction
|
123
|
+
asc = final_results.sort_by { |x| x[key] }
|
124
|
+
return asc if direction.to_s.downcase.include?('asc')
|
125
|
+
asc.reverse
|
126
|
+
end
|
127
|
+
|
128
|
+
def selelct_result_for_nil(attribute)
|
129
|
+
result_for_current_context.select do |x|
|
130
|
+
x.has_attribute?(attribute) &&
|
131
|
+
x.read_attribute(attribute).nil?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def select_result_for_not_nil(attribute)
|
136
|
+
result_for_current_context.select do |x|
|
137
|
+
x.has_attribute?(attribute) &&
|
138
|
+
!x.read_attribute(attribute).nil?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def select_result_for_array_val(attribute)
|
143
|
+
result_for_current_context.select do |x|
|
144
|
+
value.include?(x.send(attribute))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def select_result_attr_is_val(attribute, value)
|
149
|
+
result_for_current_context.select do |x|
|
150
|
+
x.read_attribute(attribute) == value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def select_result_attr_cant_send_val(attribute, value)
|
155
|
+
result_for_current_context.select do |x|
|
156
|
+
x.has_attribute?(attribute) && !value.include?(x.send(attribute))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def select_result_attr_is_not_value(attribute, value)
|
161
|
+
result_for_current_context.select do |x|
|
162
|
+
x.has_attribute?(attribute) &&
|
163
|
+
!x.read_attribute(attribute).nil? &&
|
164
|
+
(x.read_attribute(attribute) != value)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path('search', File.dirname(__FILE__))
|
2
|
+
# stub solr with "dara" array for testing.
|
3
|
+
# if there is no data then it will behave like default blank search
|
4
|
+
# this stub uses Kaminari for pagination
|
5
|
+
# once the data is given, solr search block will call search
|
6
|
+
# and that will create a new Search with arguments.
|
7
|
+
# With the search, it passes the given data as @operation_context.
|
8
|
+
# And "run_proc_block" method will go through each search conditions
|
9
|
+
# in the block. while going through the block, it will change @operation_context
|
10
|
+
#
|
11
|
+
class Sunspot::Rails::StubSessionProxy
|
12
|
+
attr_reader :block, :data, :range_fields
|
13
|
+
|
14
|
+
def initialize(original_session, data = [])
|
15
|
+
@original_session = original_session
|
16
|
+
@data = data
|
17
|
+
@range_fields = %w[time month created_at start end]
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_range_fields(arr)
|
21
|
+
@range_fields = arr
|
22
|
+
end
|
23
|
+
|
24
|
+
def search(*types, &block)
|
25
|
+
Search.new(@data, types, &block)
|
26
|
+
end
|
27
|
+
end
|
data/lib/stub_solr/version.rb
CHANGED
data/lib/stub_solr.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
+
## requires all necessary fiels
|
1
2
|
require 'kaminari'
|
3
|
+
##
|
4
|
+
# previous_page method doesn't exist in Kaminari
|
5
|
+
# but we use it for pagination
|
2
6
|
module Kaminari::PageScopeMethods
|
3
7
|
alias_method :previous_page, :prev_page
|
4
8
|
end
|
5
9
|
require 'sunspot'
|
6
10
|
require 'sunspot_rails'
|
7
11
|
require 'rails'
|
8
|
-
require
|
9
|
-
require File.expand_path('stub_solr/
|
12
|
+
require 'stub_solr/version'
|
13
|
+
require File.expand_path('stub_solr/stub_session_proxy',
|
14
|
+
File.dirname(__FILE__))
|
data/stub_solr.gemspec
CHANGED
@@ -20,9 +20,7 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
21
21
|
|
22
22
|
gem.add_dependency 'kaminari', '~> 0.17.0'
|
23
|
-
gem.add_dependency '
|
24
|
-
gem.add_dependency 'sunspot_solr', '~> 2.2.6'
|
25
|
-
gem.add_dependency 'sunspot_rails', '~> 2.2.6'
|
23
|
+
gem.add_dependency 'sunspot_rails', '~> 2.2.5'
|
26
24
|
gem.add_dependency 'rails', '>= 4'
|
27
25
|
|
28
26
|
# gem.add_development_dependency 'simplecov', '~> 0.12.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stub_solr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jaigouk Kim
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-09-
|
11
|
+
date: 2016-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kaminari
|
@@ -24,48 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.17.0
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: sunspot
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 2.2.6
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 2.2.6
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: sunspot_solr
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 2.2.6
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 2.2.6
|
55
27
|
- !ruby/object:Gem::Dependency
|
56
28
|
name: sunspot_rails
|
57
29
|
requirement: !ruby/object:Gem::Requirement
|
58
30
|
requirements:
|
59
31
|
- - "~>"
|
60
32
|
- !ruby/object:Gem::Version
|
61
|
-
version: 2.2.
|
33
|
+
version: 2.2.5
|
62
34
|
type: :runtime
|
63
35
|
prerelease: false
|
64
36
|
version_requirements: !ruby/object:Gem::Requirement
|
65
37
|
requirements:
|
66
38
|
- - "~>"
|
67
39
|
- !ruby/object:Gem::Version
|
68
|
-
version: 2.2.
|
40
|
+
version: 2.2.5
|
69
41
|
- !ruby/object:Gem::Dependency
|
70
42
|
name: rails
|
71
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,6 +101,7 @@ executables: []
|
|
129
101
|
extensions: []
|
130
102
|
extra_rdoc_files: []
|
131
103
|
files:
|
104
|
+
- ".codeclimate.yml"
|
132
105
|
- ".gitignore"
|
133
106
|
- ".rubocop.yml"
|
134
107
|
- Gemfile
|
@@ -141,7 +114,10 @@ files:
|
|
141
114
|
- gemfiles/.bundle/config
|
142
115
|
- gemfiles/rails-5.0.0.1
|
143
116
|
- lib/stub_solr.rb
|
144
|
-
- lib/stub_solr/
|
117
|
+
- lib/stub_solr/extra_dsl.rb
|
118
|
+
- lib/stub_solr/search.rb
|
119
|
+
- lib/stub_solr/search_helper.rb
|
120
|
+
- lib/stub_solr/stub_session_proxy.rb
|
145
121
|
- lib/stub_solr/version.rb
|
146
122
|
- stub_solr.gemspec
|
147
123
|
homepage: https://github.com/jaigouk/stub_solr
|
@@ -1,363 +0,0 @@
|
|
1
|
-
|
2
|
-
# stub solr with "dara" array for testing.
|
3
|
-
# if there is no data then it will behave like default blank search
|
4
|
-
# this stub uses Kaminari for pagination
|
5
|
-
# once the data is given, solr search block will call search
|
6
|
-
# and that will create a new Search with arguments.
|
7
|
-
# With the search, it passes the given data as @operation_context.
|
8
|
-
# And "run_proc_block" method will go through each search conditions
|
9
|
-
# in the block. while going through the block, it will change @operation_context
|
10
|
-
#
|
11
|
-
class Sunspot::Rails::StubSessionProxy
|
12
|
-
attr_reader :block, :data, :range_fields
|
13
|
-
|
14
|
-
def initialize(original_session, data = [])
|
15
|
-
@original_session = original_session
|
16
|
-
@data = data
|
17
|
-
@range_fields = %w[time month created_at start end]
|
18
|
-
end
|
19
|
-
|
20
|
-
def set_range_fields(arr)
|
21
|
-
@range_fields = arr
|
22
|
-
end
|
23
|
-
|
24
|
-
def search(*types, &block)
|
25
|
-
Search.new(@data, types, &block)
|
26
|
-
end
|
27
|
-
|
28
|
-
# with and fulltext can be called multiple times
|
29
|
-
# based on the block. that's why i am mutating @results
|
30
|
-
#
|
31
|
-
# for all and any block, we need to consult sunspot/lib/sunspot/dsl/standard_query.rb
|
32
|
-
#
|
33
|
-
class Search
|
34
|
-
attr_reader :block, :types, :page, :per_page, :grouped_results
|
35
|
-
def initialize(results = [], types = [], &block)
|
36
|
-
@facets = {}
|
37
|
-
@search_data = results
|
38
|
-
@block = block
|
39
|
-
@types = types
|
40
|
-
@page = 1
|
41
|
-
@per_page = 30
|
42
|
-
@grouped_results = {}
|
43
|
-
@group_key = ""
|
44
|
-
@range_search_field = nil
|
45
|
-
@operation_context = [{operation: "all", result: @search_data}]
|
46
|
-
@current_context_index = 0
|
47
|
-
@order_key = nil
|
48
|
-
@order_direction = nil
|
49
|
-
@textsearch_priorities = {}
|
50
|
-
@search_term = ""
|
51
|
-
run_proc_block
|
52
|
-
end
|
53
|
-
|
54
|
-
def final_results
|
55
|
-
@operation_context.last[:result]
|
56
|
-
end
|
57
|
-
|
58
|
-
def results
|
59
|
-
return PaginatedCollection.new if sorted_temp_result.empty?
|
60
|
-
return sorted_temp_result if final_results.is_a? Kaminari::PaginatableArray
|
61
|
-
Kaminari.paginate_array(sorted_temp_result).page(@page).per(@per_page)
|
62
|
-
end
|
63
|
-
|
64
|
-
def any_of(&bl)
|
65
|
-
previous_result = result_for_current_context
|
66
|
-
@operation_context.push({operation: "any", result: previous_result, temp_result: []})
|
67
|
-
@current_context_index += 1
|
68
|
-
|
69
|
-
Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl)
|
70
|
-
after_results = @operation_context[@current_context_index][:temp_result]
|
71
|
-
|
72
|
-
parent_temp_result = @operation_context[@current_context_index - 1][:temp_result]
|
73
|
-
|
74
|
-
|
75
|
-
unless @current_context_index == 0
|
76
|
-
@current_context_index -= 1
|
77
|
-
@operation_context.pop
|
78
|
-
end
|
79
|
-
# parent is now current context
|
80
|
-
if parent_temp_result
|
81
|
-
@operation_context[@current_context_index][:temp_result] = parent_temp_result.concat(after_results)
|
82
|
-
else
|
83
|
-
@operation_context[@current_context_index][:result] = after_results
|
84
|
-
end
|
85
|
-
self
|
86
|
-
end
|
87
|
-
|
88
|
-
def all_of(&bl)
|
89
|
-
previous_result = result_for_current_context
|
90
|
-
@operation_context.push({operation: "all", result: previous_result})
|
91
|
-
@current_context_index += 1
|
92
|
-
|
93
|
-
Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl)
|
94
|
-
after_results = result_for_current_context
|
95
|
-
parent_temp_result = @operation_context[@current_context_index - 1][:temp_result]
|
96
|
-
|
97
|
-
unless @current_context_index == 0
|
98
|
-
@current_context_index -= 1
|
99
|
-
@operation_context.pop
|
100
|
-
end
|
101
|
-
|
102
|
-
# parent is now current context
|
103
|
-
if parent_temp_result
|
104
|
-
@operation_context[@current_context_index][:temp_result] = parent_temp_result.concat(after_results)
|
105
|
-
else
|
106
|
-
@operation_context[@current_context_index][:result] = (previous_result & after_results).uniq
|
107
|
-
end
|
108
|
-
|
109
|
-
self
|
110
|
-
end
|
111
|
-
|
112
|
-
def with(attribute, value = "no_value")
|
113
|
-
skip_no_value(attribute, value)
|
114
|
-
|
115
|
-
if attribute == :search_class
|
116
|
-
matches = result_for_current_context.select {|x| x.class.name == value }
|
117
|
-
operation_context_result(matches)
|
118
|
-
return self
|
119
|
-
end
|
120
|
-
|
121
|
-
if value == nil && attribute_is_about_range?(attribute)
|
122
|
-
matches = result_for_current_context.select {|x| x.has_attribute?(attribute) && (x.read_attribute(attribute) == value)}
|
123
|
-
operation_context_result(matches)
|
124
|
-
return self
|
125
|
-
end
|
126
|
-
|
127
|
-
return self if value == "no_value" || attribute == :location
|
128
|
-
|
129
|
-
matches = if value.class.name == "Array"
|
130
|
-
result_for_current_context.select {|x| value.include?(x.send(attribute)) }
|
131
|
-
elsif @types.map{|x| x.has_attribute?(attribute)}.include?(true)
|
132
|
-
result_for_current_context.select {|x| x.read_attribute(attribute) == value }
|
133
|
-
else
|
134
|
-
result_for_current_context.select {|x| x.send(attribute) == value }
|
135
|
-
end
|
136
|
-
operation_context_result(matches)
|
137
|
-
self
|
138
|
-
end
|
139
|
-
|
140
|
-
def without(attribute, value = "no_value")
|
141
|
-
skip_no_value(attribute, value)
|
142
|
-
if value == nil && attribute_is_about_range?(attribute)
|
143
|
-
matches = result_for_current_context.select {|x| x.has_attribute?(attribute) && !(x.read_attribute(attribute) == value)}
|
144
|
-
operation_context_result(matches)
|
145
|
-
return self
|
146
|
-
end
|
147
|
-
|
148
|
-
return self if value == "no_value" || attribute == :location
|
149
|
-
matches = if value.class.name == "Array"
|
150
|
-
result_for_current_context.select {|x| x.has_attribute?(attribute) && !value.include?(x.send(attribute)) }
|
151
|
-
else
|
152
|
-
result_for_current_context.select {|x| x.has_attribute?(attribute) && !x.read_attribute(attribute).nil? && (x.read_attribute(attribute) != value) }
|
153
|
-
end
|
154
|
-
operation_context_result(matches)
|
155
|
-
self
|
156
|
-
end
|
157
|
-
|
158
|
-
def hits
|
159
|
-
self
|
160
|
-
end
|
161
|
-
|
162
|
-
# Added to pass .group_by
|
163
|
-
# Idea is save grouped hash result seperately and then
|
164
|
-
# access it with [] method.
|
165
|
-
#
|
166
|
-
# initial_grouped_hits = initial.hits.group_by(&:class_name)
|
167
|
-
# initial_grouped_hits["Activity"]
|
168
|
-
def [](key)
|
169
|
-
return if @grouped_results.empty? || @grouped_results[key].nil?
|
170
|
-
@group_key = key
|
171
|
-
self
|
172
|
-
end
|
173
|
-
|
174
|
-
# possible situation
|
175
|
-
#
|
176
|
-
# initial_grouped_hits = initial.hits.group_by(&:class_name)
|
177
|
-
# initial_grouped_hits["Activity"].map(&:primary_key)
|
178
|
-
def map(&pr)
|
179
|
-
return unless @grouped_results[@group_key]
|
180
|
-
@grouped_results[@group_key].map{|x| x.id}
|
181
|
-
end
|
182
|
-
|
183
|
-
# possible situation
|
184
|
-
#
|
185
|
-
# initial_grouped_hits = initial.hits.group_by(&:class_name)
|
186
|
-
def group_by(class_name = nil)
|
187
|
-
return self if final_results.empty?
|
188
|
-
@types.each do |type|
|
189
|
-
@grouped_results[type.name] = sorted_temp_result.group_by{|i| i.class.name == type.name }[true]
|
190
|
-
end
|
191
|
-
self
|
192
|
-
end
|
193
|
-
|
194
|
-
def phrase_fields(arg)
|
195
|
-
@textsearch_priorities = arg
|
196
|
-
return self if @search_term.empty?
|
197
|
-
fulltext(@search_term)
|
198
|
-
end
|
199
|
-
|
200
|
-
def query_phrase_slop(*args)
|
201
|
-
fulltext(@search_term)
|
202
|
-
end
|
203
|
-
|
204
|
-
# with default `StubSessionProxy`, the results is blank whatever
|
205
|
-
# argument or options you put in the search block
|
206
|
-
def fulltext(term, opt={}, &bl)
|
207
|
-
matches = []
|
208
|
-
@search_term = term
|
209
|
-
Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl) if !bl.nil?
|
210
|
-
# if there is phrase_fields option then we only search for the field
|
211
|
-
# to mimic the priority search. #string_text_fields
|
212
|
-
@types.each do |type|
|
213
|
-
string_text_fields(type).each do |field|
|
214
|
-
if type.has_attribute?(field.to_sym)
|
215
|
-
matches << type.where(type.arel_table[field.to_sym].matches("%#{term}%")).to_a
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
calculated = (result_for_current_context & matches.flatten.uniq)
|
220
|
-
operation_context_result(calculated)
|
221
|
-
self
|
222
|
-
end
|
223
|
-
|
224
|
-
def order_by(attribute, direction)
|
225
|
-
@order_key = attribute
|
226
|
-
@order_direction = direction
|
227
|
-
self
|
228
|
-
end
|
229
|
-
|
230
|
-
def in_radius(*args)
|
231
|
-
calculated = result_for_current_context.select do |r|
|
232
|
-
distance = r&.location&.distance_from([args[0], args[1]])
|
233
|
-
distance && distance < args[2].to_i
|
234
|
-
end
|
235
|
-
operation_context_result(calculated)
|
236
|
-
self
|
237
|
-
end
|
238
|
-
|
239
|
-
def greater_than(time)
|
240
|
-
calculated = result_for_current_context.select do |x|
|
241
|
-
has_attribute_and_not_nil(x) &&
|
242
|
-
x.read_attribute(@range_search_field) > time
|
243
|
-
end.uniq
|
244
|
-
operation_context_result(calculated)
|
245
|
-
self
|
246
|
-
end
|
247
|
-
|
248
|
-
def greater_than_or_equal_to(time)
|
249
|
-
calculated = result_for_current_context.select do |x|
|
250
|
-
has_attribute_and_not_nil(x) &&
|
251
|
-
x.read_attribute(@range_search_field) >= time
|
252
|
-
end.uniq
|
253
|
-
operation_context_result(calculated)
|
254
|
-
self
|
255
|
-
end
|
256
|
-
|
257
|
-
def less_than(time)
|
258
|
-
calculated = result_for_current_context.select do |x|
|
259
|
-
has_attribute_and_not_nil(x) &&
|
260
|
-
x.read_attribute(@range_search_field) < time
|
261
|
-
end.uniq
|
262
|
-
operation_context_result(calculated)
|
263
|
-
self
|
264
|
-
end
|
265
|
-
|
266
|
-
def less_than_or_equal_to(time)
|
267
|
-
calculated = result_for_current_context.select do |x|
|
268
|
-
has_attribute_and_not_nil(x) &&
|
269
|
-
x.read_attribute(@range_search_field) <= time
|
270
|
-
end.uniq
|
271
|
-
operation_context_result(calculated)
|
272
|
-
self
|
273
|
-
end
|
274
|
-
|
275
|
-
def paginate(args={})
|
276
|
-
@page = args[:page]
|
277
|
-
@per_page = args[:per_page]
|
278
|
-
self
|
279
|
-
end
|
280
|
-
|
281
|
-
def total
|
282
|
-
@operation_context.last[:result].size
|
283
|
-
end
|
284
|
-
|
285
|
-
private
|
286
|
-
|
287
|
-
# iterate the given block and change @operation_context
|
288
|
-
# block may cotain with, without, fulltext, paginate, in_raius, order_by
|
289
|
-
def run_proc_block
|
290
|
-
result = @operation_context.last[:result]
|
291
|
-
return PaginatedCollection.new if result.empty?
|
292
|
-
|
293
|
-
Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &@block)
|
294
|
-
# @operation_context.last[:result] = @operation_context.last[:result].uniq
|
295
|
-
self
|
296
|
-
end
|
297
|
-
|
298
|
-
def current_operation
|
299
|
-
@operation_context[@current_context_index][:operation]
|
300
|
-
end
|
301
|
-
|
302
|
-
def result_for_current_context
|
303
|
-
@operation_context[@current_context_index][:result]
|
304
|
-
end
|
305
|
-
|
306
|
-
def temp_result_for_current_context
|
307
|
-
@operation_context[@current_context_index][:temp_result]
|
308
|
-
end
|
309
|
-
|
310
|
-
def operation_context_result(after_results)
|
311
|
-
previous_result = result_for_current_context
|
312
|
-
# parent_result = @operation_context[@current_context_index -1][:result]
|
313
|
-
temp_result = @operation_context[@current_context_index][:temp_result]
|
314
|
-
if current_operation == "any"
|
315
|
-
@operation_context[@current_context_index][:result] = previous_result.concat(after_results).uniq
|
316
|
-
if temp_result
|
317
|
-
@operation_context[@current_context_index][:temp_result] = temp_result.concat(after_results)
|
318
|
-
end
|
319
|
-
else
|
320
|
-
@operation_context[@current_context_index][:result] = (previous_result & after_results).uniq
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
def skip_no_value(attribute, value)
|
325
|
-
if value == "no_value" && attribute_is_about_range?(attribute)
|
326
|
-
matches = result_for_current_context.select {|x| x.has_attribute?(attribute)}
|
327
|
-
operation_context_result(matches)
|
328
|
-
return self
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
def has_attribute_and_not_nil(x)
|
333
|
-
x.read_attribute(@range_search_field) &&
|
334
|
-
!x.read_attribute(@range_search_field).nil?
|
335
|
-
end
|
336
|
-
|
337
|
-
def string_text_fields(type)
|
338
|
-
return @textsearch_priorities.keys unless @textsearch_priorities.keys.empty?
|
339
|
-
type.columns.collect { |c| {"#{c.type}":"#{c.name}"} }
|
340
|
-
.map{|hash| hash.select{|k,v| [:string, :text].include? k} }.uniq
|
341
|
-
.map{|x| x.values}.flatten
|
342
|
-
end
|
343
|
-
|
344
|
-
def attribute_is_about_range?(attribute)
|
345
|
-
matched = nil
|
346
|
-
Sunspot.session.range_fields.each do |x|
|
347
|
-
if attribute.to_s.include? x
|
348
|
-
matched = true
|
349
|
-
@range_search_field = attribute
|
350
|
-
break
|
351
|
-
end
|
352
|
-
end
|
353
|
-
matched
|
354
|
-
end
|
355
|
-
|
356
|
-
def sorted_temp_result
|
357
|
-
return final_results unless @order_direction
|
358
|
-
asc = final_results.sort_by{|x| x[@order_key]}
|
359
|
-
return asc if @order_direction.to_s.downcase.include?("asc")
|
360
|
-
asc.reverse
|
361
|
-
end
|
362
|
-
end
|
363
|
-
end
|