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,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'brainstem/concerns/controller_param_management'
|
3
|
+
|
4
|
+
describe Brainstem::Concerns::ControllerParamManagement do
|
5
|
+
class TasksController
|
6
|
+
include Brainstem::Concerns::ControllerParamManagement
|
7
|
+
|
8
|
+
def controller_name
|
9
|
+
'tasks'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
before do
|
14
|
+
TasksController.brainstem_model_name = nil
|
15
|
+
TasksController.brainstem_plural_model_name = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.brainstem_model_name' do
|
19
|
+
it 'is settable on the controller' do
|
20
|
+
TasksController.brainstem_model_name = 'thingy'
|
21
|
+
expect(TasksController.new.brainstem_model_name).to eq 'thingy'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'has good defaults' do
|
25
|
+
expect(TasksController.new.brainstem_model_name).to eq 'task'
|
26
|
+
expect(TasksController.new.brainstem_plural_model_name).to eq 'tasks'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.brainstem_plural_model_name' do
|
31
|
+
it 'is infered from the singular model name' do
|
32
|
+
TasksController.brainstem_model_name = 'thingy'
|
33
|
+
expect(TasksController.new.brainstem_plural_model_name).to eq 'thingies'
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'can be overridden' do
|
37
|
+
TasksController.brainstem_model_name = 'thingy'
|
38
|
+
TasksController.brainstem_plural_model_name = 'thingzees'
|
39
|
+
expect(TasksController.new.brainstem_plural_model_name).to eq 'thingzees'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'brainstem/concerns/error_presentation'
|
3
|
+
|
4
|
+
describe Brainstem::Concerns::ErrorPresentation do
|
5
|
+
class ErrorsController
|
6
|
+
include Brainstem::Concerns::ErrorPresentation
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:controller) { ErrorsController.new }
|
10
|
+
|
11
|
+
describe "#brainstem_system_error" do
|
12
|
+
let(:options) { { type: :other } }
|
13
|
+
|
14
|
+
it "accepts a list of messages" do
|
15
|
+
error_response = {errors: [{type: :system, message: "error1"}, {type: :system, message: "error2"}]}
|
16
|
+
expect(controller.brainstem_system_error("error1", "error2")).to eq(error_response)
|
17
|
+
expect(controller.brainstem_system_error(["error1", "error2"])).to eq(error_response)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "accepts an options hash as last argument" do
|
21
|
+
error_response = {errors: [{type: :other, message: "error1"}, {type: :other, message: "error2"}]}
|
22
|
+
expect(controller.brainstem_system_error("error1", "error2", options)).to eq(error_response)
|
23
|
+
expect(controller.brainstem_system_error(["error1", "error2"], options)).to eq(error_response)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#brainstem_model_error" do
|
28
|
+
context 'with a Hash or Hashes' do
|
29
|
+
it 'has a default type' do
|
30
|
+
expect(controller.brainstem_model_error({ message: 'hello', field: 'some_field' })).to eq({ errors: [ { message: 'hello', type: 'validation', field: 'some_field' } ] })
|
31
|
+
expect(controller.brainstem_model_error([{ message: 'hello1', field: 'some_field1' }, { message: 'hello2', field: 'some_field2' }])).to eq({
|
32
|
+
errors: [
|
33
|
+
{ message: 'hello1', type: 'validation', field: 'some_field1' },
|
34
|
+
{ message: 'hello2', type: 'validation', field: 'some_field2' }
|
35
|
+
]
|
36
|
+
})
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'can override the type' do
|
40
|
+
expect(controller.brainstem_model_error({ message: 'something', type: 'foo' })[:errors].first[:type]).to eq 'foo'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises an ArgumentError when no message is given' do
|
44
|
+
expect { controller.brainstem_model_error({ type: 'foo' }) }.to raise_error(ArgumentError, /message required/)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'can handle a String' do
|
49
|
+
expect(controller.brainstem_model_error("hello")).to eq({ errors: [ { message: 'hello', type: 'validation', field: :base } ] })
|
50
|
+
expect(controller.brainstem_model_error(["hello", "world"])).to eq({
|
51
|
+
errors: [
|
52
|
+
{ message: 'hello', type: 'validation', field: :base },
|
53
|
+
{ message: 'world', type: 'validation', field: :base }
|
54
|
+
]
|
55
|
+
})
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with models' do
|
59
|
+
class Model
|
60
|
+
include ActiveModel::Validations
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'can handle a single Model' do
|
64
|
+
model = Model.new
|
65
|
+
model.errors.add(:title, 'must be present')
|
66
|
+
expect(controller.brainstem_model_error(model)).to eq({ errors: [ { message: 'Title must be present', type: 'validation', field: :title, index: 0 } ] })
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'can handle an array of Models' do
|
70
|
+
model1 = Model.new
|
71
|
+
model1.errors.add(:title, 'must be present')
|
72
|
+
model2 = Model.new
|
73
|
+
model2.errors.add(:foo, 'cannot be blank')
|
74
|
+
model2.errors.add(:base, 'This model is invalid')
|
75
|
+
|
76
|
+
expect(controller.brainstem_model_error([model1, model2])).to eq({
|
77
|
+
errors: [
|
78
|
+
{ message: 'Title must be present', type: 'validation', field: :title, index: 0 },
|
79
|
+
{ message: 'Foo cannot be blank', type: 'validation', field: :foo, index: 1 },
|
80
|
+
{ message: 'This model is invalid', type: 'validation', field: :base, index: 1 },
|
81
|
+
]
|
82
|
+
})
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'can rewrite from internal to external field names' do
|
86
|
+
model = Model.new
|
87
|
+
model.errors.add(:title, 'must be present')
|
88
|
+
model.errors.add(:foo, 'cannot be blank')
|
89
|
+
|
90
|
+
expect(controller.brainstem_model_error(model, rewrite_params: { external_title: :title })).to eq({
|
91
|
+
errors: [
|
92
|
+
{ message: 'Title must be present', type: 'validation', field: :external_title, index: 0 },
|
93
|
+
{ message: 'Foo cannot be blank', type: 'validation', field: :foo, index: 0 },
|
94
|
+
]
|
95
|
+
})
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'handles magic ^ in error message' do
|
99
|
+
model = Model.new
|
100
|
+
model.errors.add(:title, '^Your thing must have a title')
|
101
|
+
|
102
|
+
expect(controller.brainstem_model_error(model)).to eq({
|
103
|
+
errors: [
|
104
|
+
message: 'Your thing must have a title',
|
105
|
+
type: 'validation',
|
106
|
+
field: :title,
|
107
|
+
index: 0,
|
108
|
+
]
|
109
|
+
})
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'brainstem/concerns/inheritable_configuration'
|
3
|
+
|
4
|
+
describe Brainstem::Concerns::InheritableConfiguration do
|
5
|
+
let(:parent_class) do
|
6
|
+
Class.new do
|
7
|
+
include Brainstem::Concerns::InheritableConfiguration
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.configuration' do
|
12
|
+
it 'is inherited' do
|
13
|
+
expect(parent_class.configuration['empty']).to be_nil
|
14
|
+
parent_class.configuration['two'] = 2
|
15
|
+
parent_class.configuration['five'] = 5
|
16
|
+
expect(parent_class.configuration['two']).to eq 2
|
17
|
+
expect(parent_class.configuration['five']).to eq 5
|
18
|
+
|
19
|
+
subclass = Class.new(parent_class)
|
20
|
+
expect(subclass.configuration['empty']).to be_nil
|
21
|
+
expect(subclass.configuration['two']).to eq 2
|
22
|
+
expect(subclass.configuration['five']).to eq 5
|
23
|
+
|
24
|
+
subclass.configuration['two'] = 3
|
25
|
+
subclass.configuration['ten'] = 10
|
26
|
+
expect(subclass.configuration['empty']).to be_nil
|
27
|
+
expect(subclass.configuration['two']).to eq 3
|
28
|
+
expect(subclass.configuration['five']).to eq 5
|
29
|
+
expect(subclass.configuration['ten']).to eq 10
|
30
|
+
|
31
|
+
expect(parent_class.configuration['two']).to eq 2
|
32
|
+
expect(parent_class.configuration['five']).to eq 5
|
33
|
+
expect(parent_class.configuration['ten']).to be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#keys' do
|
37
|
+
it "returns the union of this class's keys with any parent keys" do
|
38
|
+
parent_class.configuration['1'] = :a
|
39
|
+
parent_class.configuration['2'] = :b
|
40
|
+
|
41
|
+
subclass = Class.new(parent_class)
|
42
|
+
subclass.configuration['2'] = :c
|
43
|
+
subclass.configuration['3'] = :d
|
44
|
+
|
45
|
+
subsubclass = Class.new(subclass)
|
46
|
+
subsubclass.configuration['3'] = :e
|
47
|
+
subsubclass.configuration['4'] = :f
|
48
|
+
|
49
|
+
expect(parent_class.configuration.keys).to eq ['1', '2']
|
50
|
+
expect(subclass.configuration.keys).to eq ['1', '2', '3']
|
51
|
+
expect(subsubclass.configuration.keys).to eq ['1', '2', '3', '4']
|
52
|
+
|
53
|
+
expect(parent_class.configuration['1']).to eq :a
|
54
|
+
expect(parent_class.configuration['2']).to eq :b
|
55
|
+
expect(parent_class.configuration['3']).to be_nil
|
56
|
+
expect(subclass.configuration['1']).to eq :a
|
57
|
+
expect(subclass.configuration['2']).to eq :c
|
58
|
+
expect(subclass.configuration['3']).to eq :d
|
59
|
+
expect(subclass.configuration['4']).to be_nil
|
60
|
+
expect(subsubclass.configuration['1']).to eq :a
|
61
|
+
expect(subsubclass.configuration['2']).to eq :c
|
62
|
+
expect(subsubclass.configuration['3']).to eq :e
|
63
|
+
expect(subsubclass.configuration['4']).to eq :f
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#nest!' do
|
68
|
+
it 'builds nested objects' do
|
69
|
+
parent_class.configuration.nest!('top_level')
|
70
|
+
expect(parent_class.configuration['top_level']).to be_a(Brainstem::DSL::Configuration)
|
71
|
+
parent_class.configuration.nest!('top_level').nest!('next_level')
|
72
|
+
expect(parent_class.configuration['top_level']['next_level']).to be_a(Brainstem::DSL::Configuration)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'is chainable' do
|
76
|
+
parent_class.configuration.nest!('top_level').nest!('sub_one')
|
77
|
+
sub_two = parent_class.configuration.nest!('top_level').nest!('sub_two')
|
78
|
+
expect(parent_class.configuration['top_level']['sub_one']).to be_a(Brainstem::DSL::Configuration)
|
79
|
+
expect(parent_class.configuration['top_level']['sub_two']).to be_a(Brainstem::DSL::Configuration)
|
80
|
+
expect(parent_class.configuration['top_level']['sub_two']).to be sub_two
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'inherits nested values' do
|
84
|
+
parent_class.configuration.nest!('top_level').nest!('sub_one')
|
85
|
+
parent_class.configuration.nest!('top_level').nest!('sub_two')
|
86
|
+
parent_class.configuration['top_level']['key'] = 'value'
|
87
|
+
parent_class.configuration['top_level']['key2'] = 'value2'
|
88
|
+
parent_class.configuration['top_level']['sub_one']['sub_one_key1'] = 'sub_one_value1'
|
89
|
+
parent_class.configuration['top_level']['sub_one']['sub_one_key2'] = 'sub_one_value2'
|
90
|
+
parent_class.configuration['top_level']['sub_two']['sub_two_key'] = 'sub_two_value'
|
91
|
+
|
92
|
+
expect(parent_class.configuration['top_level']['key']).to eq 'value'
|
93
|
+
expect(parent_class.configuration['top_level']['sub_one']['sub_one_key1']).to eq 'sub_one_value1'
|
94
|
+
|
95
|
+
subclass = Class.new(parent_class)
|
96
|
+
expect(subclass.configuration['top_level']['key']).to eq 'value'
|
97
|
+
expect(subclass.configuration['top_level']['key2']).to eq 'value2'
|
98
|
+
expect(subclass.configuration['top_level']['sub_one']['sub_one_key1']).to eq 'sub_one_value1'
|
99
|
+
expect(subclass.configuration['top_level']['sub_one']['sub_one_key2']).to eq 'sub_one_value2'
|
100
|
+
expect(subclass.configuration['top_level']['sub_two']['sub_two_key']).to eq 'sub_two_value'
|
101
|
+
|
102
|
+
# These should have no affect
|
103
|
+
subclass.configuration['top_level'].nest!('sub_one')
|
104
|
+
subclass.configuration['top_level'].nest!('sub_two')
|
105
|
+
|
106
|
+
# This should add a new nested configuration
|
107
|
+
subclass.configuration['top_level'].nest!('new_nesting')['key'] = 'hello'
|
108
|
+
|
109
|
+
subclass.configuration['top_level']['key'] = 'overriden value'
|
110
|
+
subclass.configuration['top_level']['new_key'] = 'new value'
|
111
|
+
subclass.configuration['top_level']['sub_one']['sub_one_key1'] = 'overriden nested value'
|
112
|
+
subclass.configuration['top_level']['sub_one']['new_key'] = 'new nested value'
|
113
|
+
|
114
|
+
expect(subclass.configuration['top_level']['key']).to eq 'overriden value'
|
115
|
+
expect(subclass.configuration['top_level']['key2']).to eq 'value2'
|
116
|
+
expect(subclass.configuration['top_level']['new_key']).to eq 'new value'
|
117
|
+
expect(subclass.configuration['top_level']['sub_one']['sub_one_key1']).to eq 'overriden nested value'
|
118
|
+
expect(subclass.configuration['top_level']['sub_one']['sub_one_key2']).to eq 'sub_one_value2'
|
119
|
+
expect(subclass.configuration['top_level']['sub_one']['new_key']).to eq 'new nested value'
|
120
|
+
expect(subclass.configuration['top_level']['sub_two']['sub_two_key']).to eq 'sub_two_value'
|
121
|
+
expect(subclass.configuration['top_level']['new_nesting']['key']).to eq 'hello'
|
122
|
+
|
123
|
+
expect(parent_class.configuration['top_level']['key']).to eq 'value'
|
124
|
+
expect(parent_class.configuration['top_level']['key2']).to eq 'value2'
|
125
|
+
expect(parent_class.configuration['top_level']['new_key']).to be_nil
|
126
|
+
expect(parent_class.configuration['top_level']['sub_one']['sub_one_key1']).to eq 'sub_one_value1'
|
127
|
+
expect(parent_class.configuration['top_level']['sub_one']['sub_one_key2']).to eq 'sub_one_value2'
|
128
|
+
expect(parent_class.configuration['top_level']['sub_one']['new_key']).to be_nil
|
129
|
+
expect(parent_class.configuration['top_level']['sub_two']['sub_two_key']).to eq 'sub_two_value'
|
130
|
+
expect(parent_class.configuration['top_level']['new_nesting']).to be_nil
|
131
|
+
|
132
|
+
# Adding an attribute to parent later will show up in sub
|
133
|
+
parent_class.configuration['top_level']['added_later'] = 5
|
134
|
+
expect(parent_class.configuration['top_level']['added_later']).to eq 5
|
135
|
+
expect(subclass.configuration['top_level']['added_later']).to eq 5
|
136
|
+
|
137
|
+
# Changing an attribute to parent later will show up in sub
|
138
|
+
parent_class.configuration['top_level']['added_later'] = 6
|
139
|
+
expect(parent_class.configuration['top_level']['added_later']).to eq 6
|
140
|
+
expect(subclass.configuration['top_level']['added_later']).to eq 6
|
141
|
+
|
142
|
+
# Overriding an attribute in sub only affects sub.
|
143
|
+
subclass.configuration['top_level']['added_later'] = 5
|
144
|
+
expect(parent_class.configuration['top_level']['added_later']).to eq 6
|
145
|
+
expect(subclass.configuration['top_level']['added_later']).to eq 5
|
146
|
+
|
147
|
+
# Can add nesting to sub only
|
148
|
+
subclass.configuration['top_level'].nest!('only_sub')
|
149
|
+
subclass.configuration['top_level']['only_sub']['two'] = 2
|
150
|
+
expect(subclass.configuration['top_level']['only_sub']['two']).to eq 2
|
151
|
+
expect(parent_class.configuration['top_level']['only_sub']).to be_nil
|
152
|
+
|
153
|
+
# Go even deeper
|
154
|
+
subsubclass = Class.new(subclass)
|
155
|
+
expect(subsubclass.configuration['top_level']['only_sub']['two']).to eq 2
|
156
|
+
subsubclass.configuration['top_level']['only_sub']['two'] = 3
|
157
|
+
expect(subsubclass.configuration['top_level']['only_sub']['two']).to eq 3
|
158
|
+
expect(subclass.configuration['top_level']['only_sub']['two']).to eq 2
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'will not override nested values' do
|
162
|
+
parent_class.configuration.nest!('top_level').nest!('sub_one')
|
163
|
+
subclass = Class.new(parent_class)
|
164
|
+
expect(lambda { subclass.configuration['top_level']['sub_one'] = 2 }).to raise_error('You cannot override a nested value')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#array!' do
|
169
|
+
let!(:array) { parent_class.configuration.array!('list') }
|
170
|
+
|
171
|
+
it 'builds an InheritableAppendSet' do
|
172
|
+
expect(array).to be_a(Brainstem::DSL::Configuration::InheritableAppendSet)
|
173
|
+
expect(parent_class.configuration.array!('list')).to be array
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'is inherited' do
|
177
|
+
parent_class.configuration['list'] << '2'
|
178
|
+
parent_class.configuration['list'].push 3
|
179
|
+
|
180
|
+
subclass = Class.new(parent_class)
|
181
|
+
expect(parent_class.configuration['list'].to_a).to eq ['2', 3]
|
182
|
+
expect(subclass.configuration['list'].to_a).to eq ['2', 3]
|
183
|
+
|
184
|
+
parent_class.configuration['list'].push 4
|
185
|
+
expect(parent_class.configuration['list'].to_a).to eq ['2', 3, 4]
|
186
|
+
expect(subclass.configuration['list'].to_a).to eq ['2', 3, 4]
|
187
|
+
|
188
|
+
subclass.configuration['list'].push 5
|
189
|
+
expect(parent_class.configuration['list'].to_a).to eq ['2', 3, 4]
|
190
|
+
expect(subclass.configuration['list'].to_a).to eq ['2', 3, 4, 5]
|
191
|
+
|
192
|
+
parent_class.configuration['list'].push 6
|
193
|
+
expect(parent_class.configuration['list'].to_a).to eq ['2', 3, 4, 6]
|
194
|
+
expect(subclass.configuration['list'].to_a).to eq ['2', 3, 4, 6, 5]
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'will not override arrays' do
|
198
|
+
subclass = Class.new(parent_class)
|
199
|
+
expect(lambda { subclass.configuration['list'] = 2 }).to raise_error('You cannot override an inheritable array once set')
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe '#configuration' do
|
205
|
+
it 'is available on the instance' do
|
206
|
+
parent_class.configuration['two'] = 2
|
207
|
+
expect(parent_class.new.configuration['two']).to eq 2
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,412 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'brainstem/concerns/presenter_dsl'
|
3
|
+
|
4
|
+
# preload :lead_user
|
5
|
+
#
|
6
|
+
# brainstem_key :projects
|
7
|
+
#
|
8
|
+
# conditionals do
|
9
|
+
# model :title_is_hello, lambda { workspace.title == 'hello' }, 'visible when the title is hello'
|
10
|
+
# request :user_is_bob, lambda { current_user.username == 'bob' }, 'visible only to bob'
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# fields do
|
14
|
+
# field :title, :string
|
15
|
+
# field :description, :string
|
16
|
+
# field :updated_at, :datetime
|
17
|
+
# field :dynamic_title, :string, dynamic: lambda { |model| model.title }
|
18
|
+
# field :secret, :string, 'a secret, via secret_info',
|
19
|
+
# via: :secret_info,
|
20
|
+
# if: [:user_is_bob, :title_is_hello]
|
21
|
+
#
|
22
|
+
# with_options if: :user_is_bob do
|
23
|
+
# field :bob_title, :string, 'another name for the title, only for Bob',
|
24
|
+
# via: :title
|
25
|
+
# end
|
26
|
+
# fields :nested_permissions do
|
27
|
+
# field :something_title, :string, via: :title
|
28
|
+
# field :random, :number, dynamic: lambda { rand }
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# associations do
|
33
|
+
# association :tasks, Task, 'The Tasks in this Workspace',
|
34
|
+
# restrict_to_only: true
|
35
|
+
# association :lead_user, User, 'The user who runs this Workspace'
|
36
|
+
# association :subtasks, Task, 'Only Tasks in this Workspace that are subtasks',
|
37
|
+
# dynamic: lambda { |workspace| workspace.tasks.where('parent_id IS NOT NULL') }
|
38
|
+
# association :something, :polymorphic
|
39
|
+
# end
|
40
|
+
|
41
|
+
describe Brainstem::Concerns::PresenterDSL do
|
42
|
+
let(:presenter_class) do
|
43
|
+
Class.new do
|
44
|
+
include Brainstem::Concerns::PresenterDSL
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#preload directive' do
|
49
|
+
it 'builds a list of associations to preload' do
|
50
|
+
presenter_class.preload :tasks
|
51
|
+
expect(presenter_class.configuration[:preloads].to_a).to eq [:tasks]
|
52
|
+
presenter_class.preload(lead_user: { workspaces: [:lead_user, :tasks] })
|
53
|
+
expect(presenter_class.configuration[:preloads].to_a).to eq [ :tasks, lead_user: { workspaces: [:lead_user, :tasks] } ]
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'is inherited' do
|
57
|
+
presenter_class.preload :tasks
|
58
|
+
subclass = Class.new(presenter_class)
|
59
|
+
subclass.preload :lead_user
|
60
|
+
expect(presenter_class.configuration[:preloads].to_a).to eq [:tasks]
|
61
|
+
expect(subclass.configuration[:preloads].to_a).to eq [:tasks, :lead_user]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'the conditional block' do
|
66
|
+
before do
|
67
|
+
presenter_class.conditionals do
|
68
|
+
model :title_is_hello, lambda { |workspace| workspace.title == 'hello' }, 'visible when the title is hello'
|
69
|
+
request :user_is_bob, lambda { current_user == 'bob' }, 'visible only to bob'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'is stored in the configuration' do
|
74
|
+
expect(presenter_class.configuration[:conditionals].keys).to eq %w[title_is_hello user_is_bob]
|
75
|
+
expect(presenter_class.configuration[:conditionals][:title_is_hello].action).to be_present
|
76
|
+
expect(presenter_class.configuration[:conditionals][:title_is_hello].type).to eq :model
|
77
|
+
expect(presenter_class.configuration[:conditionals][:title_is_hello].description).to eq 'visible when the title is hello'
|
78
|
+
expect(presenter_class.configuration[:conditionals][:user_is_bob].action).to be_present
|
79
|
+
expect(presenter_class.configuration[:conditionals][:user_is_bob].type).to eq :request
|
80
|
+
expect(presenter_class.configuration[:conditionals][:user_is_bob].description).to eq 'visible only to bob'
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'is inherited and overridable' do
|
84
|
+
subclass = Class.new(presenter_class)
|
85
|
+
subclass.conditionals do
|
86
|
+
model :silly_conditional, lambda { rand > 0.5 }, 'visible half the time'
|
87
|
+
model :title_is_hello, lambda { |workspace| workspace.title == 'HELLO' }, 'visible when the title is hello (in all caps)'
|
88
|
+
end
|
89
|
+
expect(presenter_class.configuration[:conditionals].keys).to eq %w[title_is_hello user_is_bob]
|
90
|
+
expect(subclass.configuration[:conditionals].keys).to eq %w[title_is_hello user_is_bob silly_conditional]
|
91
|
+
expect(presenter_class.configuration[:conditionals][:title_is_hello].description).to eq "visible when the title is hello"
|
92
|
+
expect(subclass.configuration[:conditionals][:title_is_hello].description).to eq "visible when the title is hello (in all caps)"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'the fields block' do
|
97
|
+
before do
|
98
|
+
presenter_class.fields do
|
99
|
+
field :updated_at, :datetime
|
100
|
+
field :dynamic_title, :string, dynamic: lambda { |model| model.title }
|
101
|
+
field :secret, :string,
|
102
|
+
via: :secret_info,
|
103
|
+
if: [:user_is_bob, :title_is_hello]
|
104
|
+
|
105
|
+
with_options if: :user_is_bob do
|
106
|
+
field :bob_title, :string, 'another name for the title, only for Bob',
|
107
|
+
via: :title
|
108
|
+
end
|
109
|
+
fields :nested_permissions do
|
110
|
+
field :something_title, :string, via: :title
|
111
|
+
field :random, :number, dynamic: lambda { rand }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'is stored in the configuration' do
|
117
|
+
expect(presenter_class.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions]
|
118
|
+
expect(presenter_class.configuration[:fields][:updated_at].type).to eq :datetime
|
119
|
+
expect(presenter_class.configuration[:fields][:updated_at].description).to be_nil
|
120
|
+
expect(presenter_class.configuration[:fields][:dynamic_title].type).to eq :string
|
121
|
+
expect(presenter_class.configuration[:fields][:dynamic_title].description).to be_nil
|
122
|
+
expect(presenter_class.configuration[:fields][:dynamic_title].options[:dynamic]).to be_a(Proc)
|
123
|
+
expect(presenter_class.configuration[:fields][:secret].type).to eq :string
|
124
|
+
expect(presenter_class.configuration[:fields][:secret].description).to be_nil
|
125
|
+
expect(presenter_class.configuration[:fields][:secret].options).to eq({ via: :secret_info, if: [:user_is_bob, :title_is_hello] })
|
126
|
+
expect(presenter_class.configuration[:fields][:bob_title].type).to eq :string
|
127
|
+
expect(presenter_class.configuration[:fields][:bob_title].description).to eq 'another name for the title, only for Bob'
|
128
|
+
expect(presenter_class.configuration[:fields][:bob_title].options).to eq({ via: :title, if: [:user_is_bob] })
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'handles nesting' do
|
132
|
+
expect(presenter_class.configuration[:fields][:nested_permissions][:something_title].type).to eq :string
|
133
|
+
expect(presenter_class.configuration[:fields][:nested_permissions][:something_title].options[:via]).to eq :title
|
134
|
+
expect(presenter_class.configuration[:fields][:nested_permissions][:random].options[:dynamic]).to be_a(Proc)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'is inherited and overridable' do
|
138
|
+
subclass = Class.new(presenter_class)
|
139
|
+
subclass.fields do
|
140
|
+
field :title, :string
|
141
|
+
with_options if: [:some_condition, :some_other_condition] do
|
142
|
+
field :updated_at, :datetime, 'this time I have a description and condition'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
expect(presenter_class.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions]
|
146
|
+
expect(subclass.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title title nested_permissions]
|
147
|
+
expect(presenter_class.configuration[:fields][:updated_at].description).to be_nil
|
148
|
+
expect(presenter_class.configuration[:fields][:updated_at].options).to eq({})
|
149
|
+
expect(subclass.configuration[:fields][:updated_at].description).to eq 'this time I have a description and condition'
|
150
|
+
expect(subclass.configuration[:fields][:updated_at].options).to eq({ if: [:some_condition, :some_other_condition] })
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'any :if options are combined and inherited using with_options' do
|
154
|
+
presenter_class.fields do
|
155
|
+
with_options if: :user_is_bob do
|
156
|
+
field :bob_title, :string, 'another name for the title, only for Bob',
|
157
|
+
via: :title, if: :another_condition
|
158
|
+
field :bob_title2, :string, 'another name for the title, only for Bob',
|
159
|
+
via: :title, if: :another_condition
|
160
|
+
end
|
161
|
+
end
|
162
|
+
subclass = Class.new(presenter_class)
|
163
|
+
subclass.fields do
|
164
|
+
with_options if: [:user_is_bob, :more_specific] do
|
165
|
+
field :bob_title, :string, 'another name for the title, only for Bob',
|
166
|
+
via: :title, if: [:another_condition]
|
167
|
+
field :bob_title2, :string, 'another name for the title, only for Bob',
|
168
|
+
via: :title
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
expect(presenter_class.configuration[:fields][:bob_title].options[:if]).to eq([:user_is_bob, :another_condition])
|
173
|
+
expect(subclass.configuration[:fields][:bob_title].options[:if]).to eq([:user_is_bob, :more_specific, :another_condition])
|
174
|
+
|
175
|
+
expect(presenter_class.configuration[:fields][:bob_title2].options[:if]).to eq([:user_is_bob, :another_condition])
|
176
|
+
expect(subclass.configuration[:fields][:bob_title2].options[:if]).to eq([:user_is_bob, :more_specific])
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'allows nesting to be inherited and overridden too' do
|
180
|
+
subclass = Class.new(presenter_class)
|
181
|
+
subclass.fields do
|
182
|
+
fields :nested_permissions do
|
183
|
+
field :something_title, :number, via: :title
|
184
|
+
field :new, :string, via: :title
|
185
|
+
fields :deeper do
|
186
|
+
field :something, :string, via: :title
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
fields :new_nested_permissions do
|
191
|
+
field :something, :string, via: :title
|
192
|
+
end
|
193
|
+
end
|
194
|
+
expect(presenter_class.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions]
|
195
|
+
expect(subclass.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions new_nested_permissions]
|
196
|
+
|
197
|
+
expect(presenter_class.configuration[:fields][:nested_permissions][:something_title].type).to eq :string
|
198
|
+
expect(presenter_class.configuration[:fields][:nested_permissions][:random].type).to eq :number
|
199
|
+
expect(presenter_class.configuration[:fields][:nested_permissions][:new]).to be_nil
|
200
|
+
expect(presenter_class.configuration[:fields][:nested_permissions][:deeper]).to be_nil
|
201
|
+
expect(presenter_class.configuration[:fields][:new_nested_permissions]).to be_nil
|
202
|
+
|
203
|
+
expect(subclass.configuration[:fields][:nested_permissions][:something_title].type).to eq :number # changed this
|
204
|
+
expect(subclass.configuration[:fields][:nested_permissions][:random].type).to eq :number
|
205
|
+
expect(subclass.configuration[:fields][:nested_permissions][:new].type).to eq :string
|
206
|
+
expect(subclass.configuration[:fields][:nested_permissions][:deeper][:something]).to be_present
|
207
|
+
expect(subclass.configuration[:fields][:new_nested_permissions]).to be_present
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe 'the associations block' do
|
212
|
+
before do
|
213
|
+
presenter_class.associations do
|
214
|
+
association :tasks, Task, 'The Tasks in this Workspace',
|
215
|
+
restrict_to_only: true
|
216
|
+
association :subtasks, Task, 'Only Tasks in this Workspace that are subtasks',
|
217
|
+
dynamic: lambda { |workspace| workspace.tasks.where('parent_id IS NOT NULL') }
|
218
|
+
association :something, :polymorphic
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'is stored in the configuration' do
|
223
|
+
expect(presenter_class.configuration[:associations].keys).to match_array %w[tasks subtasks something]
|
224
|
+
expect(presenter_class.configuration[:associations][:tasks].target_class).to eq Task
|
225
|
+
expect(presenter_class.configuration[:associations][:tasks].description).to eq 'The Tasks in this Workspace'
|
226
|
+
expect(presenter_class.configuration[:associations][:tasks].options).to eq({ restrict_to_only: true })
|
227
|
+
expect(presenter_class.configuration[:associations][:subtasks].target_class).to eq Task
|
228
|
+
expect(presenter_class.configuration[:associations][:subtasks].description).to eq 'Only Tasks in this Workspace that are subtasks'
|
229
|
+
expect(presenter_class.configuration[:associations][:subtasks].options.keys).to eq [:dynamic]
|
230
|
+
expect(presenter_class.configuration[:associations][:something].target_class).to eq :polymorphic
|
231
|
+
expect(presenter_class.configuration[:associations][:something].description).to be_nil
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'is inherited and overridable' do
|
235
|
+
subclass = Class.new(presenter_class)
|
236
|
+
subclass.associations do
|
237
|
+
association :tasks, Task, 'The Tasks in this Workspace'
|
238
|
+
association :lead_user, User, 'The user who runs this Workspace'
|
239
|
+
end
|
240
|
+
|
241
|
+
expect(presenter_class.configuration[:associations].keys).to match_array %w[tasks subtasks something]
|
242
|
+
expect(subclass.configuration[:associations].keys).to match_array %w[tasks subtasks lead_user something]
|
243
|
+
|
244
|
+
expect(presenter_class.configuration[:associations][:tasks].options).to eq({ restrict_to_only: true })
|
245
|
+
expect(presenter_class.configuration[:associations][:lead_user]).to be_nil
|
246
|
+
|
247
|
+
expect(subclass.configuration[:associations][:tasks].options).to eq({})
|
248
|
+
expect(subclass.configuration[:associations][:lead_user].target_class).to eq User
|
249
|
+
expect(subclass.configuration[:associations][:lead_user].description).to eq 'The user who runs this Workspace'
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
describe ".helper" do
|
254
|
+
let(:model) { Workspace.first }
|
255
|
+
|
256
|
+
let(:presenter) do
|
257
|
+
Class.new(Brainstem::Presenter) do
|
258
|
+
helper do
|
259
|
+
def method_in_block
|
260
|
+
'method_in_block'
|
261
|
+
end
|
262
|
+
|
263
|
+
def block_to_module
|
264
|
+
'i am in a block, but can see ' + method_in_module
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
fields do
|
269
|
+
field :from_module, :string, dynamic: lambda { method_in_module }
|
270
|
+
field :from_block, :string, dynamic: lambda { method_in_block }
|
271
|
+
field :block_to_module, :string, dynamic: lambda { block_to_module }
|
272
|
+
field :module_to_block, :string, dynamic: lambda { module_to_block }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
let(:sub_presenter) do
|
278
|
+
Class.new(presenter) do
|
279
|
+
helper do
|
280
|
+
def method_in_block
|
281
|
+
'overridden method_in_block'
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
let(:helper_module) do
|
288
|
+
Module.new do
|
289
|
+
def method_in_module
|
290
|
+
'method_in_module'
|
291
|
+
end
|
292
|
+
|
293
|
+
def module_to_block
|
294
|
+
'i am in a module, but can see ' + method_in_block
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
let(:sub_helper_module) do
|
300
|
+
Module.new do
|
301
|
+
def method_in_module
|
302
|
+
'overridden method_in_module'
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'provides any methods from given blocks to the lambda' do
|
308
|
+
presenter.helper helper_module
|
309
|
+
expect(presenter.new.group_present([model]).first['from_block']).to eq 'method_in_block'
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'provides any methods from given Modules to the lambda' do
|
313
|
+
presenter.helper helper_module
|
314
|
+
expect(presenter.new.group_present([model]).first['from_module']).to eq 'method_in_module'
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'allows methods in modules and blocks to see each other' do
|
318
|
+
presenter.helper helper_module
|
319
|
+
expect(presenter.new.group_present([model]).first['block_to_module']).to eq 'i am in a block, but can see method_in_module'
|
320
|
+
expect(presenter.new.group_present([model]).first['module_to_block']).to eq 'i am in a module, but can see method_in_block'
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'merges the blocks and modules into a combined helper' do
|
324
|
+
presenter.helper helper_module
|
325
|
+
expect(presenter.merged_helper_class.instance_methods).to include(:method_in_module, :module_to_block, :method_in_block, :block_to_module)
|
326
|
+
end
|
327
|
+
|
328
|
+
it 'can be cleaned up' do
|
329
|
+
expect(presenter.merged_helper_class.instance_methods).to include(:method_in_block)
|
330
|
+
expect(presenter.merged_helper_class.instance_methods).to_not include(:method_in_module)
|
331
|
+
presenter.helper helper_module
|
332
|
+
expect(presenter.merged_helper_class.instance_methods).to include(:method_in_block, :method_in_module)
|
333
|
+
presenter.reset!
|
334
|
+
expect(presenter.merged_helper_class.instance_methods).to_not include(:method_in_block)
|
335
|
+
expect(presenter.merged_helper_class.instance_methods).to_not include(:method_in_module)
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'caches the generated class' do
|
339
|
+
class1 = presenter.merged_helper_class
|
340
|
+
class2 = presenter.merged_helper_class
|
341
|
+
expect(class1).to eq class2
|
342
|
+
presenter.helper helper_module
|
343
|
+
class3 = presenter.merged_helper_class
|
344
|
+
class4 = presenter.merged_helper_class
|
345
|
+
expect(class1).not_to eq class3
|
346
|
+
expect(class3).to eq class4
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'is inheritable' do
|
350
|
+
presenter.helper helper_module
|
351
|
+
expect(sub_presenter.new.group_present([model]).first['from_block']).to eq 'overridden method_in_block'
|
352
|
+
expect(sub_presenter.new.group_present([model]).first['from_module']).to eq 'method_in_module'
|
353
|
+
expect(sub_presenter.new.group_present([model]).first['block_to_module']).to eq 'i am in a block, but can see method_in_module'
|
354
|
+
expect(sub_presenter.new.group_present([model]).first['module_to_block']).to eq 'i am in a module, but can see overridden method_in_block'
|
355
|
+
sub_presenter.helper sub_helper_module
|
356
|
+
expect(sub_presenter.new.group_present([model]).first['from_module']).to eq 'overridden method_in_module'
|
357
|
+
expect(sub_presenter.new.group_present([model]).first['block_to_module']).to eq 'i am in a block, but can see overridden method_in_module'
|
358
|
+
expect(presenter.new.group_present([model]).first['from_module']).to eq 'method_in_module'
|
359
|
+
expect(presenter.new.group_present([model]).first['block_to_module']).to eq 'i am in a block, but can see method_in_module'
|
360
|
+
end
|
361
|
+
|
362
|
+
it 'caches the generated classes with inheritance' do
|
363
|
+
class1 = presenter.merged_helper_class
|
364
|
+
class2 = sub_presenter.merged_helper_class
|
365
|
+
expect(presenter.merged_helper_class).to eq class1
|
366
|
+
expect(sub_presenter.merged_helper_class).to eq class2
|
367
|
+
|
368
|
+
presenter.helper helper_module
|
369
|
+
|
370
|
+
expect(presenter.merged_helper_class).not_to eq class1
|
371
|
+
expect(sub_presenter.merged_helper_class).not_to eq class2
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
describe ".filter" do
|
376
|
+
it "creates an entry in the filters configuration" do
|
377
|
+
presenter_class.filter(:foo, :default => true) { 1 }
|
378
|
+
expect(presenter_class.configuration[:filters][:foo][0]).to eq({"default" => true})
|
379
|
+
expect(presenter_class.configuration[:filters][:foo][1]).to be_a(Proc)
|
380
|
+
end
|
381
|
+
|
382
|
+
it "accepts names without blocks" do
|
383
|
+
presenter_class.filter(:foo)
|
384
|
+
expect(presenter_class.configuration[:filters][:foo][1]).to be_nil
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
describe ".search" do
|
389
|
+
it "creates an entry in the search configuration" do
|
390
|
+
presenter_class.search do end
|
391
|
+
expect(presenter_class.configuration[:search]).to be_a(Proc)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
describe ".brainstem_key" do
|
396
|
+
before do
|
397
|
+
presenter_class.brainstem_key(:foo)
|
398
|
+
end
|
399
|
+
|
400
|
+
it "creates an entry in the brainstem_key configuration" do
|
401
|
+
expect(presenter_class.configuration[:brainstem_key]).to eq('foo')
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'is inherited and overridable' do
|
405
|
+
subclass = Class.new(presenter_class)
|
406
|
+
expect(subclass.configuration[:brainstem_key]).to eq('foo')
|
407
|
+
subclass.brainstem_key(:bar)
|
408
|
+
expect(subclass.configuration[:brainstem_key]).to eq('bar')
|
409
|
+
expect(presenter_class.configuration[:brainstem_key]).to eq('foo')
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|