From what I understand, super keyword invokes a method with the same name as the current method in the superclass of the current class. Below in the autoload method, there is a call to super. I would like to know in which superclass I would find a method with the same name or what does the call to super do here
module ActiveSupport
module Autoload
...
def autoload(const_name, path = ##at_path)
full = [self.name, ##under_path, const_name.to_s, path].compact.join("::")
location = path || Inflector.underscore(full)
if ##eager_autoload
##autoloads[const_name] = location
end
super const_name, location
end
....
end
end
module ActiveRecord
extend ActiveSupport::Autoload
...
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
end
This code is from the rails master branch. Thanks much.
The example provided in the Ruby Docs for the super keyword:
module Vehicular
def move_forward(n)
#position += n
end
end
class Vehicle
include Vehicular # Adds Vehicular to the lookup path
end
class Car < Vehicle
def move_forward(n)
puts "Vrooom!"
super # Calls Vehicular#move_forward
end
end
Inspecting ancestors
puts Car.ancestors.inspect
# Output
# [Car, Vehicle, Vehicular, Object, Kernel, BasicObject]
Note the inclusion of the Vehicular Module object!
Check objRef.class.ancestors or ClassName.ancestors to know the inheritance chain. If the super class does not contain the method, then all modules included by the super class are checked (last included checked first). If no match, then it moves up one level to the grandparent class and so on.
You can use the list of ancestors and then call AncestorClass.methods.select{|m| m.include?("auto_load")} to zone in on the method that's being called.
(Note: the above code is Ruby 1.8. In 1.9 methods returns symbols instead of strings. so you'd have to do a m.to_s.include?(...)
Use Pry
Insert a binding.pry call right before you use super, and then invoke show-source -s (-s means superclass) to show the superclass method and find out where it's defined:
class A
def hello
puts "hi"
end
end
class B < A
def hello
binding.pry
super
end
end
b = B.new
b.hello
From: (pry) # line 7 B#hello:
7: def hello
=> 8: binding.pry
9: super
10: end
[1] (pry) #<B>: 0> show-source -s
From: (pry) # line 2:
Number of lines: 3
Owner: A # <--see owner here (i.e superclass)
Visibility: public
def hello
puts "hi"
end
[2] (pry) #<B>: 0>
The super keyword checks all the way up the ancestry tree to find the inherited method.
Do a search on the entire rails master branch. You will only find one def autoload which is exactly the one you're looking at in active_support/lib/active_support/dependencies/autoload.rb.
The method being overridden is native Ruby. It is Module#autoload
I added this method to find the owner of a method to my .irbrc, does anyone see a better way to do this, especially in handling singleton methods where the superclass of the singleton class is the singleton class of the superclass?
class Object
def find_method(method_string)
if klasses = self.class.ancestors.select { |a| a if a.methods.include? method_string }
puts "class method in #{klasses.join(',')}" unless klasses.empty?
end
if klasses = self.class.ancestors.select { |a| a if a.instance_methods.include? method_string }
puts "instance method in #{klasses.join(',')}" unless klasses.empty?
end
rescue
raise "owning class not found"
end
end
The relevant superclass method is probably Module#autoload.
Related
I've been reading this article on the difference between include & extend in ruby.
If I have this module, I understand how the first and second methods of the module will be used in the class. What I don't understand is how the class << self will be used by include or extend.
module Direction
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
class << self
def stop
puts "stopping!"
end
end
end
# This will work because `include` brings them in as instance methods
class Car
include Direction
end
Car.new.straight
Car.new.turn
# ---------------------
# Now this will also work because `extend` brings them in as class methods
class Car
extend Direction
end
Car.straight
Car.turn
# ---------------------
Now, the issue is, doing Car.stop or Car.new.stop will always result in an error:
/Users/<name>/Projects/ruby-testing/main.rb:34:in `<main>': undefined method `stop' for Car:Class (NoMethodError)
Why are class methods not carried over via include and extend?
I started thinking about this because of my research into the [forwardable source code at line 119].(https://github.com/ruby/ruby/blob/master/lib/forwardable.rb#L119)
Thank you for any help you may have!
Update from Answer Below
The following was an example given:
module Direction
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
end
class Car
include Direction
end
This I understand now, and I understand how I can implement class methods from a module into a class using def self.included(base). My question is, if we used extend inside of Car instead of include, would we still be able to get at those class methods using def self.included(base)?
When you define a method with class << self you are defining a class method. It's the same as defining the methed like this:
class Foo
def self.foo
puts 'foo'
end
# the above definition is the same as doing:
class << self
def foo
puts 'foo'
end
end
end
The above shows 2 ways of defining class methods which are called directly on the class and not on instances of the class. You might use the 2nd syntax if you want to define only class methods or several of them inside of the class << self block. But either style has the same result.
Since you've defined a class method on the Direction module, include or extend will not inherit the class method of that module. This is the expected behavior.
If you want to use inheritance with class methods from a module, you should do it like this which is explained further down in the article you've linked
module Direction
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
end
class Car
include Direction
end
Now calling class methods on Car will inherit as defined in the Direction class.
Car.stop
stopping!
=>nil # calling a method will return nil unless the method returns a value.
However always be careful using inheritance of any kind as Ruby is a dynamic language. So if you do the above code and then later redefine this method:
module Direction
module ClassMethods
def stop
puts 'go!'
end
end
end
Guess what will happen if you do this:
Car.stop
Since the method was defined inside Direction module, when the method gets called on Car it will be calling the method from the Direction module.
Car.stop
go!
=>nil
Updated based on comments:
If you prefer to use extend vs include you would need to do this instead:
module Direction
def self.extended(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
end
class Car
extend Direction
end
In this example, all the methods which were inherited from the module are "copied" to the class extending them. This avoids the problem of possible result of redefining the module method which I warned about when using include previously in my answer.
But you may want to look at answers to this question for ideas about when and why to use either case.
I'm confused about using "include" vs "extend, after searching for hours all I got is that module methods used with instance of the class including the module, and module methods used with the class itself when the class extending the module of those methods.
but this didn't help me to figure out, why this code give error when commenting the extend module line in "#extend Inventoryable"
while work when uncomment it, here's the code
module Inventoryable
def create(attributes)
object = new(attributes)
instances.push(object)
return object
end
def instances
#instances ||= []
end
def stock_count
#stock_count ||= 0
end
def stock_count=(number)
#stock_count = number
end
def in_stock?
stock_count > 0
end
end
class Shirt
#extend Inventoryable
include Inventoryable
attr_accessor :attributes
def initialize(attributes)
#attributes = attributes
end
end
shirt1 = Shirt.create(name: "MTF", size: "L")
shirt2 = Shirt.create(name: "MTF", size: "M")
puts Shirt.instances.inspect
the output is
store2.rb:52:in `<main>': undefined method `create' for Shirt:Class (NoMethodError)
while when uncomment the "extend Inventoryable" to make the code work:
module Inventoryable
def create(attributes)
object = new(attributes)
instances.push(object)
return object
end
def instances
#instances ||= []
end
def stock_count
#stock_count ||= 0
end
def stock_count=(number)
#stock_count = number
end
def in_stock?
stock_count > 0
end
end
class Shirt
extend Inventoryable
include Inventoryable
attr_accessor :attributes
def initialize(attributes)
#attributes = attributes
end
end
shirt1 = Shirt.create(name: "MTF", size: "L")
shirt2 = Shirt.create(name: "MTF", size: "M")
puts Shirt.instances.inspect
makes the code work and output the following
[#<Shirt:0x0055792cb93890 #attributes={:name=>"MTF", :size=>"L"}>, #<Shirt:0x0055792cb937a0 #attributes={:name=>"MTF", :size=>"M"}>]
it's kinda confusing, but all I need to know, is why I need to extend the module in order to avoid the error ?, and how to edit this code to make it work without the extend method ? , what's left in the code that still depends on the extend ?
When you extend a module, the methods in that module become "class methods"**. So, when you extend Inventoryable, create becomes available as a method on the Shirt class.
When you include a module, the methods in that module become "instance methods"**. So, when you include Inventoryable, create is not available on the Shirt class (but is available on an instance of Shirt).
To make create available on the Shirt class when using include, you can use the included hook. That might look something like:
module Inventoryable
module ClassMethods
def create
puts "create!"
end
end
module InstanceMethods
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.include InstanceMethods
end
end
Then if you do:
class Shirt
include Invetoryable
end
You can do:
> Shirt.create
create!
=> nil
** The ruby purists in the crowd will correctly point out that, in ruby, everything is an instance method and that there are no class methods. That is formally 100% correct, but we'll use the colloquial meaning of class and instance methods here.
When you extend a module in a class, you get the module's methods exposed as class methods but if you include the module then you get the module's method as instance methods, in your example for you to be able to call create method of Inventoryable class you need to invoke it using an instance of Shirt class (if you include the module)
shirt1 = Shirt.new(attributes).create(attributes)
Without more info I can't tell what you are trying to do but you need to redesign the initialize and create methods to decide where or what to do in those methods.
I'll try to explain it using a simple example
module A
def test
puts "ok"
end
end
class B
include A
end
class C
extend A
end
puts C.test # here you invoke the method against the class itself
puts B.new.test #here you create an instance to do it
Hope it helps.
At the end of the day, it's really simple:
C.include(M) makes the current superclass of C the superclass of M and M the superclass of C. In other words, it inserts M into C's ancestry chain.
obj.extend(M) is (roughly) the same as obj.singleton_class.include(M).
Let's say I have the following structure in ruby (no rails)
module Parent
def f
puts "in parent"
end
end
module Child
def f
super
puts "in child"
end
end
class A
include Parent
include Child
end
A.new.f # prints =>
#in parent
#in child
Now when using rails concerns
module Parent
extend ActiveSupport::Concern
included do
def f
puts "In Parent"
end
end
end
module Child
extend ActiveSupport::Concern
included do
def f
super
puts "In Child"
end
end
end
class A < ActiveRecord::Base
include Parent
include Child
end
A.new.f #exception
NoMethodError: super: no superclass method `f' for #<A:0x00000002244490>
So what am I missing here? I need to use super in concerns like in normal modules. I searched but I could not find help on this topic
The reason for this is that included method block is actually evaluated in the context of the class. That mean, that method defined in it is defined on a class when module is included, and as such takes precedence over included modules.
module Child1
extend ActiveSupport::Concern
included do
def foo
end
end
end
module Child2
def bar
end
end
class A
include Child1
include Child2
end
A.new.method(:foo).owner #=> A
A.new.method(:bar).owner #=> Child2
Method lookup
In ruby, every time you want to call a method, ruby has to find it first (not knowing whether it is method or a variable). It is done with so called method lookup. When no receiver is specified (pure call like puts) it firstly searches the current scope for any variables. When not found it searches for that method on current self. When receiver is specified (foo.bar) it naturally search for the method on given receiver.
Now the lookup - in ruby all the methods always belongs to some module/class. The first in the order is receiver's eigenclass, if it exists. If not, regular receiver's class is first.
If the method is not found on the class, it then searches all the included modules in given class in the reversed order. If nothing is found there, superclass of given class is next. The whole process goes recursively until something is found. When lookup reaches BasicObject and fails to find the method it quit and triggers search for method_missing, with default implementation defined on BasicObject.
Important thing to notice is that methods which belongs to the class always take precedence over module methods:
module M
def foo
:m_foo
end
end
class MyClass
def foo
:class_foo
end
include M
end
MyClass.new.foo #=> :class_foo
About super
Search for a super method is very similar - it is simply trying to find a method with the same name which is further in the method lookup:
module M1
def foo
"M1-" + super
end
end
module M2
def foo
'M2-' + super
end
end
module M3
def foo
'M3-' + super
end
end
class Object
def foo
'Object'
end
end
class A
include M2
include M3
end
class B < A
def foo
'B-' + super
end
include M1
end
B.new.foo #=> 'B-M1-M3-M2-Object'
ActiveSupport::Concern#included
included is a very simple method that takes a block and creates a self.included method on the current module. The block is executed using instance_eval, which means that any code in there is actually executed in the context of the class given module is being included in. Hence, when you define a method in it, this method will be owned by the class including the module, not by the module itself.
Every module can hold only one method with given name, once you tries to define second one with the same name, the previous definition is completely erased and there is no way it can be find using ruby method lookup. Since in your example you included two modules with same method definition in included block, the second definition completely overrides the first one and there is no other definition higher in method lookup, so super is bound to fail.
In a Rails controller you can pass a symbol to the layout method that corresponds to a method in you controller that will return the layout name like this:
layout :my_method
def my_method
'layout_1'
end
I want to have a similar functionality to likewise pass a symbol to my classes method and that class should call the corresponding function and use its return value, like this
myClass.foo :my_method
def my_method
'layout_1'
end
I've read posts[1] that tell me I need to pass
myClass.foo(method(:my_method))
which I find ugly and inconvenient. How is rails here different allowing to pass just the symbol without any wrapper? Can this be achieved like Rails does it?
[1] How to implement a "callback" in Ruby?
If you want to only pass a :symbol into the method, then you have to make assumptions about which method named :symbol is the one you want called for you. Probably it's either defined in the class of the caller, or some outer scope. Using the binding_of_caller gem, we can snag that information easily and evaluate the code in that context.
This surely has security implications, but those issues are up to you! :)
require 'binding_of_caller'
class Test
def foo(sym)
binding.of_caller(1).eval("method(:#{sym})").call
end
end
class Other
def blork
t = Test.new
p t.foo(:bar)
p t.foo(:quxx)
end
def bar
'baz'
end
end
def quxx
'quxx'
end
o = Other.new
o.blork
> "baz"
> "quxx"
I still don't understand, what is author asking about. He's saying about "callbacks", but only wrote how he wants to pass parameter to some method. What that method(foo) should do - i have no idea.
So I tried to predict it's implementation. On class initialising it gets the name of method and create private method, that should be called somewhere under the hood. It possible not to create new method, but store method name in class variable and then call it somewhere.
module Foo
extend ActiveSupport::Concern
module ClassMethods
def foo(method_name)
define_method :_foo do
send method_name
end
end
end
end
class BaseClass
include Foo
end
class MyClass < BaseClass
foo :my_method
private
def my_method
"Hello world"
end
end
MyClass.new.send(:_foo)
#=> "Hello world"
And really, everything is much clearer when you're not just wondering how it works in rails, but viewing the source code: layout.rb
I am using a library that is implementing a belongs_to association between two entries in a database. Since this is not the behaviour I need I want to override this method via prepend. But pry tells me that the original method is still called. I double checked and I'm using ruby 2.0.
The code that gets prepended:
module Associations
module ClassMethods
[...]
#Add the attributeName to the belongsToAttributes
#and add a field in the list for the IDs
def belongs_to(attr_name)
#belongsToAttributes ||= []
#belongstoAttributes << attr_name
create_attr attr_name.to_s
attribute belongs_to_string.concat(attr_name.to_s).to_sym
end
def belongsToAttributes
#belongsToAttributes
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
# prepend the extension
Couchbase::Model.send(:prepend, Associations)
I use this in this class:
Note: I also tried to directly override the method in this class but it still doesn't happen
require 'couchbase/model'
class AdServeModel < Couchbase::Model
[...]
#I tried to add the belongs_to method like this
#def belongs_to(attr_name)
# #belongsToAttributes ||= []
# #belongstoAttributes << attr_name
# create_attr attr_name.to_s
# attribute belongs_to_string.concat(attr_name.to_s).to_sym
# end
# def belongsToAttributes
# #belongsToAttributes
# end
end
When I check with pry it shows me that I end up in this method call:
def self.belongs_to(name, options = {})
ref = "#{name}_id"
attribute(ref)
assoc = name.to_s.camelize.constantize
define_method(name) do
assoc.find(self.send(ref))
end
end
Any pointer to what I'm doing wrong would be appreciated.
Edit:
Ok I solved the problem like this:
self.prepended(base)
class << base
prepend ClassMethods
end
end
end
# prepend the extension
Couchbase::Model.send(:prepend, Associations)
Since Arie Shaw's post contains important pointers to solve this problem I will accept his answer. Although he missed the point about extending and prepending the method that I want to call. For a more detailed discussion about my trouble with prepending the methods please refer to this question.
According to the pry trace you posted, the method you wanted to monkey patch is a class method of AdServeModel, not a instance method.
The problem with your Associations module approach is, you are calling Module#prepend to prepend the module to the existing class, however, you wrote a self.included hook method which will only be called when the module is included (not prepended). You should write Module#prepended hook instead.
The problem with the directly overriding approach is, you were actually overriding the instance method, rather than the class method. It should be something like this:
require 'couchbase/model'
class AdServeModel < Couchbase::Model
class << self
# save the original method for future use, if necessary
alias_method :orig_belongs_to, :belongs_to
def belongs_to(attr_name)
#belongsToAttributes ||= []
#belongstoAttributes << attr_name
create_attr attr_name.to_s
attribute belongs_to_string.concat(attr_name.to_s).to_sym
end
def belongsToAttributes
#belongsToAttributes
end
end
end