FriendFX
FriendFX

Reputation: 3079

Extending an instance with methods from another instance of another class

Currently I am doing something like the following to have the Runnable classes' run method access methods of the ExecutionContext:

class ExecutionContext
  def message(text)
    puts "ExecutionContext.message: #{text}"
  end 
  def answer?
    puts "ExecutionContext.answer called"
    true
  end
end

class Controller
  def do_run(context)
    @context = context
    run
    @context = nil
  end

  def method_missing(mth, *args)
    @context.send(mth, *args)
  end
end

class Runnable < Controller
  def run
    if answer?
      message "Runnable's block executing!"
    end
  end
end

runnable = Runnable.new

context = ExecutionContext.new
runnable.do_run(context)

The idea is that the Runnable class is written by "end users" who want to access functionality (such as the message and answer? methods) of an ExecutionContext instance which is provided from elsewhere. In fact, the Runnable class is all the "end user" is concerned about and it should be as small and simple as possible.

The code above works as expected in that all methods called by Runnable.run are provided by the ExecutionContext.

However I am wondering if there is a more elegant way to achieve the same thing, where I can avoid using method_missing and defining the temporary @context instance variable.

Ideally, I would like to modify the code to something similar to this (ExecutionContext stays the same):

class Controller
  def do_run(context, runnable)
    runnable.extend_from_instance(context)
    runnable.run
  end
end

class Runnable
  def run
    if answer?
      message "Runnable's block executing!"
    end
  end
end

runnable = Runnable.new
context = ExecutionContext.new
Controller.new.do_run(context, runnable)

Is there such thing as extend_from_instance?

Update

Thanks everyone for looking into this.

Since it has come up in the two answers I got so far, I realised I should mention the following restrictions: Unfortunately neither Runnable nor ExecutionContext can be turned into modules. They need to be classes as their instances will behave differently for different cases (both will have their own instance variables etc).

I really need the run method to execute as if it was part of the ExecutionContext instance (context in the code).

Upvotes: 3

Views: 284

Answers (2)

B Seven
B Seven

Reputation: 45943

Yes, you don't want to use method_missing in this case.

I don't really understand what you are trying to accomplish. Perhaps define_block is not the correct name...?

However, a module seems to have the functionality that you want.

module Messaging
  def message(text)
    puts "Provider.message: #{text}"
  end 
end

class Parent
  include Messaging
  ...

EDIT: I think you want to use binding. It allows you to pass the current scope (context) to another scope.

def print_local binding
  puts 'local is ' + binding.eval('local')
end

def foo
  local = 'value'
  print_local binding
end

foo 
=> local is value

In this case, print_local wants to print foo's local variable. But it doesn't have access to it because local exists only in foo's scope (or context).

So, you can pass in foo's scope as binding.

See binding.

Upvotes: 1

robertjlooby
robertjlooby

Reputation: 7220

I think what B Seven said was correct. It makes the most sense to create the shared functionality in a Module and include that module in the class. However if you really want something like your extend_from_instance you can use Ruby's Object#extend method to dynamically extend the module in an instance of the class (though I think this is a bad idea). Working form your desired code:

module ExecutionContext
  def message(text)
    puts "ExecutionContext.message: #{text}"
  end 
  def answer?
    puts "ExecutionContext.answer called"
    true
  end
end

class Controller
  def do_run(context_module, runnable)
    runnable.extend(context_module)
    runnable.run
  end
end

class Runnable
  def run
    if answer?
      message "Runnable's block executing!"
    end
  end
end

runnable = Runnable.new
Controller.new.do_run(ExecutionContext, runnable)

Again though, this is a bad idea and will probably come back to bite you in the long run.

Upvotes: 0

Related Questions