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.
- 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
|