decanter 1.1.10 → 2.1.0

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/bin/console +4 -3
  3. data/lib/decanter.rb +10 -18
  4. data/lib/decanter/base.rb +2 -0
  5. data/lib/decanter/configuration.rb +2 -1
  6. data/lib/decanter/core.rb +121 -140
  7. data/lib/decanter/decant.rb +11 -0
  8. data/lib/decanter/exceptions.rb +2 -0
  9. data/lib/decanter/extensions.rb +11 -11
  10. data/lib/decanter/parser.rb +5 -3
  11. data/lib/decanter/parser/base.rb +2 -1
  12. data/lib/decanter/parser/boolean_parser.rb +3 -2
  13. data/lib/decanter/parser/core.rb +9 -10
  14. data/lib/decanter/parser/date_parser.rb +7 -4
  15. data/lib/decanter/parser/date_time_parser.rb +21 -0
  16. data/lib/decanter/parser/float_parser.rb +5 -5
  17. data/lib/decanter/parser/hash_parser.rb +9 -6
  18. data/lib/decanter/parser/integer_parser.rb +4 -6
  19. data/lib/decanter/parser/join_parser.rb +4 -3
  20. data/lib/decanter/parser/key_value_splitter_parser.rb +6 -3
  21. data/lib/decanter/parser/pass_parser.rb +2 -1
  22. data/lib/decanter/parser/phone_parser.rb +3 -1
  23. data/lib/decanter/parser/string_parser.rb +3 -2
  24. data/lib/decanter/parser/time_parser.rb +19 -0
  25. data/lib/decanter/parser/utils.rb +3 -1
  26. data/lib/decanter/parser/value_parser.rb +4 -3
  27. data/lib/decanter/railtie.rb +11 -15
  28. data/lib/decanter/version.rb +3 -1
  29. data/lib/generators/decanter/install_generator.rb +5 -3
  30. data/lib/generators/decanter/templates/initializer.rb +2 -0
  31. data/lib/generators/rails/decanter_generator.rb +7 -5
  32. data/lib/generators/rails/parser_generator.rb +5 -3
  33. data/lib/generators/rails/resource_override.rb +2 -0
  34. metadata +19 -38
  35. data/.codeclimate.yml +0 -38
  36. data/.gitignore +0 -3
  37. data/.rspec +0 -2
  38. data/.ruby-version +0 -1
  39. data/Gemfile +0 -4
  40. data/Gemfile.lock +0 -102
  41. data/LICENSE.txt +0 -21
  42. data/README.md +0 -516
  43. data/Rakefile +0 -1
  44. data/decanter.gemspec +0 -39
  45. data/lib/decanter/parser/datetime_parser.rb +0 -13
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Decanter.config do |config|
2
4
  # Possible values are :with_exception, true or false
3
5
  config.strict = :with_exception
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Generators
3
5
  class DecanterGenerator < NamedBase
4
- source_root File.expand_path('../templates', __FILE__)
5
- check_class_collision :suffix => 'Decanter'
6
- ASSOCIATION_TYPES = [:has_many, :has_one, :belongs_to]
6
+ source_root File.expand_path('templates', __dir__)
7
+ check_class_collision suffix: 'Decanter'
8
+ ASSOCIATION_TYPES = %i(has_many has_one belongs_to).freeze
7
9
 
8
- argument :attributes, :type => :array, :default => [], :banner => 'field:type field:type'
10
+ argument :attributes, type: :array, default: [], banner: 'field:type field:type'
9
11
 
10
- class_option :parent, :type => :string, :desc => 'The parent class for the generated decanter'
12
+ class_option :parent, type: :string, desc: 'The parent class for the generated decanter'
11
13
 
12
14
  def create_decanter_file
13
15
  template 'decanter.rb.erb', File.join('app/decanters', class_path, "#{file_name}_decanter.rb")
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Generators
3
5
  class ParserGenerator < NamedBase
