6ft Dan
6ft Dan

Reputation: 2435

Ruby: Performing additional commands on delegated method

I'd like to use delegate to pass map from a string on to chars, then join back to a string result.

require 'forwardable'

module Map
  module String
    extend Forwardable

    def self.included(base)
      base.send :extend, Forwardable
    end

    # Map for String
    delegate map: :chars
  end
end

class String
  include Map::String
end

As it's originally a string I'd like to perform join after the delegated method has performed its duties. How do I modify the delegate line to include the additional change? The closest thing I've seen online is SimpleDelegator with __setobj__. But that's not well documented and I'm not able to ascertain how to use it for this.

I'm strictly looking for an answer in regards to delegate or SimpleDelegate

The equivalent behavior I'm looking for is this:

module Map
  module String

    def map(*args, &block)
      if (!args.compact.empty? || !block.nil?)
        self.chars.map(*args,&block).join
      else
        self.chars.map(*args,&block)
      end
    end
  end
end

class String
  include Map::String
end

I'm looking to understand how to do this with delegate.

Upvotes: 0

Views: 107

Answers (1)

7stud
7stud

Reputation: 48599

The Fowardable docs are hilarious--as if that first example will run without a hundred errors. Your pseudo code tells ruby to forward the method call String#map, which doesn't exist, to String#chars, and you want to join() the result of that? Skip all the method calls and just write puts "some_string". So your question doesn't seem to make a lot of sense. In any case, Forwardable#delegate() does not allow you to map one name to another name.

With regards to SimpleDelegat**or**, you can do this:

module Map
  require 'delegate'

  class MyStringDecorator < SimpleDelegator
    def map
      chars.shuffle.join('|')
    end
  end
end


d = Map::MyStringDecorator.new 'hello'
puts d.map

--output:--
h|o|l|l|e

Response to edit: The equivalent behavior I'm looking for..

The problem is ruby won't let you do this:

class String < SomeClass
end

which is what include does, and you need to be able to do that in order to use delegate to forward all the method calls sent to one class to another class. This is the best you can do:

require 'delegate'

class MyString < DelegateClass(String)
  def map(*args, &block)
    if (!args.compact.empty? || !block.nil?)
      self.chars.map(*args,&block).join
    else
      self.chars.map(*args,&block)
    end
  end
end

s = MyString.new 'hello'
puts s.upcase
puts s.map {|letter| letter.succ }

--output:--
HELLO
ifmmp

Or:

require 'forwardable'

class MyString
  extend Forwardable

  def initialize(str)
    @str = str
  end

  def_delegators :@str, :upcase, :capitalize, :[], :chars  #etc., etc., etc.
  #Or: delegate({[:upcase, :capitalize, :[], :chars] => :@str})
  #Or: instance_delegate({[:upcase, :capitalize, :[], :chars] => :@str})


  def map(*args, &block)
    if (!args.compact.empty? || !block.nil?)
      self.chars.map(*args,&block).join
    else
      self.chars.map(*args,&block)
    end
  end
end

s = MyString.new('hello')
puts s.upcase
puts s.map {|letter| letter.succ }

--output:--
HELLO
ifmmp

Of course, you could always override String#method_missing() to do what you want. What is it that you read about delegate that made you think it could replace include?

Upvotes: 1

Related Questions