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.
- checksums.yaml +5 -13
- data/CHANGELOG.md +16 -2
- data/Gemfile.lock +51 -36
- data/README.md +531 -110
- data/brainstem.gemspec +6 -2
- data/lib/brainstem.rb +25 -9
- data/lib/brainstem/concerns/controller_param_management.rb +22 -0
- data/lib/brainstem/concerns/error_presentation.rb +58 -0
- data/lib/brainstem/concerns/inheritable_configuration.rb +29 -0
- data/lib/brainstem/concerns/lookup.rb +30 -0
- data/lib/brainstem/concerns/presenter_dsl.rb +111 -0
- data/lib/brainstem/controller_methods.rb +17 -8
- data/lib/brainstem/dsl/association.rb +55 -0
- data/lib/brainstem/dsl/associations_block.rb +12 -0
- data/lib/brainstem/dsl/base_block.rb +31 -0
- data/lib/brainstem/dsl/conditional.rb +25 -0
- data/lib/brainstem/dsl/conditionals_block.rb +15 -0
- data/lib/brainstem/dsl/configuration.rb +112 -0
- data/lib/brainstem/dsl/field.rb +68 -0
- data/lib/brainstem/dsl/fields_block.rb +25 -0
- data/lib/brainstem/preloader.rb +98 -0
- data/lib/brainstem/presenter.rb +325 -134
- data/lib/brainstem/presenter_collection.rb +82 -286
- data/lib/brainstem/presenter_validator.rb +96 -0
- data/lib/brainstem/query_strategies/README.md +107 -0
- data/lib/brainstem/query_strategies/base_strategy.rb +62 -0
- data/lib/brainstem/query_strategies/filter_and_search.rb +50 -0
- data/lib/brainstem/query_strategies/filter_or_search.rb +103 -0
- data/lib/brainstem/test_helpers.rb +5 -1
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/concerns/controller_param_management_spec.rb +42 -0
- data/spec/brainstem/concerns/error_presentation_spec.rb +113 -0
- data/spec/brainstem/concerns/inheritable_configuration_spec.rb +210 -0
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +412 -0
- data/spec/brainstem/controller_methods_spec.rb +15 -27
- data/spec/brainstem/dsl/association_spec.rb +123 -0
- data/spec/brainstem/dsl/conditional_spec.rb +93 -0
- data/spec/brainstem/dsl/configuration_spec.rb +1 -0
- data/spec/brainstem/dsl/field_spec.rb +212 -0
- data/spec/brainstem/preloader_spec.rb +137 -0
- data/spec/brainstem/presenter_collection_spec.rb +565 -244
- data/spec/brainstem/presenter_spec.rb +726 -167
- data/spec/brainstem/presenter_validator_spec.rb +209 -0
- data/spec/brainstem/query_strategies/filter_and_search_spec.rb +46 -0
- data/spec/brainstem/query_strategies/filter_or_search_spec.rb +45 -0
- data/spec/spec_helper.rb +11 -3
- data/spec/spec_helpers/db.rb +32 -65
- data/spec/spec_helpers/presenters.rb +124 -29
- data/spec/spec_helpers/rr.rb +11 -0
- data/spec/spec_helpers/schema.rb +115 -0
- metadata +126 -30
- data/lib/brainstem/association_field.rb +0 -53
- data/lib/brainstem/engine.rb +0 -4
- data/pkg/brainstem-0.2.5.gem +0 -0
- data/pkg/brainstem-0.2.6.gem +0 -0
- 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.
|
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.
|
30
|
-
expect(@presenter_collection.presenting("workspaces", :per_page => 0) { Workspace.
|
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.
|
35
|
-
expect(struct[
|
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.
|
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.
|
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.
|
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[
|
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.
|
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[
|
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.
|
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[
|
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.
|
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[
|
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.
|
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[
|
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
|
-
|
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[
|
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.
|
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[
|
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.
|
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[
|
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.
|
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[
|
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.
|
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.
|
150
|
-
expect(result[
|
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.
|
158
|
-
expect(result[
|
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") {
|
163
|
-
expect(result[
|
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) {
|
168
|
-
expect(result[
|
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
|
182
|
-
expect(structure[
|
183
|
-
expect(structure[
|
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.
|
190
|
-
expect(result.keys).to match_array
|
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.
|
193
|
-
expect(result.keys).to match_array
|
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[
|
207
|
-
expect(result[
|
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[
|
213
|
-
expect(result[
|
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[
|
221
|
-
expect(result[
|
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[
|
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[
|
235
|
-
expect(result[
|
236
|
-
expect(result.keys).to match_array
|
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
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
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.
|
249
|
-
expect(result[
|
250
|
-
expect(result[
|
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[
|
257
|
-
expect(result[
|
337
|
+
expect(result['workspaces'].length).to eq(0)
|
338
|
+
expect(result['tasks'].length).to eq(0)
|
258
339
|
end
|
259
340
|
|
260
|
-
it "
|
261
|
-
|
262
|
-
|
263
|
-
result
|
264
|
-
expect(result[
|
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[
|
276
|
-
|
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[
|
284
|
-
expect(result.
|
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[
|
292
|
-
expect(result.
|
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.
|
299
|
-
expect(result[
|
300
|
-
expect(result[
|
301
|
-
expect(result[
|
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(
|
307
|
-
expect(result).not_to have_key(
|
308
|
-
expect(result).not_to have_key(
|
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.
|
316
|
-
expect(result[
|
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.
|
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.
|
326
|
-
expect(result[
|
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.
|
331
|
-
expect(result[
|
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.
|
334
|
-
expect(result[
|
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.
|
346
|
-
expect(result[
|
347
|
-
expect(result[
|
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.
|
352
|
-
expect(result[
|
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.
|
357
|
-
expect(result[
|
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.
|
362
|
-
expect(result[
|
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
|
-
|
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[
|
369
|
-
expect(result[
|
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
|
-
|
374
|
-
WorkspacePresenter.filter(:owned_by_bob) do |scope,
|
375
|
-
|
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(
|
468
|
+
expect(string).to be_truthy
|
381
469
|
end
|
382
470
|
|
383
471
|
it "preserves array arguments" do
|
384
|
-
|
385
|
-
WorkspacePresenter.filter(:owned_by_bob) do |scope,
|
386
|
-
|
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(
|
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[
|
484
|
+
expect(result['workspaces'].length).to eq(0)
|
398
485
|
result = @presenter_collection.presenting("workspaces", :params => { :nothing => "false" }) { Workspace.where(nil) }
|
399
|
-
expect(result[
|
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.
|
422
|
-
expect(result[
|
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[
|
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[
|
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[
|
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[
|
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.
|
443
|
-
expect(result[
|
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[
|
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[
|
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[
|
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[
|
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[
|
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[
|
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.
|
499
|
-
expect(result[
|
500
|
-
expect(result[
|
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).
|
505
|
-
result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.
|
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(
|
510
|
-
result = @presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.
|
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.
|
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
|
-
|
542
|
-
|
543
|
-
|
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.
|
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
|
-
|
727
|
+
search_options = options
|
559
728
|
[[1], 1]
|
560
729
|
end
|
561
730
|
|
562
|
-
@presenter_collection.presenting("workspaces", :params => { :search => "blah", :include => "users"}) { Workspace.
|
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
|
-
|
741
|
+
search_options = options
|
571
742
|
[[1], 1]
|
572
743
|
end
|
573
744
|
|
574
|
-
@presenter_collection.presenting("workspaces", :params => { :search => "blah" }) { Workspace.
|
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
|
-
|
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.
|
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
|
-
|
764
|
+
search_options = options
|
590
765
|
[[1], 1]
|
591
766
|
end
|
592
767
|
|
593
|
-
@presenter_collection.presenting("workspaces", :params => { :search => "blah"}) { Workspace.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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.
|
695
|
-
expect(result[
|
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.
|
704
|
-
expect(result[
|
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(
|
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[
|
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[
|
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[
|
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[
|
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[
|
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
|
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[
|
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[
|
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[
|
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[
|
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[
|
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[
|
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
|
805
|
-
it "
|
806
|
-
|
807
|
-
|
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[
|
1030
|
+
expect(result['count']).to eq(1)
|
817
1031
|
|
818
1032
|
result = @presenter_collection.presenting("workspaces") { Workspace.unscoped }
|
819
|
-
expect(result[
|
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[
|
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[
|
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
|