brainstem 0.2.6.1 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.md +16 -2
  3. data/Gemfile.lock +51 -36
  4. data/README.md +531 -110
  5. data/brainstem.gemspec +6 -2
  6. data/lib/brainstem.rb +25 -9
  7. data/lib/brainstem/concerns/controller_param_management.rb +22 -0
  8. data/lib/brainstem/concerns/error_presentation.rb +58 -0
  9. data/lib/brainstem/concerns/inheritable_configuration.rb +29 -0
  10. data/lib/brainstem/concerns/lookup.rb +30 -0
  11. data/lib/brainstem/concerns/presenter_dsl.rb +111 -0
  12. data/lib/brainstem/controller_methods.rb +17 -8
  13. data/lib/brainstem/dsl/association.rb +55 -0
  14. data/lib/brainstem/dsl/associations_block.rb +12 -0
  15. data/lib/brainstem/dsl/base_block.rb +31 -0
  16. data/lib/brainstem/dsl/conditional.rb +25 -0
  17. data/lib/brainstem/dsl/conditionals_block.rb +15 -0
  18. data/lib/brainstem/dsl/configuration.rb +112 -0
  19. data/lib/brainstem/dsl/field.rb +68 -0
  20. data/lib/brainstem/dsl/fields_block.rb +25 -0
  21. data/lib/brainstem/preloader.rb +98 -0
  22. data/lib/brainstem/presenter.rb +325 -134
  23. data/lib/brainstem/presenter_collection.rb +82 -286
  24. data/lib/brainstem/presenter_validator.rb +96 -0
  25. data/lib/brainstem/query_strategies/README.md +107 -0
  26. data/lib/brainstem/query_strategies/base_strategy.rb +62 -0
  27. data/lib/brainstem/query_strategies/filter_and_search.rb +50 -0
  28. data/lib/brainstem/query_strategies/filter_or_search.rb +103 -0
  29. data/lib/brainstem/test_helpers.rb +5 -1
  30. data/lib/brainstem/version.rb +1 -1
  31. data/spec/brainstem/concerns/controller_param_management_spec.rb +42 -0
  32. data/spec/brainstem/concerns/error_presentation_spec.rb +113 -0
  33. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +210 -0
  34. data/spec/brainstem/concerns/presenter_dsl_spec.rb +412 -0
  35. data/spec/brainstem/controller_methods_spec.rb +15 -27
  36. data/spec/brainstem/dsl/association_spec.rb +123 -0
  37. data/spec/brainstem/dsl/conditional_spec.rb +93 -0
  38. data/spec/brainstem/dsl/configuration_spec.rb +1 -0
  39. data/spec/brainstem/dsl/field_spec.rb +212 -0
  40. data/spec/brainstem/preloader_spec.rb +137 -0
  41. data/spec/brainstem/presenter_collection_spec.rb +565 -244
  42. data/spec/brainstem/presenter_spec.rb +726 -167
  43. data/spec/brainstem/presenter_validator_spec.rb +209 -0
  44. data/spec/brainstem/query_strategies/filter_and_search_spec.rb +46 -0
  45. data/spec/brainstem/query_strategies/filter_or_search_spec.rb +45 -0
  46. data/spec/spec_helper.rb +11 -3
  47. data/spec/spec_helpers/db.rb +32 -65
  48. data/spec/spec_helpers/presenters.rb +124 -29
  49. data/spec/spec_helpers/rr.rb +11 -0
  50. data/spec/spec_helpers/schema.rb +115 -0
  51. metadata +126 -30
  52. data/lib/brainstem/association_field.rb +0 -53
  53. data/lib/brainstem/engine.rb +0 -4
  54. data/pkg/brainstem-0.2.5.gem +0 -0
  55. data/pkg/brainstem-0.2.6.gem +0 -0
  56. data/spec/spec_helpers/cleanup.rb +0 -23
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/preloader'
3
+
4
+ describe Brainstem::Preloader do
5
+ let(:models) { Array.new }
6
+ let(:preloads) { Array.new }
7
+ let(:reflections) { Array.new }
8
+ let(:args) { [ models, preloads, reflections ] }
9
+ let!(:preloader) { Brainstem::Preloader.new(*args) }
10
+
11
+
12
+ describe ".preload" do
13
+ it "creates a new instance, passing args and calls it" do
14
+ preloader = mock(Object.new).call
15
+ mock(Brainstem::Preloader).new(anything, anything, anything) { preloader }
16
+ Brainstem::Preloader.preload(*args)
17
+ end
18
+ end
19
+
20
+
21
+ describe "#call" do
22
+ it "cleans" do
23
+ mock(preloader).clean!
24
+ preloader.send(:call)
25
+ end
26
+
27
+ it "preloads" do
28
+ mock(preloader).preload!
29
+ preloader.send(:call)
30
+ end
31
+ end
32
+
33
+
34
+ describe "#clean!" do
35
+ it "dedupes the associations" do
36
+ mock(preloader).dedupe!
37
+ preloader.send(:clean!)
38
+ end
39
+
40
+ it "removes unreflected preloads" do
41
+ mock(preloader).remove_unreflected_preloads!
42
+ preloader.send(:clean!)
43
+ end
44
+ end
45
+
46
+
47
+ describe "#dedupe!" do
48
+ before do
49
+ preloader.send(:dedupe!)
50
+ end
51
+
52
+ describe "conversion" do
53
+ let(:preloads) { [ :workspaces, { posts: :users } ] }
54
+
55
+ it "converts all non-hash keys to strings" do
56
+ expect(preloader.valid_preloads.keys).not_to include :workspaces
57
+ expect(preloader.valid_preloads.keys).to include "workspaces"
58
+ end
59
+
60
+ it "converts all root hash keys to strings" do
61
+ expect(preloader.valid_preloads.keys).not_to include :posts
62
+ expect(preloader.valid_preloads.keys).to include "posts"
63
+ end
64
+
65
+ it "does not convert nested objects to strings" do
66
+ expect(preloader.valid_preloads["posts"]).to eq [:users]
67
+ end
68
+ end
69
+
70
+
71
+ describe "combination" do
72
+ let(:preloads) { [
73
+ { :workspaces => :other_things },
74
+ { workspaces: :things },
75
+ { posts: { users: "posts" } },
76
+ { posts: { users: "subjects" } },
77
+ ] }
78
+
79
+ it "combines root-level keys" do
80
+ expect(preloader.valid_preloads["workspaces"]).to eq [:other_things, :things]
81
+ end
82
+
83
+ it "does not combine non-root keys" do
84
+ expect(preloader.valid_preloads["posts"].count).to eq 2
85
+ expect(preloader.valid_preloads["posts"].map(&:keys).flatten).to eq [:users, :users]
86
+ end
87
+ end
88
+ end
89
+
90
+
91
+ describe "#remove_unreflected_preloads!" do
92
+ before do
93
+ stub(preloader).dedupe!
94
+
95
+ # This is a little bit contortionist, but this is the only way to set
96
+ # this without running dedupe!
97
+ preloader.instance_variable_set(:@valid_preloads, { users: [], posts: [] })
98
+ preloader.send(:remove_unreflected_preloads!)
99
+ end
100
+
101
+ context "when preload in the list of reflections" do
102
+ let(:reflections) { { "users" => [], "posts" => [] } }
103
+
104
+ it "keeps it" do
105
+ expect(preloader.valid_preloads.keys).to eq [:users, :posts]
106
+ end
107
+ end
108
+
109
+ context "when preload not in the list of reflections" do
110
+ let(:reflections) { { "users" => [] } }
111
+
112
+ it "rejects it" do
113
+ expect(preloader.valid_preloads.keys).to eq [:users]
114
+ expect(preloader.valid_preloads.keys).not_to include :posts
115
+ end
116
+ end
117
+ end
118
+
119
+
120
+ describe "#preload!" do
121
+ let(:preload_method) { Object.new }
122
+ let(:valid_preloads) { { users: [], posts: [] } }
123
+
124
+ before do
125
+ preloader.instance_variable_set(:@valid_preloads, valid_preloads)
126
+ preloader.preload_method = preload_method
127
+ end
128
+
129
+ it "calls the preload method with its models and valid preloads" do
130
+ mock(preload_method).call(is_a(Array), is_a(Hash)) do |*args|
131
+ expect(args).to eq [models, valid_preloads]
132
+ end
133
+
134
+ preloader.send(:preload!)
135
+ end
136
+ end
137
+ end
@@ -1,12 +1,7 @@
1
1
  require 'spec_helper'
2
- require 'spec_helpers/presenters'
3
2
 
4
3
  describe Brainstem::PresenterCollection do
5
4
  before do
6
- UserPresenter.presents User
7
- TaskPresenter.presents Task
8
- WorkspacePresenter.presents Workspace
9
- PostPresenter.presents Post
10
5
  @presenter_collection = Brainstem.presenter_collection
11
6
  end
12
7
 
@@ -22,81 +17,95 @@ describe Brainstem::PresenterCollection do
22
17
  end
23
18
 
24
19
  it "has a global per_page default" do
25
- expect(@presenter_collection.presenting("workspaces") { Workspace.order('id desc') }[:workspaces].length).to eq(2)
20
+ expect(@presenter_collection.presenting("workspaces") { Workspace.unscoped }['workspaces'].length).to eq(2)
26
21
  end
27
22
 
28
23
  it "will not accept a per_page less than 1" do
29
- expect(@presenter_collection.presenting("workspaces", :params => { :per_page => 0 }) { Workspace.order('id desc') }[:workspaces].length).to eq(2)
30
- expect(@presenter_collection.presenting("workspaces", :per_page => 0) { Workspace.order('id desc') }[:workspaces].length).to eq(2)
24
+ expect(@presenter_collection.presenting("workspaces", :params => { :per_page => 0 }) { Workspace.unscoped }['workspaces'].length).to eq(2)
25
+ expect(@presenter_collection.presenting("workspaces", :per_page => 0) { Workspace.unscoped }['workspaces'].length).to eq(2)
31
26
  end
32
27
 
33
28
  it "will accept strings" do
34
- struct = @presenter_collection.presenting("workspaces", :params => { :per_page => "1", :page => "2" }) { Workspace.order('id desc') }
35
- expect(struct[:results].first[:id]).to eq(Workspace.order('id desc')[1].id.to_s)
29
+ struct = @presenter_collection.presenting("workspaces", :params => { :per_page => "1", :page => "2" }) { Workspace.unscoped }
30
+ expect(struct['results'].first['id']).to eq(Workspace.unscoped[1].id.to_s)
36
31
  end
