deserializer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +106 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +268 -0
  7. data/Rakefile +37 -0
  8. data/deserializer.gemspec +21 -0
  9. data/lib/deserializer.rb +6 -0
  10. data/lib/deserializer/base.rb +98 -0
  11. data/lib/deserializer/deserializer_error.rb +12 -0
  12. data/lib/deserializer/version.rb +3 -0
  13. data/test/deserializer_test.rb +7 -0
  14. data/test/dummy/README.rdoc +0 -0
  15. data/test/dummy/Rakefile +6 -0
  16. data/test/dummy/app/assets/images/.keep +0 -0
  17. data/test/dummy/app/assets/javascripts/application.js +13 -0
  18. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  19. data/test/dummy/app/controllers/application_controller.rb +5 -0
  20. data/test/dummy/app/controllers/concerns/.keep +0 -0
  21. data/test/dummy/app/helpers/application_helper.rb +2 -0
  22. data/test/dummy/app/mailers/.keep +0 -0
  23. data/test/dummy/app/models/.keep +0 -0
  24. data/test/dummy/app/models/concerns/.keep +0 -0
  25. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  26. data/test/dummy/bin/bundle +3 -0
  27. data/test/dummy/bin/rails +4 -0
  28. data/test/dummy/bin/rake +4 -0
  29. data/test/dummy/bin/setup +29 -0
  30. data/test/dummy/config.ru +4 -0
  31. data/test/dummy/config/application.rb +26 -0
  32. data/test/dummy/config/boot.rb +5 -0
  33. data/test/dummy/config/database.yml +25 -0
  34. data/test/dummy/config/environment.rb +5 -0
  35. data/test/dummy/config/environments/development.rb +41 -0
  36. data/test/dummy/config/environments/production.rb +79 -0
  37. data/test/dummy/config/environments/test.rb +42 -0
  38. data/test/dummy/config/initializers/assets.rb +11 -0
  39. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  41. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  42. data/test/dummy/config/initializers/inflections.rb +16 -0
  43. data/test/dummy/config/initializers/mime_types.rb +4 -0
  44. data/test/dummy/config/initializers/session_store.rb +3 -0
  45. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  46. data/test/dummy/config/locales/en.yml +23 -0
  47. data/test/dummy/config/routes.rb +4 -0
  48. data/test/dummy/config/secrets.yml +22 -0
  49. data/test/dummy/lib/assets/.keep +0 -0
  50. data/test/dummy/log/.keep +0 -0
  51. data/test/dummy/public/404.html +67 -0
  52. data/test/dummy/public/422.html +67 -0
  53. data/test/dummy/public/500.html +66 -0
  54. data/test/dummy/public/favicon.ico +0 -0
  55. data/test/integration/navigation_test.rb +10 -0
  56. data/test/test_helper.rb +20 -0
  57. metadata +145 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e0dd737dc7e4c34c7238b6dc22a16077e17c22d3
4
+ data.tar.gz: 1d3b4c23174de67667241f4dc91109222bc61d41
5
+ SHA512:
6
+ metadata.gz: d9c5cde9d336f5568761d3d91b39cc697dead376ac6d7f3260da7901aaac333961a71fe7ff1fb0eb7e72446de88b9282e3c850b9c3198aa89fbb92404f2713f0
7
+ data.tar.gz: bcedd5145f9015da536d2927bed7fcca82d0b39796e78dc7fd6877da719ce2b9f1e5e31a7d5fd2b60c34b4dd5e46fa4d720829046f4eca2c8691a7fdb07e4fa2
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ test/dummy/db/*.sqlite3
5
+ test/dummy/db/*.sqlite3-journal
6
+ test/dummy/log/*.log
7
+ test/dummy/tmp/
8
+ test/dummy/.sass-cache
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Declare your gem's dependencies in deserializer.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # Declare any dependencies that are still in development here instead of in
9
+ # your gemspec. These might include edge Rails or gems from your path or
10
+ # Git. Remember to move these dependencies to your gemspec before releasing
11
+ # your gem to rubygems.org.
12
+
13
+ # To use a debugger
14
+ # gem 'byebug', group: [:development, :test]
15
+
data/Gemfile.lock ADDED
@@ -0,0 +1,106 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ deserializer (0.0.1)
5
+ rails (~> 4.2.1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actionmailer (4.2.3)
11
+ actionpack (= 4.2.3)
12
+ actionview (= 4.2.3)
13
+ activejob (= 4.2.3)
14
+ mail (~> 2.5, >= 2.5.4)
15
+ rails-dom-testing (~> 1.0, >= 1.0.5)
16
+ actionpack (4.2.3)
17
+ actionview (= 4.2.3)
18
+ activesupport (= 4.2.3)
19
+ rack (~> 1.6)
20
+ rack-test (~> 0.6.2)
21
+ rails-dom-testing (~> 1.0, >= 1.0.5)
22
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
23
+ actionview (4.2.3)
24
+ activesupport (= 4.2.3)
25
+ builder (~> 3.1)
26
+ erubis (~> 2.7.0)
27
+ rails-dom-testing (~> 1.0, >= 1.0.5)
28
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
29
+ activejob (4.2.3)
30
+ activesupport (= 4.2.3)
31
+ globalid (>= 0.3.0)
32
+ activemodel (4.2.3)
33
+ activesupport (= 4.2.3)
34
+ builder (~> 3.1)
35
+ activerecord (4.2.3)
36
+ activemodel (= 4.2.3)
37
+ activesupport (= 4.2.3)
38
+ arel (~> 6.0)
39
+ activesupport (4.2.3)
40
+ i18n (~> 0.7)
41
+ json (~> 1.7, >= 1.7.7)
42
+ minitest (~> 5.1)
43
+ thread_safe (~> 0.3, >= 0.3.4)
44
+ tzinfo (~> 1.1)
45
+ arel (6.0.2)
46
+ builder (3.2.2)
47
+ erubis (2.7.0)
48
+ globalid (0.3.5)
49
+ activesupport (>= 4.1.0)
50
+ i18n (0.7.0)
51
+ json (1.8.3)
52
+ loofah (2.0.2)
53
+ nokogiri (>= 1.5.9)
54
+ mail (2.6.3)
55
+ mime-types (>= 1.16, < 3)
56
+ mime-types (2.6.1)
57
+ mini_portile (0.6.2)
58
+ minitest (5.7.0)
59
+ nokogiri (1.6.6.2)
60
+ mini_portile (~> 0.6.0)
61
+ rack (1.6.4)
62
+ rack-test (0.6.3)
63
+ rack (>= 1.0)
64
+ rails (4.2.3)
65
+ actionmailer (= 4.2.3)
66
+ actionpack (= 4.2.3)
67
+ actionview (= 4.2.3)
68
+ activejob (= 4.2.3)
69
+ activemodel (= 4.2.3)
70
+ activerecord (= 4.2.3)
71
+ activesupport (= 4.2.3)
72
+ bundler (>= 1.3.0, < 2.0)
73
+ railties (= 4.2.3)
74
+ sprockets-rails
75
+ rails-deprecated_sanitizer (1.0.3)
76
+ activesupport (>= 4.2.0.alpha)
77
+ rails-dom-testing (1.0.6)
78
+ activesupport (>= 4.2.0.beta, < 5.0)
79
+ nokogiri (~> 1.6.0)
80
+ rails-deprecated_sanitizer (>= 1.0.1)
81
+ rails-html-sanitizer (1.0.2)
82
+ loofah (~> 2.0)
83
+ railties (4.2.3)
84
+ actionpack (= 4.2.3)
85
+ activesupport (= 4.2.3)
86
+ rake (>= 0.8.7)
87
+ thor (>= 0.18.1, < 2.0)
88
+ rake (10.4.2)
89
+ sprockets (3.2.0)
90
+ rack (~> 1.0)
91
+ sprockets-rails (2.3.2)
92
+ actionpack (>= 3.0)
93
+ activesupport (>= 3.0)
94
+ sprockets (>= 2.8, < 4.0)
95
+ sqlite3 (1.3.10)
96
+ thor (0.19.1)
97
+ thread_safe (0.3.5)
98
+ tzinfo (1.2.2)
99
+ thread_safe (~> 0.1)
100
+
101
+ PLATFORMS
102
+ ruby
103
+
104
+ DEPENDENCIES
105
+ deserializer!
106
+ sqlite3
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Greg Orlov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,268 @@
1
+ # Deserializer
2
+
3
+ Deserialization of complex parameters into a hash that an AR model can take.
4
+
5
+ Lets you have a reverse ActiveModel::Sereializer-like interface that allows for easy create and update without having to write heavy controllers.
6
+
7
+ ## Problem
8
+
9
+ Let's say we have a API create endpoint that takes json that looks something like
10
+
11
+ ```json
12
+ {
13
+ "restaurant_id" : 13,
14
+ "user_id" : 6,
15
+ "dish_name" : "risotto con funghi",
16
+ "description" : "repulsive beyond belief",
17
+ "ratings" : {
18
+ "taste" : "terrible",
19
+ "color" : "horrendous",
20
+ "texture" : "vile",
21
+ "smell" : "delightful, somehow"
22
+ }
23
+ }
24
+ ```
25
+
26
+ that goes into a flat DishReview model that looks like
27
+
28
+ ```ruby
29
+ t.belongs_to :restaurant
30
+ t.belongs_to :user
31
+ # field name different from API
32
+ t.string :name
33
+ t.string :description
34
+ t.string :taste
35
+ t.string :color
36
+ t.string :texture
37
+ t.string :smell
38
+ ```
39
+
40
+ what do we do?
41
+
42
+ Normally, we'd have some params we permit, do some parsing and feed those into `DishReview.new`, like
43
+
44
+ ``` ruby
45
+ class DishReviewController < BaseController
46
+
47
+ def create
48
+ review_params = get_review_params(params)
49
+ @review = ProfessionalReview.new(review_params)
50
+ if @review.save
51
+ # return review
52
+ else
53
+ # return sad errors splody
54
+ end
55
+ end
56
+
57
+ # rest of RUD
58
+
59
+ protected
60
+
61
+ def permitted_params
62
+ [
63
+ :restaurant_id,
64
+ :user_id
65
+ :dish_name,
66
+ :description,
67
+ :taste,
68
+ :color,
69
+ :texture,
70
+ :smell
71
+ ]
72
+ end
73
+
74
+ def get_review_params(params)
75
+ review_params = params.require(:review)
76
+
77
+ review_params[:name] ||= review_params.delete(:dish_name)
78
+
79
+ ratings = review_params.delete(:ratings)
80
+ if (ratings.present?)
81
+ ratings.each{|rating, value| review_params[rating] = value if valid_rating?(rating) }
82
+ end
83
+
84
+ review_params.permit(permitted_params)
85
+ end
86
+
87
+ def valid_rating?(rating)
88
+ @@ratings ||= ["overall", "trusthworthy", "responsive", "knowledgeable", "communication"]
89
+
90
+ @@ratings.include? rating
91
+
92
+ end
93
+ end
94
+ ```
95
+
96
+ and that's fine, but kind of annoying, and you have to do this for every action. It makes the controllers heavy, hard to parse, fragile, and really do things that are no longer controller-y.
97
+
98
+ So what we have here is a wrapper that lets us get away from polluting the controller with all of this parsing and lets us build deserializers that look very much like our serializers.
99
+
100
+ ## Usage
101
+
102
+ Deserializer acts and looks pretty mich identical to ActiveModel::Serializer. It has attributes, attribute, and the has_one association. It does not currently support has_many, as that's an odd thing for a write endpoint to support, but can easily be added.
103
+
104
+ ### Deserializer functions
105
+
106
+ #### from_params
107
+ `MyDeserializer.from_params(params)` created the json that your AR model will then consume.
108
+ ```ruby
109
+ @review = DishReview.new( MyApi::V1::DishReviewDeserailzer.from_params(params) )
110
+ ```
111
+
112
+ #### permitted_params
113
+ If you're using strong params, this lets you avoid having multiple definitions in fragile arrays. Just call ` MyDeserailzer.permitted_params` and you'll have the full array of keys you expect params to have.
114
+
115
+ ### Deserializer Definition
116
+ To define a deserializer, you inherit from `Deserializer::Base` and define it in much the same way you would an `ActiveModel::Serializer`.
117
+
118
+ #### attributes
119
+ This is straight 1:1 mapping from params to the model, so
120
+
121
+ ```ruby
122
+ class PostDeserializer < Deserializer::Base
123
+ attributes :title,
124
+ :body
125
+ end
126
+ ```
127
+ with params `{"title" => "lorem", "body" => "ipsum"}`, will give you a hash of `{title: "lorem", body: "ipsum"}`.
128
+
129
+ #### attribute
130
+ `attribute` is the singular version of `attributes`, but like `ActiveModel::Serializer` it can take a `:key`
131
+ ```ruby
132
+ class PostDeserializer < Deserializer::Base
133
+ attribute :title
134
+ attribute :body, key: :text
135
+ end
136
+ ```
137
+ It is symmetric with `ActiveModel::Serializer`, so that :text is what it will get in params, but :body is what it will insert into the result.
138
+
139
+ For example with params of `{"title" => "lorem", "text" => "ipsum"}` this desrerializer will produce `{title: "lorem", body: "ipsum"}`.
140
+
141
+
142
+ #### has_one
143
+ NOTE: This is the only association currently supported by `Deserializer`.
144
+ `has_one` expects the param and its deserializer. So for params `{"ratings" => {"taste" => "bad", "smell" => "good"}}`
145
+ ```ruby
146
+ class DishDeserializer < Deserializer::Base
147
+ # probably other stuff
148
+ has_one :ratings, deserializer: RatingsDeserializer
149
+ end
150
+
151
+ class RatingsDeserializer < Deserializer::Base
152
+ attributes :taste,
153
+ :smell
154
+ end
155
+ ```
156
+ you would get `{ratings: {taste: "bad", smell: "good"}}`
157
+
158
+ #### Overriding Attribute Methods
159
+ So let's say in the example above, your internal representation of ratings inside `Dish` is actually called `scores`, you can do
160
+ ```ruby
161
+ class DishDeserializer < Deserializer::Base
162
+ has_one :ratings, deserializer: RatingsDeserializer
163
+
164
+ def ratings
165
+ :scores
166
+ end
167
+ end
168
+ ```
169
+ which will give you `{scores: {taste: "bad", smell: "good"}}`
170
+
171
+ or, if you want to deserialize `ratings` into your `dish` object, you can use `object`
172
+
173
+ ```ruby
174
+ class DishDeserializer < Deserializer::Base
175
+ has_one :ratings, deserializer: RatingsDeserializer
176
+
177
+ def ratings
178
+ object
179
+ end
180
+ end
181
+ ```
182
+ which will give you `{taste: "bad", smell: "good"}`
183
+
184
+ or you can deserialize into another subobject by doing
185
+ ```ruby
186
+ class DishDeserializer < Deserializer::Base
187
+ has_one :colors, deserializer: ColorsDeserializer
188
+ has_one :ratings, deserializer: RatingsDeserializer
189
+
190
+ def colors
191
+ object[:ratings]
192
+ end
193
+ end
194
+ ```
195
+ which, given params
196
+ ```
197
+ {
198
+ "ratings" =>
199
+ {
200
+ "taste" => "bad",
201
+ "smell" => "good"
202
+ },
203
+ "colors" =>
204
+ {
205
+ "color" => "red"
206
+ }
207
+ }
208
+ ```
209
+ , will give you `{ratings: {taste: "bad", smell: "good", color: "red"}}`
210
+
211
+ ### Example
212
+
213
+ So the example above will combine all of those to look like
214
+
215
+ ```ruby
216
+ module MyApi
217
+ module V1
218
+ class DishReviewDeserializer < Deserializer::Base
219
+ attributes :restaurant_id
220
+ :user_id
221
+ :description
222
+
223
+ attribute :name, key: :dish_name
224
+
225
+ has_one :ratings, :deserializer => RatingsDeserializer
226
+
227
+ def ratings
228
+ object
229
+ end
230
+
231
+ end
232
+ end
233
+ end
234
+ ```
235
+
236
+ where RatingsDeserializer looks like
237
+
238
+ ```ruby
239
+ module MyApi
240
+ module V1
241
+ class RatingsDeserializer < Deserializer::Base
242
+
243
+ attributes :taste,
244
+ :color,
245
+ :texture,
246
+ :smell
247
+ end
248
+ end
249
+ end
250
+ ```
251
+
252
+ All of this allows your controller to be so very small, like
253
+
254
+ ```ruby
255
+ class DishReviewsController < YourApiController::Base
256
+ def create
257
+ @review = DishReview.new( MyApi::V1::DishReviewDeserailzer.from_params(params) )
258
+
259
+ if @review.save
260
+ # return review
261
+ else
262
+ # return sad errors splody
263
+ end
264
+ end
265
+
266
+ # rest of RUD
267
+ end
268
+ ```
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Deserializer'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test