tscheingeld
tscheingeld

Reputation: 887

Default method for Ruby class

Is there a way to specify a class method such that when the object is used as if it were a function, that method is called? Something like this:

class MyClass

  def some_magic_method(*args)
    # stuff happens
  end

end

# create object
myob = MyClass.new

# implicitly call some_magic_method
myob 'x'

Upvotes: 3

Views: 3558

Answers (3)

Sagar Pandya
Sagar Pandya

Reputation: 9508

As mentioned by @CarySwoveland in the comments you can use method_missing. A basic example is as follows:

class MyClass

  def method_missing(method_name, *args)
    if method_name.match?(/[xyz]/)
      send(:magic_method, args.first) 
    else
      super
    end
  end

  def magic_method(a)
    a = 'none' if a.nil?
    "xyz-magic method; argument(s): #{a}"
  end

end

myob = MyClass.new
myob.x    #=> "xyz-magic method; argument(s): none"
myob.x(1) #=> "xyz-magic method; argument(s): 1"

myob.y    #=> "xyz-magic method; argument(s): none"
myob.z    #=> "xyz-magic method; argument(s): none"

This captures all methods named x, y or z. Our else branch sends all other undefined methods to the original method_missing:

myob.v    #=> test.rb:7:in `method_missing': undefined method `v' for
              #<MyClass:0x000000021914f8> (NoMethodError)
              #from test.rb:25:in `<main>'

What methods you capture is up to you and is determined by the regex /[xyz]/ in this case.


Key methods: BasicObject#method_missing, Object#send. For further info check out this question, read Eloquent Ruby by Russ Olsen (from which this answer references)

Upvotes: 3

metaphori
metaphori

Reputation: 2811

You meant to invoke some class' instance method when the object is invoked as a function. This is already supported: instance method call gets called when you "invoke" an object via the functional invocation method () (for more details, see here How do I reference a function in Ruby?).

class C
  def call(x)
    puts "Called with #{x}"
  end
end

obj = C.new
obj.(88) # Called with 88 => nil
obj (88) # NoMethodError: undefined method `obj' for main:Object

If you do want the latter syntax, a horrible trick is the following one (but works only at the top-level, unless you carry along the bindings):

module Kernel
  def method_missing(name,*args)
    obj = begin
      TOPLEVEL_BINDING.local_variable_get(name)
    rescue
      nil
    end
    return super if obj.nil? 
    obj.send :call, *args
  end
end

obj = C.new

obj 88 # Called with OK => nil

This example also wants to communicate that you should always keep in mind who is the receiver of your method calls, and what syntaxes are available for calling methods (especially when you leave out dots and parentheses).

class D
  def obj; C.new end
  def f
    #(obj) 88 # BAD 
    (obj).(88) 
    #obj() 88 # BAD 
    obj().(88) 
  end
end

The point is that you do not actually have functions, but methods that get called on objects. If you omit the receiver of a method call, the receiver defaults to self, the current object. But in your example, myob does not appear as an explicit receiver (since there is not following dot as in myob.), hence the current object is looked for a method myob.

Upvotes: 0

S.Spencer
S.Spencer

Reputation: 611

You could write a command class and make use of a ruby shortcut

class MyClass
  def self.call(text)
    puts text
  end
end

MyClass.('x')

Here MyClass.() defaults to the call class method.

Upvotes: 3

Related Questions