title: Mongoid : IPv6 addresses storage
body: <p>I need to store IPv6 addresses in a Mongodb database using Rails3 + Mongoid.</p>
  
  <p>There will also (mostly) be IPv4 addresses in the collection.</p>
  
  <p>I need addresses to be stored as decimals since I have to query for addresses that belongs to a network (I'll store networks and addresses in distinct collections).</p>
  
  <p>I've used BigDecimals to store those addresses (since IPv6 addresses are 128bit long) but when I try to find which addresses belongs to a network (concretely: between network and broadcast addresses), I don't find any working solution.</p>
  
  <p>Mongoid "gte" and "lte" only seems to work on integers (BigDecimals are actually strings) and returns an empty list, and I don't find a way to query my mongoid model for a string range.</p>
  
  <p>MongoDB seems to allow this (<a href="http://www.mongodb.org/display/DOCS/min+and+max+Query+Specifiers" rel="nofollow">http://www.mongodb.org/display/DOCS/min+and+max+Query+Specifiers</a>) but I don't find the corresponding syntax in mongoid doc.</p>
  
  <p>Queries like the following raises awful "db assertion failure" :</p>
  
  <pre><code>Network.min(address: ip.network.to_i.to_s).max(address: ip.broadcast.to_i.to_s)
  </code></pre>
  
  <p>"ip.to_i.to_s" provides a string representation of the decimal address since I'm using IPAddress gem.</p>
  
  <p>Same error with just 'to_i' or "BigDecimal.new(ip.network.to_i)"</p>
  
  <p>The other solution is to store v6 addresses in 2 64bit integers but it's far more complex for range queries and I'd like to use the same behavior for v6 and v4 addresses.</p>
  
  <p>Does anyone have experience on a clean way to deal with IPv6 addresses queries in a database ?</p>
  
  <p>Here is my current Network model :</p>
  
  <pre class="lang-rb prettyprint-override"><code>class Network
  
    # INCLUSIONS
  
      include Mongoid::Document
      include Mongoid::Timestamps
  
    # RELATIONS
  
      belongs_to :vlan
  
    # FIELDS
  
      field :description
      field :address, type: BigDecimal
      field :prefix,  type: Integer
      field :v6,      type: Boolean
      field :routed,  type: Boolean
  
    # VALIDATIONS
  
      validates :ip,
        presence: true
  
      # Address must be a valid IP address
      validate :ip do
        errors.add(:ip, :invalid) unless ip? &amp;&amp; ip == ip.network
      end
  
    # INSTANCE METHODS
  
      # Returns string representation of the address
      def address
        ip.to_s if ip
      end
  
      def address= value
        raise NoMethodError, 'address can not be set directly'
      end
  
      # Provides the IPAddress object
      def ip
        unless @ip.is_a?(IPAddress) || self[:address].blank?
          # Generate IP address
          if self[:v6]
            @ip = IPAddress::IPv6.parse_u128 self[:address].to_i
          else
            @ip = IPAddress::IPv4.parse_u32 self[:address].to_i
          end
          # Set IP prefix
          @ip.prefix = self[:prefix] if self[:prefix]
        end
        @ip
      end
  
      # Sets network IP
      def ip= value
        value          = value.to_s
        @ip            = value
        self[:address] = nil
        self[:prefix]  = nil
        self[:v6]      = nil
        begin
          @ip            = IPAddress value
          self[:address] = @ip.to_i
          self[:prefix]  = @ip.prefix
          self[:v6]      = @ip.ipv6?
        rescue
        end
      end
  
      # Whether IP is a IPAddress object
      def ip?
        ip.is_a? IPAddress
      end
  
      # Provides network prefix
      def prefix
        return ip.prefix if ip?
        self[:prefix]
      end
  
      def prefix= value
        raise NoMethodError, 'prefix can not be set directly'
      end
  
      # Provides string representation of the network
      def to_s
        ip? ? ip.to_string : @ip.to_s
      end
  
      def subnets
        networks = Network.min(address: ip.network.to_i.to_s).max(address: ip.broadcast.to_i.to_s)
        return networks
      end
  
  end
  </code></pre>
  
  <p>Subnets method is the one I'm working on, to detect networks nested into the current one.</p>
  
  <p>Note that I'd like to avoid "strong" db relations between networks/subnets and upcoming hosts addresses to keep them dynamic.</p>
  
  <p><strong>Update:</strong></p>
  
  <p>Here's my final class that's working fine to manage nested IP networks.</p>
  
  <p>Addresses are stored as fixed length strings in hexadecimal. They can be stored in base 32 to match real addresses size but hexa is better for readability.</p>
  
  <p>Subnets method provides a list of all subnets of the current network.</p>
  
  <pre class="lang-rb prettyprint-override"><code>class Network
  
    # INCLUSIONS
  
      include Mongoid::Document
      include Mongoid::Timestamps
  
    # RELATIONS
  
      belongs_to :vlan
  
    # FIELDS
  
      field :description
      field :address, type: String
      field :prefix,  type: Integer
      field :routed,  type: Boolean
      field :v6,      type: Boolean
  
    # VALIDATIONS
  
      validates :ip,
        presence: true
  
      # Address must be a valid IP address
      validate do
        errors.add(:ip, :invalid) unless ip? &amp;&amp; ip == ip.network
      end
  
      validate do
          errors.add(:ip, :prefix_invalid_v6) if ip &amp;&amp; ip.ipv6? &amp;&amp; (self[:prefix] &lt; 0 || self[:prefix] &gt; 64)
      end
  
    # INSTANCE METHODS
  
      # Returns string representation of the address
      def address
        ip.to_s if ip
      end
  
      def address= value
        raise NoMethodError, 'address can not be set directly'
      end
  
      # Provides the IPAddress object
      def ip
        unless @ip.is_a?(IPAddress) || self[:address].blank?
          # Generate IP address
          if v6
            @ip = IPAddress::IPv6.parse_u128 self[:address].to_i(16)
          else
            @ip = IPAddress::IPv4.parse_u32 self[:address].to_i(16)
          end
          # Set IP prefix
          @ip.prefix = self[:prefix] if self[:prefix]
        end
        @ip
      end
  
      # Sets network IP
      def ip= value
        value          = value.to_s
        @ip            = value
        self[:address] = nil
        self[:prefix]  = nil
        self[:v6]      = nil
        begin
          @ip            = IPAddress value
          self[:address] = @ip.to_i.to_s(16).rjust((@ip.ipv4? ? 8 : 32), '0')
          self[:prefix]  = @ip.prefix
          self[:v6]      = @ip.ipv6?
        rescue
        end
      end
  
      # Whether IP is a IPAddress object
      def ip?
        ip.is_a? IPAddress
      end
  
      # Provides network prefix
      def prefix
        return ip.prefix if ip?
        self[:prefix]
      end
  
      def prefix= value
        raise NoMethodError, 'prefix can not be set directly'
      end
  
      # Provides string representation of the network
      def to_s
        ip? ? ip.to_string : @ip.to_s
      end
  
      # Provides nested subnets list
      def subnets
        length= ip.ipv4? ? 8 : 32
        networks = Network.where(
          v6: v6,
          :address.gte =&gt; (ip.network.to_i.to_s(16)).rjust(length, '0'),
          :address.lte =&gt; (ip.broadcast.to_i.to_s(16).rjust(length, '0')),
          :prefix.gte  =&gt; ip.prefix
        ).asc(:address).asc(:prefix)
      end
  
  end 
  </code></pre>