I just encountered something that is not what I would expect. I
overrode an accessor method in my model class and within this method I
did a “super” call which failed. Of couse my real method would be doing
more but here is the basic code that shows the problem:
class Word < ActiveRecord::Base
belongs_to :verse
belongs_to :grammar_tense_code
def grammar_tense_code
obj = super
end
end
In my view I call:
<%= word.grammar_tense_code.descr %>
The error message I get is: “super: no superclass method
`grammar_tense_code’”.
I realize there is no real ‘grammar_tense_code’ method and that rails
handles this for me. BUT, I would have expected the message to be
passed up the chain and handled by Rails active record code which would
read the database. This is typical oo behavior. Does anybody know what
is happening here and how to work around it?
(Incidentally, if I remove this method everything works fine. Rails
retrieves the object from the database and gives me it’s “descr”
attribute.)
Hello Paul,
Urm, I think you may have a slight misunderstanding here of Rails,
ORM’s and possibly Database’s ;> (joking). If you want a ‘Word’ to be a
child of ‘grammar_tense_code’ then you should do
class Word < grammar_tense_code (yes yes, classes should begin with
upper case, so sue me
As it is, the 'super' class of Word is ActiveRecord::Base. The only
thing that ‘grammar_tense_code’ does is to denote a foreign key or
‘relationship’. There is no implication that grammar_tense_code is in
the dispatch chain at all. I could understand that you think belongs_to
act’s as a ‘mixin’, but, its [belongs_to] there almost there to help
active record find the relationships at the database level. There is no
‘importing’ or the objects method into this object. I honestly can’t
think of a single ORM (WebWare/SeaSide/etc) that does this. Dare I ask,
is this a typical ‘java’-ism ? (NO OFFENSE meant here, jst wondering if
this comes from a different technology that I don’t use
Active Record uses method_missing to dynamically read and write
attributes.
Use read_attribute(‘grammar_tense_code’) within your method rather than
super.
I think you are correct. However, read_attribute(‘grammar_tense_code’)
just returns to me the value of that attribute rather than triggering
the rails code that would read it from the database. So it just returns
nil in my case. Do you happen to know if there is a way to trigger the
database read that sets the attribute to begin with?
I think my example probably obscured my real question. The Word
database table has a “grammar_tense_code_id” column that is a key to
the grammar_tense_codes lookup table which has “id” and “descr”
columns.
However, take the following line of Ruby code:
descr = word.grammar_tense_code.descr.
Assuming word is an instance of Word, the grammar_tense_code message is
sent to the word object and the descr message is sent to the object
from that. This incidentally works just fine for me. That is, rails
reads the database row from the grammar_tense_codes table and returns
to me the “descr”.
However, being a rails app, my Word class does not define a
grammar_tense_code method and so when the grammer_tense_code message
gets sent, Ruby passes it up the superclass chain which eventually
generates a method_missing exception. Note that this is all Ruby up to
this point, no rails stuff involved. But the method_missing exception
must trigger a rails handler that then kicks in and retrieves the
database row.
Now for the real question: How is that process different than if I code
my own grammar_tense_code method and then do a “super”? The method is
still missing in the super class and should still trigger a
method_missing exception.
UNLESS, these are two different exceptions and rails has not taken into
account the exception generated by the missing super class method. This
might be the whole issue.
Sorry for the rambling but I come from a Smalltalk background where
this situation simply generates a #doesNotUnderstand message that gets
sent to the object no matter what kind of message send it is: simple
send or super.
I am going to investigate further but at this point I am inclined to
think the rails framework isn’t dealing with the exception generated by
a missing method on a “super” call.
Hello Paul,
Okay, I follow what your thinking, I hope The problem comes from
that the ‘belongs_to’ probably creates methods to -delegate- over to the
appropriate class/table So, a belongs_to :grammar will send forward any
methods to grammar (grammer.correct?). The problem is that this
delegation means that the delegated class is NOT in the
ancestral/hiearchy for your class, and it probably creates methods for a
getter/setter named the same as the delegated class.
So, if you override the methods which are created by the belongs_to,
by creating your own class method called ‘grammar’, then its going to
take precedence over the auto-created delegation. Your effectively
‘masking’ the auto-delegation. This coupled with ‘super’ only working on
objects in your hierarchy means method_missing
Perhaps a diagram may help.
---- Active Record Object --
| |
Word -> Delegates -> Grammar
So, calling 'super' on Word won't find any methods in Grammar,
because they are only related by delegation… does this make sense ?
Usually, calling a method the -exact- same name as a delegated class
is a bad idea
Regards
Stef
(ps. I reserve the right to be wrong, in the past, now, and in the
future)
I think you are correct. However, read_attribute(‘grammar_tense_code’)
just returns to me the value of that attribute rather than triggering
the rails code that would read it from the database. So it just
returns
nil in my case. Do you happen to know if there is a way to trigger the
database read that sets the attribute to begin with?
Try:
class Word < ActiveRecord::Base
belongs_to :verse
belongs_to :grammar_tense_code
alias :real_grammar_tense_code :grammar_tense_code
def grammar_tense_code
obj = real_grammar_tense_code
end
end
belongs_to adds the `grammar_tense_code’ method to Word. You’re then
(almost immediately) overriding it. To get what you want you need to
alias the original method then reference it inside your new method.
ar/lib/active_record/associations.rb will lead the way to wealth and
power.
belongs_to adds the `grammar_tense_code’ method to Word. You’re then
(almost immediately) overriding it. To get what you want you need to alias
the original method then reference it inside your new method.
ar/lib/active_record/associations.rb will lead the way to
wealth and power.
Did he really mean to make a question about associations, though - or
just about mapped columns in general?
I have to say I find the way AR works quite counter-intuitive.
What I’d expect is to have method_missing() implemented in the super
class and fully overridable by subclasses, but instead AR:Base decides
to fudge with its children.
There are probably some sound technical reasons for this, though. I
take it there’s no viable way for AR::Base.method_missing() to
reach/inspect the caller when called (indirectly) from a subclass?
What I’d expect is to have method_missing() implemented in the super
class and fully overridable by subclasses, but instead AR:Base decides
to fudge with its children.
As for the mapped columns, they work with super() the way you’d
expect.
Wow, indeed it does. I tried to get this working earlier, and jumped
to the conclusion that AR was extremely weird when i couldn’t…
Thanks.
Did he really mean to make a question about associations, though - or
just about mapped columns in general?
For mapped columns, Sir Kemper’s read_attribute trick works.
What I’d expect is to have method_missing() implemented in the super
class and fully overridable by subclasses, but instead AR:Base decides
to fudge with its children.
Association collection methods are not method_missing tricks. They
are methods defined on your model when belongs_to, has_many, etc is
declared. Thus, no super.
As for the mapped columns, they work with super() the way you’d
expect. But we are not talking about mapped columns in the parent’s
example, we are talking about collection methods.
However, check it:
class Story < ActiveRecord::Base
def title
"My title is: " + super
end
end
story = Story.new(:title => “Railzzzz!”)
=> #<Story:0x266c820 @attributes={“title”=>“Railzzzz!”},
@new_record=true>
story.save
=> true
story.title
=> “My title is: Railzzzz!”