Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delegate attribute methods to the parent object

I have the following class:

class Alphabet

  attr_reader :letter_freqs, :statistic_letter

  def initialize(lang)
    @lang = lang
    case lang
    when :en
      @alphabet = ('A'..'Z').to_a
      @letter_freqs = { ... }
    when :ru
      @alphabet = ('А'..'Я').to_a.insert(6, 'Ё')
      @letter_freqs = { ... }
    ...
    end
    @statistic_letter = @letter_freqs.max_by { |k, v| v }[0]
  end

end

foo = Alphabet.new(:en)

The central member here is @alphabet.

I'd like to make it some sort of a container class to invoke Array methods directly like

foo[i]
foo.include?

instead of explicitly accessing @alphabet:

foo.alphabet[i]
foo.alphabet.include?  

I know I could define a lot of methods like

def [](i)
  @alphabet[i]
end

but I'm looking for a proper way of "inheriting" them.

like image 559
Mike Roll Avatar asked Nov 19 '13 13:11

Mike Roll


2 Answers

You can use Forwardable (it is included in the Ruby standard library):

require 'forwardable'

class Alphabet

  extend Forwardable
  def_delegators :@alphabet, :[], :include?

  def initialize
    @alphabet = ('A'..'Z').to_a
  end

end

foo = Alphabet.new

p foo[0]           #=> "A"
p foo.include? 'ç' #=> false

If you wish to delegate all the methods not defined by your class you can use SimpleDelegator (also in the standard library); it lets you delegate all the methods that are not responded by the instance to an object specified by __setobj__:

require 'delegate'

class Alphabet < SimpleDelegator

  def initialize
    @alphabet = ('A'..'Z').to_a
    __setobj__(@alphabet)
  end

  def index
    'This is not @alphabet.index'
  end

end

foo = Alphabet.new

p foo[0]           #=> "A"
p foo.include? 'ç' #=> false
p foo.index        #=> "This is not @alphabet.index"

When the delegate doesn't need to be dynamic you can arrange the master class to be a subclass of DelegateClass, passing the name of the class to be delegated as argument and calling super passing the object to be delegated in the #initialize method of the master class:

class Alphabet < DelegateClass(Array)

  def initialize
    @alphabet = ('A'..'Z').to_a
    super(@alphabet)
  end

More info about the delegation design pattern in Ruby here

like image 62
mdesantis Avatar answered Oct 20 '22 18:10

mdesantis


You could extend the Forwardable module:

class Alphabet
  require 'forwardable'
  extend Forwardable
  attr_accessor :alphabet

  def initialize
    @alphabet = [1,2,3]
  end


  def_delegator :@alphabet, :[], :include?
end

Then you can do:

alpha = Alphabet.new
alpha[1]==hey.alphabet[1]
=> true

Warning:

Don't try to delegate all methods (don't know if that's even possible) since they probably share some of the same method names such as class, which would probably make chaos.

like image 40
Zippie Avatar answered Oct 20 '22 18:10

Zippie