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.
- checksums.yaml +4 -4
- data/bin/console +4 -3
- data/lib/decanter.rb +10 -18
- data/lib/decanter/base.rb +2 -0
- data/lib/decanter/configuration.rb +2 -1
- data/lib/decanter/core.rb +121 -140
- data/lib/decanter/decant.rb +11 -0
- data/lib/decanter/exceptions.rb +2 -0
- data/lib/decanter/extensions.rb +11 -11
- data/lib/decanter/parser.rb +5 -3
- data/lib/decanter/parser/base.rb +2 -1
- data/lib/decanter/parser/boolean_parser.rb +3 -2
- data/lib/decanter/parser/core.rb +9 -10
- data/lib/decanter/parser/date_parser.rb +7 -4
- data/lib/decanter/parser/date_time_parser.rb +21 -0
- data/lib/decanter/parser/float_parser.rb +5 -5
- data/lib/decanter/parser/hash_parser.rb +9 -6
- data/lib/decanter/parser/integer_parser.rb +4 -6
- data/lib/decanter/parser/join_parser.rb +4 -3
- data/lib/decanter/parser/key_value_splitter_parser.rb +6 -3
- data/lib/decanter/parser/pass_parser.rb +2 -1
- data/lib/decanter/parser/phone_parser.rb +3 -1
- data/lib/decanter/parser/string_parser.rb +3 -2
- data/lib/decanter/parser/time_parser.rb +19 -0
- data/lib/decanter/parser/utils.rb +3 -1
- data/lib/decanter/parser/value_parser.rb +4 -3
- data/lib/decanter/railtie.rb +11 -15
- data/lib/decanter/version.rb +3 -1
- data/lib/generators/decanter/install_generator.rb +5 -3
- data/lib/generators/decanter/templates/initializer.rb +2 -0
- data/lib/generators/rails/decanter_generator.rb +7 -5
- data/lib/generators/rails/parser_generator.rb +5 -3
- data/lib/generators/rails/resource_override.rb +2 -0
- metadata +19 -38
- data/.codeclimate.yml +0 -38
- data/.gitignore +0 -3
- data/.rspec +0 -2
- data/.ruby-version +0 -1
- data/Gemfile +0 -4
- data/Gemfile.lock +0 -102
- data/LICENSE.txt +0 -21
- data/README.md +0 -516
- data/Rakefile +0 -1
- data/decanter.gemspec +0 -39
- data/lib/decanter/parser/datetime_parser.rb +0 -13
@@ -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('
|
5
|
-
check_class_collision :
|
6
|
-
ASSOCIATION_TYPES =
|
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, :
|
10
|
+
argument :attributes, type: :array, default: [], banner: 'field:type field:type'
|
9
11
|
|
10
|
-
class_option :parent, :
|
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('
|
5
|
-
check_class_collision :
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
7
|
+
check_class_collision suffix: 'Parser'
|
6
8
|
|
7
|
-
class_option :parent, :
|
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")
|
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:
|
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:
|
10
|
+
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
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: '
|
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: '
|
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
|
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
|
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/
|
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
|
-
|
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
|
data/.codeclimate.yml
DELETED
@@ -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
data/.rspec
DELETED
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.6.5
|
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
@@ -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
|
data/LICENSE.txt
DELETED
@@ -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
|
-
[](https://codeclimate.com/github/LaunchPadLab/decanter) [](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
|
-
|