normalizy 0.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 +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE +21 -0
- data/README.md +341 -0
- data/lib/generators/normalizy/install_generator.rb +13 -0
- data/lib/generators/normalizy/templates/config/initializers/normalizy.rb +7 -0
- data/lib/normalizy/config.rb +35 -0
- data/lib/normalizy/extensions.rb +105 -0
- data/lib/normalizy/filters/number.rb +17 -0
- data/lib/normalizy/filters/strip.rb +19 -0
- data/lib/normalizy/filters.rb +8 -0
- data/lib/normalizy/rspec/matcher.rb +115 -0
- data/lib/normalizy/version.rb +5 -0
- data/lib/normalizy.rb +20 -0
- data/spec/normalizy/config/add_spec.rb +17 -0
- data/spec/normalizy/config/alias_spec.rb +63 -0
- data/spec/normalizy/config/default_filters_spec.rb +19 -0
- data/spec/normalizy/config/initialize_spec.rb +12 -0
- data/spec/normalizy/config/normalizy_aliases_spec.rb +9 -0
- data/spec/normalizy/config/normalizy_raws_spec.rb +9 -0
- data/spec/normalizy/extensions/apply_normalizations_spec.rb +163 -0
- data/spec/normalizy/extensions/normalizy_rules_spec.rb +244 -0
- data/spec/normalizy/extensions/normalizy_spec.rb +11 -0
- data/spec/normalizy/filters/number_spec.rb +15 -0
- data/spec/normalizy/filters/strip_spec.rb +13 -0
- data/spec/normalizy/normalizy/configure_spec.rb +11 -0
- data/spec/normalizy/rspec/matcher/description_spec.rb +26 -0
- data/spec/normalizy/rspec/matcher/failure_message_spec.rb +70 -0
- data/spec/normalizy/rspec/matcher/failure_message_when_negated_spec.rb +32 -0
- data/spec/normalizy/rspec/matcher/from_spec.rb +18 -0
- data/spec/normalizy/rspec/matcher/matchers_spec.rb +97 -0
- data/spec/normalizy/rspec/matcher/to_spec.rb +18 -0
- data/spec/normalizy/rspec/normalizy_spec.rb +8 -0
- data/spec/rails_helper.rb +9 -0
- data/spec/support/common.rb +11 -0
- data/spec/support/db/schema.rb +13 -0
- data/spec/support/filters/blacklist_filter.rb +15 -0
- data/spec/support/models/clean.rb +4 -0
- data/spec/support/models/user.rb +9 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 781b68ea361e5e2a698bba7230a2a2459f2cb758
|
4
|
+
data.tar.gz: 01d9c17b15ee6ec08c5dda0da645897a43f9370d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cef08c0e44de84158e91659510c0d4bcd50715739a977452c9c7037b44dbaa06acfe29993388b357a1c0e863e7cafa22e8b5acbc8f995b740340c8efa7aeef76
|
7
|
+
data.tar.gz: 632e6116a884839be1e018f905599766ecf9223ef9336ae5ba79c503bd549c41242fdcab975a489372f75a530f97b495ec71e0f53d649e0c8cf844c870788341
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Washington Botelho
|
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
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
# Normalizy
|
2
|
+
|
3
|
+
[](https://travis-ci.org/wbotelhos/normalizy)
|
4
|
+
[](https://badge.fury.io/rb/normalizy)
|
5
|
+
|
6
|
+
Attribute normalizer for ActiveRecord.
|
7
|
+
|
8
|
+
## Description
|
9
|
+
|
10
|
+
If you know the obvious format of an input, why not normalize it instead of raise an validation error to your use? Make the follow email ` myemail@example.org ` valid like `email@example.com` with no need to override acessors methods.
|
11
|
+
|
12
|
+
## install
|
13
|
+
|
14
|
+
Add the following code on your `Gemfile` and run `bundle install`:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'normalizy'
|
18
|
+
```
|
19
|
+
|
20
|
+
So generates an initializer for future custom configurations:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
rails g normalizy:install
|
24
|
+
```
|
25
|
+
|
26
|
+
It will generates a file `config/initializers/normalizy.rb` where you can configure you own normalizer and choose some defaults one.
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
On your model, just add `normalizy` callback with the attribute you want to normalize and the filter to be used:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class User < ApplicationRecord
|
34
|
+
normalizy :name, with: :strip
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Now some email like ` myemail@example.org ` will be saved as `email@example.com`.
|
39
|
+
|
40
|
+
## Filters
|
41
|
+
|
42
|
+
We have a couple of built-in filters. The most one is just a wrapper o the original String methods:
|
43
|
+
|
44
|
+
### Number
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
normalizy :age, with: :number
|
48
|
+
|
49
|
+
' 32'
|
50
|
+
# 32
|
51
|
+
```
|
52
|
+
|
53
|
+
By default, `number` works with input value before [Type Cast](#type-cast)
|
54
|
+
### Strip
|
55
|
+
|
56
|
+
Options:
|
57
|
+
|
58
|
+
- `side`: `:left`, `:right` or `:both`. Default: `:both`
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
normalizy :name, with: :strip
|
62
|
+
' Washington Botelho '
|
63
|
+
# 'Washington Botelho'
|
64
|
+
```
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
normalizy :name, with: { strip: { side: :left } }
|
68
|
+
' Washington Botelho '
|
69
|
+
# 'Washington Botelho '
|
70
|
+
```
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
normalizy :name, with: { strip: { side: :right } }
|
74
|
+
' Washington Botelho '
|
75
|
+
# ' Washington Botelho'
|
76
|
+
```
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
normalizy :name, with: { strip: { side: :both } }
|
80
|
+
' Washington Botelho '
|
81
|
+
# 'Washington Botelho'
|
82
|
+
```
|
83
|
+
|
84
|
+
As you can see, the rules can be passed as Symbol/String or as Hash if it has options.
|
85
|
+
|
86
|
+
## Multiple Filters
|
87
|
+
|
88
|
+
You can normalize with a couple of filters at once:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
normalizy :name, with: %i[squish titleize]
|
92
|
+
' washington botelho '
|
93
|
+
# 'Washington Botelho'
|
94
|
+
```
|
95
|
+
|
96
|
+
## Multiple Attributes
|
97
|
+
|
98
|
+
You can normalize more than one attribute at once too, with one or muiltiple filters:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
normalizy :email, :username, with: :downcase
|
102
|
+
```
|
103
|
+
|
104
|
+
Of course you can declare muiltiples attribute and multiple filters together.
|
105
|
+
It is possible to make sequential normalizy calls:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
normalizy :email, :username, with: :squish
|
109
|
+
normalizy :email , with: :downcase
|
110
|
+
```
|
111
|
+
|
112
|
+
In this case, each line will be evaluated from the top to the bottom.
|
113
|
+
|
114
|
+
## Default Filters
|
115
|
+
|
116
|
+
You can configure some default filters to be runned. Edit you initializer at `config/initializers/normalizy.rb`:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
Normalizy.configure do |config|
|
120
|
+
config.default_filters = [:squish]
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
Now, all normalization will include squish, even when no rule is declared.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
normalizy :name
|
128
|
+
' Washington Botelho '
|
129
|
+
# 'Washington Botelho'
|
130
|
+
```
|
131
|
+
|
132
|
+
If you declare some filter, the default filter `squish` will be runned together:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
normalizy :name, with: :downcase
|
136
|
+
' washington botelho '
|
137
|
+
# 'Washington Botelho'
|
138
|
+
```
|
139
|
+
|
140
|
+
## Custom Filter
|
141
|
+
|
142
|
+
You can create a custom filter that implements `call` method with an `input` as argument and an optional `options`:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
module Normalizy
|
146
|
+
module Filters
|
147
|
+
module Blacklist
|
148
|
+
def self.call(input)
|
149
|
+
input.gsub 'Fuck', replacement: '***'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
Normalizy.configure do |config|
|
158
|
+
config.add :blacklist, Normalizy::Filters::Blacklist
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
Now you can use your custom filter:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
normalizy :name, with: :blacklist
|
166
|
+
|
167
|
+
'Washington Fuck Botelho'
|
168
|
+
# 'Washington *** Botelho'
|
169
|
+
```
|
170
|
+
|
171
|
+
If you want to pass options to your filter, just call it as hash and the value will be passed to the custom filter:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
module Normalizy
|
175
|
+
module Filters
|
176
|
+
module Blacklist
|
177
|
+
def self.call(input, options: {})
|
178
|
+
input.gsub 'Fuck', replacement: options[:replacement]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
normalizy :name, with: blacklist: { replacement: '---' }
|
187
|
+
|
188
|
+
'Washington Fuck Botelho'
|
189
|
+
# 'Washington --- Botelho'
|
190
|
+
```
|
191
|
+
|
192
|
+
You can pass a block and it will be received on filter:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
module Normalizy
|
196
|
+
module Filters
|
197
|
+
module Blacklist
|
198
|
+
def self.call(input, options: {})
|
199
|
+
value = input.gsub('Fuck', 'filtered')
|
200
|
+
|
201
|
+
value = yield(value) if block_given?
|
202
|
+
|
203
|
+
value
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
normalizy :name, with: :blacklist, &->(value) { value.sub('filtered', '(filtered 2x)') }
|
212
|
+
|
213
|
+
'Washington Fuck Botelho'
|
214
|
+
# 'Washington (filtered 2x) Botelho'
|
215
|
+
```
|
216
|
+
|
217
|
+
The block
|
218
|
+
|
219
|
+
## Method Filters
|
220
|
+
|
221
|
+
If a built-in filter is not found, Normalizy will try to find a method to suply the normalize with the same name of the given filter:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
normalizy :birthday, with: :parse_date
|
225
|
+
|
226
|
+
def parse_date(input, options = {})
|
227
|
+
Time.zone.parse(input).strftime '%Y/%m/%d'
|
228
|
+
end
|
229
|
+
|
230
|
+
'1984-10-23'
|
231
|
+
# '1984/10/23'
|
232
|
+
```
|
233
|
+
|
234
|
+
If you gives an option, it will be passed to the function too:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
normalizy :birthday, with: { parse_date: { format: '%Y/%m/%d' }
|
238
|
+
|
239
|
+
def parse_date(input, options = {})
|
240
|
+
Time.zone.parse(input).strftime options[:format]
|
241
|
+
end
|
242
|
+
|
243
|
+
'1984-10-23'
|
244
|
+
# '1984/10/23'
|
245
|
+
```
|
246
|
+
|
247
|
+
Block methods works here too.
|
248
|
+
|
249
|
+
## Native Filter
|
250
|
+
|
251
|
+
After the missing built-in and class method, the fallback will be the value of native methods.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
normalizy :name, with: :reverse
|
255
|
+
|
256
|
+
'Washington Botelho'
|
257
|
+
# "ohletoB notgnihsaW"
|
258
|
+
```
|
259
|
+
|
260
|
+
## Inline Filter
|
261
|
+
|
262
|
+
Maybe you want to declare an inline filter, in this case, just use a Lambda or Proc:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
normalizy :age, with: ->(input) { input.abs }
|
266
|
+
|
267
|
+
-32
|
268
|
+
# 32
|
269
|
+
```
|
270
|
+
|
271
|
+
You can use it on filters declaration too:
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
Normalizy.configure do |config|
|
275
|
+
config.add :age, ->(input) { input.abs }
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
## Type Cast
|
280
|
+
|
281
|
+
An input field with `$ 42.00` dollars when sent to model with a field with `integer` type,
|
282
|
+
will be converted to `0`, since the type does not match. But you want to use the value before Rails do this cast the type.
|
283
|
+
|
284
|
+
To receive the value before type cast, just pass a `raw` options as `true`:
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
normalizy :amount, with: :number, raw: true
|
288
|
+
|
289
|
+
'$ 42.00'
|
290
|
+
# 4200
|
291
|
+
```
|
292
|
+
|
293
|
+
To avoid repeat the `raw: true` where you will always to use, you can register a filter with this options:
|
294
|
+
|
295
|
+
```ruby
|
296
|
+
Normalizy.configure do |config|
|
297
|
+
config.add :money, ->(input) { input.gsub(/\D/, '') }, raw: true
|
298
|
+
end
|
299
|
+
```
|
300
|
+
|
301
|
+
## Alias
|
302
|
+
|
303
|
+
Sometimes you want to give a better name to your filter, just to keep the things semantic.
|
304
|
+
But duplicates the code just to redefine a new name is not a good idea, so, just create an alias:
|
305
|
+
|
306
|
+
```ruby
|
307
|
+
Normalizy.configure do |config|
|
308
|
+
config.alias :money, :number
|
309
|
+
end
|
310
|
+
```
|
311
|
+
|
312
|
+
Now, `money` will delegate to `number` filter.
|
313
|
+
Since we already know the need of `raw` options, we can declare it here too:
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
Normalizy.configure do |config|
|
317
|
+
config.alias :money, :number, raw: true
|
318
|
+
end
|
319
|
+
```
|
320
|
+
|
321
|
+
But `number` filter already works with `raw: true`, don't need to tell it again.
|
322
|
+
An our previous example, about `amount`, was refactored to:
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
normalizy :amount, with: :money
|
326
|
+
|
327
|
+
'$ 42.00'
|
328
|
+
# 4200
|
329
|
+
```
|
330
|
+
|
331
|
+
If you need to alias multiple filters, just provide an array of them:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
Normalizy.configure do |config|
|
335
|
+
config.alias :username, %i[squish downcase]
|
336
|
+
end
|
337
|
+
```
|
338
|
+
|
339
|
+
## Love it!
|
340
|
+
|
341
|
+
Via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=X8HEP2878NDEG&item_name=normalizy) or [Gratipay](https://gratipay.com/~wbotelhos). Thanks! (:
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Normalizy
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
desc 'creates an initializer'
|
8
|
+
|
9
|
+
def copy_initializer
|
10
|
+
copy_file 'config/initializers/normalizy.rb', 'config/initializers/normalizy.rb'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'normalizy/filters'
|
4
|
+
|
5
|
+
module Normalizy
|
6
|
+
class Config
|
7
|
+
attr_accessor :default_filters
|
8
|
+
attr_reader :filters, :normalizy_aliases, :normalizy_raws
|
9
|
+
|
10
|
+
def add(name, value, raw: false)
|
11
|
+
@filters[name] = value
|
12
|
+
@normalizy_raws << name if raw
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def alias(name, to, raw: false)
|
18
|
+
@normalizy_aliases[name] = to
|
19
|
+
@normalizy_raws << name if raw
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@default_filters = {}
|
26
|
+
@normalizy_aliases = {}
|
27
|
+
@normalizy_raws = [:number]
|
28
|
+
|
29
|
+
@filters = {
|
30
|
+
number: Normalizy::Filters::Number,
|
31
|
+
strip: Normalizy::Filters::Strip
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Normalizy
|
4
|
+
module Extension
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_validation :apply_normalizy, if: -> {
|
9
|
+
self.class.respond_to? :normalizy
|
10
|
+
}
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def apply_normalizy
|
15
|
+
(self.class.normalizy_rules || {}).each do |attribute, rules|
|
16
|
+
rules.each { |rule| normalizy! rule.merge(attribute: attribute) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_filter(rule, filters: Normalizy.config.filters)
|
21
|
+
if rule.is_a?(Hash)
|
22
|
+
result = filters[rule.keys.first] || rule.keys.first
|
23
|
+
options = rule.values.first
|
24
|
+
else
|
25
|
+
result = filters[rule]
|
26
|
+
end
|
27
|
+
|
28
|
+
[result || rule, options || {}]
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_value(value, filter, options, block)
|
32
|
+
if filter.respond_to?(:call)
|
33
|
+
if filter.method(:call).arity == -2
|
34
|
+
filter.call value, options, &block
|
35
|
+
else
|
36
|
+
filter.call value, &block
|
37
|
+
end
|
38
|
+
elsif respond_to?(filter)
|
39
|
+
send filter, value, options, &block
|
40
|
+
elsif value.respond_to?(filter)
|
41
|
+
value.send filter, &block
|
42
|
+
else
|
43
|
+
value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def normalizy!(attribute:, rules:, options:, block:)
|
48
|
+
return if rules.blank? && block.blank?
|
49
|
+
|
50
|
+
aliases = Normalizy.config.normalizy_aliases
|
51
|
+
value = nil
|
52
|
+
|
53
|
+
[rules].flatten.compact.each do |rule|
|
54
|
+
value = original_value(attribute, rule, options)
|
55
|
+
aliased_rules = [aliases.key?(rule) ? aliases[rule] : rule]
|
56
|
+
|
57
|
+
aliased_rules.flatten.compact.each do |alias_rule|
|
58
|
+
filter, filter_options = extract_filter(alias_rule)
|
59
|
+
value = extract_value(value, filter, filter_options, block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
return unless value
|
64
|
+
|
65
|
+
write attribute, value
|
66
|
+
end
|
67
|
+
|
68
|
+
def original_value(attribute, rule, options)
|
69
|
+
if raw? attribute, rule, options
|
70
|
+
send "#{attribute}_before_type_cast"
|
71
|
+
else
|
72
|
+
send attribute
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def raw?(attribute, rule, options)
|
77
|
+
return false unless respond_to?("#{attribute}_before_type_cast")
|
78
|
+
|
79
|
+
options[:raw] || Normalizy.config.normalizy_raws.include?(rule)
|
80
|
+
end
|
81
|
+
|
82
|
+
def write(attribute, value)
|
83
|
+
write_attribute attribute, value
|
84
|
+
rescue ActiveModel::MissingAttributeError
|
85
|
+
send "#{attribute}=", value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module ClassMethods
|
90
|
+
attr_accessor :normalizy_rules
|
91
|
+
|
92
|
+
def normalizy(*args, &block)
|
93
|
+
options = args.extract_options!
|
94
|
+
rules = options[:with] || Normalizy.config.default_filters
|
95
|
+
|
96
|
+
self.normalizy_rules ||= {}
|
97
|
+
|
98
|
+
args.each do |field|
|
99
|
+
normalizy_rules[field] ||= []
|
100
|
+
normalizy_rules[field] << { block: block, options: options.except(:with), rules: rules }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Normalizy
|
4
|
+
module Filters
|
5
|
+
module Number
|
6
|
+
def self.call(input)
|
7
|
+
return input unless input.is_a?(String)
|
8
|
+
|
9
|
+
value = input.gsub(/\D/, '')
|
10
|
+
|
11
|
+
return nil if value.blank?
|
12
|
+
|
13
|
+
value.to_i
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Normalizy
|
4
|
+
module Filters
|
5
|
+
module Strip
|
6
|
+
def self.call(input, options = {})
|
7
|
+
return input unless input.is_a?(String)
|
8
|
+
|
9
|
+
regex = {
|
10
|
+
both: '\A\s*|\s*\z',
|
11
|
+
left: '\A\s*',
|
12
|
+
right: '\s*\z'
|
13
|
+
}[options[:side] || :both]
|
14
|
+
|
15
|
+
input.gsub Regexp.new(/#{regex}/), ''
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Normalizy
|
2
|
+
module RSpec
|
3
|
+
def normalizy(attribute)
|
4
|
+
Matcher.new attribute
|
5
|
+
end
|
6
|
+
|
7
|
+
class Matcher
|
8
|
+
def initialize(attribute)
|
9
|
+
@attribute = attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
def description
|
13
|
+
return "normalizy #{@attribute} with #{with_expected}" if @with.present?
|
14
|
+
|
15
|
+
"normalizy #{@attribute} from #{from_value} to #{to_value}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def failure_message
|
19
|
+
return "expected: #{with_expected}\n got: #{actual_value}" if @with.present?
|
20
|
+
|
21
|
+
"expected: #{to_value}\n got: #{actual_value}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def failure_message_when_negated
|
25
|
+
return "expected: value != #{with_expected}\n got: #{actual_value}" if @with.present?
|
26
|
+
|
27
|
+
"expected: value != #{to_value}\n got: #{actual_value}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def from(value)
|
31
|
+
@from = value
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches?(subject)
|
37
|
+
@subject = subject
|
38
|
+
|
39
|
+
if @with.present?
|
40
|
+
options = @subject.class.normalizy_rules[@attribute]
|
41
|
+
|
42
|
+
return false if options.blank?
|
43
|
+
|
44
|
+
options.each do |option|
|
45
|
+
rules = option[:rules]
|
46
|
+
|
47
|
+
return true if rules.is_a?(Array) && rules.include?(@with)
|
48
|
+
return true if rules == @with
|
49
|
+
end
|
50
|
+
|
51
|
+
false
|
52
|
+
else
|
53
|
+
@subject.send "#{@attribute}=", @from
|
54
|
+
|
55
|
+
@subject.send(@attribute) == @to
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def to(value)
|
60
|
+
@to = value
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def with(value)
|
66
|
+
@with = value
|
67
|
+
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def actual_value
|
74
|
+
return with_value if @with
|
75
|
+
|
76
|
+
value = @subject.send(@attribute)
|
77
|
+
|
78
|
+
value.is_a?(String) ? %("#{value}") : value
|
79
|
+
end
|
80
|
+
|
81
|
+
def from_value
|
82
|
+
@from.nil? ? :nil : %("#{@from}")
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_value
|
86
|
+
@to.nil? ? :nil : %("#{@to}")
|
87
|
+
end
|
88
|
+
|
89
|
+
def with_expected
|
90
|
+
@with
|
91
|
+
end
|
92
|
+
|
93
|
+
def with_value
|
94
|
+
options = @subject.class.normalizy_rules[@attribute]
|
95
|
+
|
96
|
+
return :nil if options.nil?
|
97
|
+
return %("#{options}") if options.blank?
|
98
|
+
|
99
|
+
result = options.map do |option|
|
100
|
+
rules = option[:rules]
|
101
|
+
|
102
|
+
if rules.nil?
|
103
|
+
:nil
|
104
|
+
elsif rules.blank?
|
105
|
+
%("#{rules}")
|
106
|
+
else
|
107
|
+
rules
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
result.join ', '
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|