levee 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/builder/base_builder_spec.rb +311 -0
- data/builder/builder_helper.rb +95 -0
- data/levee-0.0.1.gem +0 -0
- data/levee.gemspec +2 -2
- data/lib/levee/validator.rb +37 -0
- data/lib/levee/version.rb +1 -1
- data/spec/base_builder_spec.rb +310 -0
- data/spec/builder_helper.rb +95 -0
- metadata +13 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcd858e4786b09e8a9ba17285b17b0f0b8f1c824
|
4
|
+
data.tar.gz: b3c1da36324aa5c7a2198f7de69a2e8ae9d38e63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4db403789b42ba351f7a66f0bfa57b9e5e094dea2c520e8e254e44a510ccd3c87a45dc9005cefaa0145aab4270d293a4f6368862f7bfa74154d874c60f39e9ad
|
7
|
+
data.tar.gz: 3136ad1286c7ecc2b8d12546ffd76203407b15ce384927944d16215bd0e1b49a3c2c3922f96b7fec395f75f56ee2ecf73dcb25f0232fafd9335a520758d3042b
|
@@ -0,0 +1,311 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require_relative './builder_helper.rb'
|
3
|
+
|
4
|
+
#Base builder is an abstract class. A test class is made in /spec/support/test_builder.rb that defines
|
5
|
+
#a concrete child class.
|
6
|
+
|
7
|
+
describe 'BaseBuidler' do
|
8
|
+
let(:demo_class) { DemoTest }
|
9
|
+
let(:params) { {name: 'test params',
|
10
|
+
content: 'none'} }
|
11
|
+
subject { DemoTestBuilder.new(params) }
|
12
|
+
|
13
|
+
describe 'initialization' do
|
14
|
+
it 'sets requires_save to true' do
|
15
|
+
expect(subject.requires_save).to be
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#build' do
|
20
|
+
let(:object_class) { double(find_by: nil) }
|
21
|
+
before do
|
22
|
+
allow(object_class).to receive(:new) { demo_class.new }
|
23
|
+
allow(subject).to receive(:object_class) { object_class }
|
24
|
+
allow(subject).to receive(:assign_parameters_in_transaction)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when the parameters are a top level array' do
|
28
|
+
it 'calls #assign_parameters_in_transaction' do
|
29
|
+
subject.params = []
|
30
|
+
expect(subject).to receive(:assign_parameters_in_transaction)
|
31
|
+
subject.build
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when the parameters are an object that contains an id' do
|
36
|
+
before { subject.params[:id] = 4 }
|
37
|
+
|
38
|
+
it 'class #find_by on #object class with params[:id]' do
|
39
|
+
expect(object_class).to receive(:find_by).with({id: subject.params[:id]})
|
40
|
+
subject.build
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when the parameters are an object that does not contain an id' do
|
45
|
+
|
46
|
+
it 'calls #object_class' do
|
47
|
+
expect(subject).to receive(:object_class)
|
48
|
+
subject.build
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'assigns a newly instantiated object of object_class to builder#object' do
|
52
|
+
allow(subject).to receive(:assign_parameters_in_transaction)
|
53
|
+
subject.build
|
54
|
+
expect(subject.object).to be_a demo_class
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'build_nested' do
|
60
|
+
before do
|
61
|
+
allow(subject).to receive(:build)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'sets #requires_save to false' do
|
65
|
+
expect(subject).to receive(:requires_save=).with(false)
|
66
|
+
subject.build_nested
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'calls #build' do
|
70
|
+
expect(subject).to receive(:build)
|
71
|
+
subject.build_nested
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'update(object_id:, acting_user=nil' do
|
76
|
+
context 'when the object can\'t be found' do
|
77
|
+
it 'returns an errors hash with the error status 404' do
|
78
|
+
object_class = double(find_by_id: nil)
|
79
|
+
allow(subject).to receive(:object_class) {object_class}
|
80
|
+
result = subject.update(object_id: 0)
|
81
|
+
expect(result[:error_status]).to eq(404)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when an object is found' do
|
86
|
+
before do
|
87
|
+
object_class = double(find_by_id: :found_object)
|
88
|
+
allow(subject).to receive(:object_class) {object_class}
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'assigns the found object to the builder\'s object attribute' do
|
92
|
+
expect(subject).to receive(:object=).with(:found_object)
|
93
|
+
subject.update(object_id: 0)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'calls #assign_parameters_in_transaction' do
|
97
|
+
expect(subject).to receive(:assign_parameters_in_transaction)
|
98
|
+
subject.update(object_id: 0)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when the params have a root node' do
|
104
|
+
it 'throws an exception' do
|
105
|
+
expect { DemoTestBuilder.new({demo_test: 'gg'}) }.to raise_error
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#permitted_attributes' do
|
110
|
+
context 'when the attributes method is called in the class definition with attribute symbols as parameter' do
|
111
|
+
it 'those attribute symbols are available in #permitted_attributes' do
|
112
|
+
expect(DemoTestBuilder.new({}).permitted_attributes).to include :name
|
113
|
+
expect(DemoTestBuilder.new({}).permitted_attributes).to include :content
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'when params are passed that are not permitted at class definition' do
|
118
|
+
it 'does not include those in the #permitted_attributes' do
|
119
|
+
expect(DemoTestBuilder.new({}).permitted_attributes).to_not include :author
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe 'matching param keys to attributes' do
|
125
|
+
let(:unpermitted_params) { {name: 'test params',
|
126
|
+
content: 'none',
|
127
|
+
author: 'Jim'} }
|
128
|
+
it 'saves the object' do
|
129
|
+
builder = NoMethodsBuilder.new params
|
130
|
+
builder.build
|
131
|
+
expect(builder.object.saved).to be
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'does not save the object if a param key is included that is not listed as a permitted attribute' do
|
135
|
+
builder = NoMethodsBuilder.new unpermitted_params
|
136
|
+
builder.build
|
137
|
+
expect(builder.object.saved).to_not be
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'when there are no custom methods defined that match param keys' do
|
141
|
+
specify "For each param key that is included in permitted attributes, the value of the param is assigned to the object\'s attribute matching the key name" do
|
142
|
+
builder = NoMethodsBuilder.new params
|
143
|
+
builder.build
|
144
|
+
expect(builder.object.name).to eq(params[:name])
|
145
|
+
expect(builder.object.content).to eq(params[:content])
|
146
|
+
expect(builder.object.author).to_not be
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'when there are custom methods defined that match the name of attributes' do
|
151
|
+
specify 'those attributes are not automatically assigned' do
|
152
|
+
builder = MethodsBuilder.new params
|
153
|
+
builder.build
|
154
|
+
expect(builder.object.name).to_not be
|
155
|
+
expect(builder.object.content).to_not be
|
156
|
+
expect(builder.object.author).to_not be
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe '#top_level_array' do
|
162
|
+
let(:array_params) { [params,params] }
|
163
|
+
subject { DemoTestBuilder.new(array_params) }
|
164
|
+
|
165
|
+
context 'when the params contain an array of two objects' do
|
166
|
+
it 'returns two objects' do
|
167
|
+
result = subject.send(:top_level_array)
|
168
|
+
expect(result[0]).to be_a DemoTest
|
169
|
+
expect(result[1]).to be_a DemoTest
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'does not save the objects' do
|
173
|
+
result = subject.send(:top_level_array)
|
174
|
+
expect(result[0].saved).to_not be
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe 'delayed_save!(object)' do
|
180
|
+
it 'adds the object to the #nested_objects_to_save array' do
|
181
|
+
expect(subject.nested_objects_to_save).to be_a Array
|
182
|
+
subject.send(:delayed_save!, :nested_object)
|
183
|
+
expect(subject.nested_objects_to_save).to include :nested_object
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'does not add an duplicate object' do
|
187
|
+
expect(subject.nested_objects_to_save.length).to be 0
|
188
|
+
subject.send(:delayed_save!, 'nested_object')
|
189
|
+
subject.send(:delayed_save!, 'nested_object')
|
190
|
+
expect(subject.nested_objects_to_save.length).to be 1
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe 'callbacks' do
|
195
|
+
context 'when the before_save method is used in the class definition' do
|
196
|
+
it 'adds the method symbols passed to the #before_save_callbacks array' do
|
197
|
+
expect(subject.send(:before_save_callbacks)).to match_array [:before_one, :before_two]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when the after_save method is used in the class definition' do
|
202
|
+
it 'adds the method symbols passed to the #after_save_callbacks array' do
|
203
|
+
expect(subject.send(:after_save_callbacks)).to match_array [:after_one]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe 'building the object' do
|
209
|
+
context '#assign_parameters_in_transaction' do
|
210
|
+
|
211
|
+
context 'when there is a validator assigned' do
|
212
|
+
it 'calls #validate_parameters on the validator' do
|
213
|
+
subject.build
|
214
|
+
expect(subject.validator.validated).to be
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'when the validator returns errors' do
|
219
|
+
let(:error) { error = {status: 400, code: 'invalid_request_error'} }
|
220
|
+
before { allow(subject.validator).to receive(:errors) { [error] } }
|
221
|
+
|
222
|
+
it 'the method returns early, not calling the following methods' do
|
223
|
+
expect(subject).to_not receive(:top_level_array)
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'returns an errors hash' do
|
227
|
+
expect(subject.build[:errors]).to be
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
it '#calls top_level_array' do
|
232
|
+
expect(subject).to receive(:top_level_array).at_least(:once)
|
233
|
+
subject.build
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'calls #call_setter_for_each_param_key' do
|
237
|
+
expect(subject).to receive(:assign_parameters_in_transaction).at_least(:once)
|
238
|
+
subject.build
|
239
|
+
end
|
240
|
+
|
241
|
+
context 'when there are no errors' do
|
242
|
+
context 'when #requires_save is truthy' do
|
243
|
+
it 'saves the object' do
|
244
|
+
subject.build
|
245
|
+
expect(subject.object.saved).to be
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'calls save on all #nested_objects_to_save' do
|
249
|
+
nest1 = double
|
250
|
+
nest2 = double
|
251
|
+
expect(nest1).to receive(:save!)
|
252
|
+
expect(nest2).to receive(:save!)
|
253
|
+
subject.nested_objects_to_save = [ nest1, nest2 ]
|
254
|
+
subject.build
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'calls the #before_save_callbacks before the object is saved' do
|
258
|
+
subject.build
|
259
|
+
expect(subject.object.before_save_called_with_unsaved_object).to be
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'calls @after_save_callbacks after object is saved' do
|
263
|
+
subject.build
|
264
|
+
expect(subject.object.after_save_called_with_saved_object).to be
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'calls callback blocks after object is saved' do
|
268
|
+
subject = DemoTestBuilder.new(params) { |object| object.block_callback_called_after_save = true if object.saved }
|
269
|
+
subject.build
|
270
|
+
expect(subject.object.block_callback_called_after_save).to be
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context 'when #requires_save is falsey' do
|
275
|
+
it 'does not save the object' do
|
276
|
+
subject.build_nested
|
277
|
+
expect(subject.object.saved).to_not be
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
context 'when there are errors' do
|
283
|
+
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
describe 'self.validator' do
|
289
|
+
context 'when called with a validator class name' do
|
290
|
+
it 'adds that validator to the #validator method' do
|
291
|
+
expect(subject.validator).to be_a DemoParamsValidator
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'raises an exception if the class given is not a BaseParamsValidator' do
|
295
|
+
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe '#validator' do
|
301
|
+
it 'returns a BaseParamsValidator' do
|
302
|
+
expect(subject.validator).to be_a BaseParamsValidator
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'returns an object that responds to #params and returns some data' do
|
306
|
+
expect(subject.validator.params).to be
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative "../../app/application_services/base_builder"
|
2
|
+
require_relative "../../app/application_services/base_params_validator"
|
3
|
+
|
4
|
+
class DemoParamsValidator < BaseParamsValidator
|
5
|
+
|
6
|
+
attr_accessor :validated
|
7
|
+
|
8
|
+
def validate_params
|
9
|
+
@validated = true
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def errors
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class DemoTestBuilder < BaseBuilder
|
20
|
+
attributes :name, :content
|
21
|
+
before_save :before_one, :before_two
|
22
|
+
after_save :after_one
|
23
|
+
validator DemoParamsValidator
|
24
|
+
|
25
|
+
def before_one
|
26
|
+
object.before_save_called_with_unsaved_object = true unless object.saved
|
27
|
+
end
|
28
|
+
|
29
|
+
def before_two; end
|
30
|
+
|
31
|
+
def after_one
|
32
|
+
object.after_save_called_with_saved_object = true if object.saved
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class NoMethodsBuilder < BaseBuilder
|
37
|
+
attributes :name, :content
|
38
|
+
end
|
39
|
+
|
40
|
+
class MethodsBuilder < BaseBuilder
|
41
|
+
attributes :name, :content
|
42
|
+
before_save :before_method
|
43
|
+
after_save :after_method
|
44
|
+
validator DemoParamsValidator
|
45
|
+
|
46
|
+
def name(data); end
|
47
|
+
|
48
|
+
def content(data); end
|
49
|
+
|
50
|
+
def before_method
|
51
|
+
object.before_method_called = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def after_method
|
55
|
+
object.after_method_called = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class DemoTest
|
60
|
+
attr_accessor :name, :content, :author
|
61
|
+
attr_reader :saved, :updated
|
62
|
+
attr_accessor :before_method_called, :after_method_called
|
63
|
+
attr_accessor :before_save_called_with_unsaved_object
|
64
|
+
attr_accessor :after_save_called_with_saved_object
|
65
|
+
attr_accessor :block_callback_called_after_save
|
66
|
+
|
67
|
+
def save!
|
68
|
+
@saved = true
|
69
|
+
end
|
70
|
+
|
71
|
+
def update!
|
72
|
+
@updated = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.find_by(argument)
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def persisted?
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class PlainValidator; end
|
85
|
+
|
86
|
+
class BadValidatorBuilder < BaseBuilder
|
87
|
+
validator PlainValidator
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
#Used so that the buidlers with different names can all use the DemoTest class
|
93
|
+
NoMethods = DemoTest
|
94
|
+
Methods = DemoTest
|
95
|
+
BadValidator = DemoTest
|
data/levee-0.0.1.gem
ADDED
Binary file
|
data/levee.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["mike.j.martinson@gmail.com"]
|
11
11
|
spec.summary = %q{Flexible builder template for mapping complex rails post params onto ActiveRecord models}
|
12
12
|
spec.description = Levee.gem_description
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/mmartinson/levee"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_runtime_dependency
|
21
|
+
spec.add_runtime_dependency "activerecord", ">=4.0"
|
22
22
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.7"
|
24
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Levee
|
2
|
+
class Validator
|
3
|
+
attr_accessor :errors, :params
|
4
|
+
|
5
|
+
def initialize(params)
|
6
|
+
self.errors = []
|
7
|
+
self.params = params
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate_params
|
11
|
+
validations.each { |val| send(val) }
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_invalid_request_error(message)
|
16
|
+
error = {status: 400, code: 'invalid_request_error', message: message}
|
17
|
+
errors << error
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_invalid_request_error!(message)
|
21
|
+
add_invalid_request_error(message)
|
22
|
+
raise message
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.validations(*args)
|
26
|
+
@validations = args
|
27
|
+
end
|
28
|
+
|
29
|
+
def self._validations
|
30
|
+
@validations
|
31
|
+
end
|
32
|
+
|
33
|
+
def validations
|
34
|
+
self.class._validations
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/levee/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Levee
|
2
2
|
extend self
|
3
3
|
|
4
|
-
VERSION = "0.0.
|
4
|
+
VERSION = "0.0.2"
|
5
5
|
|
6
6
|
def gem_description
|
7
7
|
%Q(The purpose of the builder object is to create a layer of abstraction between the controller and models in a Rails application. The builder is particularly useful for receiving complex post and put requests with multiple parameters, but is lightweight enough to use for simple writes when some filtering or parameter combination validation might be useful before writing to the database. Since it wraps the entire write action to mulitple models in a single transaction, any failure in the builder will result in the entire request being rolled back.)
|
@@ -0,0 +1,310 @@
|
|
1
|
+
require_relative './builder_helper.rb'
|
2
|
+
|
3
|
+
#Base builder is an abstract class. A test class is made in /spec/support/test_builder.rb that defines
|
4
|
+
#a concrete child class.
|
5
|
+
|
6
|
+
describe 'BaseBuidler' do
|
7
|
+
let(:demo_class) { DemoTest }
|
8
|
+
let(:params) { {name: 'test params',
|
9
|
+
content: 'none'} }
|
10
|
+
subject { DemoTestBuilder.new(params) }
|
11
|
+
|
12
|
+
describe 'initialization' do
|
13
|
+
it 'sets requires_save to true' do
|
14
|
+
expect(subject.requires_save).to be
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#build' do
|
19
|
+
let(:object_class) { double(find_by: nil) }
|
20
|
+
before do
|
21
|
+
allow(object_class).to receive(:new) { demo_class.new }
|
22
|
+
allow(subject).to receive(:object_class) { object_class }
|
23
|
+
allow(subject).to receive(:assign_parameters_in_transaction)
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when the parameters are a top level array' do
|
27
|
+
it 'calls #assign_parameters_in_transaction' do
|
28
|
+
subject.params = []
|
29
|
+
expect(subject).to receive(:assign_parameters_in_transaction)
|
30
|
+
subject.build
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when the parameters are an object that contains an id' do
|
35
|
+
before { subject.params[:id] = 4 }
|
36
|
+
|
37
|
+
it 'class #find_by on #object class with params[:id]' do
|
38
|
+
expect(object_class).to receive(:find_by).with({id: subject.params[:id]})
|
39
|
+
subject.build
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when the parameters are an object that does not contain an id' do
|
44
|
+
|
45
|
+
it 'calls #object_class' do
|
46
|
+
expect(subject).to receive(:object_class)
|
47
|
+
subject.build
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'assigns a newly instantiated object of object_class to builder#object' do
|
51
|
+
allow(subject).to receive(:assign_parameters_in_transaction)
|
52
|
+
subject.build
|
53
|
+
expect(subject.object).to be_a demo_class
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'build_nested' do
|
59
|
+
before do
|
60
|
+
allow(subject).to receive(:build)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'sets #requires_save to false' do
|
64
|
+
expect(subject).to receive(:requires_save=).with(false)
|
65
|
+
subject.build_nested
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'calls #build' do
|
69
|
+
expect(subject).to receive(:build)
|
70
|
+
subject.build_nested
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'update(object_id:, acting_user=nil' do
|
75
|
+
context 'when the object can\'t be found' do
|
76
|
+
it 'returns an errors hash with the error status 404' do
|
77
|
+
object_class = double(find_by_id: nil)
|
78
|
+
allow(subject).to receive(:object_class) {object_class}
|
79
|
+
result = subject.update(object_id: 0)
|
80
|
+
expect(result[:error_status]).to eq(404)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when an object is found' do
|
85
|
+
before do
|
86
|
+
object_class = double(find_by_id: :found_object)
|
87
|
+
allow(subject).to receive(:object_class) {object_class}
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'assigns the found object to the builder\'s object attribute' do
|
91
|
+
expect(subject).to receive(:object=).with(:found_object)
|
92
|
+
subject.update(object_id: 0)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'calls #assign_parameters_in_transaction' do
|
96
|
+
expect(subject).to receive(:assign_parameters_in_transaction)
|
97
|
+
subject.update(object_id: 0)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when the params have a root node' do
|
103
|
+
it 'throws an exception' do
|
104
|
+
expect { DemoTestBuilder.new({demo_test: 'gg'}) }.to raise_error
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#permitted_attributes' do
|
109
|
+
context 'when the attributes method is called in the class definition with attribute symbols as parameter' do
|
110
|
+
it 'those attribute symbols are available in #permitted_attributes' do
|
111
|
+
expect(DemoTestBuilder.new({}).permitted_attributes).to include :name
|
112
|
+
expect(DemoTestBuilder.new({}).permitted_attributes).to include :content
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'when params are passed that are not permitted at class definition' do
|
117
|
+
it 'does not include those in the #permitted_attributes' do
|
118
|
+
expect(DemoTestBuilder.new({}).permitted_attributes).to_not include :author
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'matching param keys to attributes' do
|
124
|
+
let(:unpermitted_params) { {name: 'test params',
|
125
|
+
content: 'none',
|
126
|
+
author: 'Jim'} }
|
127
|
+
it 'saves the object' do
|
128
|
+
builder = NoMethodsBuilder.new params
|
129
|
+
builder.build
|
130
|
+
expect(builder.object.saved).to be
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'does not save the object if a param key is included that is not listed as a permitted attribute' do
|
134
|
+
builder = NoMethodsBuilder.new unpermitted_params
|
135
|
+
builder.build
|
136
|
+
expect(builder.object.saved).to_not be
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'when there are no custom methods defined that match param keys' do
|
140
|
+
specify "For each param key that is included in permitted attributes, the value of the param is assigned to the object\'s attribute matching the key name" do
|
141
|
+
builder = NoMethodsBuilder.new params
|
142
|
+
builder.build
|
143
|
+
expect(builder.object.name).to eq(params[:name])
|
144
|
+
expect(builder.object.content).to eq(params[:content])
|
145
|
+
expect(builder.object.author).to_not be
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when there are custom methods defined that match the name of attributes' do
|
150
|
+
specify 'those attributes are not automatically assigned' do
|
151
|
+
builder = MethodsBuilder.new params
|
152
|
+
builder.build
|
153
|
+
expect(builder.object.name).to_not be
|
154
|
+
expect(builder.object.content).to_not be
|
155
|
+
expect(builder.object.author).to_not be
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#top_level_array' do
|
161
|
+
let(:array_params) { [params,params] }
|
162
|
+
subject { DemoTestBuilder.new(array_params) }
|
163
|
+
|
164
|
+
context 'when the params contain an array of two objects' do
|
165
|
+
it 'returns two objects' do
|
166
|
+
result = subject.send(:top_level_array)
|
167
|
+
expect(result[0]).to be_a DemoTest
|
168
|
+
expect(result[1]).to be_a DemoTest
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'does not save the objects' do
|
172
|
+
result = subject.send(:top_level_array)
|
173
|
+
expect(result[0].saved).to_not be
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe 'delayed_save!(object)' do
|
179
|
+
it 'adds the object to the #nested_objects_to_save array' do
|
180
|
+
expect(subject.nested_objects_to_save).to be_a Array
|
181
|
+
subject.send(:delayed_save!, :nested_object)
|
182
|
+
expect(subject.nested_objects_to_save).to include :nested_object
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'does not add an duplicate object' do
|
186
|
+
expect(subject.nested_objects_to_save.length).to be 0
|
187
|
+
subject.send(:delayed_save!, 'nested_object')
|
188
|
+
subject.send(:delayed_save!, 'nested_object')
|
189
|
+
expect(subject.nested_objects_to_save.length).to be 1
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'callbacks' do
|
194
|
+
context 'when the before_save method is used in the class definition' do
|
195
|
+
it 'adds the method symbols passed to the #before_save_callbacks array' do
|
196
|
+
expect(subject.send(:before_save_callbacks)).to match_array [:before_one, :before_two]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'when the after_save method is used in the class definition' do
|
201
|
+
it 'adds the method symbols passed to the #after_save_callbacks array' do
|
202
|
+
expect(subject.send(:after_save_callbacks)).to match_array [:after_one]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe 'building the object' do
|
208
|
+
context '#assign_parameters_in_transaction' do
|
209
|
+
|
210
|
+
context 'when there is a validator assigned' do
|
211
|
+
it 'calls #validate_parameters on the validator' do
|
212
|
+
subject.build
|
213
|
+
expect(subject.validator.validated).to be
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'when the validator returns errors' do
|
218
|
+
let(:error) { error = {status: 400, code: 'invalid_request_error'} }
|
219
|
+
before { allow(subject.validator).to receive(:errors) { [error] } }
|
220
|
+
|
221
|
+
it 'the method returns early, not calling the following methods' do
|
222
|
+
expect(subject).to_not receive(:top_level_array)
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'returns an errors hash' do
|
226
|
+
expect(subject.build[:errors]).to be
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it '#calls top_level_array' do
|
231
|
+
expect(subject).to receive(:top_level_array).at_least(:once)
|
232
|
+
subject.build
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'calls #call_setter_for_each_param_key' do
|
236
|
+
expect(subject).to receive(:assign_parameters_in_transaction).at_least(:once)
|
237
|
+
subject.build
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'when there are no errors' do
|
241
|
+
context 'when #requires_save is truthy' do
|
242
|
+
it 'saves the object' do
|
243
|
+
subject.build
|
244
|
+
expect(subject.object.saved).to be
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'calls save on all #nested_objects_to_save' do
|
248
|
+
nest1 = double
|
249
|
+
nest2 = double
|
250
|
+
expect(nest1).to receive(:save!)
|
251
|
+
expect(nest2).to receive(:save!)
|
252
|
+
subject.nested_objects_to_save = [ nest1, nest2 ]
|
253
|
+
subject.build
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'calls the #before_save_callbacks before the object is saved' do
|
257
|
+
subject.build
|
258
|
+
expect(subject.object.before_save_called_with_unsaved_object).to be
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'calls @after_save_callbacks after object is saved' do
|
262
|
+
subject.build
|
263
|
+
expect(subject.object.after_save_called_with_saved_object).to be
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'calls callback blocks after object is saved' do
|
267
|
+
subject = DemoTestBuilder.new(params) { |object| object.block_callback_called_after_save = true if object.saved }
|
268
|
+
subject.build
|
269
|
+
expect(subject.object.block_callback_called_after_save).to be
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
context 'when #requires_save is falsey' do
|
274
|
+
it 'does not save the object' do
|
275
|
+
subject.build_nested
|
276
|
+
expect(subject.object.saved).to_not be
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
context 'when there are errors' do
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe 'self.validator' do
|
288
|
+
context 'when called with a validator class name' do
|
289
|
+
it 'adds that validator to the #validator method' do
|
290
|
+
expect(subject.validator).to be_a DemoParamsValidator
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'raises an exception if the class given is not a BaseParamsValidator' do
|
294
|
+
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
describe '#validator' do
|
300
|
+
it 'returns a BaseParamsValidator' do
|
301
|
+
expect(subject.validator).to be_a BaseParamsValidator
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'returns an object that responds to #params and returns some data' do
|
305
|
+
expect(subject.validator.params).to be
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# require_relative "../../app/application_services/base_builder"
|
2
|
+
# require_relative "../../app/application_services/base_params_validator"
|
3
|
+
|
4
|
+
class DemoParamsValidator < BaseParamsValidator
|
5
|
+
|
6
|
+
attr_accessor :validated
|
7
|
+
|
8
|
+
def validate_params
|
9
|
+
@validated = true
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def errors
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class DemoTestBuilder < BaseBuilder
|
20
|
+
attributes :name, :content
|
21
|
+
before_save :before_one, :before_two
|
22
|
+
after_save :after_one
|
23
|
+
validator DemoParamsValidator
|
24
|
+
|
25
|
+
def before_one
|
26
|
+
object.before_save_called_with_unsaved_object = true unless object.saved
|
27
|
+
end
|
28
|
+
|
29
|
+
def before_two; end
|
30
|
+
|
31
|
+
def after_one
|
32
|
+
object.after_save_called_with_saved_object = true if object.saved
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class NoMethodsBuilder < BaseBuilder
|
37
|
+
attributes :name, :content
|
38
|
+
end
|
39
|
+
|
40
|
+
class MethodsBuilder < BaseBuilder
|
41
|
+
attributes :name, :content
|
42
|
+
before_save :before_method
|
43
|
+
after_save :after_method
|
44
|
+
validator DemoParamsValidator
|
45
|
+
|
46
|
+
def name(data); end
|
47
|
+
|
48
|
+
def content(data); end
|
49
|
+
|
50
|
+
def before_method
|
51
|
+
object.before_method_called = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def after_method
|
55
|
+
object.after_method_called = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class DemoTest
|
60
|
+
attr_accessor :name, :content, :author
|
61
|
+
attr_reader :saved, :updated
|
62
|
+
attr_accessor :before_method_called, :after_method_called
|
63
|
+
attr_accessor :before_save_called_with_unsaved_object
|
64
|
+
attr_accessor :after_save_called_with_saved_object
|
65
|
+
attr_accessor :block_callback_called_after_save
|
66
|
+
|
67
|
+
def save!
|
68
|
+
@saved = true
|
69
|
+
end
|
70
|
+
|
71
|
+
def update!
|
72
|
+
@updated = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.find_by(argument)
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def persisted?
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class PlainValidator; end
|
85
|
+
|
86
|
+
class BadValidatorBuilder < BaseBuilder
|
87
|
+
validator PlainValidator
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
#Used so that the buidlers with different names can all use the DemoTest class
|
93
|
+
NoMethods = DemoTest
|
94
|
+
Methods = DemoTest
|
95
|
+
BadValidator = DemoTest
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: levee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Martinson
|
@@ -14,14 +14,14 @@ dependencies:
|
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '4.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -70,11 +70,17 @@ files:
|
|
70
70
|
- LICENSE.txt
|
71
71
|
- README.md
|
72
72
|
- Rakefile
|
73
|
+
- builder/base_builder_spec.rb
|
74
|
+
- builder/builder_helper.rb
|
75
|
+
- levee-0.0.1.gem
|
73
76
|
- levee.gemspec
|
74
77
|
- lib/levee.rb
|
75
78
|
- lib/levee/builder.rb
|
79
|
+
- lib/levee/validator.rb
|
76
80
|
- lib/levee/version.rb
|
77
|
-
|
81
|
+
- spec/base_builder_spec.rb
|
82
|
+
- spec/builder_helper.rb
|
83
|
+
homepage: https://github.com/mmartinson/levee
|
78
84
|
licenses:
|
79
85
|
- MIT
|
80
86
|
metadata: {}
|
@@ -99,5 +105,7 @@ signing_key:
|
|
99
105
|
specification_version: 4
|
100
106
|
summary: Flexible builder template for mapping complex rails post params onto ActiveRecord
|
101
107
|
models
|
102
|
-
test_files:
|
108
|
+
test_files:
|
109
|
+
- spec/base_builder_spec.rb
|
110
|
+
- spec/builder_helper.rb
|
103
111
|
has_rdoc:
|