brainstem 0.2.6.1 → 1.0.0.pre.1
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.
- 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
|