Martijn Kerckhaert
Martijn Kerckhaert

Reputation: 494

Understanding self with method chaining

I'm trying to understand self in Ruby.

In the code pasted below, if I create a new instance of Animal with

fox = Animal.new.name("Fox").color("red").natural_habitat("forest").specie("mammal")

and then call

fox.to_s

It does not do anything if I do not return self in every method.

Why do I need self in every method? Isn't the variable already saved once I create a new Animal?

class Animal
  def name(name)
    @name = name
    self
  end
  def specie(specie)
    @specie = specie
    self 
  end
  def color(color)
    @color = color
    self 
  end
  def natural_habitat(natural_habitat)
    @natural_habitat = natural_habitat
    self 
  end
  def to_s
    "Name: #{@name}, Specie: #{@specie}, Color: #{@color}, Natural Habitat: #{@natural_habitat}"
  end
    end

Upvotes: 2

Views: 2190

Answers (3)

MravAtomski
MravAtomski

Reputation: 26

if the #name method would be implemented without calling self, like so:

def name(name)
  @name = name
end

This method would return a string when called.

Animal.new.name #=> returns "name of animal"

This means that

Animal.new.name.specie

would call #specie method on a string object (which probably raises NotImplemented error) instead of object of Animal class which implements the method.

Upvotes: 1

tadman
tadman

Reputation: 211600

This pattern is used infrequently in Ruby, it's much more common in languages like Java and JavaScript, where it's notably rampant in jQuery. Part of the reason why is the verbosity you're describing here, and secondly because Ruby provides a convenient mutator generator in the form of attr_accessor or attr_writer.

One problem with these accessor/mutator dual purpose methods is ambiguity. The implementation you have is incomplete, you're unable to read from them. What you need is this:

def color(*color)
  case (color.length)
  when 1
    @color = color
    self
  when 0
    @color
  else
    raise ArgumentError, "wrong number of arguments (%d for 0)" % color.length
  end
end

That's a whole lot of code to implement something that can be used in two ways:

animal.color('red')
animal_color = animal.color

If you want to use these, you'll need to write your own meta-programming method that can generate them, though I'd highly discourage going down that path in the first place. Use attr_accessor methods and an options Hash.

Here's the equivalent Ruby pattern:

# Up-front assignment via Hash
animal = Animal.new(
  name: 'Fox',
  color: 'red',
  natural_habitat: 'forest',
  specie: 'mammal'
)

# Assignment after the fact
animal.color = 'white'

Upvotes: 3

Yu Hao
Yu Hao

Reputation: 122383

In your example, using self as the return value is convenient. The methods return the instance itself, so that you can call:

fox = Animal.new
fox.name("Fox").color("red").natural_habitat("forest").specie("mammal")

The value of fox.name("Fox") is the instance itself, that's why you can call .color("red") on it.

Upvotes: 2

Related Questions