rubymetaprogrammingeigenclass

How to create a method that executes a previously given block in Ruby?


I have a class that was built for subclassing.

class A
  def initialize(name)
  end

  def some
    # to define in subclass
  end
end

# usage
p A.new('foo').some
#=> nil

In my use case, I don't want to create a subclass since I need just one instance. Therefore, I'll change the initialize method to support the following usage.

p A.new('foo') { 'YEAH' }.some
#=> YEAH

How could I support the usage above?


BTW: I found the following solutions for a Ruby 1.8.7 project, but they look awkward to me.

class A
  def singleton_class
    class << self; self; end
  end

  def initialize(name, &block)
    @name = name
    self.singleton_class.send(:define_method, :some) { block.call } if block_given?
  end

  def some
    # to define in subclass
  end
end

Solution

  • You can store the block argument in an instance variable and call it later on:

    class A
      def initialize(name, &block)
        @name  = name
        @block = block
      end
    
      def some
        @block.call
      end
    end
    
    A.new('foo') { 'YEAH' }.some
    #=> "YEAH"
    

    You can also pass arguments into the block:

    class A
      # ...
      def some
        @block.call(@name)
      end
    end
    
    A.new('foo') { |s| s.upcase }.some
    #=> "FOO"
    

    Or instance_exec the block in the context of the receiver:

    class A
      # ...
    
      def some
        instance_exec(&@block)
      end
    end
    

    Which allows you to bypass encapsulation:

    A.new('foo') { @name.upcase }.some
    #=> "FOO"