37
32
 
38
33
  it "has a global max_per_page default" do
39
- expect(@presenter_collection.presenting("workspaces", :params => { :per_page => 5 }) { Workspace.order('id desc') }[:workspaces].length).to eq(3)
34
+ expect(@presenter_collection.presenting("workspaces", :params => { :per_page => 5 }) { Workspace.unscoped }['workspaces'].length).to eq(3)
40
35
  end
41
36
 
42
37
  it "takes a configurable default page size and max page size" do
43
- expect(@presenter_collection.presenting("workspaces", :params => { :per_page => 5 }, :max_per_page => 4) { Workspace.order('id desc') }[:workspaces].length).to eq(4)
38
+ expect(@presenter_collection.presenting("workspaces", :params => { :per_page => 5 }, :max_per_page => 4) { Workspace.unscoped }['workspaces'].length).to eq(4)
44
39
  end
45
40
 
46
41
  describe "limits and offsets" do
47
42
  context "when only per_page and page are present" do
48
43
  it "honors the user's requested page size and page and returns counts" do
49
- result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 2 }) { Workspace.order('id desc') }[:results]
44
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 2 }) { Workspace.unscoped }['results']
50
45
  expect(result.length).to eq(1)
51
- expect(result.first[:id]).to eq(Workspace.order('id desc')[1].id.to_s)
46
+ expect(result.first['id']).to eq(Workspace.unscoped[1].id.to_s)
52
47
 
53
- result = @presenter_collection.presenting("workspaces", :params => { :per_page => 2, :page => 2 }) { Workspace.order('id desc') }[:results]
48
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 2, :page => 2 }) { Workspace.unscoped }['results']
54
49
  expect(result.length).to eq(2)
55
- expect(result.map { |m| m[:id] }).to eq(Workspace.order('id desc')[2..3].map(&:id).map(&:to_s))
50
+ expect(result.map { |m| m['id'] }).to eq(Workspace.unscoped[2..3].map(&:id).map(&:to_s))
56
51
  end
57
52
 
58
53
  it "defaults to 1 if the page number is less than 1" do
59
- result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 0 }) { Workspace.order('id desc') }[:results]
54
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 0 }) { Workspace.unscoped }['results']
60
55
  expect(result.length).to eq(1)
61
- expect(result.first[:id]).to eq(Workspace.order('id desc')[0].id.to_s)
56
+ expect(result.first['id']).to eq(Workspace.unscoped[0].id.to_s)
57
+ end
58
+
59
+ it "restricts the per_page to options[:max_per_page], if provided, otherwise default_max_per_page" do
60
+ result = @presenter_collection.presenting("workspaces", :max_per_page => 5, :params => { :per_page => 10000, :page => 1 }) { Workspace.unscoped }['results']
61
+ expect(result.length).to eq(5)
62
+
63
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 10000, :page => 1 }) { Workspace.unscoped }['results']
64
+ expect(result.length).to eq(3)
62
65
  end
63
66
  end
64
67
 
65
68
  context "when only limit and offset are present" do
66
69
  it "honors the user's requested limit and offset and returns counts" do
67
- result = @presenter_collection.presenting("workspaces", :params => { :limit => 1, :offset => 2 }) { Workspace.order('id desc') }[:results]
70
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 1, :offset => 2 }) { Workspace.unscoped }['results']
68
71
  expect(result.length).to eq(1)
69
- expect(result.first[:id]).to eq(Workspace.order('id desc')[2].id.to_s)
72
+ expect(result.first['id']).to eq(Workspace.unscoped[2].id.to_s)
70
73
 
71
- result = @presenter_collection.presenting("workspaces", :params => { :limit => 2, :offset => 2 }) { Workspace.order('id desc') }[:results]
74
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 2, :offset => 2 }) { Workspace.unscoped }['results']
72
75
  expect(result.length).to eq(2)
73
- expect(result.map { |m| m[:id] }).to eq(Workspace.order('id desc')[2..3].map(&:id).map(&:to_s))
76
+ expect(result.map { |m| m['id'] }).to eq(Workspace.unscoped[2..3].map(&:id).map(&:to_s))
74
77
  end
75
78
 
76
79
  it "defaults to offset 0 if the passed offset is less than 0 and limit to 1 if the passed limit is less than 1" do
77
- stub.proxy(@presenter_collection).calculate_offset(anything).times(1)
78
- stub.proxy(@presenter_collection).calculate_limit(anything).times(1)
79
- result = @presenter_collection.presenting("workspaces", :params => { :limit => -1, :offset => -1 }) { Workspace.order('id desc') }[:results]
80
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => -1, :offset => -1 }) { Workspace.unscoped }['results']
80
81
  expect(result.length).to eq(1)
81
- expect(result.first[:id]).to eq(Workspace.order('id desc')[0].id.to_s)
82
+ expect(result.first['id']).to eq(Workspace.unscoped[0].id.to_s)
83
+ end
84
+
85
+ it "restricts the limit to options[:max_per_page], if provided, otherwise default_max_per_page" do
86
+ result = @presenter_collection.presenting("workspaces", :max_per_page => 5, :params => { :limit => 100, :offset => 0 }) { Workspace.unscoped }['results']
87
+ expect(result.length).to eq(5)
88
+
89
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 100, :offset => 0 }) { Workspace.unscoped }['results']
90
+ expect(result.length).to eq(3)
82
91
  end
83
92
  end
84
93
 
85
94
  context "when both sets of params are present" do
86
95
  it "prefers limit and offset over per_page and page" do
87
- result = @presenter_collection.presenting("workspaces", :params => { :limit => 1, :offset => 0, :per_page => 2, :page => 2 }) { Workspace.order('id desc') }[:results]
96
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 1, :offset => 0, :per_page => 2, :page => 2 }) { Workspace.unscoped }['results']
88
97
  expect(result.length).to eq(1)
89
- expect(result.first[:id]).to eq(Workspace.order('id desc')[0].id.to_s)
98
+ expect(result.first['id']).to eq(Workspace.unscoped[0].id.to_s)
90
99
  end
91
100
 
92
101
  it "uses per_page and page if limit and offset are not complete" do
93
- result = @presenter_collection.presenting("workspaces", :params => { :limit => 5, :per_page => 1, :page => 0 }) { Workspace.order('id desc') }[:results]
102
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 5, :per_page => 1, :page => 0 }) { Workspace.unscoped }['results']
94
103
  expect(result.length).to eq(1)
95
- expect(result.first[:id]).to eq(Workspace.order('id desc')[0].id.to_s)
104
+ expect(result.first['id']).to eq(Workspace.unscoped[0].id.to_s)
96
105
 
97
- result = @presenter_collection.presenting("workspaces", :params => { :offset => 5, :per_page => 1, :page => 0 }) { Workspace.order('id desc') }[:results]
106
+ result = @presenter_collection.presenting("workspaces", :params => { :offset => 5, :per_page => 1, :page => 0 }) { Workspace.unscoped }['results']
98
107
  expect(result.length).to eq(1)
99
- expect(result.first[:id]).to eq(Workspace.order('id desc')[0].id.to_s)
108
+ expect(result.first['id']).to eq(Workspace.unscoped[0].id.to_s)
100
109
  end
101
110
  end
102
111
  end
@@ -124,7 +133,7 @@ describe Brainstem::PresenterCollection do
124
133
  expect(Workspace.count).to be > 0
125
134
 
126
135
  expect {
127
- @presenter_collection.presenting("workspaces", :raise_on_empty => true) { Workspace.order('id desc') }
136
+ @presenter_collection.presenting("workspaces", :raise_on_empty => true) { Workspace.unscoped }
128
137
  }.not_to raise_error
129
138
  end
130
139
  end
@@ -132,7 +141,7 @@ describe Brainstem::PresenterCollection do
132
141
 
133
142
  context "raise_on_empty is false" do
134
143
  it "should not raise an exception when the results are empty" do
135
- expect {
144
+ expect {
136
145
  @presenter_collection.presenting("workspaces") { Workspace.where(:id => nil) }
137
146
  }.not_to raise_error
138
147
  end
@@ -146,84 +155,146 @@ describe Brainstem::PresenterCollection do
146
155
  end
147
156
 
148
157
  it "returns the unique count by model id" do
149
- result = @presenter_collection.presenting("workspaces", :params => { :per_page => 2, :page => 1 }) { Workspace.order('id desc') }
150
- expect(result[:count]).to eq(Workspace.count)
158
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 2, :page => 1 }) { Workspace.unscoped }
159
+ expect(result['count']).to eq(Workspace.count)
160
+ end
161
+ end
162
+ end
163
+
164
+ describe 'strategies' do
165
+ let(:params) { { search: "tomato" } }
166
+
167
+ context 'the user does not specify a strategy with the presenter DSL' do
168
+ it 'uses the legacy FilterOrSearch strategy' do
169
+ mock.proxy(Brainstem::QueryStrategies::FilterOrSearch).new(anything).times(1)
170
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
171
+ expect(result['workspaces'].length).to eq(Workspace.count)
172
+ end
173
+ end
174
+
175
+ context 'the user specifies the filter_and_search strategy as a symbol' do
176
+ before do
177
+ WorkspacePresenter.query_strategy :filter_and_search
178
+ end
179
+
180
+ context 'the user is searching' do
181
+ before do
182
+ WorkspacePresenter.search do |string|
183
+ [[5, 3], 2]
184
+ end
185
+ end
186
+
187
+ context 'the scope size is below default_max_filter_and_search_page' do
188
+ before do
189
+ @presenter_collection.default_max_filter_and_search_page = 500
190
+ end
191
+
192
+ it 'uses the FilterAndSearch strategy' do
193
+ mock.proxy(Brainstem::QueryStrategies::FilterAndSearch).new(anything).times(1)
194
+ result = @presenter_collection.presenting("workspaces", params: params) { Workspace.unscoped }
195
+ expect(result['workspaces'].length).to eq(2)
196
+ end
197
+ end
198
+
199
+ context 'the scope size is above default_max_filter_and_search_page' do
200
+ before do
201
+ @presenter_collection.default_max_filter_and_search_page = 2
202
+ end
203
+
204
+ # TODO: this will become the third, faster strategy for large pagesizes
205
+ it 'uses the FilterOrSearch strategy' do
206
+ mock.proxy(Brainstem::QueryStrategies::FilterOrSearch).new(anything).times(1)
207
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
208
+ expect(result['workspaces'].length).to eq(Workspace.count)
209
+ end
210
+ end
211
+ end
212
+
213
+ context 'the user is not searching' do
214
+ it 'uses the legacy FilterOrSearch strategy' do
215
+ mock.proxy(Brainstem::QueryStrategies::FilterOrSearch).new(anything).times(1)
216
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
217
+ expect(result['workspaces'].length).to eq(Workspace.count)
218
+ end
219
+ end
220
+ end
221
+
222
+ context 'the user passes a lambda as the query_strategy' do
223
+ before do
224
+ WorkspacePresenter.query_strategy lambda { :filter_and_search }
225
+
226
+ WorkspacePresenter.search do |string|
227
+ [[5, 3], 2]
228
+ end
229
+ end
230
+
231
+ it 'uses the strategy returned by that lambda' do
232
+ mock.proxy(Brainstem::QueryStrategies::FilterAndSearch).new(anything).times(1)
233
+ result = @presenter_collection.presenting("workspaces", params: params) { Workspace.unscoped }
234
+ expect(result['workspaces'].length).to eq(2)
151
235
  end