4
- source_root File.expand_path('../templates', __FILE__)
5
- check_class_collision :suffix => 'Parser'
6
+ source_root File.expand_path('templates', __dir__)
7
+ check_class_collision suffix: 'Parser'
6
8
 
7
- class_option :parent, :type => :string, :desc => 'The parent class for the generated parser'
9
+ class_option :parent, type: :string, desc: 'The parent class for the generated parser'
8
10
 
9
11
  def create_parser_file
10
12
  template 'parser.rb.erb', File.join('lib/decanter/parsers', class_path, "#{file_name}_parser.rb")
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/generators'
2
4
  require 'rails/generators/rails/resource/resource_generator'
3
5
 
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decanter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.10
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Francis
8
8
  - David Corwin
9
9
  autorequire:
10
- bindir: exe
10
+ bindir: bin
11
11
  cert_chain: []
12
- date: 2020-01-27 00:00:00.000000000 Z
12
+ date: 2019-01-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -55,20 +55,6 @@ dependencies:
55
55
  version: 1.0.4
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: bundler
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - "~>"
61
- - !ruby/object:Gem::Version
62
- version: '1.9'
63
- type: :development
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - "~>"
68
- - !ruby/object:Gem::Version
69
- version: '1.9'
70
- - !ruby/object:Gem::Dependency
71
- name: dotenv
72
58
  requirement: !ruby/object:Gem::Requirement
73
59
  requirements:
74
60
  - - ">="
@@ -85,16 +71,16 @@ dependencies:
85
71
  name: rake
86
72
  requirement: !ruby/object:Gem::Requirement
87
73
  requirements:
88
- - - "~>"
74
+ - - ">="
89
75
  - !ruby/object:Gem::Version
90
- version: '10.0'
76
+ version: '0'
91
77
  type: :development
92
78
  prerelease: false
93
79
  version_requirements: !ruby/object:Gem::Requirement
94
80
  requirements:
95
- - - "~>"
81
+ - - ">="
96
82
  - !ruby/object:Gem::Version
97
- version: '10.0'
83
+ version: '0'
98
84
  - !ruby/object:Gem::Dependency
99
85
  name: rspec-rails
100
86
  requirement: !ruby/object:Gem::Requirement
@@ -113,40 +99,33 @@ dependencies:
113
99
  name: simplecov
114
100
  requirement: !ruby/object:Gem::Requirement
115
101
  requirements:
116
- - - "~>"
102
+ - - ">="
117
103
  - !ruby/object:Gem::Version
118
- version: 0.15.1
104
+ version: '0'
119
105
  type: :development
120
106
  prerelease: false
121
107
  version_requirements: !ruby/object:Gem::Requirement
122
108
  requirements:
123
- - - "~>"
109
+ - - ">="
124
110
  - !ruby/object:Gem::Version
125
- version: 0.15.1
111
+ version: '0'
126
112
  description: Decanter aims to reduce complexity in Rails controllers by creating a
127
113
  place for transforming data before it hits the model and database.
128
114
  email:
129
115
  - ryan@launchpadlab.com
130
- executables: []
116
+ executables:
117
+ - console
118
+ - setup
131
119
  extensions: []
132
120
  extra_rdoc_files: []
133
121
  files:
134
- - ".codeclimate.yml"
135
- - ".gitignore"
136
- - ".rspec"
137
- - ".ruby-version"
138
- - Gemfile
139
- - Gemfile.lock
140
- - LICENSE.txt
141
- - README.md
142
- - Rakefile
143
122
  - bin/console
144
123
  - bin/setup
145
- - decanter.gemspec
146
124
  - lib/decanter.rb
147
125
  - lib/decanter/base.rb
148
126
  - lib/decanter/configuration.rb
149
127
  - lib/decanter/core.rb
128
+ - lib/decanter/decant.rb
150
129
  - lib/decanter/exceptions.rb
151
130
  - lib/decanter/extensions.rb
152
131
  - lib/decanter/parser.rb
@@ -154,7 +133,7 @@ files:
154
133
  - lib/decanter/parser/boolean_parser.rb
