brainstem 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ module Brainstem
2
+ class Presenter
3
+ # This constant stores an array of classes that we will treat as times.
4
+ # Unfortunately, ActiveSupport::TimeWithZone does not descend from
5
+ # Time, so we put them into this array for later use.
6
+ TIME_CLASSES = [Time]
7
+
8
+ begin
9
+ require 'active_support/time_with_zone'
10
+ TIME_CLASSES << ActiveSupport::TimeWithZone
11
+ rescue LoadError
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Brainstem
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+ require 'spec_helpers/presenters'
3
+
4
+ describe Brainstem::ControllerMethods do
5
+ class FakeController
6
+ include Brainstem::ControllerMethods
7
+
8
+ attr_accessor :call_results
9
+
10
+ def params
11
+ { :a => :b }
12
+ end
13
+ end
14
+
15
+ before do
16
+ UserPresenter.presents User
17
+ TaskPresenter.presents Task
18
+ WorkspacePresenter.presents Workspace
19
+ PostPresenter.presents Post
20
+ end
21
+
22
+ describe "#present_object" do
23
+ before do
24
+ @controller = FakeController.new
25
+ end
26
+
27
+ describe "calling #present with sensible params" do
28
+ before do
29
+ def @controller.present(klass, options)
30
+ @call_results = { :klass => klass, :options => options, :block_result => yield }
31
+ end
32
+ end
33
+
34
+ it "works with arrays of ActiveRecord objects" do
35
+ @controller.present_object([Workspace.find(1), Workspace.find(3)])
36
+ @controller.call_results[:klass].should == Workspace
37
+ @controller.call_results[:options][:as].should == "workspaces"
38
+ @controller.call_results[:block_result].pluck(:id).should == [1, 3]
39
+ end
40
+
41
+ it "works with a Relation" do
42
+ @controller.present_object(Workspace.owned_by(1))
43
+ @controller.call_results[:klass].should == Workspace
44
+ @controller.call_results[:options][:as].should == "workspaces"
45
+ @controller.call_results[:block_result].pluck(:id).should == [1, 2, 3, 4]
46
+ end
47
+
48
+ it "works with singleton objects" do
49
+ @controller.present_object(Workspace.find(1))
50
+ @controller.call_results[:klass].should == Workspace
51
+ @controller.call_results[:options][:as].should == "workspaces"
52
+ @controller.call_results[:block_result].pluck(:id).should == [1]
53
+ end
54
+
55
+ it "accepts a key map" do
56
+ @controller.present_object(Workspace.find(1), :key_map => { "Workspace" => "your_workspaces" })
57
+ @controller.call_results[:klass].should == Workspace
58
+ @controller.call_results[:options][:as].should == "your_workspaces"
59
+ @controller.call_results[:block_result].pluck(:id).should == [1]
60
+ end
61
+
62
+ it "passes :apply_default_filters => false to the PresenterCollection so that filters are not applied by default" do
63
+ @controller.present_object(Workspace.find(1))
64
+ @controller.call_results[:options][:apply_default_filters].should == false
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,486 @@
1
+ require 'spec_helper'
2
+ require 'spec_helpers/presenters'
3
+
4
+ describe Brainstem::PresenterCollection do
5
+ before do
6
+ UserPresenter.presents User
7
+ TaskPresenter.presents Task
8
+ WorkspacePresenter.presents Workspace
9
+ PostPresenter.presents Post
10
+ @presenter_collection = Brainstem.presenter_collection
11
+ end
12
+
13
+ describe "#presenting" do
14
+ describe "#pagination" do
15
+ before do
16
+ @presenter_collection.default_per_page = 2
17
+ @presenter_collection.default_max_per_page = 3
18
+ end
19
+
20
+ it "has a global per_page default" do
21
+ @presenter_collection.presenting("workspaces") { Workspace.order('id desc') }[:workspaces].length.should == 2
22
+ end
23
+
24
+ it "will not accept a per_page less than 1" do
25
+ @presenter_collection.presenting("workspaces", :params => { :per_page => 0 }) { Workspace.order('id desc') }[:workspaces].length.should == 2
26
+ end
27
+
28
+ it "will accept strings" do
29
+ struct = @presenter_collection.presenting("workspaces", :params => { :per_page => "1", :page => "2" }) { Workspace.order('id desc') }
30
+ struct[:results].first[:id].should == Workspace.order('id desc')[1].id.to_s
31
+ end
32
+
33
+ it "has a global max_per_page default" do
34
+ @presenter_collection.presenting("workspaces", :params => { :per_page => 5 }) { Workspace.order('id desc') }[:workspaces].length.should == 3
35
+ end
36
+
37
+ it "takes a configurable default page size and max page size" do
38
+ @presenter_collection.presenting("workspaces", :params => { :per_page => 5 }, :max_per_page => 4) { Workspace.order('id desc') }[:workspaces].length.should == 4
39
+ end
40
+
41
+ describe "limits and offsets" do
42
+ it "honors the user's requested page size and page and returns counts" do
43
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 2 }) { Workspace.order('id desc') }[:results]
44
+ result.length.should == 1
45
+ result.first[:id].should == Workspace.order('id desc')[1].id.to_s
46
+
47
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 2, :page => 2 }) { Workspace.order('id desc') }[:results]
48
+ result.length.should == 2
49
+ result.map { |m| m[:id] }.should == Workspace.order('id desc')[2..3].map(&:id).map(&:to_s)
50
+ end
51
+
52
+ it "defaults to 1 if the page number is less than 1" do
53
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 0 }) { Workspace.order('id desc') }[:results]
54
+ result.length.should == 1
55
+ result.first[:id].should == Workspace.order('id desc')[0].id.to_s
56
+ end
57
+ end
58
+
59
+ describe "counts" do
60
+ before do
61
+ @presenter_collection.default_per_page = 500
62
+ @presenter_collection.default_max_per_page = 500
63
+ end
64
+
65
+ it "returns the unique count by model id" do
66
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 2, :page => 1 }) { Workspace.order('id desc') }
67
+ result[:count].should == Workspace.count
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "uses presenters" do
73
+ it "finds presenter by table name string" do
74
+ result = @presenter_collection.presenting("workspaces") { Workspace.order('id desc') }
75
+ result[:workspaces].length.should eq(Workspace.count)
76
+ end
77
+
78
+ it "finds presenter by model name string" do
79
+ result = @presenter_collection.presenting("Workspace") { order('id desc') }
80
+ result[:workspaces].length.should eq(Workspace.count)
81
+ end
82
+
83
+ it "finds presenter by model" do
84
+ result = @presenter_collection.presenting(Workspace) { order('id desc') }
85
+ result[:workspaces].length.should eq(Workspace.count)
86
+ end
87
+
88
+ it "infers the table name from the model" do
89
+ result = @presenter_collection.presenting("not_workspaces", :model => "Workspace", :params => { :per_page => 2, :page => 1 }) { Workspace.order('id desc') }
90
+ result[:not_workspaces].should_not be_empty
91
+ result[:count].should == Workspace.count
92
+ end
93
+ end
94
+
95
+ describe "the 'results' top level key" do
96
+ it "comes back with an explicit list of the matching results" do
97
+ structure = @presenter_collection.presenting("workspaces", :params => { :include => "tasks" }, :max_per_page => 2) { Workspace.where(:id => 1) }
98
+ structure.keys.should =~ [:workspaces, :tasks, :count, :results]
99
+ structure[:results].should == Workspace.where(:id => 1).limit(2).map {|w| { :key => "workspaces", :id => w.id.to_s } }
100
+ structure[:workspaces].keys.should == %w[1]
101
+ end
102
+ end
103
+
104
+ describe "includes" do
105
+ it "reads allowed includes from the presenter" do
106
+ result = @presenter_collection.presenting("workspaces", :params => { :include => "drop table,tasks,users" }) { Workspace.order('id desc') }
107
+ result.keys.should =~ [:count, :workspaces, :tasks, :results]
108
+
109
+ result = @presenter_collection.presenting("workspaces", :params => { :include => "foo,tasks,lead_user" }) { Workspace.order('id desc') }
110
+ result.keys.should =~ [:count, :workspaces, :tasks, :users, :results]
111
+ end
112
+
113
+ it "allows the allowed includes list to have different json names and association names" do
114
+ result = @presenter_collection.presenting("tasks",
115
+ :params => { :include => "other_tasks" }) { Task.order('id desc') }
116
+ result[:tasks].should be_present
117
+ result[:other_tasks].should be_present
118
+ end
119
+
120
+ it "defaults to not include any allowed includes" do
121
+ tasked_workspace = Task.first
122
+ result = @presenter_collection.presenting("workspaces", :max_per_page => 2) { Workspace.where(:id => tasked_workspace.workspace_id) }
123
+ result[:workspaces].keys.should == [ tasked_workspace.workspace_id.to_s ]
124
+ result[:tasks].should be_nil
125
+ end
126
+
127
+ it "loads has_many associations and returns them when requested" do
128
+ result = @presenter_collection.presenting("workspaces", :params => { :include => "tasks" }, :max_per_page => 2) { Workspace.where(:id => 1) }
129
+ result[:tasks].keys.should =~ Workspace.first.tasks.map(&:id).map(&:to_s)
130
+ result[:workspaces]["1"][:task_ids].should =~ Workspace.first.tasks.map(&:id).map(&:to_s)
131
+ end
132
+
133
+ it "returns appropriate fields" do
134
+ result = @presenter_collection.presenting("workspaces",
135
+ :params => { :include => "tasks" },
136
+ :max_per_page => 2) { Workspace.where(:id => 1) }
137
+ result[:workspaces].values.first.should have_key(:description)
138
+ result[:tasks].values.first.should have_key(:name)
139
+ end
140
+
141
+ it "loads belongs_tos and returns them when requested" do
142
+ result = @presenter_collection.presenting("tasks", :params => { :include => "workspace" }, :max_per_page => 2) { Task.where(:id => 1) }
143
+ result[:workspaces].keys.should == %w[1]
144
+ end
145
+
146
+ it "doesn't return nils when belong_tos are missing" do
147
+ t = Task.first
148
+ t.update_attribute :workspace, nil
149
+ t.reload.workspace.should be_nil
150
+ result = @presenter_collection.presenting("tasks", :params => { :include => "workspace" }, :max_per_page => 2) { Task.where(:id => t.id) }
151
+ result[:tasks].keys.should == [ t.id.to_s ]
152
+ result[:workspaces].should eq({})
153
+ result.keys.should =~ [:tasks, :workspaces, :count, :results]
154
+ end
155
+
156
+ it "returns sensible data when including something of the same type as the primary model" do
157
+ result = @presenter_collection.presenting("tasks", :params => { :include => "sub_tasks" }) { Task.where(:id => 2) }
158
+ sub_task_ids = Task.find(2).sub_tasks.map(&:id).map(&:to_s)
159
+ result[:tasks].keys.should =~ sub_task_ids + ["2"]
160
+ result[:tasks]["2"][:sub_task_ids].should == sub_task_ids # The primary should have a sub_story_ids array.
161
+ result[:tasks][sub_task_ids.first][:sub_task_ids].should_not be_present # Sub stories should not have a sub_story_ids array.
162
+ end
163
+
164
+ it "includes requested includes even when all records are filtered" do
165
+ result = @presenter_collection.presenting("workspaces", :params => { :only => "not an id", :include => "not an include,tasks" }) { Workspace.order("id desc") }
166
+ result[:workspaces].length.should == 0
167
+ result[:tasks].length.should == 0
168
+ end
169
+
170
+ it "includes requested includes even when the scope has no records" do
171
+ Workspace.where(:id => 123456789).should be_empty
172
+ result = @presenter_collection.presenting("workspaces", :params => { :include => "not an include,tasks" }) { Workspace.where(:id => 123456789) }
173
+ result[:workspaces].length.should == 0
174
+ result[:tasks].length.should == 0
175
+ end
176
+
177
+ it "preloads associations when they are full model-level associations" do
178
+ # Here, primary_maven is a method on Workspace, not a true association.
179
+ mock(ActiveRecord::Associations::Preloader).new(anything, [:tasks]) { mock!.run }
180
+ result = @presenter_collection.presenting("workspaces", :params => { :include => "tasks" }) { Workspace.order('id desc') }
181
+ result[:tasks].length.should > 0
182
+ end
183
+
184
+ it "works with model methods that load records (but without preloading)" do
185
+ result = @presenter_collection.presenting("workspaces", :params => { :include => "lead_user" }) { Workspace.order('id desc') }
186
+ result[:workspaces][Workspace.first.id.to_s].should be_present
187
+ result[:users][Workspace.first.lead_user.id.to_s].should be_present
188
+ end
189
+
190
+ describe "polymorphic associations" do
191
+ it "works with polymorphic associations" do
192
+ result = @presenter_collection.presenting("posts", :params => { :include => "subject" }) { Post.order('id desc') }
193
+ result[:posts][Post.first.id.to_s].should be_present
194
+ result[:workspaces][Workspace.first.id.to_s].should be_present
195
+ result[:tasks][Task.first.id.to_s].should be_present
196
+ end
197
+
198
+ it "does not return an empty hash when none are found" do
199
+ result = @presenter_collection.presenting("posts", :params => { :include => "subject" }) { Post.where(:id => nil) }
200
+ result.should have_key(:posts)
201
+ result.should_not have_key(:workspaces)
202
+ result.should_not have_key(:tasks)
203
+ end
204
+ end
205
+ end
206
+
207
+ describe "handling of only" do
208
+ it "accepts params[:only] as a list of ids to limit to" do
209
+ result = @presenter_collection.presenting("workspaces", :params => { :only => Workspace.limit(2).pluck(:id).join(",") }) { Workspace.order("id desc") }
210
+ result[:workspaces].keys.should match_array(Workspace.limit(2).pluck(:id).map(&:to_s))
211
+ end
212
+
213
+ it "does not paginate only requests" do
214
+ dont_allow(@presenter_collection).paginate
215
+ @presenter_collection.presenting("workspaces", :params => { :only => Workspace.limit(2).pluck(:id).join(",") }) { Workspace.order("id desc") }
216
+ end
217
+
218
+ it "escapes ids" do
219
+ result = @presenter_collection.presenting("workspaces", :params => { :only => "#{Workspace.first.id}foo,;drop tables;,#{Workspace.first.id}" }) { Workspace.order("id desc") }
220
+ result[:workspaces].length.should == 1
221
+ end
222
+
223
+ it "only runs when it receives ids" do
224
+ result = @presenter_collection.presenting("workspaces", :params => { :only => "" }) { Workspace.order("id desc") }
225
+ result[:workspaces].length.should > 1
226
+
227
+ result = @presenter_collection.presenting("workspaces", :params => { :only => "1" }) { Workspace.order("id desc") }
228
+ result[:workspaces].length.should <= 1
229
+ end
230
+ end
231
+
232
+ describe "filters" do
233
+ before do
234
+ WorkspacePresenter.filter(:owned_by) { |scope, user_id| scope.owned_by(user_id.to_i) }
235
+ WorkspacePresenter.filter(:title) { |scope, title| scope.where(:title => title) }
236
+ end
237
+
238
+ let(:bob) { User.where(:username => "bob").first }
239
+ let(:bob_workspaces_ids) { bob.workspaces.map(&:id) }
240
+
241
+ it "limits records to those matching given filters" do
242
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by => bob.id.to_s }) { Workspace.order("id desc") } # hit the API, filtering on owned_by:bob
243
+ result[:workspaces].should be_present
244
+ result[:workspaces].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }.should be_true # all of the returned workspaces should contain bob
245
+ end
246
+
247
+ it "returns all records if filters are not given" do
248
+ result = @presenter_collection.presenting("workspaces") { Workspace.order("id desc") } # hit the API again, this time not filtering on anything
249
+ result[:workspaces].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }.should be_false # the returned workspaces no longer all contain bob
250
+ end
251
+
252
+ it "ignores unknown filters" do
253
+ result = @presenter_collection.presenting("workspaces", :params => { :wut => "is this?" }) { Workspace.order("id desc") }
254
+ result[:workspaces].keys.all? {|id| bob_workspaces_ids.map(&:to_s).include?(id) }.should be_false
255
+ end
256
+
257
+ it "limits records to those matching all given filters" do
258
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by => bob.id.to_s, :title => "bob workspace 1" }) { Workspace.order("id desc") } # try two filters
259
+ result[:results].first[:id].should == Workspace.where(:title => "bob workspace 1").first.id.to_s
260
+ end
261
+
262
+ it "converts boolean parameters from strings to booleans" do
263
+ WorkspacePresenter.filter(:owned_by_bob) { |scope, boolean| boolean ? scope.where(:user_id => bob.id) : scope }
264
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => "false" }) { Workspace.scoped }
265
+ result[:workspaces].values.find { |workspace| workspace[:title].include?("jane") }.should be
266
+ end
267
+
268
+ it "ensures arguments are strings" do
269
+ WorkspacePresenter.filter(:owned_by_bob) { |scope, string| string.should be_a(String); scope }
270
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => [1, 2] }) { Workspace.scoped }
271
+ end
272
+
273
+ it "allows filters to be called with false as an argument" do
274
+ WorkspacePresenter.filter(:nothing) { |scope, bool| bool ? scope.where(:id => nil) : scope }
275
+ result = @presenter_collection.presenting("workspaces", :params => { :nothing => "true" }) { Workspace.scoped }
276
+ result[:workspaces].length.should eq(0)
277
+ result = @presenter_collection.presenting("workspaces", :params => { :nothing => "false" }) { Workspace.scoped }
278
+ result[:workspaces].length.should_not eq(0)
279
+ end
280
+
281
+ it "passes colon separated params through as a string" do
282
+ WorkspacePresenter.filter(:between) { |scope, a_and_b|
283
+ a, b = a_and_b.split(':')
284
+ a.should == "1"
285
+ b.should == "10"
286
+ scope
287
+ }
288
+
289
+ @presenter_collection.presenting("workspaces", :params => { :between => "1:10" }) { Workspace.scoped }
290
+ end
291
+
292
+ context "with defaults" do
293
+ before do
294
+ WorkspacePresenter.filter(:owner, :default => bob.id) { |scope, id| scope.owned_by(id) }
295
+ end
296
+
297
+ let(:jane) { User.where(:username => "jane").first }
298
+
299
+ it "applies the filter when it is not requested" do
300
+ result = @presenter_collection.presenting("workspaces") { Workspace.order('id desc') }
301
+ result[:workspaces].keys.should match_array(bob.workspaces.map(&:id).map(&:to_s))
302
+ end
303
+
304
+ it "allows falsy defaults" do
305
+ WorkspacePresenter.filter(:include_early_workspaces, :default => false) { |scope, bool| bool ? scope : scope.where("id > 3") }
306
+ result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
307
+ result[:workspaces]["2"].should_not be_present
308
+ result = @presenter_collection.presenting("workspaces", :params => { :include_early_workspaces => "true" }) { Workspace.unscoped }
309
+ result[:workspaces]["2"].should be_present
310
+ end
311
+
312
+ it "allows defaults to be skipped if :apply_default_filters is false" do
313
+ WorkspacePresenter.filter(:include_early_workspaces, :default => false) { |scope, bool| bool ? scope : scope.where("id > 3") }
314
+ result = @presenter_collection.presenting("workspaces", :apply_default_filters => true) { Workspace.unscoped }
315
+ result[:workspaces]["2"].should_not be_present
316
+ result = @presenter_collection.presenting("workspaces", :apply_default_filters => false) { Workspace.unscoped }
317
+ result[:workspaces]["2"].should be_present
318
+ end
319
+
320
+ it "allows the default value to be overridden" do
321
+ result = @presenter_collection.presenting("workspaces", :params => { :owner => jane.id.to_s }) { Workspace.order('id desc') }
322
+ result[:workspaces].keys.should match_array(jane.workspaces.map(&:id).map(&:to_s))
323
+ end
324
+ end
325
+
326
+ context "without blocks" do
327
+ let(:bob) { User.where(:username => "bob").first }
328
+ let(:jane) { User.where(:username => "jane").first }
329
+
330
+ before do
331
+ WorkspacePresenter.filter(:owned_by, :default => bob.id)
332
+ WorkspacePresenter.presents("Workspace")
333
+ end
334
+
335
+ it "calls the named scope with default arguments" do
336
+ result = @presenter_collection.presenting("workspaces") { Workspace.scoped }
337
+ result[:workspaces].keys.should eq(bob.workspaces.pluck(:id).map(&:to_s))
338
+ end
339
+
340
+ it "calls the named scope with given arguments" do
341
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by => jane.id.to_s }) { Workspace.scoped }
342
+ result[:workspaces].keys.should eq(jane.workspaces.pluck(:id).map(&:to_s))
343
+ end
344
+
345
+ it "allows scopes that take no arguments" do
346
+ WorkspacePresenter.filter(:numeric_description)
347
+ result = @presenter_collection.presenting("workspaces") { Workspace.scoped }
348
+ result[:workspaces].keys.should eq(bob.workspaces.pluck(:id).map(&:to_s))
349
+ result = @presenter_collection.presenting("workspaces", :params => { :numeric_description => "true" }) { Workspace.scoped }
350
+ result[:workspaces].keys.should =~ ["2", "4"]
351
+ result = @presenter_collection.presenting("workspaces", :params => { :numeric_description => "fadlse" }) { Workspace.scoped }
352
+ result[:workspaces].keys.should eq(bob.workspaces.pluck(:id).map(&:to_s))
353
+ end
354
+ end
355
+ end
356
+
357
+ describe "search" do
358
+ context "with search method defined" do
359
+ before do
360
+ WorkspacePresenter.search do |string|
361
+ [3,5]
362
+ end
363
+ end
364
+
365
+ context "and a search request is made" do
366
+ it "calls the search method" do
367
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
368
+ result[:workspaces].keys.should eq(%w[3 5])
369
+ end
370
+ end
371
+
372
+ context "and there is no search request" do
373
+ it "does not call the search method" do
374
+ result = @presenter_collection.presenting("workspaces") { Workspace.order("id asc") }
375
+ result[:workspaces].keys.should eq(Workspace.pluck(:id).map(&:to_s))
376
+ end
377
+ end
378
+ end
379
+
380
+ context "without search method defined" do
381
+ context "and a search request is made" do
382
+ it "returns as if there was no search" do
383
+ result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.order("id asc") }
384
+ result[:workspaces].keys.should eq(Workspace.pluck(:id).map(&:to_s))
385
+ end
386
+ end
387
+ end
388
+ end
389
+
390
+ describe "sorting and ordering" do
391
+ context "when there is no sort provided" do
392
+ it "returns an empty array when there are no objects" do
393
+ result = @presenter_collection.presenting("workspaces") { Workspace.where(:id => nil) }
394
+ result.should eq(:count => 0, :workspaces => {}, :results => [])
395
+ end
396
+
397
+ it "falls back to the object's sort order when nothing is provided" do
398
+ result = @presenter_collection.presenting("workspaces") { Workspace.where(:id => [1, 3]) }
399
+ result[:workspaces].keys.should == %w[1 3]
400
+ end
401
+ end
402
+
403
+ it "allows default ordering descending" do
404
+ WorkspacePresenter.sort_order(:description, "workspaces.description")
405
+ WorkspacePresenter.default_sort_order("description:desc")
406
+ result = @presenter_collection.presenting("workspaces") { Workspace.where("id is not null") }
407
+ result[:results].map {|i| result[:workspaces][i[:id]][:description] }.should eq(%w(c b a 3 2 1))
408
+ end
409
+
410
+ it "allows default ordering ascending" do
411
+ WorkspacePresenter.sort_order(:description, "workspaces.description")
412
+ WorkspacePresenter.default_sort_order("description:asc")
413
+ result = @presenter_collection.presenting("workspaces") { Workspace.where("id is not null") }
414
+ result[:results].map {|i| result[:workspaces][i[:id]][:description] }.should eq(%w(1 2 3 a b c))
415
+ end
416
+
417
+ it "applies orders that match the default order" do
418
+ WorkspacePresenter.sort_order(:description, "workspaces.description")
419
+ WorkspacePresenter.default_sort_order("description:desc")
420
+ result = @presenter_collection.presenting("workspaces", :params => { :order => "description:desc"} ) { Workspace.where("id is not null") }
421
+ result[:results].map {|i| result[:workspaces][i[:id]][:description] }.should eq(%w(c b a 3 2 1))
422
+ end
423
+
424
+ it "applies orders that conflict with the default order" do
425
+ WorkspacePresenter.sort_order(:description, "workspaces.description")
426
+ WorkspacePresenter.default_sort_order("description:desc")
427
+ result = @presenter_collection.presenting("workspaces", :params => { :order => "description:asc"} ) { Workspace.where("id is not null") }
428
+ result[:results].map {|i| result[:workspaces][i[:id]][:description] }.should eq(%w(1 2 3 a b c))
429
+ end
430
+
431
+ it "cleans the direction param" do
432
+ result = @presenter_collection.presenting("workspaces", :params => { :order => "updated_at:drop table" }) { Workspace.where("id is not null") }
433
+ result.keys.should =~ [:count, :workspaces, :results]
434
+ end
435
+
436
+ it "can take a proc" do
437
+ WorkspacePresenter.sort_order(:description){ Workspace.order("workspaces.description") }
438
+ WorkspacePresenter.default_sort_order("description:asc")
439
+ result = @presenter_collection.presenting("workspaces") { Workspace.where("id is not null") }
440
+ result[:results].map {|i| result[:workspaces][i[:id]][:description] }.should eq(%w(1 2 3 a b c))
441
+ end
442
+ end
443
+
444
+ describe "the :as param" do
445
+ it "determines the chosen top-level key name" do
446
+ result = @presenter_collection.presenting("workspaces", :as => :my_workspaces) { Workspace.where(:id => 1) }
447
+ result.keys.should eq([:count, :my_workspaces, :results])
448
+ end
449
+ end
450
+ end
451
+
452
+ describe "collection methods" do
453
+ describe "for method" do
454
+ module V1
455
+ class ArrayPresenter < Brainstem::Presenter
456
+ end
457
+ end
458
+
459
+ before do
460
+ V1::ArrayPresenter.presents Array
461
+ end
462
+
463
+ it "returns the presenter for a given class" do
464
+ Brainstem.presenter_collection("v1").for(Array).should be_a(V1::ArrayPresenter)
465
+ end
466
+
467
+ it "returns nil when given nil" do
468
+ Brainstem.presenter_collection("v1").for(nil).should be_nil
469
+ end
470
+
471
+ it "returns nil when a given class has no presenter" do
472
+ Brainstem.presenter_collection("v1").for(String).should be_nil
473
+ end
474
+
475
+ it "uses the default namespace when the passed namespace is nil" do
476
+ Brainstem.presenter_collection.should eq(Brainstem.presenter_collection(nil))
477
+ end
478
+ end
479
+
480
+ describe "for! method" do
481
+ it "raises if there is no presenter for the given class" do
482
+ lambda{ Brainstem.presenter_collection("v1").for!(String) }.should raise_error(ArgumentError)
483
+ end
484
+ end
485
+ end
486
+ end