152
236
  end
153
237
  end
154
238
 
155
239
  describe "uses presenters" do
156
240
  it "finds presenter by table name string" do
157
- result = @presenter_collection.presenting("workspaces") { Workspace.order('id desc') }
158
- expect(result[:workspaces].length).to eq(Workspace.count)
241
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
242
+ expect(result['workspaces'].length).to eq(Workspace.count)
159
243
  end
160
244
 
161
245
  it "finds presenter by model name string" do
162
- result = @presenter_collection.presenting("Workspace") { order('id desc') }
163
- expect(result[:workspaces].length).to eq(Workspace.count)
246
+ result = @presenter_collection.presenting("Workspace") { Workspace.unscoped }
247
+ expect(result['workspaces'].length).to eq(Workspace.count)
164
248
  end
165
249
 
166
250
  it "finds presenter by model" do
167
- result = @presenter_collection.presenting(Workspace) { order('id desc') }
168
- expect(result[:workspaces].length).to eq(Workspace.count)
169
- end
170
-
171
- it "infers the table name from the model" do
172
- result = @presenter_collection.presenting("not_workspaces", :model => "Workspace", :params => { :per_page => 2, :page => 1 }) { Workspace.order('id desc') }
173
- expect(result[:not_workspaces]).not_to be_empty
174
- expect(result[:count]).to eq(Workspace.count)
251
+ result = @presenter_collection.presenting(Workspace) { Workspace.unscoped }
252
+ expect(result['workspaces'].length).to eq(Workspace.count)
175
253
  end
176
254
  end
177
255
 
178
256
  describe "the 'results' top level key" do
179
257
  it "comes back with an explicit list of the matching results" do
180
258
  structure = @presenter_collection.presenting("workspaces", :params => { :include => "tasks" }, :max_per_page => 2) { Workspace.where(:id => 1) }
181
- expect(structure.keys).to match_array([:workspaces, :tasks, :count, :results])
182
- expect(structure[:results]).to eq(Workspace.where(:id => 1).limit(2).map {|w| { :key => "workspaces", :id => w.id.to_s } })
183
- expect(structure[:workspaces].keys).to eq(%w[1])
259
+ expect(structure.keys).to match_array %w[workspaces tasks count results]
260
+ expect(structure['results']).to eq(Workspace.where(:id => 1).limit(2).map {|w| { 'key' => 'workspaces', 'id' => w.id.to_s } })
261
+ expect(structure['workspaces'].keys).to eq(%w[1])
184
262
  end
185
263
  end
186
264
 
187
265
  describe "includes" do
188
266
  it "reads allowed includes from the presenter" do
189
- result = @presenter_collection.presenting("workspaces", :params => { :include => "drop table,tasks,users" }) { Workspace.order('id desc') }
190
- expect(result.keys).to match_array([:count, :workspaces, :tasks, :results])
267
+ 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]
191
269
 
192
- result = @presenter_collection.presenting("workspaces", :params => { :include => "foo,tasks,lead_user" }) { Workspace.order('id desc') }
193
- expect(result.keys).to match_array([:count, :workspaces, :tasks, :users, :results])
194
- end
195
-
196
- it "allows the allowed includes list to have different json names and association names" do
197
- result = @presenter_collection.presenting("tasks",
198
- :params => { :include => "other_tasks" }) { Task.order('id desc') }
199
- expect(result[:tasks]).to be_present
200
- expect(result[:other_tasks]).to be_present
270
+ 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]
201
272
  end
202
273
 
203
274
  it "defaults to not include any allowed includes" do
204
275
  tasked_workspace = Task.first
205
276
  result = @presenter_collection.presenting("workspaces", :max_per_page => 2) { Workspace.where(:id => tasked_workspace.workspace_id) }
206
- expect(result[:workspaces].keys).to eq([ tasked_workspace.workspace_id.to_s ])
207
- expect(result[:tasks]).to be_nil
277
+ expect(result['workspaces'].keys).to eq([ tasked_workspace.workspace_id.to_s ])
278
+ expect(result['tasks']).to be_nil
208
279
  end
209
280
 
210
281
  it "loads has_many associations and returns them when requested" do
211
282
  result = @presenter_collection.presenting("workspaces", :params => { :include => "tasks" }, :max_per_page => 2) { Workspace.where(:id => 1) }
212
- expect(result[:tasks].keys).to match_array(Workspace.first.tasks.map(&:id).map(&:to_s))
213
- expect(result[:workspaces]["1"][:task_ids]).to match_array(Workspace.first.tasks.map(&:id).map(&:to_s))
283
+ expect(result['tasks'].keys).to match_array(Workspace.first.tasks.map(&:id).map(&:to_s))
284
+ expect(result['workspaces']['1']['task_ids']).to match_array(Workspace.first.tasks.map(&:id).map(&:to_s))
214
285
  end
215
286
 
216
287
  it "returns appropriate fields" do
217
288
  result = @presenter_collection.presenting("workspaces",
218
289
  :params => { :include => "tasks" },
219
290
  :max_per_page => 2) { Workspace.where(:id => 1) }
220
- expect(result[:workspaces].values.first).to have_key(:description)
221
- expect(result[:tasks].values.first).to have_key(:name)
291
+ expect(result['workspaces'].values.first).to have_key('description')
292
+ expect(result['tasks'].values.first).to have_key('name')
222
293
  end
223
294
 
224
295
  it "loads belongs_tos and returns them when requested" do
225
296
  result = @presenter_collection.presenting("tasks", :params => { :include => "workspace" }, :max_per_page => 2) { Task.where(:id => 1) }
226
- expect(result[:workspaces].keys).to eq(%w[1])
297
+ expect(result['workspaces'].keys).to eq(%w[1])
227
298
  end
228
299
 
229
300
  it "doesn't return nils when belong_tos are missing" do
@@ -231,107 +302,123 @@ describe Brainstem::PresenterCollection do
231
302
  t.update_attribute :workspace, nil
232
303
  expect(t.reload.workspace).to be_nil
233
304
  result = @presenter_collection.presenting("tasks", :params => { :include => "workspace" }, :max_per_page => 2) { Task.where(:id => t.id) }
234
- expect(result[:tasks].keys).to eq([ t.id.to_s ])
235
- expect(result[:workspaces]).to eq({})
236
- expect(result.keys).to match_array([:tasks, :workspaces, :count, :results])
305
+ expect(result['tasks'].keys).to eq([ t.id.to_s ])
306
+ expect(result['workspaces']).to eq({})
307
+ expect(result.keys).to match_array %w[tasks workspaces count results]
237
308
  end
238
309
 
239
- it "returns sensible data when including something of the same type as the primary model" do
240
- result = @presenter_collection.presenting("tasks", :params => { :include => "sub_tasks" }) { Task.where(:id => 2) }
241
- sub_task_ids = Task.find(2).sub_tasks.map(&:id).map(&:to_s)
242
- expect(result[:tasks].keys).to match_array(sub_task_ids + ["2"])
243
- expect(result[:tasks]["2"][:sub_task_ids]).to eq(sub_task_ids) # The primary should have a sub_story_ids array.
244
- expect(result[:tasks][sub_task_ids.first][:sub_task_ids]).not_to be_present # Sub stories should not have a sub_story_ids array.
310
+ context 'when including something of the same type as the primary model' do
311
+ it "returns sensible data" do
312
+ result = @presenter_collection.presenting("tasks", :params => { :include => "sub_tasks" }) { Task.where(:id => 2) }
313
+ sub_task_ids = Task.find(2).sub_tasks.map(&:id).map(&:to_s)
314
+ expect(result['tasks'].keys).to match_array(sub_task_ids + ["2"])
315
+ expect(result['tasks']['2']['sub_task_ids']).to eq(sub_task_ids) # The primary should have a sub_story_ids array.
316
+ expect(result['tasks'][sub_task_ids.first]).not_to have_key('sub_task_ids') # Sub stories should not have a sub_story_ids array.
317
+ end
318
+
319
+ it 'uses the loaded data for the primary request when a model is found both in the primary set and in an inclusion' do
320
+ result = @presenter_collection.presenting("tasks", :params => { :include => "sub_tasks" }) { Task.all }
321
+ sub_task_ids = Task.find(2).sub_tasks.map(&:id).map(&:to_s)
322
+ expect(result['tasks']['2']['sub_task_ids']).to eq(sub_task_ids) # Sub stories were all loaded in the primary request this time,
323
+ expect(result['tasks'][sub_task_ids.first]).to have_key('sub_task_ids') # and should have associations loaded.
324
+ expect(result['tasks'][sub_task_ids.first]['sub_task_ids']).to eq []
325
+ end
245
326
  end
246
327
 
247
328
  it "includes requested includes even when all records are filtered" do
248
- result = @presenter_collection.presenting("workspaces", :params => { :only => "not an id", :include => "not an include,tasks" }) { Workspace.order("id desc") }
249
- expect(result[:workspaces].length).to eq(0)
250
- expect(result[:tasks].length).to eq(0)
329
+ result = @presenter_collection.presenting("workspaces", :params => { :only => "not an id", :include => "not an include,tasks" }) { Workspace.unscoped }
330
+ expect(result['workspaces'].length).to eq(0)
331
+ expect(result['tasks'].length).to eq(0)
251
332
  end
