I am curious how this works. For example if I create a factory pattern based class where you can "register" classes for later use and then do something like
FactoryClass.register('YourClassName', [param, param, ...]);
FactoryClass.create('your_class_name').call_method_from_this_object
where 'class_name'
is a key in a hash that maps to value: ClassName
is there anything like php reflection, where I can create an instance of a class based on a string name and pass in the arguments in? (in php the arguments would be an array of them that php then knows how what to do with)
So if we take a real world example:
class Foo
attr_reader :something
def initialize(input)
@something = input
end
def get_something
return @something
end
end
# In the factory class, foo is then placed in a hash: {'foo' => 'Foo'}
# This step might not be required??
FactoryClass.create('Foo', ['hello'])
# Some where in your code:
FactoryClass.create('foo').get_something # => hello
Is this possible to do in ruby? I know everything is essentially an object, but I haven't seen any API or docs on creating class instances from string names like this and also passing in objects.
As for the hash above, thinking about it now I would probably have to do something like:
{'foo' => {'class' => 'Foo', 'params' => [param, param, ...]}}
This way when you call .create
on the FactoryClass
it would know, ok I can instantiate Foo
with the associated params.
If I am way off base, please feel free to educate me.
Here is an answer that doesn't use eval
.
PHP's Reflection
is called Metaprogramming
in Ruby, but they are quite different. Everything in Ruby is open and could be accessed.
Consider the following code:
class Foo
attr_reader :something
def initialize(input)
@something = input
end
def get_something
return @something
end
end
@registered = { }
def register(reference_name, class_name, params=[])
@registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = @registered[reference_name]
Object.const_get(h[:class_name]).new(*(h[:params]))
end
register('foo', 'Foo', ['something'])
puts create('foo').get_something
You can use Object#const_get
to get objects from strings. Object.const_get('Foo')
will give you the object Foo
.
However, you don't need to send class name as string. You can also pass around the class name as object and use that directly.
class Foo
attr_reader :something
def initialize(input)
@something = input
end
def get_something
return @something
end
end
@registered = { }
def register(reference_name, class_name, params=[])
@registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = @registered[reference_name]
h[:class_name].new(*(h[:params]))
end
register('foo', Foo, ['something else'])
puts create('foo').get_something
Check out Module#const_get
(retrieving a constant from a String) and Object#send
(calling a method from a String).
Actually one of the strong points in ruby is meta-programming. So this is really easy to do in ruby.
I am going to skip the registering part, and jump straight to the creation
A simple implementation would be this
class FactoryClass
def self.create(class_name, params)
klass = Object.const_get(class_name)
klass.new(*params)
end
end
and then you can just do:
FactoryClass.create('YourClassName', [param, param, ...]);
and this would be equivalent to calling
YourClassName.new(param, param, ...)