I'm trying to use ruby's forwardable
module to make some variables in one class accessible to another class. However I am having some trouble doing this.
It seems that I'm able to 'forward' some variables within the self
(first bit of code) but I'm unable to forward some variable within a class (second bit of code)
The following works:
require 'forwardable'
module ModuleName
#
class << self
attr_accessor :config
def run
@config = {hey: 'hi', jay: 'ji'}
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def_delegators ModuleName, :config
def run
puts "2) Config = #{config}"
end
end
end
ModuleName.run
(ModuleName::Start.new).run
#=> 1) Config = {:hey=>"hi", :jay=>"ji"}
#=> 2) Config = {:hey=>"hi", :jay=>"ji"}
BUT this doesn't
require 'forwardable'
module ModuleName
#
class Data
attr_accessor :config
def run
@config = {hey: 'hi', jay: 'ji'}
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def_delegators ModuleName::Data, :config
def run
puts "2) Config = #{config}"
end
end
end
(ModuleName::Data.new).run
(ModuleName::Start.new).run
#=> 1) Config = {:hey=>"hi", :jay=>"ji"}
#=> /Users/ismailm/Desktop/ex.rb:17:in `run': undefined method `config' for ModuleName::Data:Class (NoMethodError)
Can you help in fixing this part of the code...
Typically when you delegate between two classes, one object contains an instance of the other. The fact that you call (ModuleName::Data.new).run
implies to me that is what you are trying to do, but are somehow missing the fact that you need an instance of the contained class to be stored somewhere in order to receive the call to :config
This variation of your second piece of code is closer to what I would expect to see in a delegation scenario:
require 'forwardable'
module ModuleName
#
class Data
attr_accessor :config
def initialize
@config = {hey: 'hi', jay: 'ji'}
end
def run
puts "1) Config = #{config}"
end
end
#
class Start
extend Forwardable
def initialize data_obj = Data.new()
@data = data_obj
end
def_delegators :@data, :config
def run
puts "2) Config = #{config}"
end
end
end
(ModuleName::Data.new).run
(ModuleName::Start.new).run
I changed the constructor to ModuleName::Start
in order to show a common pattern, not used here. Namely, you often pass in the wrapped object, or even more commonly the params that allow you to construct a new one and assign it to the instance variable that you wish to delegate to.
A minor related change: In your original code, the value of @config
was only set via calling :run
, so delegating direct to :config
won't read the value you expect in the test. It worked in the first version because @config
was set in the first call to run
on the module, and then read globally as a singleton method on the second delegated call. I have worked around that here by setting it in Data
s constructor, but of course anything that sets the value of @config
on the instance you are delegating to would work.