252
333
 
253
334
  it "includes requested includes even when the scope has no records" do
254
335
  expect(Workspace.where(:id => 123456789)).to be_empty
255
336
  result = @presenter_collection.presenting("workspaces", :params => { :include => "not an include,tasks" }) { Workspace.where(:id => 123456789) }
256
- expect(result[:workspaces].length).to eq(0)
257
- expect(result[:tasks].length).to eq(0)
337
+ expect(result['workspaces'].length).to eq(0)
338
+ expect(result['tasks'].length).to eq(0)
258
339
  end
259
340
 
260
- it "preloads associations when they are full model-level associations" do
261
- # Here, primary_maven is a method on Workspace, not a true association.
262
- mock(Brainstem::PresenterCollection).preload(anything, [:tasks])
263
- result = @presenter_collection.presenting("workspaces", :params => { :include => "tasks" }) { Workspace.order('id desc') }
264
- expect(result[:tasks].length).to be > 0
265
- end
266
-
267
- it "works with model methods that load records (but without preloading)" do
268
- result = @presenter_collection.presenting("workspaces", :params => { :include => "lead_user" }) { Workspace.order('id desc') }
269
- expect(result[:workspaces][Workspace.first.id.to_s]).to be_present
270
- expect(result[:users][Workspace.first.lead_user.id.to_s]).to be_present
341
+ it "works with model methods that load records" do
342
+ result = @presenter_collection.presenting("workspaces", :params => { :include => "lead_user" }) { Workspace.unscoped }
343
+ expect(result['workspaces'][Workspace.first.id.to_s]).to be_present
344
+ expect(result['workspaces'][Workspace.first.id.to_s]['lead_user_id']).to eq Workspace.first.lead_user.id.to_s
345
+ expect(result['users'][Workspace.first.lead_user.id.to_s]).to be_present
271
346
  end
272
347
 
273
348
  it "can accept a lambda for the association and uses that when present" do
274
349
  result = @presenter_collection.presenting("users", :params => { :include => "odd_workspaces" }) { User.where(:id => 1) }
275
- expect(result[:odd_workspaces][Workspace.first.id.to_s]).to be_present
276
- expect(result[:users][Workspace.first.lead_user.id.to_s]).to be_present
350
+ expect(result['users'][User.first.id.to_s]).to be_present
351
+ odd_workspace_ids = User.first.workspaces.select { |w| w.id % 2 == 1 }.map(&:id).map(&:to_s)
352
+ expect(result['users'][User.first.id.to_s]['odd_workspace_ids']).to eq odd_workspace_ids
353
+ expect(result['workspaces'].keys).to eq odd_workspace_ids
277
354
  end
278
355
 
279
356
  describe "restricted associations" do
280
357
  it "does apply includes that are restricted to only queries in an only query" do
281
358
  t = Task.first
282
359
  result = @presenter_collection.presenting("tasks", :params => { :include => "restricted", :only => t.id.to_s }, :max_per_page => 2) { Task.where(:id => t.id) }
283
- expect(result[:tasks][t.id.to_s].keys).to include(:restricted_id)
284
- expect(result.keys).to include(:restricted_associations)
360
+ expect(result['tasks'][t.id.to_s].keys).to include('restricted_id')
361
+ expect(result['tasks'][Task.last.id.to_s]).to be_present
285
362
  end
286
363
 
287
364
  it "does not apply includes that are restricted to only queries in a non-only query" do
288
365
  t = Task.first
289
366
  result = @presenter_collection.presenting("tasks", :params => { :include => "restricted" }, :max_per_page => 2) { Task.where(:id => t.id) }
290
367
 
291
- expect(result[:tasks][t.id.to_s].keys).not_to include(:restricted_id)
292
- expect(result.keys).not_to include(:restricted_associations)
368
+ expect(result['tasks'][t.id.to_s].keys).not_to include('restricted_id')
369
+ expect(result['tasks'][Task.last.id.to_s]).not_to be_present
293
370
  end
294
371
  end
295
372
 
296
373
  describe "polymorphic associations" do
297
374
  it "works with polymorphic associations" do
298
- result = @presenter_collection.presenting("posts", :params => { :include => "subject" }) { Post.order('id desc') }
299
- expect(result[:posts][Post.first.id.to_s]).to be_present
300
- expect(result[:workspaces][Workspace.first.id.to_s]).to be_present
301
- expect(result[:tasks][Task.first.id.to_s]).to be_present
375
+ result = @presenter_collection.presenting("posts", :params => { :include => "subject" }) { Post.unscoped }
376
+ expect(result['posts'][Post.first.id.to_s]).to be_present
377
+ expect(result['workspaces'][Workspace.first.id.to_s]).to be_present
378
+ expect(result['tasks'][Task.first.id.to_s]).to be_present
379
+ expect(result['posts']['1']['subject_ref']).to eq({ 'id' => '1', 'key' => 'workspaces' })
380
+ expect(result['posts']['2']['subject_ref']).to eq({ 'id' => '1', 'key' => 'tasks' })
381
+ end
382
+
383
+ it "uses the correct brainstem_key from the associated presenter" do
384
+ result = @presenter_collection.presenting("posts", :params => { :include => "attachments" }) { Post.unscoped }
385
+ expect(result['posts']['1']).to be_present
386
+ expect(result['attachments']['1']).to be_present
387
+ expect(result['posts']['1']['attachment_ids']).to eq ['1']
388
+ expect(result['attachments']['1']).to_not have_key('subject_ref')
302
389
  end
303
390
 
304
391
  it "does not return an empty hash when none are found" do
305
392
  result = @presenter_collection.presenting("posts", :params => { :include => "subject" }) { Post.where(:id => nil) }
306
- expect(result).to have_key(:posts)
307
- expect(result).not_to have_key(:workspaces)
308
- expect(result).not_to have_key(:tasks)
393
+ expect(result).to have_key('posts')
394
+ expect(result).not_to have_key('workspaces')
395
+ expect(result).not_to have_key('tasks')
309
396
  end
310
397
  end
311
398
  end
312
399
 
313
400
  describe "handling of only" do
314
401
  it "accepts params[:only] as a list of ids to limit to" do
315
- result = @presenter_collection.presenting("workspaces", :params => { :only => Workspace.limit(2).pluck(:id).join(",") }) { Workspace.order("id desc") }
316
- expect(result[:workspaces].keys).to match_array(Workspace.limit(2).pluck(:id).map(&:to_s))
402
+ result = @presenter_collection.presenting("workspaces", :params => { :only => Workspace.limit(2).pluck(:id).join(",") }) { Workspace.unscoped }
403
+ expect(result['workspaces'].keys).to match_array(Workspace.limit(2).pluck(:id).map(&:to_s))
317
404
  end
318
405
 
319
406
  it "does not paginate only requests" do
320
407
  dont_allow(@presenter_collection).paginate
321
- @presenter_collection.presenting("workspaces", :params => { :only => Workspace.limit(2).pluck(:id).join(",") }) { Workspace.order("id desc") }
408
+ @presenter_collection.presenting("workspaces", :params => { :only => Workspace.limit(2).pluck(:id).join(",") }) { Workspace.unscoped }
322
409
  end
323
410
 
324
411
  it "escapes ids" do
325
- result = @presenter_collection.presenting("workspaces", :params => { :only => "#{Workspace.first.id}foo,;drop tables;,#{Workspace.first.id}" }) { Workspace.order("id desc") }
326
- expect(result[:workspaces].length).to eq(1)
412
+ result = @presenter_collection.presenting("workspaces", :params => { :only => "#{Workspace.first.id}foo,;drop tables;,#{Workspace.first.id}" }) { Workspace.unscoped }
413
+ expect(result['workspaces'].length).to eq(1)
327
414
  end
328
415
 
329
416
  it "only runs when it receives ids" do
330
- result = @presenter_collection.presenting("workspaces", :params => { :only => "" }) { Workspace.order("id desc") }
331
- expect(result[:workspaces].length).to be > 1
417
+ result = @presenter_collection.presenting("workspaces", :params => { :only => "" }) { Workspace.unscoped }
418
+ expect(result['workspaces'].length).to be > 1
332
419
 
333
- result = @presenter_collection.presenting("workspaces", :params => { :only => "1" }) { Workspace.order("id desc") }
334
- expect(result[:workspaces].length).to be <= 1
420
+ result = @presenter_collection.presenting("workspaces", :params => { :only => "1" }) { Workspace.unscoped }
421
+ expect(result['workspaces'].length).to be <= 1
335
422
  end
336
423
  end
337
424
 
@@ -342,72 +429,91 @@ describe Brainstem::PresenterCollection do
342
429
  end
343
430
 
344
431
  it "limits records to those matching given filters" do
345
- result = @presenter_collection.presenting("workspaces", :params => { :owned_by => bob.id.to_s }) { Workspace.order("id desc") } # hit the API, filtering on owned_by:bob
346
- expect(result[:workspaces]).to be_present
347
- expect(result[:workspaces].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }).to be_truthy # all of the returned workspaces should contain bob
432
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by => bob.id.to_s }) { Workspace.unscoped } # hit the API, filtering on owned_by:bob
433
+ expect(result['workspaces']).to be_present
434
+ expect(result['workspaces'].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }).to be_truthy # all of the returned workspaces should contain bob
348
435
  end
349
436
 
350
437
  it "returns all records if filters are not given" do
351
- result = @presenter_collection.presenting("workspaces") { Workspace.order("id desc") } # hit the API again, this time not filtering on anything
352
- expect(result[:workspaces].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }).to be_falsey # the returned workspaces no longer all contain bob
438
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped } # hit the API again, this time not filtering on anything
439
+ expect(result['workspaces'].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }).to be_falsey # the returned workspaces no longer all contain bob
353
440
  end
354
441
 
355
442
  it "ignores unknown filters" do
356
- result = @presenter_collection.presenting("workspaces", :params => { :wut => "is this?" }) { Workspace.order("id desc") }
357
- expect(result[:workspaces].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }).to be_falsey
443
+ result = @presenter_collection.presenting("workspaces", :params => { :wut => "is this?" }) { Workspace.unscoped }
444
+ expect(result['workspaces'].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }).to be_falsey
358
445
  end
