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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac51414d35dc2d20439b9b06d0d7f82b84594fc2
4
- data.tar.gz: 5a8172b7e1bd55767a34ebbb498cb832469d7a1f
3
+ metadata.gz: fcd858e4786b09e8a9ba17285b17b0f0b8f1c824
4
+ data.tar.gz: b3c1da36324aa5c7a2198f7de69a2e8ae9d38e63
5
5
  SHA512:
6
- metadata.gz: 6493cba2bbe24d3b5364e0c784aeeb4c789ba7bf329fba3622c2d0e5d84fc5f2e664b8f2f22f5465549ca269a964f548dd69d709c25f6c84452c36c4f38e00f4
7
- data.tar.gz: ac03887aac3e66fbf540e6a6121323e39bbe30b78316b5b7b6b9a7066dafcb009e1ef1c46c6131940af19972e35830bd46e179131bce968bd97532cf657131dc
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 'activerecord', '~> 4.0'
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.1"
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.1
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
- homepage: ''
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: