brainstem 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Gemfile.lock +51 -0
- data/Guardfile +8 -0
- data/LICENSE +22 -0
- data/README.md +181 -0
- data/Rakefile +6 -0
- data/brainstem.gemspec +28 -0
- data/lib/brainstem.rb +63 -0
- data/lib/brainstem/association_field.rb +35 -0
- data/lib/brainstem/controller_methods.rb +44 -0
- data/lib/brainstem/engine.rb +4 -0
- data/lib/brainstem/presenter.rb +210 -0
- data/lib/brainstem/presenter_collection.rb +279 -0
- data/lib/brainstem/time_classes.rb +14 -0
- data/lib/brainstem/version.rb +3 -0
- data/spec/brainstem/controller_methods_spec.rb +68 -0
- data/spec/brainstem/presenter_collection_spec.rb +486 -0
- data/spec/brainstem/presenter_spec.rb +252 -0
- data/spec/brainstem_spec.rb +25 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/spec_helpers/cleanup.rb +23 -0
- data/spec/spec_helpers/db.rb +79 -0
- data/spec/spec_helpers/presenters.rb +39 -0
- metadata +201 -0
@@ -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,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
|