What happens when we use `super` trying to override the attribute method in AR subclass

Hi, here is the example of the wrong usage of the `super` keyword
trying to override the attribute method.

class User < ActiveRecord::Base
  def name
    super.to_s.capitalize
  end
end

The result of this code looks 'pseudo'-correct and some people assume
it's fine to use that like if it were self[:name].to_s.capitalize
If we take a look at the code below - the results are different:

class User < ActiveRecord::Base
  def name
    'hi ' + super.to_s.capitalize
  end
end

u = User.first
p u.name # => 'hi Hi bob'
p u.name # => 'hi Bob'
p u.name # => 'hi Bob'

My question is why the first call to u.name produces that result.
I'm looking at
if self.class.generated_methods.include?(method_name)
  return self.send(method_id, *args, &block)
end

in
module ActiveRecord
  module AttributeMethods
    def method_missing

but need some help to figure the things out.
Simply want to know what is actually happening...

The first time you call name, your super call actually calls
method_missing, since the name method doesn't exist higher in the
method chain. method_missing doesn't think the attribute methods have
been defined yet, so it calls a method to define them in an anonymous
module included in the class, then calls the method again. Basically,
the first time you call it:

User#name -> ActiveRecord::AttributeMethods#method_missing ->
User#name -> generated_methods_module#name

Future calls don't hit method_missing because
generated_methods_module#name already exists:

User#name -> generated_methods_module#name

Your example works fine in Sequel, BTW, because Sequel sets up the
attribute methods in an anonymous module included in the class at
class definition time, instead of when the attribute method is first
called. I wouldn't be surprised if the ActiveRecord way of defining
attribute methods is subject to a race condition in threaded code, in
addition to the problem you are experiencing.

Jeremy

Hi Jeremy,
Yes, the Sequel is doing a good job for that edge case.

So in AR `super` makes method_missing to define the `ghost` attribute
method `name` in order to reduce the method lookup next time.
After it gets defined it also calls that method - at that time it
should only return "bob". After it gets called in method_missing,
shouldn't it just return back into our User#name scope?
Since the second time we call User#name we get the expected result, we
can assume that method_missing defined the `ghost` attribute
correctly.
Right?
But it is still not very clear to me why the 'Hi' gets capitalized. I
would expect 'bob' to be repeated twice if we have the case of doubled
method call. # Smth. like this: 'hi Bob bob'

BTW, I found this investigation interesting and I'd like to summarize
it into a small article (I've started it here: https://gist.github.com/930514).
Would you like to participate?

So in AR `super` makes method_missing to define the `ghost` attribute
method `name` in order to reduce the method lookup next time.
After it gets defined it also calls that method - at that time it
should only return "bob". After it gets called in method_missing,
shouldn't it just return back into our User#name scope?

Nope, you can't do that in ruby. The ActiveRecord code is the closest
you can come, restarting the lookup process.

Since the second time we call User#name we get the expected result, we
can assume that method_missing defined the `ghost` attribute
correctly.
Right?

method_missing does define the method correctly, but it doesn't check
that the method was already defined before it defined attribute
methods. If it did that, it could determine whether the method was
actually missing or whether it was called via super.

Another way to fix this (besides what Sequel does) would be to perform
the action the attribute method would have performed had it been
defined, without using send to restart the method lookup process.
That would entail copying the logic for the attribute methods into
method_missing (or something called by method_missing).

But it is still not very clear to me why the 'Hi' gets capitalized. I
would expect 'bob' to be repeated twice if we have the case of doubled
method call. # Smth. like this: 'hi Bob bob'

I would expect: hi Hi Bob

Work backwards through the method chain:

generated_methods_module#name => bob
User#name -> hi Bob
ActiveRecord::AttributeMethods#method_missing -> hi Bob
User#name -> hi Hi Bob

If you actually got "hi Hi bob", I'm not sure why.

Jeremy

method_missing does define the method correctly, but it doesn't check
that the method was already defined before it defined attribute
methods. If it did that, it could determine whether the method was
actually missing or whether it was called via super.

Another way to fix this (besides what Sequel does) would be to perform
the action the attribute method would have performed had it been
defined, without using send to restart the method lookup process.
That would entail copying the logic for the attribute methods into
method_missing (or something called by method_missing).

Ahh, I see...
Now I'm wondering, if it's fair enough to say that it is a mistake to
overload an attribute this way?:

  def name; 'hi ' + super.to_s.capitalize; end

Technically we don't even overload anything, since the `name`
attribute will come into existence only after we bump into
method_missing.
And at first I thought it is a semantic misuse of the `super` keyword
at least since we cannot expect Sequel::Model or AR::Base to know
about the attributes we are going to use.

Lets say, when I use `super` in that context, I have an intent to
delegate the `name` message from the current object into it's parent's
class wherever it is Sequel::Model or AR::Base, and the method look up
chain reminds me just the regular call of Model#attribute_name, which
makes me think that maybe there is no such a big technical mistake to
use `super` there...