155
134
  - lib/decanter/parser/core.rb
156
135
  - lib/decanter/parser/date_parser.rb
157
- - lib/decanter/parser/datetime_parser.rb
136
+ - lib/decanter/parser/date_time_parser.rb
158
137
  - lib/decanter/parser/float_parser.rb
159
138
  - lib/decanter/parser/hash_parser.rb
160
139
  - lib/decanter/parser/integer_parser.rb
@@ -163,6 +142,7 @@ files:
163
142
  - lib/decanter/parser/pass_parser.rb
164
143
  - lib/decanter/parser/phone_parser.rb
165
144
  - lib/decanter/parser/string_parser.rb
145
+ - lib/decanter/parser/time_parser.rb
166
146
  - lib/decanter/parser/utils.rb
167
147
  - lib/decanter/parser/value_parser.rb
168
148
  - lib/decanter/railtie.rb
@@ -194,7 +174,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
174
  - !ruby/object:Gem::Version
195
175
  version: '0'
196
176
  requirements: []
197
- rubygems_version: 3.0.3
177
+ rubyforge_project:
178
+ rubygems_version: 2.7.6
198
179
  signing_key:
199
180
  specification_version: 4
200
181
  summary: Form Parser for Rails
@@ -1,38 +0,0 @@
1
- engines:
2
- rubocop:
3
- enabled: true
4
- checks:
5
- Rubocop/Style/Documentation:
6
- enabled: false
7
- Rubocop/Metrics/LineLength:
8
- enabled: false
9
- Rubocop/Rails/Validation:
10
- enabled: false
11
- Rubocop/Style/IndentationConsistency:
12
- enabled: false
13
- Rubocop/Style/EmptyLines:
14
- enabled: false
15
- Rubocop/Style/ClassAndModuleChildren:
16
- enabled: false
17
- Rubocop/Style/AccessorMethodName:
18
- enabled: false
19
- golint:
20
- enabled: true
21
- gofmt:
22
- enabled: true
23
- eslint:
24
- enabled: true
25
- csslint:
26
- enabled: true
27
- brakeman:
28
- enabled: false
29
- bundler-audit:
30
- enabled: true
31
- ratings:
32
- paths:
33
- - lib/**
34
- - "**.rb"
35
- exclude_paths:
36
- - bin/**/*
37
- - spec/**/*
38
- - coverage/**/*
data/.gitignore DELETED
@@ -1,3 +0,0 @@
1
- /coverage
2
- .env
3
- .DS_Store
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --color
2
- --format documentation
@@ -1 +0,0 @@
1
- 2.6.5
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in goaltender.gemspec
4
- gemspec
@@ -1,102 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- decanter (1.1.10)
5
- actionpack (>= 4.2.10)
6
- activesupport
7
- rails-html-sanitizer (>= 1.0.4)
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- actionpack (5.1.4)
13
- actionview (= 5.1.4)
14
- activesupport (= 5.1.4)
15
- rack (~> 2.0)
16
- rack-test (>= 0.6.3)
17
- rails-dom-testing (~> 2.0)
18
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
19
- actionview (5.1.4)
20
- activesupport (= 5.1.4)
21
- builder (~> 3.1)
22
- erubi (~> 1.4)
23
- rails-dom-testing (~> 2.0)
24
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
25
- activesupport (5.1.4)
26
- concurrent-ruby (~> 1.0, >= 1.0.2)
27
- i18n (~> 0.7)
28
- minitest (~> 5.1)
29
- tzinfo (~> 1.1)
30
- builder (3.2.3)
31
- concurrent-ruby (1.0.5)
32
- crass (1.0.4)
33
- diff-lcs (1.3)
34
- docile (1.1.5)
35
- dotenv (2.2.1)
36
- erubi (1.7.0)
37
- i18n (0.9.3)
38
- concurrent-ruby (~> 1.0)
39
- json (2.1.0)
40
- loofah (2.2.2)
41
- crass (~> 1.0.2)
42
- nokogiri (>= 1.5.9)
43
- method_source (0.9.0)
44
- mini_portile2 (2.3.0)
45
- minitest (5.11.3)
46
- nokogiri (1.8.5)
47
- mini_portile2 (~> 2.3.0)
48
- rack (2.0.4)
49
- rack-test (0.8.2)
50
- rack (>= 1.0, < 3)
51
- rails-dom-testing (2.0.3)
52
- activesupport (>= 4.2.0)
53
- nokogiri (>= 1.6)
54
- rails-html-sanitizer (1.0.4)
55
- loofah (~> 2.2, >= 2.2.2)
56
- railties (5.1.4)
57
- actionpack (= 5.1.4)
58
- activesupport (= 5.1.4)
59
- method_source
60
- rake (>= 0.8.7)
61
- thor (>= 0.18.1, < 2.0)
62
- rake (10.5.0)
63
- rspec-core (3.7.1)
64
- rspec-support (~> 3.7.0)
65
- rspec-expectations (3.7.0)
66
- diff-lcs (>= 1.2.0, < 2.0)
67
- rspec-support (~> 3.7.0)
68
- rspec-mocks (3.7.0)
69
- diff-lcs (>= 1.2.0, < 2.0)
70
- rspec-support (~> 3.7.0)
71
- rspec-rails (3.7.2)
72
- actionpack (>= 3.0)
73
- activesupport (>= 3.0)
74
- railties (>= 3.0)
75
- rspec-core (~> 3.7.0)
76
- rspec-expectations (~> 3.7.0)
77
- rspec-mocks (~> 3.7.0)
78
- rspec-support (~> 3.7.0)
79
- rspec-support (3.7.1)
80
- simplecov (0.15.1)
81
- docile (~> 1.1.0)
82
- json (>= 1.8, < 3)
83
- simplecov-html (~> 0.10.0)
84
- simplecov-html (0.10.2)
85
- thor (0.20.0)
86
- thread_safe (0.3.6)
87
- tzinfo (1.2.5)
88
- thread_safe (~> 0.1)
89
-
90
- PLATFORMS
91
- ruby
92
-
93
- DEPENDENCIES
94
- bundler (~> 1.9)
95
- decanter!
96
- dotenv
97
- rake (~> 10.0)
98
- rspec-rails
99
- simplecov (~> 0.15.1)
100
-
101
- BUNDLED WITH
102
- 1.17.2
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2015 TODO: Write your name
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
data/README.md DELETED
@@ -1,516 +0,0 @@
1
- Decanter
2
- ===
3
-
4
- [![Code Climate](https://codeclimate.com/github/LaunchPadLab/decanter/badges/gpa.svg?ignore=me)](https://codeclimate.com/github/LaunchPadLab/decanter) [![Test Coverage](https://codeclimate.com/github/LaunchPadLab/decanter/badges/coverage.svg?ignore=me)](https://codeclimate.com/github/LaunchPadLab/decanter/coverage)
5
- ---
6
-
7
-
8
- What is Decanter?
9
- ---
10
-
11
- Decanter is a Rails gem that makes it easy to transform incoming data before it hits the model. The basic idea is that form data entered by a user often needs to be processed before it is stored into the database. A typical example of this is a datepicker. A user selects January 15th, 2015 as the date, but this is going to come into our controller as a string like "01/15/2015", so we need to convert this string to a Ruby Date object before it is stored in our database. Many developers perform this conversion right in the controller, which results in errors and unnecessary complexity, especially as the application grows.
12
-
13
- You can think of Decanter as the opposite of Active Model Serializer. Whereas AMS transforms your outbound data into a format that your frontend consumes, Decanter transforms your incoming data into a format that your backend consumes.
14
-
15
- Installation
16
- ---
17
-
18
- ```ruby
19
- gem "decanter"
20
- ```
21
-
22
- ```
23
- bundle
24
- ```
25
-
26
- Basic Usage
27
- ---
28
-
29
- ```
30
- rails g decanter Trip name:string start_date:date end_date:date
31
- ```
32
-
33
- **app/decanters/trip_decanter.rb**
34
-
35
- ```ruby
36
- class TripDecanter < Decanter::Base
37
- input :name, :string
38
- input :start_date, :date
39
- input :end_date, :date
40
- end
41
- ```
42
-
43
- By default, Decanter will use the [default parser](https://github.com/LaunchPadLab/decanter#default-parsers) that matches your input data type.
44
-
45
- ```ruby
46
- input :name, :string #=> StringParser
47
- ```
48
-
49
- To reference a custom or modified parser,
50
-
51
- ```ruby
52
- input :name, :string, :custom_string_parser
53
- ```
54
-
55
- In your controller:
56
-
57
- ```ruby
58
- def create
59
- @trip = Trip.decant_new(params[:trip])
60
-
61
- if @trip.save
62
- redirect_to trips_path
63
- else
64
- render "new"
65
- end
66
- end
67
-
68
- def update
69
- @trip = Trip.find(params[:id])
70
-
71
- if @trip.decant_update(params[:trip])
72
- redirect_to trips_path
73
- else
74
- render "new"
75
- end
76
- end
77
- ```
78
-
79
- Or, if you would prefer to get the parsed hash and then do your own logic, you can do the following:
80
-
81
- ```ruby
82
- def create
83
- parsed_params = Trip.decant(params[:trip])
84
- @trip = Trip.new(parsed_params)
85
-
86
- # save logic here
87
- end
88
- ```
89
-
90
- Basic Example
91
- ---
92
-
93
- We have a form where users can create a new Trip, which has the following attributes: name, start_date, and end_date
94
-
95
- Without Decanter, here is what our create action may look like:
96
-
97
- ```ruby
98
- class TripsController < ApplicationController
99
- def create
100
- @trip = Trip.new(params[:trip])
101
- start_date = Date.strptime(params[:trip][:start_date], '%m/%d/%Y')
102
- end_date = Date.strptime(params[:trip][:end_date], '%m/%d/%Y')
103
- @trip.start_date = start_date
104
- @trip.end_date = end_date
105
-
106
- if @trip.save
107
- redirect_to trips_path
108
- else
109
- render 'new'
110
- end
111
- end
112
- end
113
- ```
114
-
115
- We can see here that converting start_date and end_date to a Ruby date is creating complexity. Could you imagine the complexity involved with performing similar parsing with a nested resource? If you're curious how ugly it would get, we took the liberty of implementing an example here: [Nested Example (Without Decanter)](https://github.com/LaunchPadLab/decanter_demo/blob/master/app/controllers/nested_example/trips_no_decanter_controller.rb)
116
-
117
- With Decanter installed, here is what the same controller action would look like:
118
-
119
- ```ruby
120
- class TripsController < ApplicationController
121
- def create
122
- @trip = Trip.decant_new(params[:trip])
123
-
124
- if @trip.save
125
- redirect_to trips_path
126
- else
127
- render 'new'
128
- end
129
- end
130
- end
131
- ```
132
-
133
- As you can see, we no longer need to parse the start and end date. Let's take a look at how we accomplished that.
134
-
135
- From terminal we ran:
136
-
137
- ```
138
- rails g decanter Trip name:string start_date:date end_date:date
139
- ```
140
-
141
- Which generates app/decanters/trip_decanter.rb:
142
-
143
- ```ruby
144
- class TripDecanter < Decanter::Base
145
- input :name, :string
146
- input :start_date, :date
147
- input :end_date, :date
148
- end
149
- ```
150
-
151
- You'll also notice that instead of ```@trip = Trip.new(params[:trip])``` we do ```@trip = Trip.decant_new(params[:trip])```. ```decant_new``` is where the magic happens. It is converting the params from this:
152
-
153
- ```ruby
154
- {
155
- name: "My Trip",
156
- start_date: "01/15/2015",
157
- end_date: "01/20/2015"
158
- }
159
- ```
160
-
161
- to this:
162
-
163
- ```ruby
164
- {
165
- name: "My Trip",
166
- start_date: Mon, 15 Jan 2015,
167
- end_date: Mon, 20 Jan 2015
168
- }
169
- ```
170
-
171
- As you can see, the converted params hash has converted start_date and end_date to a Ruby Date object that is ready to be stored in our database.
172
-
173
- Adding Custom Parsers
174
- ---
175
-
176
- In the above example, start_date and end_date are ran through a DateParser that lives in Decanter. Let's take a look at the DateParser:
177
-
178
- ** Note this changed in version 0.7.2. Now parser must inherit from Decanter::Parser::ValueParser or Decanter::Parser::HashParser instead of Decanter::Parser::Base **
179
-
180
- ```ruby
181
- class DateParser < Decanter::Parser::ValueParser
182
-
183
- allow Date
184
-
185
- parser do |value, options|
186
- parse_format = options.fetch(:parse_format, '%m/%d/%Y')
187
- ::Date.strptime(value, parse_format)
188
- end
189
- end
190
- ```
191
-
192
- ```allow Date``` basically tells Decanter that if the value comes in as a Date object, we don't need to parse it at all. Other than that, the parser is really just doing ```Date.strptime("01/15/2015", '%m/%d/%Y')```, which is just a vanilla date parse.
193
-
194
- You'll notice that the above ```parser do``` block takes a ```:parse_format``` option. This allows you to specify the format your date string will come in. For example, if you expect "2016-01-15" instead of "01/15/2016", you can adjust the TripDecanter like so:
195
-
196
- ```ruby
197
- # app/decanters/trip_decanter.rb
198
-
199
- class TripDecanter < Decanter::Base
200
- input :name, :string
201
- input :start_date, :date, parse_format: '%Y-%m-%d'
202
- input :end_date, :date, parse_format: '%Y-%m-%d'
203
- end
204
- ```
205
-
206
- You can add your own parser if you want more control over the logic, or if you have a peculiar format type we don't support.
207
-
208
- ```
209
- rails g parser Date
210
- ```
211
-
212
- **lib/decanter/parsers/date_parser**
213
-
214
- ```ruby
215
- class DateParser < Decanter::Parser::ValueParser
216
- parser do |value, options|
217
- # your parsing logic here
218
- end
219
- end
220
- ```
221
-
222
-
223
- By inheriting from Decanter::Parser::ValueParser, the assumption is that the value returned from the parser will be the value associated with the provided key. If you need more control over the result, for example, you want a parser that returns multiple key value pairs, you should instead inherit from Decanter::Parser::HashParser. This requires that the value returned is a hash. For example:
224
-
225
- ```ruby
226
- class KeyValueSplitterParser < Decanter::Parser::HashParser
227
- ITEM_DELIM = ','
228
- PAIR_DELIM = ':'
229
-
230
- parser do |name, val, options|
231
- # val = 'color:blue,price:45.31'
232
-
233
- item_delimiter = options.fetch(:item_delimiter, ITEM_DELIM)
234
- pair_delimiter = options.fetch(:pair_delimiter, PAIR_DELIM)
235
-
236
- pairs = val.split(item_delimiter) # ['color:blue', 'price:45.31']
237
-
238
- hash = {}
239
- pairs.each do |pair|
240
- key, value = pair.split(pair_delimiter) # 'color', 'blue'
241
- hash[key] = value
242
- end
243
- return hash
244
- end
245
- end
246
- ```
247
-
248
- The ```parser``` block takes the 'name' as an additional argument and must return a hash.
249
-
250
- Nested Example
251
- ---
252
-
253
- Let's say we have two models in our app: a Trip and a Destination. A trip has many destinations, and is prepared to accept nested attributes from the form.
254
-
255
- ```ruby
256
- # app/models/trip.rb
257
-
258
- class Trip < ActiveRecord::Base
259
- has_many :destinations
260
- accepts_nested_attributes_for :destinations
261
- end
262
- ```
263
-
264
- ```ruby
265
- # app/models/destination.rb
266
-
267
- class Destination < ActiveRecord::Base
268
- belongs_to :trip
269
- end
270
- ```
271
-
272
- First, let's create our decanters for Trip and Destination. Note: decanters are automatically created whenever you run ```rails g resource```.
273
-
274
- ```
275
- rails g decanter Trip name destinations:has_many
276
- rails g decanter Destination city state arrival_date:date departure_date:date
277
- ```
278
-
279
- Which produces app/decanters/trip and app/decanters/destination:
280
-
281
- ```ruby
282
- class TripDecanter < Decanter::Base
283
- input :name, :string
284
- has_many :destinations
285
- end
286
- ```
287
-
288
- ```ruby
289
- class DestinationDecanter < Decanter::Base
290
- input :city, :string
291
- input :state, :string
292
- input :arrival_date, :date
293
- input :departure_date, :date
294
- end
295
- ```
296
-
297
- With that, we can use the same vanilla create action syntax you saw in the basic example above:
298
-
299
- ```ruby
300
- class TripsController < ApplicationController
301
- def create
302
- @trip = Trip.decant_new(params[:trip])
303
-
304
- if @trip.save
305
- redirect_to trips_path
306
- else
307
- render 'new'
308
- end
309
- end
310
- end
311
- ```
312
-
313
- Each of the destinations in our params[:trip] are automatically parsed according to the DestinationDecanter inputs set above. This means that ```arrival_date``` and ```departure_date``` are converted to Ruby Date objects for each of the destinations passed through the nested params. Yeehaw!
314
-
315
- Non Database-Backed Objects
316
- ---
317
-
318
- Decanter will work for your non database-backed objects as well. We just need to call ```decant``` to parse our params according to our decanter logic.
319
-
320
- Let's say we have a search filtering object called ```SearchFilter```. We start by generating our decanter:
321
-
322
- ```
323
- rails g decanter SearchFilter start_date:date end_date:date city:string state:string
324
- ```
325
-
326
- ```ruby
327
- # app/decanters/search_filter_decanter.rb
328
-
329
- class SearchFilterDecanter < Decanter::Base
330
-
331
- end
332
- ```
333
-
334
- ```ruby
335
- # app/controllers/search_controller.rb
336
-
337
- def search
338
- decanted_params = SearchFilterDecanter.decant(params[:search])
339
- # decanted_params is now parsed according to the parsers defined
340
- # in SearchFilterDecanter
341
- end
342
- ```
343
-
344
- Default Parsers
345
- ---
346
-
347
- Decanter comes with the following parsers:
348
- - boolean
349
- - date
350
- - date_time
351
- - float
352
- - integer
353
- - join
354
- - key_value_splitter
355
- - pass
356
- - phone
357
- - string
358
-
359
- As an example as to how these parsers differ, let's consider ```float```. The float parser will perform a regex to find only characters that are digits or decimals. By doing that, your users can enter in commas and currency symbols without your backend throwing a hissy fit.
360
-
361
- We encourage you to create your own parsers for other needs in your app, or generate one of the above listed parsers to override its behavior.
362
-
363
- ```
364
- rails g parser Zip
365
- ```
366
-
367
- Squashing Inputs
368
- ---
369
-
370
- Sometimes, you may want to take several inputs and combine them into one finished input prior to sending to your model. For example, if day, month, and year come in as separate parameters, but your database really only cares about start_date.
371
-
372
- ```ruby
373
- class TripDecanter < Decanter::Base
374
- input [:day, :month, :year], :squash_date, key: :start_date
375
- end
376
- ```
377
-
378
- ```
379
- rails g parser SquashDate
380
- ```
381
-
382
- ```ruby
383
- # lib/decanter/parsers/squash_date_parser.rb
384
-
385
- class SquashDateParser < Decanter::Parser::ValueParser
386
- parser do |values, options|
387
- day, month, year = values.map(&:to_i)
388
- Date.new(year, month, day)
389
- end
390
- end
391
- ```
392
-
393
- Chaining Parsers
394
- ---
395
-
396
- Parsers are composable! Suppose you want a parser that takes an incoming percentage like "50.3%" and converts it into a float for your database like .503. You could implement this with:
397
-
398
- ```ruby
399
- class PercentParser < Decanter::Parser::ValueParser
400
- REGEX = /(\d|[.])/
401
-
402
- parser do |val, options|
403
- my_float = val.scan(REGEX).join.try(:to_f)
404
- my_float / 100 if my_float
405
- end
406
- end
407
- ```
408
-
409
- This works, but it duplicates logic that already exists in `FloatParser`. Instead, you can specify a parser that should always run before your parsing logic, then you can assume that your parser receives a float:
410
-
411
- ```ruby
412
- class SmartPercentParser < Decanter::Parser::ValueParser
413
-
414
- pre :float
415
-
416
- parser do |val, options|
417
- val / 100
418
- end
419
- end
420
- ```
421
-
422
- If a preparser returns nil or an empty string, subsequent parsers will not be called, just like normal!
423
-
424
- This can also be achieved by providing multiple parsers in your decanter:
425
-
426
- ```ruby
427
- class SomeDecanter < Decanter::Base
428
- input :some_percent, [:float, :percent]
429
- end
430
- ```
431
-
432
- No Need for Strong Params
433
- ---
434
-
435
- Since you are already defining your expected inputs in Decanter, you really don't need strong params anymore.
436
-
437
- Note: starting with version 0.7.2, the default strict mode is ```:with_exception```. You can modify your default strict mode in your configuration file (see the "Configuration" section below).
438
-
439
- #### Mode: with_exception (default mode)
440
-
441
- To raise exceptions when parameters arrive in your Decanter that you didn't expect:
442
-
443
- ```ruby
444
- class TripDecanter < Decanter::Base
445
- strict :with_exception
446
-
447
- input :name
448
- end
449
- ```
450
-
451
- #### Mode: strict
452
-
453
- In order to tell Decanter to ignore the params not defined in your Decanter, just add the ```strict``` flag to your Decanters:
454
-
455
- ```ruby
456
- class TripDecanter < Decanter::Base
457
- strict true
458
-
459
- input :name
460
- end
461
- ```
462
-
463
- #### Requiring Params
464
-
465
- If you provide the option `:required` for an input in your decanter, an exception will be thrown if the parameters is nil or an empty string.
466
-
467
- ```ruby
468
- class TripDecanter < Decanter::Base
469
- input :name, :string, required: true
470
- end
471
- ```
472
-
473
- #### Ignoring Params
474
-
475
- If you anticipate your decanter will receive certain params that you simply want to ignore and therefore do not want Decanter to raise an exception, you can do so by calling the `ignore` method:
476
-
477
- ```ruby
478
- class TripDecanter < Decanter::Base
479
- ignore :created_at, :updated_at
480
-
481
- input :name, :string
482
- end
483
- ```
484
-
485
- Configuration
486
- ---
487
-
488
- You can generate a local copy of the default configuration with ```rails generate decanter:install```. This will create the initializer ```../config/initializers/decanter.rb```.
489
-
490
- Starting with version 0.7.2, the default strict mode is ```:with_exception```. If this is what you prefer, you no longer have to set it in every decanter. You can still set this on individual decanters or you can configure it globally in the initializer:
491
-
492
- ```ruby
493
- # ../config/initializers/decanter.rb
494
-
495
- Decanter.config do |config|
496
- config.strict = true
497
- end
498
-
499
- # Or
500
-
501
- Decanter.configuration.strict = true
502
- ```
503
-
504
- Likewise, you can put the above code in a specific environment configuration.
505
-
506
- Decanter Exceptions
507
- ---
508
-
509
- - MissingRequiredInputValue
510
-
511
- Raised when required inputs have been enabled, but provided arguments to `decant()` do not contain values for those required inputs.
512
-
513
- - UnhandledKeysError
514
-
515
- Raised when there are unhandled keys.
516
-