Aaron Yodaiken
Aaron Yodaiken

Reputation: 19551

Is there a way to create methods just for the instance of a Ruby class from inside that instance?

Let there be class Example defined as:

class Example
  def initialize(test='hey')
    self.class.send(:define_method, :say_hello, lambda { test })
  end
end

On calling Example.new; Example.new I get a warning: method redefined; discarding old say_hello. This, I conclude, must be because it defines a method in the actual class (which makes sense, from the syntax). And that, of course, would prove disastrous should there be multiple instances of Example with different values in their methods.

Is there a way to create methods just for the instance of a class from inside that instance?

Upvotes: 53

Views: 33673

Answers (4)

Daniel Garmoshka
Daniel Garmoshka

Reputation: 6352

Define instance method from outside:

example = Example.new
def example.say_hello
  puts 'hello'
end

From inside:

class Example
  def initialize(word='hey')
    @word = word
    def self.say_hello
      puts "example: #{@word}"
    end
  end
end

Tested on ruby 2.5

Upvotes: 2

Selvamani
Selvamani

Reputation: 7684

I know it was asked two years back, but I would like to add one more answer. .instance_eval will help to add methods to instance object

    string = "String"
    string.instance_eval do
      def new_method
        self.reverse
      end
    end

Upvotes: 11

thorncp
thorncp

Reputation: 3627

You need to grab a reference to the instance's singleton class, the class that holds all the instance specific stuff, and define the method on it. In ruby 1.8, it looks a little messy. (if you find a cleaner solution let me know!)

Ruby 1.8

class Example
  def initialize(test='hey')
    singleton = class << self; self end
    singleton.send :define_method, :say_hello, lambda { test }
  end
end

Ruby 1.9 however, provides a much easier way in.

Ruby 1.9

class Example
  def initialize(test='hey')
    define_singleton_method :say_hello, lambda { test }
  end
end

Upvotes: 82

J&#246;rg W Mittag
J&#246;rg W Mittag

Reputation: 369458

First off, a small style tip:

self.class.send(:define_method, :say_hello, lambda { test })

You can make this look a little bit nicer by using the new proc literal in Ruby 1.9:

self.class.send(:define_method, :say_hello, -> { test })

But you don't need that. Ruby has something called blocks, which are basically a piece of code that you can pass as an argument to a method. In fact, you already used blocks, since lambda is just a method which takes a block as an argument and returns a Proc. However, define_method already takes a block anyway, there is no need to pass a block to lambda which converts it to a Proc which it passes to define_method which then converts it back into a block:

self.class.send(:define_method, :say_hello) { test }

As you already noticed, you are defining the method on the wrong class. You are defining it on the Example class, since inside an instance method like initialize, self is the current object (i.e. ex1 or ex2 in @mikej's example), which means that self.class is ex1's class, which is Example. So, you are overwriting the same method over and over again.

This leads to the following unwanted behavior:

ex1 = Example.new('ex1')
ex2 = Example.new('ex2') # warning: method redefined; discarding old say_hello
ex1.say_hello            # => ex2 # Huh?!?

Instead, if you want a singleton method, you need to define it on the singleton class:

(class << self; self end).send(:define_method, :say_hello) { test }

This works as intended:

ex1 = Example.new('ex1')
ex2 = Example.new('ex2')
ex1.say_hello            # => ex1
ex2.say_hello            # => ex2

In Ruby 1.9, there's a method that does that:

define_singleton_method(:say_hello) { test }

Now, this works the way you want it to, but there's a higher-level problem here: this is not Ruby code. It is Ruby syntax, but it's not Ruby code, it's Scheme.

Now, Scheme is a brilliant language and writing Scheme code in Ruby syntax is certainly not a bad thing to do. It beats the hell out of writing Java or PHP code in Ruby syntax, or, as was the case in a StackOverflow question yesterday, Fortran-57 code in Ruby syntax. But it's not as good as writing Ruby code in Ruby syntax.

Scheme is a functional language. Functional languages use functions (more precisely, function closures) for encapsulation and state. But Ruby is not a functional language, it is an object-oriented language and OO languages use objects for encapsulation and state.

So, function closures become objects and captured variables become instance variables.

We can also come at this from a completely different angle: what you are doing is that you are defining a singleton method, which is a method whose purpose it is to define behavior which is specific to one object. But you are defining that singleton method for every instance of the class, and you are defining the same singleton method for every instance of the class. We already have a mechanism for defining behavior for every instance of a class: instance methods.

Both of these arguments come from completely opposite directions, but they arrive at the same destination:

class Example
  def initialize(test='hey')
    @test = test
  end

  def say_hello
    @test
  end
end

Upvotes: 40

Related Questions