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:

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.