Marcio Trindade

Problemas com campos inet no Rails

A partir do Rails 4 podemos utilizar campos específicos do PostgreSql com está descrito no Rails Guide. Um destes campos que resolvi utilizam ontem foi o campo inet que representa um host ou uma rede com IPv4 ou IPv6. Sendo assim podemos armazenar registros de range de IPs como por exemplo 192.168.0.0/24.

Como podemos ver no Pull Request #6196 o Rails converte este tipo de campo no objeto IPAddr do ruby então vamos entender como este objeto funciona. Vamos começar pelo range de IPs 192.168.0.0/32 que podemos entender que o 192.168.0.0 representa o IP e o /30 representa o prefixo. Quando instanciamos um objeto IPAddr com este range de IP o mesmo deixa de utilizar o prefixo e passa a utilizar uma representação com máscara 255.255.255.252, veja o exemplo abaixo.

ruby
IPAddr.new('192.168.0.0/30')
# => #<IPAddr: IPv4:192.168.0.0/255.255.255.252>

Depois que instanciava o Objeto não consegui mais representar o mesmo através de ip/prefixo, assim quando criei um formulário eu podia entrar com o valor de um range, mas ao editar o mesmo ele me trazia somente o IP e ignorava o prefixo. Olhando o código do Rails vi que como é feito a representação ip/prefixo que é salva no banco e é como eu precisaria mostrar no formulário de novo, então passei a utilizar o mesmo código na minha aplicação.

Após alguns testes observei um outro problema, quando eu altero somente o prefixo de um range o Rails não salva esta alteração no banco e entendi que o problema está no jeito como o IPAddr trata a comparação entre os objetos. Para este tipo de objeto a comparação é feita somente com o IP e ignorado o prefixo como podemos ver no código abaixo.

ruby
IPAddr.new('192.168.0.0/24') == IPAddr.new('192.168.0.0/30')
# => true

Tendo isso em mente e entendendo o Dirty Models do Rails ficou fácil perceber por que quando altero somente o prefixo o novo valor não é salvo no banco de dados. Então pra solucionar este segundo problema utilizo o método attribute_will_change! (no meu caso address_will_change!) para informar o Rails que precisa salvar este no banco sempre.