Rails Serializers and INET_NTOA
MySQL doesn’t have a built in type for an IP Address, PostgreSQL does though. You’ll find that ip addresses are often stored as an integer. You can translate between an integer and ip address and vice versa with a built in MySQL functions. In a recent piece of work we had to detect a user’s country code based on their incoming IP via against a range of IPs (stored as integers).
mysql> SELECT INET_ATON('192.168.0.1');
+--------------------------+
| INET_ATON('192.168.0.1') |
+--------------------------+
| 3232235521 |
+--------------------------+
1 row in set (0.00 sec)
mysql> SELECT INET_NTOA('3232235521');
+-------------------------+
| INET_NTOA('3232235521') |
+-------------------------+
| 192.168.0.1 |
+-------------------------+
1 row in set (0.00 sec)
Wouldn’t it be nice to get a Rails model to accept an ip address and store it as an integer. Well its basically serializing the ip address and using Rails 3.1’s new serialization api we can do the following.
class IpEncoder
#
# Converts IP to number
# inet_aton
#
def load(n)
return unless n
[n].pack("N").unpack("C*").join "."
end
#
# Converts number to IP
# inet_ntoa
#
def dump(n)
n.split(/\./).map(&:to_i).pack("C*").unpack("N").first
end
end
Basicially a class with two methods IpEncoder#load encodes its input, and IpEncoder#dump decodes it. Then you simply add the following to your model.
require 'ip_encoder'
class Log < ActiveRecord::Base
serialize :ip_address, IpEncoder.new
end
And there you have it.
Rob-Laceys-MacBook-Pro:loathsome roblacey$ ./script/rails c
Loading development environment (Rails 3.1.2)
>> Log.create(:ip_address => '192.168.0.1')
SQL (0.5ms) INSERT INTO "logs" ("ip_address") VALUES (?) [["ip_address", 3232235521]]
=> #<Log id: 1, ip_address: "192.168.0.1">