# Normalizy [![Build Status](https://travis-ci.org/wbotelhos/normalizy.svg)](https://travis-ci.org/wbotelhos/normalizy) [![Gem Version](https://badge.fury.io/rb/normalizy.svg)](https://badge.fury.io/rb/normalizy) Attribute normalizer for ActiveRecord. ## Description 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. ## install Add the following code on your `Gemfile` and run `bundle install`: ```ruby gem 'normalizy' ``` So generates an initializer for future custom configurations: ```ruby rails g normalizy:install ``` It will generates a file `config/initializers/normalizy.rb` where you can configure you own normalizer and choose some defaults one. ## Usage On your model, just add `normalizy` callback with the attribute you want to normalize and the filter to be used: ```ruby class User < ApplicationRecord normalizy :name, with: :strip end ``` Now some email like ` myemail@example.org ` will be saved as `email@example.com`. ## Filters We have a couple of built-in filters. The most one is just a wrapper o the original String methods: ### Number ```ruby normalizy :age, with: :number ' 32' # 32 ``` By default, `number` works with input value before [Type Cast](#type-cast) ### Strip Options: - `side`: `:left`, `:right` or `:both`. Default: `:both` ```ruby normalizy :name, with: :strip ' Washington Botelho ' # 'Washington Botelho' ``` ```ruby normalizy :name, with: { strip: { side: :left } } ' Washington Botelho ' # 'Washington Botelho ' ``` ```ruby normalizy :name, with: { strip: { side: :right } } ' Washington Botelho ' # ' Washington Botelho' ``` ```ruby normalizy :name, with: { strip: { side: :both } } ' Washington Botelho ' # 'Washington Botelho' ``` As you can see, the rules can be passed as Symbol/String or as Hash if it has options. ## Multiple Filters You can normalize with a couple of filters at once: ```ruby normalizy :name, with: %i[squish titleize] ' washington botelho ' # 'Washington Botelho' ``` ## Multiple Attributes You can normalize more than one attribute at once too, with one or muiltiple filters: ```ruby normalizy :email, :username, with: :downcase ``` Of course you can declare muiltiples attribute and multiple filters together. It is possible to make sequential normalizy calls: ```ruby normalizy :email, :username, with: :squish normalizy :email , with: :downcase ``` In this case, each line will be evaluated from the top to the bottom. ## Default Filters You can configure some default filters to be runned. Edit you initializer at `config/initializers/normalizy.rb`: ```ruby Normalizy.configure do |config| config.default_filters = [:squish] end ``` Now, all normalization will include squish, even when no rule is declared. ```ruby normalizy :name ' Washington Botelho ' # 'Washington Botelho' ``` If you declare some filter, the default filter `squish` will be runned together: ```ruby normalizy :name, with: :downcase ' washington botelho ' # 'Washington Botelho' ``` ## Custom Filter You can create a custom filter that implements `call` method with an `input` as argument and an optional `options`: ```ruby module Normalizy module Filters module Blacklist def self.call(input) input.gsub 'Fuck', replacement: '***' end end end end ``` ```ruby Normalizy.configure do |config| config.add :blacklist, Normalizy::Filters::Blacklist end ``` Now you can use your custom filter: ```ruby normalizy :name, with: :blacklist 'Washington Fuck Botelho' # 'Washington *** Botelho' ``` If you want to pass options to your filter, just call it as hash and the value will be passed to the custom filter: ```ruby module Normalizy module Filters module Blacklist def self.call(input, options: {}) input.gsub 'Fuck', replacement: options[:replacement] end end end end ``` ```ruby normalizy :name, with: blacklist: { replacement: '---' } 'Washington Fuck Botelho' # 'Washington --- Botelho' ``` You can pass a block and it will be received on filter: ```ruby module Normalizy module Filters module Blacklist def self.call(input, options: {}) value = input.gsub('Fuck', 'filtered') value = yield(value) if block_given? value end end end end ``` ```ruby normalizy :name, with: :blacklist, &->(value) { value.sub('filtered', '(filtered 2x)') } 'Washington Fuck Botelho' # 'Washington (filtered 2x) Botelho' ``` The block ## Method Filters 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: ```ruby normalizy :birthday, with: :parse_date def parse_date(input, options = {}) Time.zone.parse(input).strftime '%Y/%m/%d' end '1984-10-23' # '1984/10/23' ``` If you gives an option, it will be passed to the function too: ```ruby normalizy :birthday, with: { parse_date: { format: '%Y/%m/%d' } def parse_date(input, options = {}) Time.zone.parse(input).strftime options[:format] end '1984-10-23' # '1984/10/23' ``` Block methods works here too. ## Native Filter After the missing built-in and class method, the fallback will be the value of native methods. ```ruby normalizy :name, with: :reverse 'Washington Botelho' # "ohletoB notgnihsaW" ``` ## Inline Filter Maybe you want to declare an inline filter, in this case, just use a Lambda or Proc: ```ruby normalizy :age, with: ->(input) { input.abs } -32 # 32 ``` You can use it on filters declaration too: ```ruby Normalizy.configure do |config| config.add :age, ->(input) { input.abs } end ``` ## Type Cast An input field with `$ 42.00` dollars when sent to model with a field with `integer` type, 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. To receive the value before type cast, just pass a `raw` options as `true`: ```ruby normalizy :amount, with: :number, raw: true '$ 42.00' # 4200 ``` To avoid repeat the `raw: true` where you will always to use, you can register a filter with this options: ```ruby Normalizy.configure do |config| config.add :money, ->(input) { input.gsub(/\D/, '') }, raw: true end ``` ## Alias Sometimes you want to give a better name to your filter, just to keep the things semantic. But duplicates the code just to redefine a new name is not a good idea, so, just create an alias: ```ruby Normalizy.configure do |config| config.alias :money, :number end ``` Now, `money` will delegate to `number` filter. Since we already know the need of `raw` options, we can declare it here too: ```ruby Normalizy.configure do |config| config.alias :money, :number, raw: true end ``` But `number` filter already works with `raw: true`, don't need to tell it again. An our previous example, about `amount`, was refactored to: ```ruby normalizy :amount, with: :money '$ 42.00' # 4200 ``` If you need to alias multiple filters, just provide an array of them: ```ruby Normalizy.configure do |config| config.alias :username, %i[squish downcase] end ``` ## Love it! Via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=X8HEP2878NDEG&item_name=normalizy) or [Gratipay](https://gratipay.com/~wbotelhos). Thanks! (: