params_deserializers 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2829c56aa834160422e33c79e254b7803552c48f
4
+ data.tar.gz: 3a25a942e11b6511a2794707f201267bcfa98b69
5
+ SHA512:
6
+ metadata.gz: 16ee347201fa6f0c6d7a4827d0cdccf71d45e571168fa598fa877299f6baab25a766ee6a3c7625b2b3828eeafce6daa986ba6d5bb2963d31148a8a8721359557
7
+ data.tar.gz: b898af99a150774fc67ea2841c21ad8cecdf30fd4a51444dda181566d1002ade385b9f135d72be0b496c16781c3080f0dc8626b3457eb337f6b2ac8be71ce76a
data/README.md ADDED
@@ -0,0 +1,281 @@
1
+ # Params Deserializers
2
+
3
+ ## Purpose
4
+
5
+ Modeled after [`active_model_serializers`](https://github.com/rails-api/active_model_serializers), this gem allows you to create deserializer classes to modify incoming parameters in your Rails app.
6
+
7
+ Rails controllers often receive incoming parameters (via JSON payloads, for instance) that are not in the format Rails expects for things like saving models. With Params Deserializers, you can easily transform these incoming parameters into the format you want ([example](#an-example)).
8
+
9
+ ## Getting started
10
+
11
+ Create a subclass of `ParamsDeserializer`:
12
+
13
+ ```ruby
14
+ # app/deserializers/user_params_deserializer.rb
15
+
16
+ class UserParamsDeserializer < ParamsDeserializer
17
+ end
18
+ ```
19
+
20
+ To deserialize a hash with your deserializer, instantiate your deserializer with the hash, and call `deserialize` on it:
21
+
22
+ ```ruby
23
+ new_hash = UserParamsDeserializer.new(old_hash).deserialize
24
+ ```
25
+
26
+ ## Listing params
27
+
28
+ Call the class method `attributes` to list incoming params:
29
+
30
+ ```ruby
31
+ class UserParamsDeserializer < ParamsDeserializer
32
+ attributes :first_name, :last_name, :birthday
33
+ end
34
+ ```
35
+
36
+ This class indicates that it expects `first_name`, `last_name`, and `birthday` keys on an incoming hash. Any other keys in the hash will be discarded.
37
+
38
+ Keys can also be listed individually using `attribute`:
39
+
40
+ ```ruby
41
+ class UserParamsDeserializer < ParamsDeserializer
42
+ attribute :first_name
43
+ attribute :last_name
44
+ attribute :birthday
45
+ end
46
+ ```
47
+
48
+ ## Renaming params
49
+
50
+ You can pass an options hash to `attribute`. If you include the `:rename_to` key in the options hash, your deserialized params will contain the renamed key instead of the original:
51
+
52
+ ```ruby
53
+ class UserParamsDeserializer < ParamsDeserializer
54
+ attribute :firstName, rename_to: :first_name
55
+ attribute :lastName, rename_to: :last_name
56
+ attribute :birthday
57
+ end
58
+
59
+ # Incoming hash: { :firstName => "Grace", :lastName => "Hopper, :birthday => "12/9/1906" }
60
+ # Deserialized params: { :first_name => "Grace", :last_name => "Hopper, :birthday => "12/9/1906" }
61
+ ```
62
+
63
+ ## Changing params case
64
+
65
+ You can apply snake_case, CamelCase, or lowerCamel cases to all your keys automatically using `format_keys`:
66
+
67
+ ```ruby
68
+ class UserParamsDeserializer < ParamsDeserializer
69
+ attributes :firstName, :lastName, :birthday
70
+ format_keys :snake_case # (or :camel_case or :lower_camel)
71
+ end
72
+
73
+ # Incoming hash: { :firstName => "Grace", :lastName => "Hopper, :birthday => "12/9/1906" }
74
+ # Deserialized params: { :first_name => "Grace", :last_name => "Hopper, :birthday => "12/9/1906" }
75
+ ```
76
+
77
+ ## Dealing with a root key
78
+
79
+ If you expect a root key containing an object with all the other keys, call the `root` class method on your deserializer. Otherwise, it will look for the attributes you've listed at the top level:
80
+
81
+ ```ruby
82
+ class UserParamsDeserializer < ParamsDeserializer
83
+ root: :user
84
+ attributes :first_name, :last_name
85
+ end
86
+
87
+ # Incoming hash: { :user => { :first_name => "Grace", :last_name => "Hopper" } }
88
+ # Deserialized params: { :user => { :first_name => "Grace", :last_name => "Hopper" } }
89
+ ```
90
+
91
+ To discard the root key in your deserialized params, pass `{ discard: true }` as the second argument to `root`:
92
+
93
+ ```ruby
94
+ class UserParamsDeserializer < ParamsDeserializer
95
+ root: :user, discard: true
96
+ attributes :first_name, :last_name
97
+ end
98
+
99
+ # Incoming hash: { :user => { :first_name => "Grace", :last_name => "Hopper" } }
100
+ # Deserialized params: { :first_name => "Grace", :last_name => "Hopper" }
101
+ ```
102
+
103
+ ## Deciding whether a key should be present
104
+
105
+ By default, any key listed via `attribute` or `attributes` will be included in the deserialized params if the key is present in `params`. However, the `attribute` class method takes an options hash. If the hash includes a `:present_if` key with a lambda value, it will execute that lambda to determine whether that key should be present in the deserialized params:
106
+
107
+ ```ruby
108
+ class UserParamsDeserializer < ParamsDeserializer
109
+ attribute :first_name
110
+ attribute :last_name, present_if: -> { params[:last_name].length > 5 }
111
+ end
112
+
113
+ # Incoming hash: { :first_name => "Grace", :last_name => "Hop" }
114
+ # Deserialized params: { :first_name => "Grace" }
115
+ ```
116
+
117
+ The lambda has access to the `params` hash, and can use it to determine whether the key should be present. In the example above, `last_name` is only included when its length is greater than 5.
118
+
119
+ ## Overriding a key's value
120
+
121
+ If you define a method of the same name as a parameter, that method will be called to provide the parameter's value:
122
+
123
+ ```ruby
124
+ class UserParamsDeserializer < ParamsDeserializer
125
+ attribute :birthday
126
+
127
+ def birthday
128
+ month, day, year = params[:birthday].split("/")
129
+ Time.new(year.to_i, month.to_i, day.to_i)
130
+ end
131
+ end
132
+
133
+ # Incoming hash: { :birthday => "12/9/1906" }
134
+ # Deserialized params: { :birthday => 1906-12-09 00:00:00 -0000 }
135
+ ```
136
+
137
+ Note that the method name should equal to the *renamed* parameter name, if the parameter has been renamed. For example, if you had passed `{ rename_to: :birth_date }` to `attribute` in the example above, the method you define would have to be `birth_date` instead of `birthday`.
138
+
139
+ ## Integrating your deserializer with your controller
140
+
141
+ You can deserialize incoming parameters in two ways:
142
+
143
+ ### Via `deserialize_params_with`
144
+
145
+ If you `include ParamsDeserializers` in your controller, your controller will have access to a class method called `deserialize_params_with`. By default, `deserialize_params_with` will create an instance variable called `deserialized_params`. Think of this as the `params` instance variable, but transformed according to your deserializer.
146
+
147
+ ```ruby
148
+ # /app/controllers/users_controller.rb
149
+
150
+ class UsersController < ApplicationController
151
+ include ParamsDeserializers
152
+ deserialize_params_with UserParamsDeserializer
153
+
154
+ def create
155
+ @user = User.create(deserialized_params)
156
+ render json: @user
157
+ end
158
+ end
159
+ ```
160
+
161
+ If you'd like a different instance variable name, such as `better_params`, pass an options hash to `deserialize_params_with` that contains an `:as` key:
162
+
163
+ ```ruby
164
+ deserialize_params_with UserParamsDeserializer, as: :better_params
165
+ ```
166
+
167
+ `better_params` will now be available to your controller action.
168
+
169
+ `deserialize_params_with` passes on any remaining keys in the options hash to `before_filter`, which means you can restrict deserialization to only certain controller actions:
170
+
171
+ ```ruby
172
+ deserialize_params_with UserParamsDeserializer, as: :better_params, only: [:create, :update]
173
+ ```
174
+
175
+ ### Via instantiation of your deserializer
176
+
177
+ You can also simply instantiate your deserializer with `params` in your controller action, and then call `deserialize` on it to get the deserialized params:
178
+
179
+ ```ruby
180
+ # /app/controllers/users_controller.rb
181
+
182
+ class UsersController < ApplicationController
183
+ def create
184
+ deserialized_params = UserParamsDeserializer.new(params).deserialize
185
+ @user = User.create(deserialized_params)
186
+ render json: @user
187
+ end
188
+ end
189
+ ```
190
+
191
+ ## An example
192
+
193
+ Create a subclass of `ParamsDeserializer`:
194
+
195
+ ```ruby
196
+ # app/deserializers/user_params_deserializer.rb
197
+
198
+ class UserParamsDeserializer < ParamsDeserializer
199
+ root :user, discard: true
200
+
201
+ format_keys :snake_case
202
+
203
+ attributes :birthday,
204
+ :firstName,
205
+ :lastName
206
+
207
+ attribute :heightInInches,
208
+ rename_to: :height_in_feet,
209
+ present_if: -> { params[:height_in_inches].is_a? Integer }
210
+
211
+ def birthday
212
+ month, day, year = params[:birthday].split("/")
213
+ Time.new(year.to_i, month.to_i, day.to_i)
214
+ end
215
+
216
+ def height_in_feet
217
+ params[:heightInInches] / 12.0
218
+ end
219
+ end
220
+ ```
221
+
222
+ Then, include it in your `UsersController`:
223
+
224
+ ```ruby
225
+ # app/controllers/users_controller.rb
226
+
227
+ class UsersController < ApplicationController
228
+ include ParamsDeserializers
229
+ deserialize_params_with UserParamsDeserializer, as: :new_params, only: :create
230
+
231
+ def create
232
+ @user = User.create(new_params)
233
+ render json: @user
234
+ end
235
+ end
236
+ ```
237
+
238
+ Now, when an API user hits `UsersController#create` with the following JSON payload:
239
+
240
+ ```json
241
+ {
242
+ "user": {
243
+ "firstName": "Grace",
244
+ "lastName": "Hopper",
245
+ "birthday": "12/9/1906",
246
+ "heightInInches": 66
247
+ }
248
+ }
249
+ ```
250
+
251
+ ...`UsersController#create` will have access to an instance variable called `new_params` with the following contents:
252
+
253
+ ```ruby
254
+ {
255
+ :first_name => "Grace",
256
+ :last_name => "Hopper",
257
+ :birthday => 1906-12-09 00:00:00 -0000,
258
+ :height_in_feet => 5.5
259
+ }
260
+ ```
261
+
262
+ ## Development
263
+
264
+ - Clone this repo.
265
+ - `bundle install`
266
+ - `bundle exec rspec` (or `bundle exec guard` to watch for changes)
267
+
268
+ ## License
269
+
270
+ Copyright (c) 2015, Groupon, Inc.
271
+ All rights reserved.
272
+
273
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
274
+
275
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
276
+
277
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
278
+
279
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
280
+
281
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ # Copyright (c) 2015, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ begin
31
+ require 'bundler/setup'
32
+ rescue LoadError
33
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
34
+ end
35
+
36
+ require 'rdoc/task'
37
+
38
+ RDoc::Task.new(:rdoc) do |rdoc|
39
+ rdoc.rdoc_dir = 'rdoc'
40
+ rdoc.title = 'ParamsDeserializers'
41
+ rdoc.options << '--line-numbers'
42
+ rdoc.rdoc_files.include('README.rdoc')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
45
+
46
+
47
+
48
+
49
+
50
+
51
+ Bundler::GemHelper.install_tasks
52
+
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2015, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ class ParamsDeserializer
31
+ class Attribute
32
+ attr_reader :name, :present_if
33
+
34
+ def initialize(original_name, name = original_name, present_if = nil)
35
+ @original_name, @name, @present_if = original_name, name, present_if
36
+ @present_if ||= -> { params_root.has_key?(original_name) }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,43 @@
1
+ # Copyright (c) 2015, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ class ParamsDeserializer
31
+ class AttributeCollection < Array
32
+ class NameCollisionError < StandardError; end
33
+
34
+ def <<(new_attr)
35
+ if any? { |attr| attr.name == new_attr.name }
36
+ raise NameCollisionError,
37
+ "Attribute \"#{new_attr.name}\" was defined multiple times."
38
+ end
39
+
40
+ super
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2015, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'active_support/concern'
31
+
32
+ module ParamsDeserializers
33
+ extend ActiveSupport::Concern
34
+
35
+ module ClassMethods
36
+ def deserialize_params_with(deserializer, options = {})
37
+ deserialized_params_name = options.delete(:as).try(:to_sym) || :deserialized_params
38
+ attr_reader deserialized_params_name
39
+
40
+ before_filter(options) do
41
+ instance_variable_set("@#{deserialized_params_name}", deserializer.new(params).deserialize)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,137 @@
1
+ # Copyright (c) 2015, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'active_support/core_ext/hash/indifferent_access'
31
+ require 'active_support/core_ext/string/inflections'
32
+
33
+ class ParamsDeserializer
34
+ class MissingRootKeyError < StandardError; end
35
+
36
+ def initialize(params)
37
+ @params = params
38
+ verify_root_key_exists
39
+ end
40
+
41
+ def deserialize
42
+ deserialized_params = {}
43
+ self.class.attrs.each do |attr|
44
+ next unless instance_exec(&attr.present_if)
45
+ deserialized_params[attr.name] = self.send(attr.name)
46
+ end
47
+ format_keys(deserialized_params).with_indifferent_access
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :params
53
+
54
+ def format_key(key)
55
+ case self.class.key_format
56
+ when :snake_case then key.to_s.underscore.to_sym
57
+ when :camel_case then key.to_s.camelize.to_sym
58
+ when :lower_camel then key.to_s.camelize(:lower).to_sym
59
+ else key
60
+ end
61
+ end
62
+
63
+ def format_keys(hash)
64
+ hash = Hash[hash.map { |k, v| [format_key(k), v] }] if self.class.key_format
65
+ optionally_include_root_key(hash)
66
+ end
67
+
68
+ def optionally_include_root_key(hash)
69
+ return hash unless self.class.include_root_key?
70
+ { format_key(self.class.root_key) => hash }
71
+ end
72
+
73
+ def params_root
74
+ return @params_root if @params_root
75
+
76
+ if self.class.root_key
77
+ @params_root = params[self.class.root_key]
78
+ else
79
+ @params_root = params
80
+ end
81
+ end
82
+
83
+ def verify_root_key_exists
84
+ if self.class.root_key && !params.has_key?(self.class.root_key)
85
+ raise MissingRootKeyError, "Root key #{self.class.root_key} is missing from params."
86
+ end
87
+ end
88
+
89
+ class << self
90
+ attr_reader :root_key
91
+ attr_accessor :key_format
92
+ alias_method :format_keys, :key_format=
93
+
94
+ def attrs
95
+ @attrs ||= AttributeCollection.new
96
+ end
97
+
98
+ def attribute(attr, options = {})
99
+ define_getter_method(attr, options) do
100
+ params_root[attr]
101
+ end
102
+ end
103
+
104
+ def attributes(*args)
105
+ args.each do |attr|
106
+ attribute(attr)
107
+ end
108
+ end
109
+
110
+ def has_many(attr, options = {})
111
+ define_getter_method(attr, options) do
112
+ return params_root[attr] unless options[:deserializer]
113
+
114
+ params_root[attr].map do |relation|
115
+ options[:deserializer].new(relation).deserialize
116
+ end if params_root[attr].is_a?(Array)
117
+ end
118
+ end
119
+
120
+ def root(key, options = {})
121
+ @root_key = key
122
+ @discard_root_key = options[:discard]
123
+ end
124
+
125
+ def include_root_key?
126
+ @root_key && !@discard_root_key
127
+ end
128
+
129
+ private
130
+
131
+ def define_getter_method(attr, options = {}, &block)
132
+ options[:rename_to] ||= attr
133
+ attrs << Attribute.new(attr, options[:rename_to], options[:present_if])
134
+ define_method(options[:rename_to], &block) unless method_defined?(options[:rename_to])
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,32 @@
1
+ # Copyright (c) 2015, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ module ParamsDeserializers
31
+ VERSION = "1.0.1"
32
+ end
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2015, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require_relative './params_deserializers/attribute'
31
+ require_relative './params_deserializers/attribute_collection'
32
+ require_relative './params_deserializers/params_deserializer'
33
+ require_relative './params_deserializers/deserialize_params_with'
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: params_deserializers
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jesse Pinho
8
+ - Trek Glowacki
9
+ - Jim Challenger
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2015-11-08 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.16
22
+ - - <
23
+ - !ruby/object:Gem::Version
24
+ version: '5.0'
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - '>='
30
+ - !ruby/object:Gem::Version
31
+ version: 3.2.16
32
+ - - <
33
+ - !ruby/object:Gem::Version
34
+ version: '5.0'
35
+ - !ruby/object:Gem::Dependency
36
+ name: guard-rspec
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ type: :development
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ - !ruby/object:Gem::Dependency
50
+ name: sqlite3
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: rails
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '4.2'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '4.2'
77
+ - !ruby/object:Gem::Dependency
78
+ name: rspec-rails
79
+ requirement: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ type: :development
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ description: Modeled after active_model_serializers, this gem allows you to create
92
+ deserializer classes for incoming Rails parameters.
93
+ email:
94
+ - jesse@jessepinho.com
95
+ executables: []
96
+ extensions: []
97
+ extra_rdoc_files: []
98
+ files:
99
+ - README.md
100
+ - Rakefile
101
+ - lib/params_deserializers.rb
102
+ - lib/params_deserializers/attribute.rb
103
+ - lib/params_deserializers/attribute_collection.rb
104
+ - lib/params_deserializers/deserialize_params_with.rb
105
+ - lib/params_deserializers/params_deserializer.rb
106
+ - lib/params_deserializers/version.rb
107
+ homepage: https://github.com/groupon/params_deserializers
108
+ licenses:
109
+ - BSD-3-Clause
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.4.8
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Deserializers for Rails params
131
+ test_files: []