359
446
 
360
447
  it "limits records to those matching all given filters" do
361
- result = @presenter_collection.presenting("workspaces", :params => { :owned_by => bob.id.to_s, :title => "bob workspace 1" }) { Workspace.order("id desc") } # try two filters
362
- expect(result[:results].first[:id]).to eq(Workspace.where(:title => "bob workspace 1").first.id.to_s)
448
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by => bob.id.to_s, :title => "bob workspace 1" }) { Workspace.unscoped } # try two filters
449
+ expect(result['results'].first['id']).to eq(Workspace.where(:title => "bob workspace 1").first.id.to_s)
363
450
  end
364
451
 
365
452
  it "converts boolean parameters from strings to booleans" do
366
- WorkspacePresenter.filter(:owned_by_bob) { |scope, boolean| boolean ? scope.where(:user_id => bob.id) : scope.where(:user_id => jane.id) }
453
+ jane_id = jane.id
454
+ bob_id = bob.id
455
+ WorkspacePresenter.filter(:owned_by_bob) { |scope, boolean| boolean ? scope.where(:user_id => bob_id) : scope.where(:user_id => jane_id) }
367
456
  result = @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => "false" }) { Workspace.where(nil) }
368
- expect(result[:workspaces].values.find { |workspace| workspace[:title].include?("jane") }).to be
369
- expect(result[:workspaces].values.find { |workspace| workspace[:title].include?("bob") }).not_to be
457
+ expect(result['workspaces'].values.find { |workspace| workspace['title'].include?("jane") }).to be
458
+ expect(result['workspaces'].values.find { |workspace| workspace['title'].include?("bob") }).not_to be
370
459
  end
371
460
 
372
461
  it "ensures arguments are strings if they are not arrays" do
373
- filter_was_run = false
374
- WorkspacePresenter.filter(:owned_by_bob) do |scope, string|
375
- filter_was_run = true
376
- expect(string).to be_a(String)
462
+ string = nil
463
+ WorkspacePresenter.filter(:owned_by_bob) do |scope, arg|
464
+ string = arg
377
465
  scope
378
466
  end
379
467
  @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => { :wut => "is this?" } }) { Workspace.where(nil) }
380
- expect(filter_was_run).to be_truthy
468
+ expect(string).to be_truthy
381
469
  end
382
470
 
383
471
  it "preserves array arguments" do
384
- filter_was_run = false
385
- WorkspacePresenter.filter(:owned_by_bob) do |scope, array|
386
- filter_was_run = true
387
- expect(array).to be_a(Array)
472
+ array = nil
473
+ WorkspacePresenter.filter(:owned_by_bob) do |scope, arg|
474
+ array = arg
388
475
  scope
389
476
  end
390
477
  @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => [1, 2] }) { Workspace.where(nil) }
391
- expect(filter_was_run).to be_truthy
478
+ expect(array).to be_a(Array)
392
479
  end
393
480
 
394
481
  it "allows filters to be called with false as an argument" do
395
482
  WorkspacePresenter.filter(:nothing) { |scope, bool| bool ? scope.where(:id => nil) : scope }
396
483
  result = @presenter_collection.presenting("workspaces", :params => { :nothing => "true" }) { Workspace.where(nil) }
397
- expect(result[:workspaces].length).to eq(0)
484
+ expect(result['workspaces'].length).to eq(0)
398
485
  result = @presenter_collection.presenting("workspaces", :params => { :nothing => "false" }) { Workspace.where(nil) }
399
- expect(result[:workspaces].length).not_to eq(0)
486
+ expect(result['workspaces'].length).not_to eq(0)
400
487
  end
401
488
 
402
489
  it "passes colon separated params through as a string" do
490
+ a, b = nil, nil
403
491
  WorkspacePresenter.filter(:between) { |scope, a_and_b|
404
492
  a, b = a_and_b.split(':')
405
- expect(a).to eq("1")
406
- expect(b).to eq("10")
407
493
  scope
408
494
  }
409
495
 
410
496
  @presenter_collection.presenting("workspaces", :params => { :between => "1:10" }) { Workspace.where(nil) }
497
+
498
+ expect(a).to eq("1")
499
+ expect(b).to eq("10")
500
+ end
501
+
502
+ it "provides helpers to the block" do
503
+ WorkspacePresenter.helper do
504
+ def some_method
505
+ end
506
+ end
507
+
508
+ called = false
509
+ WorkspacePresenter.filter(:something) { |scope, string|
510
+ called = true
511
+ some_method
512
+ scope
513
+ }
514
+
515
+ @presenter_collection.presenting("workspaces", :params => { :something => "hello" }) { Workspace.where(nil) }
516
+ expect(called).to eq true
411
517
  end
412
518
 
413
519
  context "with defaults" do
@@ -418,33 +524,53 @@ describe Brainstem::PresenterCollection do
418
524
  let(:jane) { User.where(:username => "jane").first }
419
525
 
420
526
  it "applies the filter when it is not requested" do
421
- result = @presenter_collection.presenting("workspaces") { Workspace.order('id desc') }
422
- expect(result[:workspaces].keys).to match_array(bob.workspaces.map(&:id).map(&:to_s))
527
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
528
+ expect(result['workspaces'].keys).to match_array(bob.workspaces.map(&:id).map(&:to_s))
423
529
  end
424
530
 
425
531
  it "allows falsy defaults" do
426
532
  WorkspacePresenter.filter(:include_early_workspaces, :default => false) { |scope, bool| bool ? scope : scope.where("id > 3") }
427
533
  result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
428
- expect(result[:workspaces]["2"]).not_to be_present
534
+ expect(result['workspaces']['2']).not_to be_present
429
535
  result = @presenter_collection.presenting("workspaces", :params => { :include_early_workspaces => "true" }) { Workspace.unscoped }
430
- expect(result[:workspaces]["2"]).to be_present
536
+ expect(result['workspaces']['2']).to be_present
431
537
  end
432
538
 
433
539
  it "allows defaults to be skipped if :apply_default_filters is false" do
434
540
  WorkspacePresenter.filter(:include_early_workspaces, :default => false) { |scope, bool| bool ? scope : scope.where("id > 3") }
435
541
  result = @presenter_collection.presenting("workspaces", :apply_default_filters => true) { Workspace.unscoped }
436
- expect(result[:workspaces]["2"]).not_to be_present
542
+ expect(result['workspaces']['2']).not_to be_present
437
543
  result = @presenter_collection.presenting("workspaces", :apply_default_filters => false) { Workspace.unscoped }
438
- expect(result[:workspaces]["2"]).to be_present
544
+ expect(result['workspaces']['2']).to be_present
545
+ end
546
+
547
+ it "allows defaults set to false to be skipped if params contain :apply_default_filters with a false value" do
548
+ WorkspacePresenter.filter(:include_early_workspaces, :default => false) { |scope, bool| bool ? scope : scope.where("id > 3") }
549
+
550
+ result = @presenter_collection.presenting("workspaces", :params => { :apply_default_filters => "true" }) { Workspace.unscoped }
551
+ expect(result['workspaces']['2']).not_to be_present
552
+
553
+ result = @presenter_collection.presenting("workspaces", :params => { :apply_default_filters => true }) { Workspace.unscoped }
554
+ expect(result['workspaces']['2']).not_to be_present
555
+ end
556
+
557
+ it "allows defaults set to true to be skipped if params contain :apply_default_filters with a false value" do
558
+ WorkspacePresenter.filter(:include_early_workspaces, :default => true) { |scope, bool| bool ? scope : scope.where("id > 3") }
559
+
560
+ result = @presenter_collection.presenting("workspaces", :params => { :apply_default_filters => "false" }) { Workspace.unscoped }
561
+ expect(result['workspaces']['2']).to be_present
562
+
563
+ result = @presenter_collection.presenting("workspaces", :params => { :apply_default_filters => false }) { Workspace.unscoped }
564
+ expect(result['workspaces']['2']).to be_present
439
565
  end
440
566
 
441
567
  it "allows the default value to be overridden" do
442
- result = @presenter_collection.presenting("workspaces", :params => { :owner => jane.id.to_s }) { Workspace.order('id desc') }
443
- expect(result[:workspaces].keys).to match_array(jane.workspaces.map(&:id).map(&:to_s))
568
+ result = @presenter_collection.presenting("workspaces", :params => { :owner => jane.id.to_s }) { Workspace.unscoped }
569
+ expect(result['workspaces'].keys).to match_array(jane.workspaces.map(&:id).map(&:to_s))
444
570
 
445
571
  WorkspacePresenter.filter(:include_early_workspaces, :default => true) { |scope, bool| bool ? scope : scope.where("id > 3") }
446
572
  result = @presenter_collection.presenting("workspaces", :params => { :include_early_workspaces => "false" }) { Workspace.unscoped }
447
- expect(result[:workspaces]["2"]).not_to be_present
573
+ expect(result['workspaces']['2']).not_to be_present
448
574
  end
449
575
  end
450
576
 
@@ -454,32 +580,49 @@ describe Brainstem::PresenterCollection do
454
580
 
455
581
  before do
456
582
  WorkspacePresenter.filter(:owned_by, :default => bob.id)
457
- WorkspacePresenter.presents("Workspace")
458
583
  end
459
584
 
460
585
  it "calls the named scope with default arguments" do
461
586
  result = @presenter_collection.presenting("workspaces") { Workspace.where(nil) }
462
- expect(result[:workspaces].keys).to eq(bob.workspaces.pluck(:id).map(&:to_s))
587
+ expect(result['workspaces'].keys).to eq(bob.workspaces.pluck(:id).map(&:to_s))
463
588
  end
464
589
 
465
590
  it "calls the named scope with given arguments" do
466
591
  result = @presenter_collection.presenting("workspaces", :params => { :owned_by => jane.id.to_s }) { Workspace.where(nil) }
467
- expect(result[:workspaces].keys).to eq(jane.workspaces.pluck(:id).map(&:to_s))
592
+ expect(result['workspaces'].keys).to eq(jane.workspaces.pluck(:id).map(&:to_s))
468
593
  end
469
594
 
470
595
  it "can use filters without lambdas in the presenter or model, but behaves strangely when false is given" do
471
596
  WorkspacePresenter.filter(:numeric_description)
472
597
 
473
598
  result = @presenter_collection.presenting("workspaces") { Workspace.where(nil) }
474
- expect(result[:workspaces].keys).to eq(%w[1 2 3 4])
599
+ expect(result['workspaces'].keys).to eq(%w[1 2 3 4])
475
600
 
476
601
  result = @presenter_collection.presenting("workspaces", :params => { :numeric_description => "true" }) { Workspace.where(nil) }
477
- expect(result[:workspaces].keys).to eq(%w[2 4])
602
+ expect(result['workspaces'].keys).to eq(%w[2 4])
478
603
 
479
604
  # This is probably not the behavior that the developer or user intends. You should always use a one-argument lambda in your
480
605
  # model scope declaration!
481
606
  result = @presenter_collection.presenting("workspaces", :params => { :numeric_description => "false" }) { Workspace.where(nil) }
482
- expect(result[:workspaces].keys).to eq(%w[2 4])
607
+ expect(result['workspaces'].keys).to eq(%w[2 4])
608
+ end
609
+ end
610
+
611
+ context "with include_params" do
612
+ it "passes the params into the filter block" do
613
+ WorkspacePresenter.filter(:other_filter) { |scope, opt| scope }
614
+ WorkspacePresenter.filter(:unused_filter) { |scope, opt| scope }
615
+ WorkspacePresenter.filter(:other_filter_with_default, default: true) { |scope, opt| scope }
616
+
617
+ provided_params = nil
618
+ WorkspacePresenter.filter :filter_with_param, :include_params => true do |scope, option, params|
619
+ provided_params = params
620
+ scope
621
+ end
622
+
623
+ @presenter_collection.presenting("workspaces", :params => { :filter_with_param => "arg", :other_filter => 'another_arg' }) { Workspace.where(nil) }
624
+
625
+ expect(provided_params).to eq({ "filter_with_param" => "arg", "other_filter" => "another_arg", "other_filter_with_default" => true })
483
626
  end
484
627
  end
485
628
  end
@@ -495,34 +638,41 @@ describe Brainstem::PresenterCollection do
495
638
 
496
639
  context "and a search request is made" do
497
640
  it "calls the search method and maintains the resulting order" do
498
- result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
499
- expect(result[:workspaces].keys).to eq(%w[5 3])
500
- expect(result[:count]).to eq(2)
641
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.unscoped }
642
+ expect(result['workspaces'].keys).to eq(%w[5 3])
643
+ expect(result['count']).to eq(2)
644
+ end
645
+
646
+ it "calls the search block in the context of helpers" do
647
+ called = false
648
+ WorkspacePresenter.search { |string|
649
+ current_user
650
+ called = true
651
+ [[5, 3], 2]
652
+ }
653
+
654
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.unscoped }
655
+ expect(called).to eq true
501
656
  end
502
657
 
503
658
  it "does not apply filters" do
504
- mock(@presenter_collection).run_filters(anything, anything).times(0)
505
- result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
659
+ mock(@presenter_collection).apply_filters_to_scope(anything, anything).times(0)
660
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.unscoped }
506
661
  end
507
662
 
508
663
  it "does not apply ordering" do
509
- mock(@presenter_collection).handle_ordering(anything, anything).times(0)
510
- result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
664
+ mock.any_instance_of(Brainstem::Presenter).apply_ordering_to_scope(anything, anything).times(0)
665
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.unscoped }
511
666
  end
512
667
 
513
668
  it "does not try to handle only's" do
514
669
  mock(@presenter_collection).handle_only(anything, anything).times(0)
515
- result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
670
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.unscoped }
516
671
  end
517
672
 
518
673
  it "does not apply pagination" do
519
674
  mock(@presenter_collection).paginate(anything, anything).times(0)
520
- result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
521
- end
522
-
523
- it "keeps the records in the order returned by search" do
524
675
  result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.unscoped }
525
-
526
676
  end
527
677
 
528
678
  it "throws a SearchUnavailableError if the search block returns false" do
@@ -535,155 +685,197 @@ describe Brainstem::PresenterCollection do
535
685
  }.to raise_error(Brainstem::SearchUnavailableError)
536
686
  end
537
687
 
688
+ context "when the search results give ids that cannot be found" do
689
+ before do
690
+ WorkspacePresenter.search do |string|
691
+ [[5, 8, 3], 3]
692
+ end
693
+ end
694
+
695
+ it "will generate a compacted list, without nil or 0 values" do
696
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
697
+ expect(result['workspaces'].keys).to eq(%w[5 3])
698
+ expect(result['count']).to eq(3)
699
+ end
700
+ end
701
+
538
702
  describe "passing options to the search block" do
539
703
  it "passes the search method, the search string, includes, order, and paging options" do
540
704
  WorkspacePresenter.filter(:owned_by) { |scope| scope }
541
- WorkspacePresenter.search do |string, options|
542
- expect(string).to eq("blah")
543
- expect(options[:include]).to eq(["tasks", "lead_user"])
544
- expect(options[:owned_by]).to eq(false)
545
- expect(options[:order][:sort_order]).to eq("description")
546
- expect(options[:order][:direction]).to eq("desc")
547
- expect(options[:page]).to eq(2)
548
- expect(options[:per_page]).to eq(5)
705
+ search_args = nil
706
+ WorkspacePresenter.search do |*args|
707
+ search_args = args
549
708
  [[1], 1] # returned ids, count - not testing this in this set of specs
550
709
  end
551
710
 
552
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :include => "tasks,lead_user", :owned_by => "false", :order => "description:desc", :page => 2, :per_page => 5 }) { Workspace.order("id asc") }
711
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :include => "tasks,lead_user", :owned_by => "false", :order => "description:desc", :page => 2, :per_page => 5 }) { Workspace.unscoped }
712
+
713
+ string, options = search_args
714
+ expect(string).to eq("blah")
715
+ expect(options[:include]).to eq(["tasks", "lead_user"])
716
+ expect(options[:owned_by]).to eq(false)
717
+ expect(options[:order][:sort_order]).to eq("description")
718
+ expect(options[:order][:direction]).to eq("desc")
719
+ expect(options[:page]).to eq(2)
720
+ expect(options[:per_page]).to eq(5)
553
721
  end
554
722
 
555
723
  describe "includes" do
556
724
  it "throws out requested inlcudes that the presenter does not have associations for" do
725
+ search_options = nil
557
726
  WorkspacePresenter.search do |string, options|
558
- expect(options[:include]).to eq([])
727
+ search_options = options
559
728
  [[1], 1]
560
729
  end
561
730
 
562
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :include => "users"}) { Workspace.order("id asc") }
731
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :include => "users"}) { Workspace.unscoped }
732
+ expect(search_options[:include]).to eq([])
563
733
  end
564
734
  end
565
735
 
566
736
  describe "filters" do
567
737
  it "passes through the default filters if no filter is requested" do
568
738
  WorkspacePresenter.filter(:owned_by, :default => true) { |scope| scope }
739
+ search_options = nil
569
740
  WorkspacePresenter.search do |string, options|
570
- expect(options[:owned_by]).to eq(true)
741
+ search_options = options
571
742
  [[1], 1]
572
743
  end
573
744
 
574
- @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
745
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.unscoped }
746
+ expect(search_options[:owned_by]).to eq(true)
575
747
  end
576
748
 
577
749
  it "throws out requested filters that the presenter does not have" do
750
+ search_options = nil
578
751
  WorkspacePresenter.search do |string, options|
579
- expect(options[:highest_rated]).to be_nil
752
+ search_options = options
580
753
  [[1], 1]
581
754
  end
582
755
 
583
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :highest_rated => true}) { Workspace.order("id asc") }
756
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :highest_rated => true}) { Workspace.unscoped }
757
+ expect(search_options[:highest_rated]).to be_nil
584
758
  end
585
759
 
586
760
  it "does not pass through existing non-default filters that are not requested" do
587
761
  WorkspacePresenter.filter(:owned_by) { |scope| scope }
762
+ search_options = nil
588
763
  WorkspacePresenter.search do |string, options|
589
- expect(options.has_key?(:owned_by)).to eq(false)
764
+ search_options = options
590
765
  [[1], 1]
591
766
  end
592
767
 
593
- @presenter_collection.presenting("workspaces", :params => { :search => "blah"}) { Workspace.order("id asc") }
768
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah"}) { Workspace.unscoped }
769
+ expect(search_options.has_key?(:owned_by)).to eq(false)
594
770
  end
595
771
  end
596
772
 
597
773
  describe "orders" do
598
774
  it "passes through the default sort order if no order is requested" do
599
775
  WorkspacePresenter.default_sort_order("description:desc")
776
+ search_options = nil
600
777
  WorkspacePresenter.search do |string, options|
601
- expect(options[:order][:sort_order]).to eq("description")
602
- expect(options[:order][:direction]).to eq("desc")
778
+ search_options = options
603
779
  [[1], 1]
604
780
  end
605
781
 
606
- @presenter_collection.presenting("workspaces", :params => { :search => "blah"}) { Workspace.order("id asc") }
782
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah"}) { Workspace.unscoped }
783
+ expect(search_options[:order][:sort_order]).to eq("description")
784
+ expect(search_options[:order][:direction]).to eq("desc")
607
785
  end
608
786
 
609
787
  it "makes the sort order 'updated_at:desc' if the requested order doesn't match an existing sort order and there is no default" do
788
+ search_options = nil
610
789
  WorkspacePresenter.search do |string, options|
611
- expect(options[:order][:sort_order]).to eq("updated_at")
612
- expect(options[:order][:direction]).to eq("desc")
790
+ search_options = options
613
791
  [[1], 1]
614
792
  end
615
793
 
616
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :order => "created_at:asc"}) { Workspace.order("id asc") }
794
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :order => "created_at:asc"}) { Workspace.unscoped }
795
+ expect(search_options[:order][:sort_order]).to eq("updated_at")
796
+ expect(search_options[:order][:direction]).to eq("desc")
617
797
  end
618
798
 
619
799
  it "sanitizes sort orders" do
800
+ search_options = nil
620
801
  WorkspacePresenter.search do |string, options|
621
- expect(options[:order][:sort_order]).to eq("description")
622
- expect(options[:order][:direction]).to eq("asc")
802
+ search_options = options
623
803
  [[1], 1]
624
804
  end
625
805
 
626
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :order => "description:owned"}) { Workspace.order("id asc") }
806
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :order => "description:owned"}) { Workspace.unscoped }
807
+ expect(search_options[:order][:sort_order]).to eq("description")
808
+ expect(search_options[:order][:direction]).to eq("asc")
627
809
  end
628
810
  end
629
811
 
630
812
  describe "pagination" do
631
813
  it "passes through limit and offset if they are requested" do
814
+ search_options = nil
632
815
  WorkspacePresenter.search do |string, options|
633
- expect(options[:limit]).to eq(1)
634
- expect(options[:offset]).to eq(2)
816
+ search_options = options
635
817
  [[1], 1]
636
818
  end
637
819
 
638
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :offset => 2}) { Workspace.order("id asc") }
820
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :offset => 2}) { Workspace.unscoped }
821
+ expect(search_options[:limit]).to eq(1)
822
+ expect(search_options[:offset]).to eq(2)
639
823
  end
640
824
 
641
825
  it "passes through only limit and offset if all pagination options are requested" do
826
+ search_options = nil
642
827
  WorkspacePresenter.search do |string, options|
643
- expect(options[:limit]).to eq(1)
644
- expect(options[:offset]).to eq(2)
645
- expect(options[:per_page]).to eq(nil)
646
- expect(options[:page]).to eq(nil)
828
+ search_options = options
647
829
  [[1], 1]
648
830
  end
649
831
 
650
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :offset => 2, :per_page => 3, :page => 4}) { Workspace.order("id asc") }
832
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :offset => 2, :per_page => 3, :page => 4}) { Workspace.unscoped }
833
+ expect(search_options[:limit]).to eq(1)
834
+ expect(search_options[:offset]).to eq(2)
835
+ expect(search_options[:per_page]).to eq(nil)
836
+ expect(search_options[:page]).to eq(nil)
651
837
  end
652
838
 
653
839
  it "passes through page and per_page when limit not present" do
840
+ search_options = nil
654
841
  WorkspacePresenter.search do |string, options|
655
- expect(options[:limit]).to eq(nil)
656
- expect(options[:offset]).to eq(nil)
657
- expect(options[:per_page]).to eq(3)
658
- expect(options[:page]).to eq(4)
842
+ search_options = options
659
843
  [[1], 1]
660
844
  end
661
845
 
662
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :offset => 2, :per_page => 3, :page => 4}) { Workspace.order("id asc") }
846
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :offset => 2, :per_page => 3, :page => 4}) { Workspace.unscoped }
847
+ expect(search_options[:limit]).to eq(nil)
848
+ expect(search_options[:offset]).to eq(nil)
849
+ expect(search_options[:per_page]).to eq(3)
850
+ expect(search_options[:page]).to eq(4)
663
851
  end
664
852
 
665
853
  it "passes through page and per_page when offset not present" do
854
+ search_options = nil
666
855
  WorkspacePresenter.search do |string, options|
667
- expect(options[:limit]).to eq(nil)
668
- expect(options[:offset]).to eq(nil)
669
- expect(options[:per_page]).to eq(3)
670
- expect(options[:page]).to eq(4)
856
+ search_options = options
671
857
  [[1], 1]
672
858
  end
673
859
 
674
- @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :per_page => 3, :page => 4}) { Workspace.order("id asc") }
860
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :per_page => 3, :page => 4}) { Workspace.unscoped }
861
+ expect(search_options[:limit]).to eq(nil)
862
+ expect(search_options[:offset]).to eq(nil)
863
+ expect(search_options[:per_page]).to eq(3)
864
+ expect(search_options[:page]).to eq(4)
675
865
  end
676
866
 
677
867
  it "passes through page and per_page by default" do
868
+ search_options = nil
678
869
  WorkspacePresenter.search do |string, options|
679
- expect(options[:limit]).to eq(nil)
680
- expect(options[:offset]).to eq(nil)
681
- expect(options[:per_page]).to eq(20)
682
- expect(options[:page]).to eq(1)
870
+ search_options = options
683
871
  [[1], 1]
684
872
  end
685
873
 
686
- @presenter_collection.presenting("workspaces", :params => { :search => "blah"}) { Workspace.order("id asc") }
874
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah"}) { Workspace.unscoped }
875
+ expect(search_options[:limit]).to eq(nil)
876
+ expect(search_options[:offset]).to eq(nil)
877
+ expect(search_options[:per_page]).to eq(20)
878
+ expect(search_options[:page]).to eq(1)
687
879
  end
688
880
  end
689
881
  end
@@ -691,8 +883,8 @@ describe Brainstem::PresenterCollection do
691
883
 
692
884
  context "and there is no search request" do
693
885
  it "does not call the search method" do
694
- result = @presenter_collection.presenting("workspaces") { Workspace.order("id asc") }
695
- expect(result[:workspaces].keys).to eq(Workspace.pluck(:id).map(&:to_s))
886
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
887
+ expect(result['workspaces'].keys).to eq(Workspace.pluck(:id).map(&:to_s))
696
888
  end
697
889
  end
698
890
  end
@@ -700,8 +892,8 @@ describe Brainstem::PresenterCollection do
700
892
  context "without search method defined" do
701
893
  context "and a search request is made" do
702
894
  it "returns as if there was no search" do
703
- result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
704
- expect(result[:workspaces].keys).to eq(Workspace.pluck(:id).map(&:to_s))
895
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.unscoped }
896
+ expect(result['workspaces'].keys).to eq(Workspace.pluck(:id).map(&:to_s))
705
897
  end
706
898
  end
707
899
  end
@@ -711,12 +903,12 @@ describe Brainstem::PresenterCollection do
711
903
  context "when there is no sort provided" do
712
904
  it "returns an empty array when there are no objects" do
713
905
  result = @presenter_collection.presenting("workspaces") { Workspace.where(:id => nil) }
714
- expect(result).to eq(:count => 0, :workspaces => {}, :results => [])
906
+ expect(result).to eq('count' => 0, 'workspaces' => {}, 'results' => [])
715
907
  end
716
908
 
717
909
  it "falls back to the object's sort order when nothing is provided" do
718
910
  result = @presenter_collection.presenting("workspaces") { Workspace.where(:id => [1, 3]) }
719
- expect(result[:workspaces].keys).to eq(%w[1 3])
911
+ expect(result['workspaces'].keys).to eq(%w[1 3])
720
912
  end
721
913
  end
722
914
 
@@ -724,28 +916,35 @@ describe Brainstem::PresenterCollection do
724
916
  WorkspacePresenter.sort_order(:description, "workspaces.description")
725
917
  WorkspacePresenter.default_sort_order("description:desc")
726
918
  result = @presenter_collection.presenting("workspaces") { Workspace.where("id is not null") }
727
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:description] }).to eq(%w(c b a 3 2 1))
919
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['description'] }).to eq(%w(c b a 3 2 1))
728
920
  end
729
921
 
730
922
  it "allows default ordering ascending" do
731
923
  WorkspacePresenter.sort_order(:description, "workspaces.description")
732
924
  WorkspacePresenter.default_sort_order("description:asc")
733
925
  result = @presenter_collection.presenting("workspaces") { Workspace.where("id is not null") }
734
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:description] }).to eq(%w(1 2 3 a b c))
926
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['description'] }).to eq(%w(1 2 3 a b c))
927
+ end
928
+
929
+ it "overrides any Arel ordering" do
930
+ WorkspacePresenter.sort_order(:description, "workspaces.description")
931
+ WorkspacePresenter.default_sort_order("description:desc")
932
+ result = @presenter_collection.presenting("workspaces") { Workspace.where("id is not null").reorder('workspaces.title asc') }
933
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['description'] }).to eq(%w(c b a 3 2 1))
735
934
  end
736
935
 
737
936
  it "applies orders that match the default order" do
738
937
  WorkspacePresenter.sort_order(:description, "workspaces.description")
739
938
  WorkspacePresenter.default_sort_order("description:desc")
740
939
  result = @presenter_collection.presenting("workspaces", :params => { :order => "description:desc"} ) { Workspace.where("id is not null") }
741
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:description] }).to eq(%w(c b a 3 2 1))
940
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['description'] }).to eq(%w(c b a 3 2 1))
742
941
  end
743
942
 
744
943
  it "applies orders that conflict with the default order" do
745
944
  WorkspacePresenter.sort_order(:description, "workspaces.description")
746
945
  WorkspacePresenter.default_sort_order("description:desc")
747
946
  result = @presenter_collection.presenting("workspaces", :params => { :order => "description:asc"} ) { Workspace.where("id is not null") }
748
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:description] }).to eq(%w(1 2 3 a b c))
947
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['description'] }).to eq(%w(1 2 3 a b c))
749
948
  end
750
949
 
751
950
  it "cleans the params" do
@@ -759,7 +958,7 @@ describe Brainstem::PresenterCollection do
759
958
 
760
959
  result = @presenter_collection.presenting("workspaces", :params => { :order => "description:drop table" }) { Workspace.where("id is not null") }
761
960
  expect(last_direction).to eq('asc')
762
- expect(result.keys).to match_array([:count, :workspaces, :results])
961
+ expect(result.keys).to match_array %w[count workspaces results]
763
962
 
764
963
  result = @presenter_collection.presenting("workspaces", :params => { :order => "description:;;hacker;;" }) { Workspace.where("id is not null") }
765
964
  expect(last_direction).to eq('asc')
@@ -774,13 +973,13 @@ describe Brainstem::PresenterCollection do
774
973
  expect(last_direction).to eq('desc')
775
974
 
776
975
  result = @presenter_collection.presenting("workspaces", :params => { :order => "title:desc" }) { Workspace.where("id is not null") }
777
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:title] }).to eq(["jane workspace 2", "jane workspace 1", "bob workspace 4", "bob workspace 3", "bob workspace 2", "bob workspace 1"])
976
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['title'] }).to eq(["jane workspace 2", "jane workspace 1", "bob workspace 4", "bob workspace 3", "bob workspace 2", "bob workspace 1"])
778
977
 
779
978
  result = @presenter_collection.presenting("workspaces", :params => { :order => "title:hacker" }) { Workspace.where("id is not null") }
780
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:title] }).to eq(["bob workspace 1", "bob workspace 2", "bob workspace 3", "bob workspace 4", "jane workspace 1", "jane workspace 2"])
979
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['title'] }).to eq(["bob workspace 1", "bob workspace 2", "bob workspace 3", "bob workspace 4", "jane workspace 1", "jane workspace 2"])
781
980
 
782
981
  result = @presenter_collection.presenting("workspaces", :params => { :order => "title:;;;drop table;;" }) { Workspace.where("id is not null") }
783
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:title] }).to eq(["bob workspace 1", "bob workspace 2", "bob workspace 3", "bob workspace 4", "jane workspace 1", "jane workspace 2"])
982
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['title'] }).to eq(["bob workspace 1", "bob workspace 2", "bob workspace 3", "bob workspace 4", "jane workspace 1", "jane workspace 2"])
784
983
  end
785
984
 
786
985
  it "can take a proc" do
@@ -789,22 +988,37 @@ describe Brainstem::PresenterCollection do
789
988
 
790
989
  # Default
791
990
  result = @presenter_collection.presenting("workspaces") { Workspace.where("id is not null") }
792
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:description] }).to eq(%w(a 1 b 2 c 3))
991
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['description'] }).to eq(%w(a 1 b 2 c 3))
793
992
 
794
993
  # Asc
795
994
  result = @presenter_collection.presenting("workspaces", :params => { :order => "id:asc" }) { Workspace.where("id is not null") }
796
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:description] }).to eq(%w(a 1 b 2 c 3))
995
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['description'] }).to eq(%w(a 1 b 2 c 3))
797
996
 
798
997
  # Desc
799
998
  result = @presenter_collection.presenting("workspaces", :params => { :order => "id:desc" }) { Workspace.where("id is not null") }
800
- expect(result[:results].map {|i| result[:workspaces][i[:id]][:description] }).to eq(%w(3 c 2 b 1 a))
999
+ expect(result['results'].map {|i| result['workspaces'][i['id']]['description'] }).to eq(%w(3 c 2 b 1 a))
1000
+ end
1001
+
1002
+ it "runs procs in the context of any provided helpers" do
1003
+ WorkspacePresenter.helper do
1004
+ def some_method
1005
+ end
1006
+ end
1007
+
1008
+ called = false
1009
+ WorkspacePresenter.sort_order(:id) { |scope, direction| some_method; called = true; scope.order("workspaces.id #{direction}") }
1010
+ WorkspacePresenter.default_sort_order("id:asc")
1011
+
1012
+ result = @presenter_collection.presenting("workspaces") { Workspace.where("id is not null") }
1013
+ expect(called).to be true
801
1014
  end
802
1015
  end
803
1016
 
804
- describe "the :as param" do
805
- it "determines the chosen top-level key name" do
806
- result = @presenter_collection.presenting("workspaces", :as => :my_workspaces) { Workspace.where(:id => 1) }
807
- expect(result.keys).to eq([:count, :my_workspaces, :results])
1017
+ describe "the :as option" do
1018
+ it "is no longer supported" do
1019
+ expect(lambda {
1020
+ @presenter_collection.presenting("workspaces", as: :my_workspaces) { Workspace.where(:id => 1) }
1021
+ }).to raise_error(/brainstem_key annotation/)
808
1022
  end
809
1023
  end
810
1024
 
@@ -813,16 +1027,92 @@ describe Brainstem::PresenterCollection do
813
1027
  WorkspacePresenter.filter(:owned_by) { |scope, user_id| scope.owned_by(user_id.to_i) }
814
1028
 
815
1029
  result = @presenter_collection.presenting("workspaces") { Workspace.where(:id => 1) }
816
- expect(result[:count]).to eq(1)
1030
+ expect(result['count']).to eq(1)
817
1031
 
818
1032
  result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
819
- expect(result[:count]).to eq(Workspace.count)
1033
+ expect(result['count']).to eq(Workspace.count)
820
1034
 
821
1035
  result = @presenter_collection.presenting("workspaces", :params => { :owned_by => bob.to_param }) { Workspace.unscoped }
822
- expect(result[:count]).to eq(Workspace.owned_by(bob.to_param).count)
1036
+ expect(result['count']).to eq(Workspace.owned_by(bob.to_param).count)
823
1037
 
824
1038
  result = @presenter_collection.presenting("workspaces", :params => { :owned_by => bob.to_param }) { Workspace.group(:id) }
825
- expect(result[:count]).to eq(Workspace.owned_by(bob.to_param).count)
1039
+ expect(result['count']).to eq(Workspace.owned_by(bob.to_param).count)
1040
+ end
1041
+ end
1042
+
1043
+ describe "providing a specific Presenter with the :primary_presenter option" do
1044
+ it "overrides the infered presenter" do
1045
+ some_presenter_klass = Class.new(WorkspacePresenter) do
1046
+ fields do
1047
+ field :secret_info, :string
1048
+ end
1049
+ end
1050
+
1051
+ result = @presenter_collection.presenting("workspaces", primary_presenter: some_presenter_klass.new) { Workspace.where(id: 1) }
1052
+ expect(result['workspaces']['1']['secret_info']).to eq(Workspace.find(1).secret_info)
1053
+ end
1054
+ end
1055
+
1056
+ describe "when optional fields exist" do
1057
+ it 'does not include optional field by default' do
1058
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
1059
+ workspaces = result['workspaces'].values
1060
+ expect(workspaces.any? {|w| w.has_key?('expensive_title') }).to be_falsey
1061
+ expect(workspaces.any? {|w| w.has_key?('expensive_title2') }).to be_falsey
1062
+ expect(workspaces.any? {|w| w.has_key?('expensive_title3') }).to be_falsey
1063
+ end
1064
+
1065
+ it 'includes the optional field when explicitly requested' do
1066
+ result = @presenter_collection.presenting("workspaces", :params => { :optional_fields => 'expensive_title,expensive_title2' }) { Workspace.unscoped }
1067
+ workspaces = result['workspaces'].values
1068
+ expect(workspaces.all? {|w| w.has_key?('expensive_title') }).to be_truthy
1069
+ expect(workspaces.all? {|w| w.has_key?('expensive_title2') }).to be_truthy
1070
+ expect(workspaces.any? {|w| w.has_key?('expensive_title3') }).to be_falsey
1071
+ end
1072
+
1073
+ it 'ignores unknown fields' do
1074
+ mock.proxy.any_instance_of(Brainstem::Presenter).group_present(anything, [], { optional_fields: ['expensive_title', 'expensive_title2'], load_associations_into: {} })
1075
+ @presenter_collection.presenting("workspaces", :params => { :optional_fields => 'expensive_title , expensive_title2,foo' }) { Workspace.unscoped }
1076
+ end
1077
+ end
1078
+ end
1079
+
1080
+ describe '#structure_response' do
1081
+ let(:options) { {params: {}, primary_presenter: @presenter_collection.for!(Workspace) } }
1082
+ let(:response_body) { @presenter_collection.structure_response(Workspace, Workspace.all, 17, options) }
1083
+
1084
+ it 'has a count' do
1085
+ expect(response_body['count']).to eq(17)
1086
+ end
1087
+
1088
+ it 'has a list of results' do
1089
+ expect(response_body['results'].length).to eq(Workspace.count)
1090
+
1091
+ response_body['results'].each do |result|
1092
+ expect(result['key']).to eq('workspaces')
1093
+ expect(result['id'].to_i).not_to eq(0)
1094
+ end
1095
+ end
1096
+
1097
+ it 'has id-attributes maps for all objects' do
1098
+ response_body['results'].each do |result|
1099
+ expect(response_body[result['key']][result['id']]).not_to be_empty
1100
+ end
1101
+ end
1102
+ end
1103
+
1104
+ describe "#validate!" do
1105
+ it 'should raise an error when a presenter is invalid' do
1106
+ WorkspacePresenter.fields do
1107
+ field :title, :string, if: [:wat]
1108
+ field :oh_noes, :string
1109
+ end
1110
+ expect(lambda { Brainstem.presenter_collection.validate! }).to raise_error(/Workspace: Fields 'oh_noes' is not valid because/)
1111
+ end
1112
+
1113
+ describe 'checking out spec fixtures' do
1114
+ specify 'they should be valid' do
1115
+ expect(lambda { Brainstem.presenter_collection.validate! }).to_not raise_error
826
1116
  end
827
1117
  end
828
1118
  end
@@ -842,6 +1132,12 @@ describe Brainstem::PresenterCollection do
842
1132
  expect(Brainstem.presenter_collection("v1").for(Array)).to be_a(V1::ArrayPresenter)
843
1133
  end
844
1134
 
1135
+ it "returns a new instance of the presenter class each time" do
1136
+ presenter1 = Brainstem.presenter_collection("v1").for(Array)
1137
+ presenter2 = Brainstem.presenter_collection("v1").for(Array)
1138
+ expect(presenter1).not_to eq presenter2
1139
+ end
1140
+
845
1141
  it "returns nil when given nil" do
846
1142
  expect(Brainstem.presenter_collection("v1").for(nil)).to be_nil
847
1143
  end
@@ -860,5 +1156,30 @@ describe Brainstem::PresenterCollection do
860
1156
  expect{ Brainstem.presenter_collection("v1").for!(String) }.to raise_error(ArgumentError)
861
1157
  end
862
1158
  end
1159
+
1160
+ describe "brainstem_key_for! method" do
1161
+ class AnotherWorkspace < Workspace
1162
+ end
1163
+
1164
+ let!(:another_workspace_presenter_class) do
1165
+ Class.new(Brainstem::Presenter) do
1166
+ presents AnotherWorkspace
1167
+ end
1168
+ end
1169
+
1170
+ it "defaults to the table name" do
1171
+ expect(Brainstem.presenter_collection.for!(AnotherWorkspace)).to be_a(another_workspace_presenter_class)
1172
+ expect(Brainstem.presenter_collection.brainstem_key_for!(AnotherWorkspace)).to eq 'workspaces'
1173
+ end
1174
+
1175
+ it "uses the given brainstem_key if present" do
1176
+ another_workspace_presenter_class.brainstem_key(:projects)
1177
+ expect(Brainstem.presenter_collection.brainstem_key_for!(AnotherWorkspace)).to eq 'projects'
1178
+ end
1179
+
1180
+ it "raises if there is no presenter for the given class" do
1181
+ expect{ Brainstem.presenter_collection("v1").brainstem_key_for!(String) }.to raise_error(ArgumentError)
1182
+ end
1183
+ end
863
1184
  end
864
1185
  end