Bug in Rails 2.0 when overloading a find_by method?

I've got an interesting replecatable bug that has sprung up since I migrated some code i've written from rails 1.2.3 to 2.0.2. I am using a mysql backend database, in which I am storing IPv4 addresses as 32 bit unsigned integers. Of course, I don't want my users to have to enter the IP they are searching for in this fashion, nor do I want to have to make the conversion myself every time I call the find_by_ip function or find_or_create_by_ip function for the model, so I have overloaded those two functions. find_by_ip now reads as follows:

def self.find_by_ip(ip)   super(NetAddr::CIDR.create(ip).to_i) end

This works, the first time IP.find_by_ip(address) is called (this test done in script/console):

ip = Ip.find_by_ip("10.21.1.8")

=> #<Ip id: 13, ip: 169148680>

However any subsequent calls to find_by_ip just return nil, even for the same IP address, until the environment is reloaded:

reload!

Reloading... => true

ip = Ip.find_by_ip("10.21.1.8")

=> #<Ip id: 13, ip: 169148680>

ip = Ip.find_by_ip("10.21.1.8")

=> nil

If I add some puts statements in my overloaded find_by_ip, they never get printed out after the first call to it has been done. Equally, if I call find_by_ip with a 32 bit int form of an IPv4 address it works reliably:

def self.find_by_ip(ip)   puts "Testing\n"   super(NetAddr::CIDR.create(ip).to_i) end

?> reload! Reloading... => true

ip = Ip.find_by_ip("10.21.1.8")

Testing => #<Ip id: 13, ip: 169148680>

ip = Ip.find_by_ip("10.21.1.8")

=> nil

ip = Ip.find_by_ip(169148680)

=> #<Ip id: 13, ip: 169148680>

It is as if, after the first call to my overloaded find_by_ip, rails decides to ignore my overloaded function and go straight to the base functionality it has for creating find_by functions. Can anyone else test this to prove it's not just me, and/or suggest who/where I should report it as a bug?

Thanks

Dan Meyers Network Support, Lancaster University

I've got an interesting replecatable bug that has sprung up since I migrated some code i've written from rails 1.2.3 to 2.0.2. I am using a mysql backend database, in which I am storing IPv4 addresses as 32 bit unsigned integers. Of course, I don't want my users to have to enter the IP they are searching for in this fashion, nor do I want to have to make the conversion myself every time I call the find_by_ip function or find_or_create_by_ip function for the model, so I have overloaded those two functions. find_by_ip now reads as follows:

def self.find_by_ip(ip) super(NetAddr::CIDR.create(ip).to_i) end

I expect what happens is as follows: you call find_by_ip and go through to your method. You call super and
hit method_missing. In rails 1.2 this just called find for you with
the right arguments. In rails 2.x, rails actually creates a find_by_ip
method (so overwriting your one) and calls that. On your next call to find_by_ip you go straight to the rails generate
one, which isn't coercing the string to the appropriate ip. You could create a ticket on dev.rubyonrails.org, but i'd first bring
up the issues on rubyonrails-core. I'm not sure what the best way of
resolving this would be.

Fred

def self.find_by_ip(ip) super(NetAddr::CIDR.create(ip).to_i) end

[...]

It is as if, after the first call to my overloaded find_by_ip, rails decides to ignore my overloaded function and go straight to the base functionality it has for creating find_by functions. Can anyone else test this to prove it's not just me, and/or suggest who/where I should report it as a bug?

- You've defined find_by_ip in your AR::B subclass to overload a dynamic finder. And in your method, you call super.

- In Rails < 2.* (like 1.2.* series), dynamic finders are handled by a method_missing mechanism.

So in 1.2.*, that's how things work :

Ip.find_by_ip("10.21.1.8") calls your defined find_by_ip method. With super, Ruby tries to see if there's a find_by_ip in parent classes. There is not, so method_missing is called, that makes your AR::B.find on the fly and retrieves for you the right results.

- In Rails 2.0.*, dynamic finders are handled by a method missing mechanism at the first call, a finder method is generated at the first call, this fresh method handles your query.

In the doc "Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future attempts to use it do not run through method_missing."

So in 2.0.* your stuff works like that :

Ip.find_by_ip("10.21.1.8") calls your defined class method in Ip class. your method invokes super : Ruby looks for a find_by_ip method in parent classes. There aren't. method_missing is called. First time called, a find_by_ip is generated, overiding your defined method. Rails call the new method, retrieving the right results. At the second call, Rails use the new method that overrides yours, but can't deal string like "10.21.1.8", so your query returns nil. If you pass an integer as argument it works, using the one-the-fly generated method, not yours.

3/ In a nutshell, in your defined find_by_ip, call AR::B.find directly (or another dynamic finder !), don't use super, to bypass all this method_missing mechanism.

HTH,

    -- Jean-François.