Normatron
Normatron is a Ruby On Rails plugin that perform attribute normalizations for ActiveRecord objects. With it you can normalize attributes to the desired format before saving them in the database. This gem inhibits the work of having to override attributes or create a specific method to perform most of the normalizations.
Installation
Let the bundler install the gem by adding the following into your application gemfile:
gem 'normatron'
And then bundle it up:
$ bundle install
Or install it by yourself:
$ gem install normatron
Then run the generator:
$ rails generator normatron:install
The problem
Suppose you have a product model as the following:
# ./db/migrate/20120101010000_create_products.rb
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.decimal :price, :precision => 10, :scale => 2
end
end
end
# ./app/models/products.rb
class Product < ActiveRecord::Base
attr_accessible :name, :price
end
And we want the name attribute be uppercased before saving it into the database. The most usual approach to do this includes:
- Override the name setter and convert the value to an uppercased string.
- Write a method or block and bind it to the before_validation callback.
Both ways are ilenegant, boring, error prone and very expensive. What led me to make this gem and offer a third way to solve this issue:
Usage
Normatron uses :squish and :blank as default filters. These filters are applied to all attributes through normalize function, since no options is given.
# ./app/models/products.rb
class Product < ActiveRecord::Base
attr_accessible :name, :price
normalize :name
end
$ rails console
> memory = Product.create name: " memory card "
=> #<Product id: nil, name: "memory card", price: nil>
> null = Product.create name: " "
=> #<Product id: nil, name: nil, price: nil>
Configurations
normatron:install generator creates a configuration file located at ./config/initializers/normatron.rb
.
For now, you can set the default normalization filters or disable normatron by commenting the add_orm
line.
Applying normalizations
Methods like create, valid? or save always call the apply_normalizations method, thought before_validation callback. This is the method that invokes all filters to their attributes. So you can perform the normalizations without necessarily having to perform the model validations.
$ rails console
> product = Product.new name: " hard drive"
=> #<Product id: nil, name: " hard drive", price: nil>
> product.apply_normalizations
> product
=> #<Product id: nil, name: "hard drive", price: nil>
Setting normalization rules
The :with
option allows to bind filters to one or more attribute.
class Product < ActiveRecord::Base
normalize :name, :with => :upcase
end
$ rails console
> Product.normalize_filters
=> { :name => { :upcase => [] } }
The filters passed throught :with
option will not stack with default filters.
When normalize method is used multiple times for the same attribute, it will stack the filter bindings.
class Product < ActiveRecord::Base
normalize :name
normalize :name, :with => :upcase
end
$ rails console
> Product.normalize_filters
=> { :name => { :squish => [],
:blank => [],
:upcase => [] } }
The same result can be obtained using:
normalize :name, :with => [:squish, :blank, :upcase]
Some filters may use arguments to perferm normalizations. There are two approaches to deal with filter arguments in Normatron:
a) Using a Hash where the key is the filter name and the value is an arguments Array.
class Product < ActiveRecord::Base
normalize :name, :with => [ { :keep => [:Latin], :remove => [:Nd, :Zs] } ]
normalize :description, :with => :squeeze
normalize :brand, :with => { :squeeze => ["a-z"] }
end
b) Using an Array where the first element if the attribute name and rest is the filter arguments.
class Product < ActiveRecord::Base
normalize :name, :with => [ [:keep, :Latin], [:remove, :Nd, :Zs] ]
normalize :description, :with => :squeeze
normalize :brand, :with => [ [:squeeze, "a-z"] ]
end
Both ways will produce the same result:
$ rails console
> Product.normalize_filters
=> { :name => { :keep => [:Latin],
:remove => [:Nd, :Zs] },
:description => { :squeeze => [] },
:brand => { :squeeze => ["a-z"] } }
Using instance method as filter
Create an instance method returning the value as you want. The first argument is mandatory, and will receive the original value of the attribute. If you need to use aditional arguments or varargs, just add them after the first argument.
# ./app/models/client.rb
class Client < ActiveRecord::Base
normalize :phone, :with => [:custom_a, [:custom_b, :a, :b], {:custom_c => [:a, :b, :c]}]
normalize :mobile, :with => [:custom_a, {:custom_b => [:a, :b]}, [:custom_c, :a, :b, :c]]
def custom_a(value)
# ...
end
def custom_b(value, *args)
# ...
end
def custom_c(value, arg_a, arg_b, arg_c)
# ...
end
end
Filters
Information about native filters and how to use them can be found in Normatron::Filters.
Contributing
There are several ways to make this gem even better:
- Forking this project
- Adding new features or bug fixes
- Making tests
- Commiting your changes
- Reporting any bug or unexpected behavior
- Suggesting any improvement
- Sharing with your friends, forums, communities, job, etc…
- Helping users with difficulty using this gem
- Paying me a beer =]
Credits
This gem was initially inspired on:
- normalize_attributes – I liked the cleaner code and simplicity of this gem.
- attribute_normalizer – Very powerful.
The idea is to mix the good things of both gems, adding some features and changing something to fit my taste.
License
See file attached to source code or click here.