Sergey Blohin
Sergey Blohin

Reputation: 620

Call method by method in Ruby

I have a module that generates a phone number in the format I need.

module PhoneNumber
  def self.prefix
    '+'
  end
  def self.country
    rand(1..9).to_s
  end
  def self.code
    rand(100..999).to_s
  end
  def self.number
    rand(1000000..9999999).to_s
  end
end

I use it as follows. Or as a formatted string "#{}#{}".

phone_number = PhoneNumber.prefix +
               PhoneNumber.country +
               PhoneNumber.code +
               PhoneNumber.number

I want to rewrite the body of the module in this way, so that I can use it in a dotted format.

PhoneNumber.prefix.code.number

Upvotes: 3

Views: 2187

Answers (3)

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84343

Wrap Your String Construction Into a Class Method

"Dotted format" is really a set of chained methods. While you could conceivably do something similar to what you're trying to do by appending each class method's output to a String in your method chain, I would consider this a brittle and unsavory OOP design.

On a semantic level, each method in a chain means "do x to the return value of the previous method." When you want to describe the members of an object, or a different kind of initialization, there are more idiomatic ways to do it.

Without making significant changes to your existing code, you can simply add a PhoneNumber#create class method to do the heavy lifting for you. For example:

module PhoneNumber
  def self.create
    [self.prefix, self.country, self.code, self.number].join
  end

  def self.prefix
    '+'
  end

  def self.country
    rand(1..9).to_s
  end

  def self.code
    rand(100..999).to_s
  end

  def self.number
    rand(1000000..9999999).to_s
  end
end

if __FILE__ == $0
  puts PhoneNumber.create
end

Joining the array of String objects returned by your other class methods is reasonably idiomatic, semantically clear, and sidesteps the need to alter the existing class methods, which other objects in your programs may currently rely on. This localizes change, which is often a good thing in OOP design.

Parameterizing a method or converting your module to a class, as described in other answers to your question, are also reasonable alternatives. Naturally, your mileage and stylistic tastes may vary.

Upvotes: 1

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

To chain methods one should basically constantly return self from all the methods you are going to chain:

module PhoneNumber
  @number = ''
  def self.prefix
    @number << '+'
    self
  end
  def self.country
    @number << rand(1..9).to_s
    self
  end
  def self.code
    @number << rand(100..999).to_s
    self
  end
  def self.number
    @number << rand(1000000..9999999).to_s
    self
  end
  def self.to_s
    @number
  end
end

puts PhoneNumber.prefix.code.number
#⇒ +9065560843

Note the explicit #to_s implementation for the last step, since you probably want a string as an outcome, not the class itself.

This implementation has a glitch: it is hardly reusable, since there is a single shared @number, hence you’d better make all methods as instance methods and do:

class PhoneNumber
  def initialize
    @number = ''
  end
  def prefix
    @number << '+'
    self
  end
  def country
    @number << rand(1..9).to_s
    self
  end
  def code
    @number << rand(100..999).to_s
    self
  end
  def number
    @number << rand(1000000..9999999).to_s
    self
  end
  def to_s
    @number
  end
end

puts PhoneNumber.new.prefix.code.number
#⇒ +6117160297

Now it works for subsequent calls.

Upvotes: 3

elliotcm
elliotcm

Reputation: 759

I agree with Todd that you shouldn't do this; it will be awkward to implement and confusing for people who read the code (including yourself in the future) as it's not the typical way to write the sort of operations you're talking about.

That said, if you really want to do so, you'll need to provide a method called prefix which returns another object which has a method code and so on, storing state along the chain and combining the strings at the end. It would get even more complicated if you want to swap around the order.

Strongly recommend a method that looks like this instead:

PhoneNumber.build(:prefix, :code, :number)

Upvotes: 6

Related Questions