levee 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ac51414d35dc2d20439b9b06d0d7f82b84594fc2
4
+ data.tar.gz: 5a8172b7e1bd55767a34ebbb498cb832469d7a1f
5
+ SHA512:
6
+ metadata.gz: 6493cba2bbe24d3b5364e0c784aeeb4c789ba7bf329fba3622c2d0e5d84fc5f2e664b8f2f22f5465549ca269a964f548dd69d709c25f6c84452c36c4f38e00f4
7
+ data.tar.gz: ac03887aac3e66fbf540e6a6121323e39bbe30b78316b5b7b6b9a7066dafcb009e1ef1c46c6131940af19972e35830bd46e179131bce968bd97532cf657131dc
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in levee.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Mike Martinson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,328 @@
1
+ # Levee
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'levee'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install levee
20
+
21
+ ## Usage
22
+
23
+ Overview
24
+ ------------------------
25
+
26
+ The abstract builder and validator classes are abstract classes for concrete builder classes to inherit from. They will be extracted to a gem once they are imporved and generalized through this project.
27
+
28
+ 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.
29
+
30
+
31
+ Features
32
+ ----------------------
33
+
34
+
35
+ - Entire build action wrapped in transaction by default
36
+ - Automatic rollback if errors present
37
+ - Lightwight, flexible DSL that can be easily overridden or extended
38
+ - Macro-style validators
39
+ - Transaction-level callbacks
40
+ - Automatic generation of errors object
41
+ - Mass-assignment protection, parameter whitelisting outside of controller
42
+ - Find-or-instantiate based on class name inference
43
+
44
+
45
+
46
+ The TL;DR copy & paste version:
47
+ ---------------------------------
48
+
49
+
50
+ Save this somewhere in your app directory. Maybe in a builders folder?
51
+
52
+
53
+ class PostBuilder < BaseBuilder
54
+ #matches the class name
55
+
56
+ #make sure you list all params that are passed in (you can skip id if you want)
57
+ attributes :title,
58
+ :content,
59
+ :tweet_required,
60
+ :author,
61
+ :comment
62
+
63
+ #list as many before_save and after_save callbacks as you want
64
+ after_save :tweet_post
65
+
66
+ #choose a validator class to run automatically (must be a legit base validator)
67
+ validator PostParamsValidator
68
+
69
+ #access the model using #object
70
+ #this method is completely redundant
71
+ #it is what is called if you don't define a method for a listed attribute
72
+ def title(data)
73
+ object.title = data
74
+ end
75
+
76
+ #override the automatic writer so it doesn't try to set it on object
77
+ #needs to take exactly one parameter
78
+ def tweet_required(data); end
79
+
80
+
81
+ #use #delayed_save be be sure the object is saved after the parent object
82
+ def comment(data)
83
+ comment = Comment.new(content: data, author: current_user)
84
+ object.comments << comment
85
+ delayed_save!(comment)
86
+ end
87
+
88
+ private
89
+
90
+ #get data that you passed in in the builder_options hash
91
+ def current_user
92
+ @current_user ||= User.find(builder_options[:user_id])
93
+ end
94
+
95
+ #access the params you passed in anywhere you need to
96
+ def tweet_post
97
+ TwitterMachine.spam(object) if params[:tweet_required]
98
+ end
99
+ end
100
+
101
+
102
+ ######These are the methods you have access to inside the builder:
103
+
104
+ :params
105
+ :errors #Array
106
+ :object #ActiveRecord::Base
107
+ :permitted_attributes #Array
108
+ :requires_save #Boolean
109
+ :builder_options #Hash
110
+
111
+
112
+
113
+
114
+ The Full Deets on What is Going on Here:
115
+ ------------------------------------
116
+
117
+
118
+ The API is for the builder is intended to closely resemble that of ActiveModel::Serializers, as the builder is used in a way similar way but for parsing data into instead of serializing data out of Rails models.
119
+
120
+
121
+
122
+ Creating a Builder
123
+ ==================================
124
+
125
+
126
+ Each builder class inherits from BaseBuilder and maps directly onto one ActiveRecord model. The model name is inferred from the builder name, so for a Post model builder must be named PostBuilder. Class inference works with namespaced builders as well, leveraging the magic of regex voodoo. Currently there is no way to use a builder with a non-matching model.
127
+
128
+
129
+ ###Attribute Mapping and Whitelisting
130
+
131
+
132
+ All parameters that are used within the builder must be explicitly whitelisted as attributes. This can either provide super-reduntant mass-assignment protection on top of the Rails strong params or can be used in place of them to stop param whitelisting from ruining your short controller zen. Because the builder iteratively assigns each parmeter using the attribute writers you can pass paremters from the controller without whitelisting them there if you prefer. See 'Using the Builder' below for more on how to work with the rails params hash.
133
+
134
+ By default, the builder will attempt to map every parameter onto the attribute writer on the target model with the same name. If parameters are passed that are not whitelisted an exception will be raised. In order to implement custom behaviour for certain parmeters, methods written inside the class will be called when they match the name of whitelisted attribute that receives data from the params. These overwriter methods must take one parameter, which will hold the value of the submitted parameter. When an automatic attribute method is overridden by a custom method, the return value of the method does not map to the matching attribute writer
135
+
136
+
137
+ Let me say that again. The return value of custom methods does not automatically map to the matchiang attribute writer. Anytime a default mapping is overridden, you are left with full flexibility to do what you want with the parameter value, including throw it away. This allows to builder to utilize parameters that were never intended to be written straight as model data. For parameter values that you want to throw away or handle elsewhere inside the builder, simply define a for the parameter key name that does nothing. To make it explicit that it is intentionally empty, write in like this: ``def parameter_key_name(value); end``
138
+
139
+
140
+ #####A note on :id
141
+
142
+ The id key is the only key in the params hash that does not need to be whitelisted and will not be mapped by default. It can still be overridden if you want to catch the id and do someting with it, or if you want to cue some other action just before the attribute mapping executes.
143
+
144
+
145
+ ###The object Object
146
+
147
+
148
+ As in ActiveModel::Serializers, the builder makes use of the object object within the class. This allows for code that easily reusable between create and update requests. Behind the scenes the base builder uses the id the the params hash to look up the existing object form the inferred model and represents it as object. If no :id parameter exists a new empty object of that class is instantiated and assigned to object. Buidler methods then don't need to know or care about whether object is new or existing and can treat it the same. In the cases where this might be important, you are of course free to dig into object and ask it all sorts of ActiveRecord questions about its state inside your overwriter methods.
149
+
150
+
151
+ Just like you use object in your serializer methods to query things about the object. Use object in your builder methods to call methods on objects or set values. This can be super useful when you want to so something like initiate changes to a state machine while maintaining a RESTful web interface. Changes to state can be included in an update action, then the builder can check for the paremeters used for initiating a state change and call methods on object (or anywhere else really). Once again, the return values of the builder methods are thrown away and are completely unimportant.
152
+
153
+
154
+ Using the Builder
155
+ ======================================
156
+
157
+
158
+ Currently the builder name is not inferred from the controller name and it msut be called explicitly in the controller. On initialization the builder takes the params as a mandatory first argument for parmas, a number of optional keyword arguments and an optional block. The block is used to quickly assign a single :after_save callback from data that is available in the controller.
159
+
160
+ The builder does not expect to get the entire params hash in the controller but instead wants a hash or array with no root node. When making a new post, for example, you would call ``PostBuilder.new(params[:post])``. This means that you have the choice of whether you whitelist in the controller using Rails strong params or pass the raw params in. The builder will force you to whitelist them inside regardless.
161
+
162
+ The builder will throw an exception on initialization if you pass it something besides an array or hash as the params, or if the params have a root node matching the class name. It won't creating the regular errors hash, because the client shouldn't be exposed to this error message. Any extra stuff in the params should be filtered out by the controller.
163
+
164
+
165
+
166
+ ###The Builder#build method
167
+
168
+
169
+ The build method fires the the entire builder for both create and update. Two other public methods are also available. #build_nested will perform the build without saving. In doing so it completely skips the callbacks so use it carefully. #update is used for explicitly passing in :object_id at initialization that will take precedence over any id value that is included in the parameters. This can be used if you watch to ensure that an id from the query string params is used instead of the request body.
170
+
171
+
172
+ The build method returns either the ActiveRecord object or an errors hash. Expected errors will be rescued and added to the errors hash, where unexpected errors will be raised. In both cases the entire build transaction will be rolled back.
173
+
174
+
175
+
176
+
177
+ Moar Features
178
+ ======================================
179
+
180
+
181
+ ###Callbacks
182
+
183
+
184
+ You can list macro-style callbacks inside the class just like in your ActiveRecord models.
185
+
186
+ class PostBuilder
187
+
188
+ attributes :title,
189
+ :content,
190
+ :author
191
+
192
+ before_save :update_state
193
+
194
+ after_save :add_email_job,
195
+ :count_records
196
+
197
+ def update_state
198
+ end
199
+
200
+ def add_email_job
201
+ end
202
+
203
+ #you get the idea
204
+
205
+ end
206
+
207
+ Just be sure you define the methods you list somewhere within the class, otherwise you get an explosion. An important point to remember is that callbacks ARE NOT CALLED EVER if you use the #build_nested instead of the #build method.
208
+
209
+
210
+ ### #delayed_save(object)
211
+
212
+
213
+
214
+ The builder implements #delayed_saved(object) to be used inside a builder class. This is a shortcut for calling save! on any object as part of the after save callbacks. It is useful for when you are creating nested objects inside a builder and you want to be sure that the parent object is saved first, as is the case with associations where you need to be sure the parent has all attributes in place to pass validattion before the child forces a save. Note that delayed_save callbacks fire before the rest of the after_save callbacks.
215
+
216
+
217
+ class PostBuilder
218
+
219
+ attributes :title,
220
+ :content,
221
+ :author
222
+ :comment
223
+
224
+ def comment(data)
225
+ new_comment = Comment.new(content: data)
226
+ object.comments < new_comment
227
+ delayed_save!(new_comment)
228
+ end
229
+ end
230
+
231
+
232
+ ###Builder Options
233
+
234
+
235
+ You probably have situations where you want to get stuff from the controller inside the builder that doesn't come in the params. You can include any number of keyword arguments after the parameters argument at initialization that will be available inside the builder in the ``builder options`` hash.
236
+
237
+
238
+ When you make the builder like this in the contoller: ``PostBuilder.new(params[:post], user: current_user).build``
239
+
240
+
241
+ Inside the builder you can get the current user by calling ``builder_options[:current_user]``
242
+
243
+
244
+ This way you can easily pass in any extra query string stuff you get in the params. It's recommended that you perform any lookups inside a method in the builder instead of in the controller when you don't need to reuse them for anything out there. For example, if you only need the current user in the builder, pass in the user_id from the query string params and define a method like this in your builder:
245
+
246
+
247
+ def user
248
+ @user ||= User.find(builder_options[:user_id])
249
+ end
250
+
251
+
252
+ That way the controller stays nice and snappy and the builder has a reusable method that's not 30 chars long.
253
+
254
+
255
+ ###Validator
256
+
257
+
258
+ You can make a validator class to be used with your builder. It's a good place to do validations that check for certain combinations of parameters, especially if they affect multiple models and therefore don't really make sense to have in one model class or the other. The validator uses only a tiny bit of custom syntax, and it's really just there to make your life easier.
259
+
260
+
261
+ #####Make the validator:
262
+
263
+
264
+ Make the class like this. You can put it anywhere in the app directory, but it feels very at home in a folder called validations. It doesn't use class inference so you can call it whatever
265
+
266
+
267
+ class UltraSweetValidator < BaseValidator
268
+
269
+ validations :first_method,
270
+ :other_method
271
+
272
+ def first_method
273
+ if params[:a_thing]
274
+ return true if params[:that_other_thing_that_has_to_be_there]
275
+ else
276
+ message "You can't do that"
277
+ add_invalid_request_error(message)
278
+ end
279
+ end
280
+
281
+ def other_method
282
+ #return value doesn't matter
283
+ end
284
+ end
285
+
286
+
287
+ Just list all the validation methods you want at the top and then define them below. The builder knows how to call them. The validator has access to two useful objects and has one useful custom method. You can, obviously, access the params that you passed into the builder (not the ones in the controller), and you can also access the errors hash.
288
+
289
+
290
+ When you find something you don't like, you can either use the super userful #add_invalid_request_error(message) method, or you can just add whatever you want to the errors hash. If the errors hash is anyting but completely empty the buidler transaction will roll back and return whatever is in there to the controller. The method just adds your message there is a nice formatted way and includes a 400 'bad request' status code. If you want to skip the the rest of the validations as soon as one fails just add a bang (!) to the end of the method. Don't add it after the message argument, that would obviously be wrong.
291
+
292
+
293
+ #####Use the validator:
294
+
295
+
296
+ Call the validator inside the builder by listing the class name
297
+
298
+
299
+ class PostBuilder < BaseBuilder
300
+
301
+ attributes :title,
302
+ :content,
303
+ :author
304
+ :comment
305
+
306
+ validator UltraSweetValidator
307
+
308
+ end
309
+
310
+
311
+ That's it. The validator is called at the very beginning of the build method (and the other similar ones) and if it snags any errors in the errors hash it will return them instead of trying to run the rest of the build action. If all goes well, this will give you a way to stop your builder from exploding because it's missing data it expects to be there. The validators are there to keep messy error handling out of the builders. You should definitely use them.
312
+
313
+
314
+ Don't bother validating simple params in your validator that map straight onto the model attributes. The builder always calls #save! instead of #save and then catches the errors so you can jsut use your regular old ActiveRecord validators and still have a errors hash at the end.
315
+
316
+
317
+
318
+
319
+
320
+ TODO: Write usage instructions here
321
+
322
+ ## Contributing
323
+
324
+ 1. Fork it ( https://github.com/[my-github-username]/levee/fork )
325
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
326
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
327
+ 4. Push to the branch (`git push origin my-new-feature`)
328
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/levee.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'levee/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "levee"
8
+ spec.version = Levee::VERSION
9
+ spec.authors = ["Mike Martinson"]
10
+ spec.email = ["mike.j.martinson@gmail.com"]
11
+ spec.summary = %q{Flexible builder template for mapping complex rails post params onto ActiveRecord models}
12
+ spec.description = Levee.gem_description
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'activerecord', '~> 4.0'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ end
26
+
27
+
@@ -0,0 +1,230 @@
1
+ module Levee
2
+ class Builder
3
+
4
+ attr_accessor :params,
5
+ :errors,
6
+ :object,
7
+ :nested_objects_to_save,
8
+ :permitted_attributes,
9
+ :requires_save,
10
+ :builder_options
11
+
12
+ def initialize(params, options={}, &blk)
13
+ self.params = params
14
+ validate_params
15
+ self.errors = []
16
+ self.nested_objects_to_save = []
17
+ self.permitted_attributes = [:id]
18
+ self.requires_save = true
19
+ self.builder_options = options
20
+ @callback_blocks = [*blk]
21
+ end
22
+
23
+ def build
24
+ unless params.is_a? Array
25
+ self.object = object_class.find_by(id: params[:id]) || object_class.new
26
+ end
27
+ assign_parameters_in_transaction
28
+ end
29
+
30
+ def build_nested(parent_object: nil, parent_builder: nil)
31
+ self.requires_save = false
32
+ build
33
+ end
34
+
35
+ def update(object_id:)
36
+ self.object = object_class.find_by_id(object_id)
37
+ return {error_status: 404, errors:[{status: 404, code: 'record_not_found'}]} unless object
38
+ assign_parameters_in_transaction
39
+ end
40
+
41
+ def permitted_attributes
42
+ self.class._permitted_attributes || []
43
+ end
44
+
45
+ def validator
46
+ return nil unless self.class._validator
47
+ @validator ||= self.class._validator.new(params)
48
+ end
49
+
50
+ private
51
+
52
+ def assign_parameters_in_transaction
53
+ ActiveRecord::Base.transaction do
54
+ begin
55
+ perform_in_transaction
56
+ rescue => e
57
+ raise_error = -> { raise e }
58
+ rescue_errors(e) || raise_error.call
59
+ ensure
60
+ self.errors = (errors << validator.errors).flatten if validator
61
+ raise ActiveRecord::Rollback unless errors.flatten.empty?
62
+ end
63
+ end
64
+ Rails.logger.error "Builder Errors:"
65
+ Rails.logger.error errors.to_yaml
66
+ self.object = object.reload if errors.empty? && object.try(:persisted?)
67
+ errors.empty? ? object : {errors: errors, error_status: errors.first[:status]}
68
+ end
69
+
70
+ def perform_in_transaction
71
+ self.errors += validator.validate_params.errors if validator
72
+ return false if errors.any?
73
+ self.object = top_level_array || call_setter_for_each_param_key
74
+ return true unless requires_save && !top_level_array
75
+ before_save_callbacks.each { |callback| send(callback) }
76
+ [*object].each(&:save!)
77
+ nested_objects_to_save.flatten.each(&:save!)
78
+ @callback_blocks.each_with_object(object, &:call)
79
+ after_save_callbacks.each { |callback| send(callback) }
80
+ end
81
+
82
+ def call_setter_for_each_param_key
83
+ params.each do |key, value|
84
+ send_if_key_included_in_attributes(key.to_sym, value)
85
+ end
86
+ object
87
+ end
88
+
89
+ def send_if_key_included_in_attributes(key, value)
90
+ if permitted_attributes.include?(key)
91
+ self.send(key, value)
92
+ else
93
+ errors << {status: 400, message: "Unpermitted parameter key #{key}"}
94
+ raise ActiveRecord::Rollback
95
+ end
96
+ end
97
+
98
+ def top_level_array
99
+ return false unless params.is_a?(Array)
100
+ @top_level_array ||= params.map do |param_object|
101
+ self.class.new(param_object, builder_options).build_nested
102
+ end
103
+ end
104
+
105
+ #need to use attributes to handle errors that aren't meant to be model methods
106
+ def method_missing(method_name, *args)
107
+ return super unless permitted_attributes.include?(method_name)
108
+ begin
109
+ object.send(:"#{method_name}=", args.first)
110
+ rescue => e
111
+ if params.has_key?(method_name)
112
+ message = "Unable to process value for :#{method_name}, no attribute writer. Be sure to override the automatic setters for all params that do not map straight to a model attribute."
113
+ self.errors << {status: 422, message: message}
114
+ else
115
+ raise e
116
+ end
117
+ end
118
+ end
119
+
120
+ def delayed_save!(nested_object)
121
+ self.nested_objects_to_save = (nested_objects_to_save << nested_object).uniq
122
+ end
123
+
124
+ def object_class
125
+ klass = self.class.to_s
126
+ start_position = (/::\w+$/ =~ klass).try(:+,2)
127
+ klass = klass[start_position..-1] if start_position
128
+ suffix_position = klass =~ /Builder$/
129
+ if suffix_position
130
+ try_constant = klass[0...suffix_position]
131
+ begin
132
+ try_constant.constantize
133
+ rescue
134
+ raise NameError.new "#{try_constant} does not exist. Builder class name must map the the name of an existing class"
135
+ end
136
+ else
137
+ raise "#{self.class} must be named ModelNameBuilder to be used as a builder class"
138
+ end
139
+ end
140
+
141
+ def rescue_errors(rescued_error)
142
+ raise_if_validation_error(rescued_error)
143
+ raise_if_argument_error(rescued_error)
144
+ raise_if_unknown_attribute_error(rescued_error)
145
+ end
146
+
147
+ def raise_if_validation_error(rescued_error)
148
+ if rescued_error.is_a? ActiveRecord::RecordInvalid
149
+ self.errors << { status: 422, code: 'validation_error', message: object.errors.full_messages, record: rescued_error.record }
150
+ raise ActiveRecord::Rollback
151
+ end
152
+ end
153
+
154
+ def raise_if_argument_error(rescued_error)
155
+ if rescued_error.is_a? ArgumentError
156
+ message = "All methods on the builder that override attribute setters must accept one argument to catch the parameter value"
157
+ self.errors << { status: 500, code: 'builder_error', message: message }
158
+ raise ArgumentError.new message
159
+ end
160
+ end
161
+
162
+ def raise_if_unknown_attribute_error(rescued_error)
163
+ if rescued_error.is_a? ActiveRecord::UnknownAttributeError
164
+ self.errors << { status: 400, code: 'unknown_attribute_error', message: rescued_error.message, record: rescued_error.record }
165
+ raise ActiveRecord::Rollback
166
+ end
167
+ end
168
+
169
+ def self.attributes(*args)
170
+ @permitted_attributes ||= []
171
+ args.each { |a| @permitted_attributes << a unless @permitted_attributes.include?(a) }
172
+ end
173
+
174
+ def self._permitted_attributes
175
+ @permitted_attributes
176
+ end
177
+
178
+ def validate_params
179
+ message = "Params passed to builder must be a hash or top level array"
180
+ raise message unless params.respond_to?(:fetch)
181
+ return true if params.is_a? Array
182
+ message = "Params passed to builder must not have a root node"
183
+ key = params.keys.first
184
+ raise message if key && key.to_s.camelize == object_class.to_s
185
+ end
186
+
187
+ #used so that id setter is not called by default
188
+ def id(data)
189
+ true
190
+ end
191
+
192
+ #######################
193
+ ## callbacks
194
+ ###########################
195
+
196
+ def self.before_save(*args)
197
+ @before_save_callbacks ||= []
198
+ args.each { |a| @before_save_callbacks << a unless @before_save_callbacks.include?(a) }
199
+ end
200
+
201
+ def self._before_save_callbacks
202
+ @before_save_callbacks
203
+ end
204
+
205
+ def self._after_save_callbacks
206
+ @after_save_callbacks
207
+ end
208
+
209
+ def self.after_save(*args)
210
+ @after_save_callbacks ||= []
211
+ args.each { |a| @after_save_callbacks << a unless @after_save_callbacks.include?(a) }
212
+ end
213
+
214
+ def before_save_callbacks
215
+ self.class._before_save_callbacks || []
216
+ end
217
+
218
+ def after_save_callbacks
219
+ self.class._after_save_callbacks || []
220
+ end
221
+
222
+ def self.validator(validator)
223
+ @validator = validator
224
+ end
225
+
226
+ def self._validator
227
+ @validator
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,9 @@
1
+ module Levee
2
+ extend self
3
+
4
+ VERSION = "0.0.1"
5
+
6
+ def gem_description
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.)
8
+ end
9
+ end
data/lib/levee.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "levee/version"
2
+ require "levee/builder"
3
+
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: levee
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mike Martinson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: The purpose of the builder object is to create a layer of abstraction
56
+ between the controller and models in a Rails application. The builder is particularly
57
+ useful for receiving complex post and put requests with multiple parameters, but
58
+ is lightweight enough to use for simple writes when some filtering or parameter
59
+ combination validation might be useful before writing to the database. Since it
60
+ wraps the entire write action to mulitple models in a single transaction, any failure
61
+ in the builder will result in the entire request being rolled back.
62
+ email:
63
+ - mike.j.martinson@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - ".gitignore"
69
+ - Gemfile
70
+ - LICENSE.txt
71
+ - README.md
72
+ - Rakefile
73
+ - levee.gemspec
74
+ - lib/levee.rb
75
+ - lib/levee/builder.rb
76
+ - lib/levee/version.rb
77
+ homepage: ''
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.4.5
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Flexible builder template for mapping complex rails post params onto ActiveRecord
101
+ models
102
+ test_files: []
103
+ has_rdoc: