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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 115ca6ee598304821711410e09788b194b4463d3
4
- data.tar.gz: eb49966eafe33595f32048f087ee7148a0b4da36
3
+ metadata.gz: 9bf2b0cdf9ecd18e7a90f8484238fe6e7f63ad43
4
+ data.tar.gz: 7be99f891a90e26715f553fcf0971931bf51c404
5
5
  SHA512:
6
- metadata.gz: 45af28ac0b45a91167174b0809e79c22a20864cb2facb3c59c23fdad0d71e62dec0299db8f74748a0c0306169aa05eaf756f5564459f43d19edb2b0045bbb30c
7
- data.tar.gz: 8050a810b6b1046d00d7aa961bebe48698604be562f28c73943c5160d4668b47c92ff9c4bb52a03e67eb67804d4f9d5a8391507cd5328b8138546e7febefcdc8
6
+ metadata.gz: 138e7db36bf3692bde7fac6c61777ea8695195d32769dec30555bc2d4b5124679dd8f22e08624503ba3148e64c058bcad31f72b24842f2bf864bb4291ca8e052
7
+ data.tar.gz: 37760c56b41a4fbf8dbb5e19b543229c878e61fd88a7a8400f961140565b70c9b738b1882b7aeee1dc3c41e651695e02ab43ed95efd1483a80eafbdf38abf50c
@@ -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]`.
@@ -1,60 +1,64 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brainstem (1.0.0.pre)
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.0.2)
12
- activesupport (= 5.0.2)
13
- activerecord (5.0.2)
14
- activemodel (= 5.0.2)
15
- activesupport (= 5.0.2)
16
- arel (~> 7.0)
17
- activesupport (5.0.2)
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 (7.1.4)
23
- coderay (1.1.1)
22
+ arel (8.0.0)
23
+ coderay (1.1.2)
24
24
  concurrent-ruby (1.0.5)
25
- database_cleaner (1.5.3)
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.8.1)
28
- method_source (0.8.2)
29
- minitest (5.10.1)
30
- pry (0.10.4)
31
- coderay (~> 1.1.0)
32
- method_source (~> 0.8.1)
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.0.0)
40
+ rake (12.2.1)
37
41
  redcarpet (3.4.0)
38
- rr (1.2.0)
39
- rspec (3.5.0)
40
- rspec-core (~> 3.5.0)
41
- rspec-expectations (~> 3.5.0)
42
- rspec-mocks (~> 3.5.0)
43
- rspec-core (3.5.4)
44
- rspec-support (~> 3.5.0)
45
- rspec-expectations (3.5.0)
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.5.0)
48
- rspec-mocks (3.5.0)
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.5.0)
51
- rspec-support (3.5.0)
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.2)
59
+ tzinfo (1.2.4)
56
60
  thread_safe (~> 0.1)
57
- yard (0.9.8)
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
@@ -31,4 +31,5 @@ Gem::Specification.new do |gem|
31
31
  gem.add_development_dependency "yard"
32
32
  gem.add_development_dependency "pry"
33
33
  gem.add_development_dependency "pry-nav"
34
+ gem.add_development_dependency "db-query-matchers"
34
35
  end
@@ -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
- primary_models, count = strategy(options, scope).execute(scope)
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
- struct = { 'count' => count, brainstem_key => {}, 'results' => [] }
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 strategy(options, scope)
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
- scope = @options[:primary_presenter].apply_ordering_to_scope(scope, @options[:params])
9
- count = scope.count
10
- scope = paginate(scope)
11
- primary_models = evaluate_scope(scope)
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 paginate(scope)
38
- if @options[:params][:limit].present? && @options[:params][:offset].present?
39
- limit = calculate_limit
40
- offset = calculate_offset
41
- else
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
- if @options[:params][:limit].present? && @options[:params][:offset].present?
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
@@ -1,3 +1,3 @@
1
1
  module Brainstem
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -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 "#pagination" do
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(:params) { {
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
- CheesePresenter.search do |string, options|
25
- [[2,3,4,5,6,8,9,10,11,12], 11]
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
- it 'takes the intersection of the search and filter results' do
33
- results, count = described_class.new(options).execute(Cheese.unscoped)
34
- expect(count).to eq(8)
35
- expect(results.map(&:id)).to eq([2,3,4,5,8,10,11])
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
- it "applies ordering to the scope" do
39
- options[:params]["order"] = 'id:desc'
40
- proxy.instance_of(Brainstem::Presenter).apply_ordering_to_scope(anything, anything).times(1)
41
- results, count = described_class.new(options).execute(Cheese.unscoped)
42
- expect(count).to eq(8)
43
- expect(results.map(&:id)).to eq([12,11,10,8,5,4,3])
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
@@ -6,6 +6,7 @@ require 'sqlite3'
6
6
  require 'database_cleaner'
7
7
  require 'pry'
8
8
  require 'pry-nav'
9
+ require 'db-query-matchers'
9
10
 
10
11
  require 'brainstem'
11
12
  require_relative 'spec_helpers/schema'
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.0.0
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: 2017-07-20 00:00:00.000000000 Z
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.5.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