Delegate attribute methods to the parent object
Asked Answered
K

3

5

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.

Knot answered 19/11, 2013 at 13:20 Comment(0)
O
10

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

Overcloud answered 19/11, 2013 at 13:32 Comment(4)
Wow that's nice :) Any chance I can delegate ALL methods? or maybe that's not a good idea?Knot
Don't delegate all methods since they probably share some of the same method names such as class,, which would probably make chaos.Plotter
@Zippie, accepting this answer because it has info on delegating all methods. Thanks for your warning, though :)Knot
No problem, this is the better answerPlotter
P
2

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.

Plotter answered 19/11, 2013 at 13:32 Comment(0)
F
0

In Ruby you can extend Objects like this.

class Array
  def second
    self[1]
  end
end

[1, 2, 3, 4, 5].second
# => 2

Or if you want to inherit an Array.

class Foo < Array
  def second
    self[1]
  end
end

[1, 2, 3, 4, 5].include? 2
# => true

[1, 2, 3, 4, 5].second
# => 2

If you have any further questions, comment and I will update the answer.

Flawed answered 19/11, 2013 at 13:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.