“...I've been working since 2008 with Ruby / Ruby on Rails, love a bit of Elixir / Phoenix and learning Rust. I also poke through other people's code and make PRs for OpenSource Ruby projects that sometimes make it. Currently working for InPay...”

Rob Lacey (contact@robl.me)
Senior Software Engineer, Brighton, UK

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">