ivanpoval
2011-Apr-26 23:44 UTC
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... -- Thanks, Ivan Povalyukhin -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To post to this group, send email to rubyonrails-core@googlegroups.com. To unsubscribe from this group, send email to rubyonrails-core+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.
Jeremy Evans
2011-Apr-27 16:09 UTC
Re: What happens when we use `super` trying to override the attribute method in AR subclass
On Tue, Apr 26, 2011 at 4:44 PM, ivanpoval <ivanpoval@gmail.com> wrote:> 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 -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To post to this group, send email to rubyonrails-core@googlegroups.com. To unsubscribe from this group, send email to rubyonrails-core+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.
ivanpoval
2011-Apr-27 22:13 UTC
Re: What happens when we use `super` trying to override the attribute method in AR subclass
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? -- Thanks On Apr 27, 9:09 am, Jeremy Evans <jeremyeva...@gmail.com> wrote:> On Tue, Apr 26, 2011 at 4:44 PM, ivanpoval <ivanpo...@gmail.com> wrote: > > 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-- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To post to this group, send email to rubyonrails-core@googlegroups.com. To unsubscribe from this group, send email to rubyonrails-core+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.
Jeremy Evans
2011-Apr-28 00:21 UTC
Re: Re: What happens when we use `super` trying to override the attribute method in AR subclass
On Wed, Apr 27, 2011 at 3:13 PM, ivanpoval <ivanpoval@gmail.com> wrote:> 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 -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To post to this group, send email to rubyonrails-core@googlegroups.com. To unsubscribe from this group, send email to rubyonrails-core+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.
ivanpoval
2011-Apr-28 06:01 UTC
Re: What happens when we use `super` trying to override the attribute method in AR subclass
> 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... -- Thanks, Ivan -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To post to this group, send email to rubyonrails-core@googlegroups.com. To unsubscribe from this group, send email to rubyonrails-core+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.