brainstem 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/Gemfile.lock +37 -32
- data/README.md +15 -0
- data/brainstem.gemspec +1 -0
- data/lib/brainstem/presenter_collection.rb +18 -6
- data/lib/brainstem/query_strategies/base_strategy.rb +33 -6
- data/lib/brainstem/query_strategies/filter_and_search.rb +38 -17
- data/lib/brainstem/query_strategies/filter_or_search.rb +1 -23
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/presenter_collection_spec.rb +67 -8
- data/spec/brainstem/query_strategies/filter_and_search_spec.rb +136 -15
- data/spec/brainstem/query_strategies/filter_or_search_spec.rb +2 -0
- data/spec/shared/base_strategy.rb +36 -0
- data/spec/spec_helper.rb +1 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9bf2b0cdf9ecd18e7a90f8484238fe6e7f63ad43
|
4
|
+
data.tar.gz: 7be99f891a90e26715f553fcf0971931bf51c404
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 138e7db36bf3692bde7fac6c61777ea8695195d32769dec30555bc2d4b5124679dd8f22e08624503ba3148e64c058bcad31f72b24842f2bf864bb4291ca8e052
|
7
|
+
data.tar.gz: 37760c56b41a4fbf8dbb5e19b543229c878e61fd88a7a8400f961140565b70c9b738b1882b7aeee1dc3c41e651695e02ab43ed95efd1483a80eafbdf38abf50c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
+ **1.1.0 - _12/18/2017_
|
4
|
+
- Add `meta` key to API responses which includes `page_number`, `page_count`, and `page_size` keys.
|
5
|
+
|
3
6
|
+ **1.0.0 - _07/20/2017_
|
4
7
|
- Add the capability to generate the documentation extracted from your properly annotated
|
5
8
|
presenters and controllers using `bundle exec brainstem generate [ARGS]`.
|
data/Gemfile.lock
CHANGED
@@ -1,60 +1,64 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
brainstem (1.
|
4
|
+
brainstem (1.1.0)
|
5
5
|
activerecord (>= 4.1)
|
6
6
|
activesupport (>= 4.1)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (5.
|
12
|
-
activesupport (= 5.
|
13
|
-
activerecord (5.
|
14
|
-
activemodel (= 5.
|
15
|
-
activesupport (= 5.
|
16
|
-
arel (~>
|
17
|
-
activesupport (5.
|
11
|
+
activemodel (5.1.4)
|
12
|
+
activesupport (= 5.1.4)
|
13
|
+
activerecord (5.1.4)
|
14
|
+
activemodel (= 5.1.4)
|
15
|
+
activesupport (= 5.1.4)
|
16
|
+
arel (~> 8.0)
|
17
|
+
activesupport (5.1.4)
|
18
18
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
19
|
i18n (~> 0.7)
|
20
20
|
minitest (~> 5.1)
|
21
21
|
tzinfo (~> 1.1)
|
22
|
-
arel (
|
23
|
-
coderay (1.1.
|
22
|
+
arel (8.0.0)
|
23
|
+
coderay (1.1.2)
|
24
24
|
concurrent-ruby (1.0.5)
|
25
|
-
database_cleaner (1.
|
25
|
+
database_cleaner (1.6.2)
|
26
|
+
db-query-matchers (0.9.0)
|
27
|
+
activesupport (>= 4.0, <= 6.0)
|
28
|
+
rspec (~> 3.0)
|
26
29
|
diff-lcs (1.3)
|
27
|
-
i18n (0.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
i18n (0.9.1)
|
31
|
+
concurrent-ruby (~> 1.0)
|
32
|
+
method_source (0.9.0)
|
33
|
+
minitest (5.10.3)
|
34
|
+
pry (0.9.12.6)
|
35
|
+
coderay (~> 1.0)
|
36
|
+
method_source (~> 0.8)
|
33
37
|
slop (~> 3.4)
|
34
38
|
pry-nav (0.2.4)
|
35
39
|
pry (>= 0.9.10, < 0.11.0)
|
36
|
-
rake (12.
|
40
|
+
rake (12.2.1)
|
37
41
|
redcarpet (3.4.0)
|
38
|
-
rr (1.2.
|
39
|
-
rspec (3.
|
40
|
-
rspec-core (~> 3.
|
41
|
-
rspec-expectations (~> 3.
|
42
|
-
rspec-mocks (~> 3.
|
43
|
-
rspec-core (3.
|
44
|
-
rspec-support (~> 3.
|
45
|
-
rspec-expectations (3.
|
42
|
+
rr (1.2.1)
|
43
|
+
rspec (3.7.0)
|
44
|
+
rspec-core (~> 3.7.0)
|
45
|
+
rspec-expectations (~> 3.7.0)
|
46
|
+
rspec-mocks (~> 3.7.0)
|
47
|
+
rspec-core (3.7.0)
|
48
|
+
rspec-support (~> 3.7.0)
|
49
|
+
rspec-expectations (3.7.0)
|
46
50
|
diff-lcs (>= 1.2.0, < 2.0)
|
47
|
-
rspec-support (~> 3.
|
48
|
-
rspec-mocks (3.
|
51
|
+
rspec-support (~> 3.7.0)
|
52
|
+
rspec-mocks (3.7.0)
|
49
53
|
diff-lcs (>= 1.2.0, < 2.0)
|
50
|
-
rspec-support (~> 3.
|
51
|
-
rspec-support (3.
|
54
|
+
rspec-support (~> 3.7.0)
|
55
|
+
rspec-support (3.7.0)
|
52
56
|
slop (3.6.0)
|
53
57
|
sqlite3 (1.3.13)
|
54
58
|
thread_safe (0.3.6)
|
55
|
-
tzinfo (1.2.
|
59
|
+
tzinfo (1.2.4)
|
56
60
|
thread_safe (~> 0.1)
|
57
|
-
yard (0.9.
|
61
|
+
yard (0.9.9)
|
58
62
|
|
59
63
|
PLATFORMS
|
60
64
|
ruby
|
@@ -62,6 +66,7 @@ PLATFORMS
|
|
62
66
|
DEPENDENCIES
|
63
67
|
brainstem!
|
64
68
|
database_cleaner
|
69
|
+
db-query-matchers
|
65
70
|
pry
|
66
71
|
pry-nav
|
67
72
|
rake
|
data/README.md
CHANGED
@@ -216,6 +216,21 @@ Responses will look like the following:
|
|
216
216
|
# Total number of results that matched the query.
|
217
217
|
count: 5,
|
218
218
|
|
219
|
+
# Information about the request and response.
|
220
|
+
meta: {
|
221
|
+
# Total number of results that matched the query.
|
222
|
+
count: 5,
|
223
|
+
|
224
|
+
# Current page returned in the response.
|
225
|
+
page_number: 1,
|
226
|
+
|
227
|
+
# Total number pages available.
|
228
|
+
page_count: 1,
|
229
|
+
|
230
|
+
# Number of results per page.
|
231
|
+
page_size: 20,
|
232
|
+
},
|
233
|
+
|
219
234
|
# A lookup table to top-level keys. Necessary
|
220
235
|
# because some objects can have associations of
|
221
236
|
# the same type as themselves. Also helps to
|
data/brainstem.gemspec
CHANGED
@@ -53,17 +53,18 @@ module Brainstem
|
|
53
53
|
options[:default_max_per_page] = default_max_per_page
|
54
54
|
options[:default_max_filter_and_search_page] = default_max_filter_and_search_page
|
55
55
|
|
56
|
-
|
56
|
+
strategy = get_strategy(options, scope)
|
57
|
+
primary_models, count = strategy.execute(scope)
|
57
58
|
|
58
59
|
# Determine if an exception should be raised on an empty result set.
|
59
60
|
if options[:raise_on_empty] && primary_models.empty?
|
60
61
|
raise options[:empty_error_class] || ActiveRecord::RecordNotFound
|
61
62
|
end
|
62
63
|
|
63
|
-
structure_response(presented_class, primary_models, count, options)
|
64
|
+
structure_response(presented_class, primary_models, strategy, count, options)
|
64
65
|
end
|
65
66
|
|
66
|
-
def structure_response(presented_class, primary_models, count, options)
|
67
|
+
def structure_response(presented_class, primary_models, strategy, count, options)
|
67
68
|
# key these models will use in the struct that is output
|
68
69
|
brainstem_key = brainstem_key_for!(presented_class)
|
69
70
|
|
@@ -71,8 +72,19 @@ module Brainstem
|
|
71
72
|
selected_associations = filter_includes(options)
|
72
73
|
|
73
74
|
optional_fields = filter_optional_fields(options)
|
74
|
-
|
75
|
-
|
75
|
+
page_size = strategy.calculate_per_page
|
76
|
+
|
77
|
+
struct = {
|
78
|
+
'count' => count,
|
79
|
+
'results' => [],
|
80
|
+
brainstem_key => {},
|
81
|
+
'meta' => {
|
82
|
+
'count' => count,
|
83
|
+
'page_count' => count > 0 ? (count.to_f / page_size).ceil : 0,
|
84
|
+
'page_number' => count > 0 ? options[:params].fetch(:page, 1).to_i : 0,
|
85
|
+
'page_size' => page_size,
|
86
|
+
}
|
87
|
+
}
|
76
88
|
|
77
89
|
# Build top-level keys for all requested associations.
|
78
90
|
selected_associations.each do |association|
|
@@ -145,7 +157,7 @@ module Brainstem
|
|
145
157
|
|
146
158
|
private
|
147
159
|
|
148
|
-
def
|
160
|
+
def get_strategy(options, scope)
|
149
161
|
strat = options[:primary_presenter].get_query_strategy
|
150
162
|
|
151
163
|
return Brainstem::QueryStrategies::FilterAndSearch.new(options) if strat == :filter_and_search && searching?(options)
|
@@ -26,6 +26,12 @@ module Brainstem
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
def calculate_per_page
|
30
|
+
per_page = [(@options[:params][:per_page] || @options[:per_page] || @options[:default_per_page]).to_i, (@options[:max_per_page] || @options[:default_max_per_page]).to_i].min
|
31
|
+
per_page = @options[:default_per_page] if per_page < 1
|
32
|
+
per_page
|
33
|
+
end
|
34
|
+
|
29
35
|
private
|
30
36
|
|
31
37
|
def calculate_limit
|
@@ -36,12 +42,6 @@ module Brainstem
|
|
36
42
|
[@options[:params][:offset].to_i, 0].max
|
37
43
|
end
|
38
44
|
|
39
|
-
def calculate_per_page
|
40
|
-
per_page = [(@options[:params][:per_page] || @options[:per_page] || @options[:default_per_page]).to_i, (@options[:max_per_page] || @options[:default_max_per_page]).to_i].min
|
41
|
-
per_page = @options[:default_per_page] if per_page < 1
|
42
|
-
per_page
|
43
|
-
end
|
44
|
-
|
45
45
|
def calculate_page
|
46
46
|
[(@options[:params][:page] || 1).to_i, 1].max
|
47
47
|
end
|
@@ -57,6 +57,33 @@ module Brainstem
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
60
|
+
|
61
|
+
def order_for_search(records, ordered_search_ids, options = {})
|
62
|
+
ids_to_position = {}
|
63
|
+
ordered_records = []
|
64
|
+
|
65
|
+
ordered_search_ids.each_with_index do |id, index|
|
66
|
+
ids_to_position[id] = index
|
67
|
+
end
|
68
|
+
|
69
|
+
records.each do |record|
|
70
|
+
ordered_records[ids_to_position[options[:with_ids] ? record : record.id]] = record
|
71
|
+
end
|
72
|
+
|
73
|
+
ordered_records.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
def calculate_limit_and_offset
|
77
|
+
if @options[:params][:limit].present? && @options[:params][:offset].present?
|
78
|
+
limit = calculate_limit
|
79
|
+
offset = calculate_offset
|
80
|
+
else
|
81
|
+
limit = calculate_per_page
|
82
|
+
offset = limit * (calculate_page - 1)
|
83
|
+
end
|
84
|
+
|
85
|
+
[limit, offset]
|
86
|
+
end
|
60
87
|
end
|
61
88
|
end
|
62
89
|
end
|
@@ -2,13 +2,31 @@ module Brainstem
|
|
2
2
|
module QueryStrategies
|
3
3
|
class FilterAndSearch < BaseStrategy
|
4
4
|
def execute(scope)
|
5
|
-
ordered_search_ids = run_search(scope, filter_includes.map(&:name))
|
6
|
-
scope = scope.where(id: ordered_search_ids)
|
5
|
+
scope, ordered_search_ids = run_search(scope, filter_includes.map(&:name))
|
7
6
|
scope = @options[:primary_presenter].apply_filters_to_scope(scope, @options[:params], @options)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
|
8
|
+
if ordering?
|
9
|
+
count = scope.count
|
10
|
+
scope = paginate(scope)
|
11
|
+
scope = @options[:primary_presenter].apply_ordering_to_scope(scope, @options[:params])
|
12
|
+
primary_models = evaluate_scope(scope)
|
13
|
+
else
|
14
|
+
filtered_ids = scope.pluck(:id)
|
15
|
+
count = filtered_ids.size
|
16
|
+
|
17
|
+
# order a potentially large set of ids
|
18
|
+
ordered_ids = order_for_search(filtered_ids, ordered_search_ids, with_ids: true)
|
19
|
+
ordered_paginated_ids = paginate_array(ordered_ids)
|
20
|
+
|
21
|
+
scope = scope.unscoped.where(id: ordered_paginated_ids)
|
22
|
+
# not using `evaluate_scope` because we are already instantiating
|
23
|
+
# a scope based on ids
|
24
|
+
primary_models = scope.to_a
|
25
|
+
|
26
|
+
# Once hydrated, a page worth of models needs to be reordered
|
27
|
+
# due to the `scope.unscoped.where(id: ...` clobbering our ordering
|
28
|
+
primary_models = order_for_search(primary_models, ordered_paginated_ids)
|
29
|
+
end
|
12
30
|
|
13
31
|
[primary_models, count]
|
14
32
|
end
|
@@ -16,10 +34,8 @@ module Brainstem
|
|
16
34
|
private
|
17
35
|
|
18
36
|
def run_search(scope, includes)
|
19
|
-
sort_name, direction = @options[:primary_presenter].calculate_sort_name_and_direction @options[:params]
|
20
37
|
search_options = HashWithIndifferentAccess.new(
|
21
38
|
include: includes,
|
22
|
-
order: { sort_order: sort_name, direction: direction },
|
23
39
|
limit: @options[:default_max_filter_and_search_page],
|
24
40
|
offset: 0
|
25
41
|
)
|
@@ -28,23 +44,28 @@ module Brainstem
|
|
28
44
|
|
29
45
|
result_ids, _ = @options[:primary_presenter].run_search(@options[:params][:search], search_options)
|
30
46
|
if result_ids
|
31
|
-
result_ids
|
47
|
+
resulting_scope = scope.where(id: result_ids)
|
48
|
+
[resulting_scope, result_ids]
|
32
49
|
else
|
33
50
|
raise(SearchUnavailableError, 'Search is currently unavailable')
|
34
51
|
end
|
35
52
|
end
|
36
53
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
limit = calculate_per_page
|
43
|
-
offset = limit * (calculate_page - 1)
|
44
|
-
end
|
54
|
+
def ordering?
|
55
|
+
sort_name = @options[:params][:order].to_s.split(":").first
|
56
|
+
sort_orders = @options[:primary_presenter].configuration[:sort_orders]
|
57
|
+
sort_name.present? && sort_orders && sort_orders[sort_name].present?
|
58
|
+
end
|
45
59
|
|
60
|
+
def paginate(scope)
|
61
|
+
limit, offset = calculate_limit_and_offset
|
46
62
|
scope.limit(limit).offset(offset).distinct
|
47
63
|
end
|
64
|
+
|
65
|
+
def paginate_array(array)
|
66
|
+
limit, offset = calculate_limit_and_offset
|
67
|
+
array.drop(offset).first(limit) # do we need to uniq this?
|
68
|
+
end
|
48
69
|
end
|
49
70
|
end
|
50
71
|
end
|
@@ -68,14 +68,7 @@ module Brainstem
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def paginate(scope)
|
71
|
-
|
72
|
-
limit = calculate_limit
|
73
|
-
offset = calculate_offset
|
74
|
-
else
|
75
|
-
limit = calculate_per_page
|
76
|
-
offset = limit * (calculate_page - 1)
|
77
|
-
end
|
78
|
-
|
71
|
+
limit, offset = calculate_limit_and_offset
|
79
72
|
[scope.limit(limit).offset(offset).distinct, scope.select("distinct #{scope.connection.quote_table_name @options[:table_name]}.id").count]
|
80
73
|
end
|
81
74
|
|
@@ -83,21 +76,6 @@ module Brainstem
|
|
83
76
|
ids = (only || "").split(",").select {|id| id =~ /\A\d+\z/}.uniq
|
84
77
|
[scope.where(:id => ids), scope.where(:id => ids).count]
|
85
78
|
end
|
86
|
-
|
87
|
-
def order_for_search(records, ordered_search_ids)
|
88
|
-
ids_to_position = {}
|
89
|
-
ordered_records = []
|
90
|
-
|
91
|
-
ordered_search_ids.each_with_index do |id, index|
|
92
|
-
ids_to_position[id] = index
|
93
|
-
end
|
94
|
-
|
95
|
-
records.each do |record|
|
96
|
-
ordered_records[ids_to_position[record.id]] = record
|
97
|
-
end
|
98
|
-
|
99
|
-
ordered_records.compact
|
100
|
-
end
|
101
79
|
end
|
102
80
|
end
|
103
81
|
end
|
data/lib/brainstem/version.rb
CHANGED
@@ -10,7 +10,7 @@ describe Brainstem::PresenterCollection do
|
|
10
10
|
let(:jane) { User.where(:username => "jane").first }
|
11
11
|
|
12
12
|
describe "#presenting" do
|
13
|
-
describe "
|
13
|
+
describe "pagination" do
|
14
14
|
before do
|
15
15
|
@presenter_collection.default_per_page = 2
|
16
16
|
@presenter_collection.default_max_per_page = 3
|
@@ -159,6 +159,64 @@ describe Brainstem::PresenterCollection do
|
|
159
159
|
expect(result['count']).to eq(Workspace.count)
|
160
160
|
end
|
161
161
|
end
|
162
|
+
|
163
|
+
describe "meta keys" do
|
164
|
+
let(:max_per_page) { nil }
|
165
|
+
let(:meta_keys) { result["meta"] }
|
166
|
+
let(:result) { @presenter_collection.presenting("workspaces", params: params, max_per_page: max_per_page) { Workspace.unscoped } }
|
167
|
+
|
168
|
+
describe "count" do
|
169
|
+
let(:params) { {} }
|
170
|
+
|
171
|
+
it "includes the count" do
|
172
|
+
expect(meta_keys["count"]).to eq(6)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "page_number" do
|
177
|
+
describe "when page is provided" do
|
178
|
+
let(:params) { { page: 2 } }
|
179
|
+
|
180
|
+
it "indicates the provided page" do
|
181
|
+
expect(meta_keys["page_number"]).to eq(2)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "when page is not profided" do
|
186
|
+
let(:params) { {} }
|
187
|
+
|
188
|
+
it "indicates the first page" do
|
189
|
+
expect(meta_keys["page_number"]).to eq(1)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "page_count" do
|
195
|
+
describe "when per_page is provided" do
|
196
|
+
let(:params) { { per_page: 4 } }
|
197
|
+
|
198
|
+
it "calculates the correct page count" do
|
199
|
+
expect(meta_keys["page_count"]).to eq(2)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "when per_page is not provided" do
|
204
|
+
let(:params) { {} }
|
205
|
+
|
206
|
+
it "calculates the correct page count based on the default page size" do
|
207
|
+
expect(meta_keys["page_count"]).to eq(3)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "page_size" do
|
213
|
+
let(:params) { {} }
|
214
|
+
|
215
|
+
it "calculates the correct page size" do
|
216
|
+
expect(meta_keys["page_count"]).to eq(3)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
162
220
|
end
|
163
221
|
|
164
222
|
describe 'strategies' do
|
@@ -256,7 +314,7 @@ describe Brainstem::PresenterCollection do
|
|
256
314
|
describe "the 'results' top level key" do
|
257
315
|
it "comes back with an explicit list of the matching results" do
|
258
316
|
structure = @presenter_collection.presenting("workspaces", :params => { :include => "tasks" }, :max_per_page => 2) { Workspace.where(:id => 1) }
|
259
|
-
expect(structure.keys).to match_array %w[workspaces tasks count results]
|
317
|
+
expect(structure.keys).to match_array %w[workspaces tasks count results meta]
|
260
318
|
expect(structure['results']).to eq(Workspace.where(:id => 1).limit(2).map {|w| { 'key' => 'workspaces', 'id' => w.id.to_s } })
|
261
319
|
expect(structure['workspaces'].keys).to eq(%w[1])
|
262
320
|
end
|
@@ -265,10 +323,10 @@ describe Brainstem::PresenterCollection do
|
|
265
323
|
describe "includes" do
|
266
324
|
it "reads allowed includes from the presenter" do
|
267
325
|
result = @presenter_collection.presenting("workspaces", :params => { :include => "drop table,tasks,users" }) { Workspace.unscoped }
|
268
|
-
expect(result.keys).to match_array %w[count workspaces tasks results]
|
326
|
+
expect(result.keys).to match_array %w[count meta workspaces tasks results]
|
269
327
|
|
270
328
|
result = @presenter_collection.presenting("workspaces", :params => { :include => "foo,tasks,lead_user" }) { Workspace.unscoped }
|
271
|
-
expect(result.keys).to match_array %w[count workspaces tasks users results]
|
329
|
+
expect(result.keys).to match_array %w[count meta workspaces tasks users results]
|
272
330
|
end
|
273
331
|
|
274
332
|
it "defaults to not include any allowed includes" do
|
@@ -304,7 +362,7 @@ describe Brainstem::PresenterCollection do
|
|
304
362
|
result = @presenter_collection.presenting("tasks", :params => { :include => "workspace" }, :max_per_page => 2) { Task.where(:id => t.id) }
|
305
363
|
expect(result['tasks'].keys).to eq([ t.id.to_s ])
|
306
364
|
expect(result['workspaces']).to eq({})
|
307
|
-
expect(result.keys).to match_array %w[tasks workspaces count results]
|
365
|
+
expect(result.keys).to match_array %w[tasks workspaces count meta results]
|
308
366
|
end
|
309
367
|
|
310
368
|
context 'when including something of the same type as the primary model' do
|
@@ -911,7 +969,7 @@ describe Brainstem::PresenterCollection do
|
|
911
969
|
context "when there is no sort provided" do
|
912
970
|
it "returns an empty array when there are no objects" do
|
913
971
|
result = @presenter_collection.presenting("workspaces") { Workspace.where(:id => nil) }
|
914
|
-
expect(result).to eq('count' => 0, 'workspaces' => {}, 'results' => [])
|
972
|
+
expect(result).to eq('count' => 0, 'meta' => { 'count' => 0, 'page_count' => 0, 'page_number' => 0, 'page_size' => 20 }, 'workspaces' => {}, 'results' => [])
|
915
973
|
end
|
916
974
|
|
917
975
|
it "falls back to the object's sort order when nothing is provided" do
|
@@ -966,7 +1024,7 @@ describe Brainstem::PresenterCollection do
|
|
966
1024
|
|
967
1025
|
result = @presenter_collection.presenting("workspaces", :params => { :order => "description:drop table" }) { Workspace.where("id is not null") }
|
968
1026
|
expect(last_direction).to eq('asc')
|
969
|
-
expect(result.keys).to match_array %w[count workspaces results]
|
1027
|
+
expect(result.keys).to match_array %w[count meta workspaces results]
|
970
1028
|
|
971
1029
|
result = @presenter_collection.presenting("workspaces", :params => { :order => "description:;;hacker;;" }) { Workspace.where("id is not null") }
|
972
1030
|
expect(last_direction).to eq('asc')
|
@@ -1087,7 +1145,8 @@ describe Brainstem::PresenterCollection do
|
|
1087
1145
|
|
1088
1146
|
describe '#structure_response' do
|
1089
1147
|
let(:options) { {params: {}, primary_presenter: @presenter_collection.for!(Workspace) } }
|
1090
|
-
let(:response_body) { @presenter_collection.structure_response(Workspace, Workspace.all, 17, options) }
|
1148
|
+
let(:response_body) { @presenter_collection.structure_response(Workspace, Workspace.all, strategy, 17, options) }
|
1149
|
+
let(:strategy) { OpenStruct.new(calculate_per_page: 25) }
|
1091
1150
|
|
1092
1151
|
it 'has a count' do
|
1093
1152
|
expect(response_body['count']).to eq(17)
|
@@ -2,11 +2,10 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Brainstem::QueryStrategies::FilterAndSearch do
|
4
4
|
let(:bob) { User.find_by_username('bob') }
|
5
|
+
let(:jane) { User.find_by_username('jane') }
|
5
6
|
|
6
|
-
let(:
|
7
|
+
let(:default_params) { {
|
7
8
|
"owned_by" => bob.id.to_s,
|
8
|
-
per_page: 7,
|
9
|
-
page: 1,
|
10
9
|
search: 'toot, otto, toot',
|
11
10
|
} }
|
12
11
|
|
@@ -19,28 +18,150 @@ describe Brainstem::QueryStrategies::FilterAndSearch do
|
|
19
18
|
params: params
|
20
19
|
} }
|
21
20
|
|
21
|
+
it_behaves_like Brainstem::QueryStrategies::BaseStrategy
|
22
|
+
|
22
23
|
describe '#execute' do
|
24
|
+
def run_query
|
25
|
+
described_class.new(options).execute(Cheese.all)
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:owned_by_bob) { Cheese.owned_by(bob.id)}
|
29
|
+
let(:owned_by_jane) { Cheese.owned_by(jane.id)}
|
30
|
+
|
23
31
|
before do
|
24
|
-
|
25
|
-
|
32
|
+
@search_results = search_results = Cheese.all.pluck(:id).shuffle
|
33
|
+
|
34
|
+
CheesePresenter.search do
|
35
|
+
[search_results, search_results.count]
|
26
36
|
end
|
27
37
|
|
28
38
|
CheesePresenter.filter(:owned_by) { |scope, user_id| scope.owned_by(user_id.to_i) }
|
29
39
|
CheesePresenter.sort_order(:id) { |scope, direction| scope.order("cheeses.id #{direction}") }
|
30
40
|
end
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
42
|
+
context 'when an order is specified' do
|
43
|
+
let(:order) { 'id:asc' }
|
44
|
+
let(:params) { default_params.merge({ order: order })}
|
45
|
+
let(:expected_ordered_ids) { owned_by_bob.order("cheeses.id ASC").pluck(:id) }
|
46
|
+
|
47
|
+
it 'returns the filtered, ordered search results' do
|
48
|
+
results, count = run_query
|
49
|
+
expect(count).to eq(owned_by_bob.count)
|
50
|
+
expect(results.map(&:id)).to eq(expected_ordered_ids)
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'with limit and offset params' do
|
54
|
+
let(:limit) { 2 }
|
55
|
+
let(:offset) { 4 }
|
56
|
+
let(:params) { default_params.merge({ order: order, limit: limit, offset: offset })}
|
57
|
+
let(:expected_paginated_ids) { expected_ordered_ids.drop(offset).first(limit) }
|
58
|
+
|
59
|
+
it 'returns the filtered, ordered, paginated results' do
|
60
|
+
results, count = run_query
|
61
|
+
expect(count).to eq(owned_by_bob.count)
|
62
|
+
expect(results.map(&:id)).to eq(expected_paginated_ids)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with page and per_page params' do
|
67
|
+
let(:page) { 2 }
|
68
|
+
let(:per_page) { 3 }
|
69
|
+
let(:params) { default_params.merge({ order: order, page: page, per_page: per_page })}
|
70
|
+
let(:expected_paginated_ids) { expected_ordered_ids.drop(per_page).first(per_page) }
|
71
|
+
|
72
|
+
it 'returns the filtered, ordered, paginated results' do
|
73
|
+
results, count = run_query
|
74
|
+
expect(count).to eq(owned_by_bob.count)
|
75
|
+
expect(results.map(&:id)).to eq(expected_paginated_ids)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when no order is specified' do
|
81
|
+
let(:params) { default_params }
|
82
|
+
let(:expected_ordered_ids) { @search_results - owned_by_jane.pluck(:id) }
|
83
|
+
|
84
|
+
before do
|
85
|
+
expect(params[:order]).not_to be_present
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns the filtered results ordered by search' do
|
89
|
+
results, count = run_query
|
90
|
+
expect(count).to eq(owned_by_bob.count)
|
91
|
+
expect(results.map(&:id)).to eq(expected_ordered_ids)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'only does two database queries' do
|
95
|
+
expect { run_query }.to make_database_queries(count: 2)
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'with limit and offset params' do
|
99
|
+
let(:limit) { 2 }
|
100
|
+
let(:offset) { 4 }
|
101
|
+
let(:params) { default_params.merge({ limit: limit, offset: offset })}
|
102
|
+
let(:expected_paginated_ids) { expected_ordered_ids.drop(offset).first(limit) }
|
103
|
+
|
104
|
+
it 'returns the filtered, ordered, paginated results' do
|
105
|
+
results, count = run_query
|
106
|
+
expect(count).to eq(owned_by_bob.count)
|
107
|
+
expect(results.map(&:id)).to eq(expected_paginated_ids)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'with page and per_page params' do
|
112
|
+
let(:page) { 2 }
|
113
|
+
let(:per_page) { 3 }
|
114
|
+
let(:params) { default_params.merge({ page: page, per_page: per_page })}
|
115
|
+
let(:expected_paginated_ids) { expected_ordered_ids.drop(per_page).first(per_page) }
|
116
|
+
|
117
|
+
it 'returns the filtered, ordered, paginated results' do
|
118
|
+
results, count = run_query
|
119
|
+
expect(count).to eq(owned_by_bob.count)
|
120
|
+
expect(results.map(&:id)).to eq(expected_paginated_ids)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#ordering?' do
|
127
|
+
context 'when the order param is passed' do
|
128
|
+
let(:params) { default_params.merge({ order: 'canadianness' })}
|
129
|
+
|
130
|
+
context 'and it exists on the presenter' do
|
131
|
+
before do
|
132
|
+
CheesePresenter.sort_order(:canadianness) { |scope, direction| scope.order("cheeses.hockey #{direction}") }
|
133
|
+
expect(CheesePresenter.configuration[:sort_orders][:canadianness]).to be_present
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'returns true' do
|
137
|
+
query_strat = described_class.new(options)
|
138
|
+
expect(query_strat.send(:ordering?)).to eq(true)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'and it does not exist on the presenter' do
|
143
|
+
before do
|
144
|
+
expect(CheesePresenter.configuration[:sort_orders][:canadianness]).not_to be_present
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'returns false' do
|
148
|
+
query_strat = described_class.new(options)
|
149
|
+
expect(query_strat.send(:ordering?)).to eq(false)
|
150
|
+
end
|
151
|
+
end
|
36
152
|
end
|
37
153
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
154
|
+
context 'when the order param is not passed' do
|
155
|
+
let(:params) { default_params }
|
156
|
+
|
157
|
+
before do
|
158
|
+
expect(params[:order]).not_to be_present
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'returns false' do
|
162
|
+
query_strat = described_class.new(options)
|
163
|
+
expect(query_strat.send(:ordering?)).to eq(false)
|
164
|
+
end
|
44
165
|
end
|
45
166
|
end
|
46
167
|
end
|
@@ -3,6 +3,8 @@ require 'spec_helper'
|
|
3
3
|
# The functionality of this is mainly tested in more integration-y tests in presenter_collection_spec.rb
|
4
4
|
|
5
5
|
describe Brainstem::QueryStrategies::FilterOrSearch do
|
6
|
+
it_behaves_like Brainstem::QueryStrategies::BaseStrategy
|
7
|
+
|
6
8
|
describe '#execute' do
|
7
9
|
context 'we are searching' do
|
8
10
|
let(:subject) { described_class.new(@options) }
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
shared_examples_for Brainstem::QueryStrategies::BaseStrategy do
|
4
|
+
let(:strategy) { described_class.new(options) }
|
5
|
+
|
6
|
+
describe "#calculate_per_page" do
|
7
|
+
let(:result) { strategy.calculate_per_page }
|
8
|
+
|
9
|
+
[
|
10
|
+
# per_page default_per_page max_per_page default_max_per_page expected situation used
|
11
|
+
[ 10, 20, 100, 200, 10, "per page < max", "the per page"],
|
12
|
+
[ nil, 20, 100, 200, 20, "no per page and default < max", "the default"],
|
13
|
+
[ nil, 200, 100, 200, 100, "no per page and default > max", "the max"],
|
14
|
+
[ 150, 20, 100, 200, 100, "per page > max", "the max"],
|
15
|
+
[ 150, 20, nil, 200, 150, "no max and per page < default max", "the per page"],
|
16
|
+
[ 250, 20, nil, 200, 200, "no max and per page > default max", "the default max"],
|
17
|
+
].each do |per_page, default_per_page, max_per_page, default_max_per_page, expected, situation, used|
|
18
|
+
describe "when #{situation}" do
|
19
|
+
let(:options) {
|
20
|
+
{
|
21
|
+
params: {
|
22
|
+
per_page: per_page,
|
23
|
+
},
|
24
|
+
default_per_page: default_per_page,
|
25
|
+
default_max_per_page: default_max_per_page,
|
26
|
+
max_per_page: max_per_page,
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
it "uses #{used}" do
|
31
|
+
expect(result).to eq(expected)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brainstem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mavenlink
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: db-query-matchers
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
167
181
|
description: Brainstem allows you to create rich API presenters that know how to filter,
|
168
182
|
sort, and include associations.
|
169
183
|
email:
|
@@ -291,6 +305,7 @@ files:
|
|
291
305
|
- spec/brainstem_spec.rb
|
292
306
|
- spec/dummy/rails.rb
|
293
307
|
- spec/shared/atlas_taker.rb
|
308
|
+
- spec/shared/base_strategy.rb
|
294
309
|
- spec/shared/formattable.rb
|
295
310
|
- spec/spec_helper.rb
|
296
311
|
- spec/spec_helpers/db.rb
|
@@ -317,7 +332,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
317
332
|
version: '0'
|
318
333
|
requirements: []
|
319
334
|
rubyforge_project:
|
320
|
-
rubygems_version: 2.
|
335
|
+
rubygems_version: 2.6.13
|
321
336
|
signing_key:
|
322
337
|
specification_version: 4
|
323
338
|
summary: ActiveRecord presenters with a rich request API
|
@@ -368,6 +383,7 @@ test_files:
|
|
368
383
|
- spec/brainstem_spec.rb
|
369
384
|
- spec/dummy/rails.rb
|
370
385
|
- spec/shared/atlas_taker.rb
|
386
|
+
- spec/shared/base_strategy.rb
|
371
387
|
- spec/shared/formattable.rb
|
372
388
|
- spec/spec_helper.rb
|
373
389
|
- spec/spec_helpers/db.rb
|