Yep, still trying to unwrap my brain from Perl and re-wrap it in Ruby...
Is there a way to dynamically assign instance variables?
class SomeClass
def initialize(hash)
hash.each do |key,val|
@#{key} = val # Doesn't work
end
end
end
Is there a better way to do this?
Thanks in advance.
Regards,
Dan
wconrad@pluto:~/lab/foo1$ cat foo.rb
#!/usr/bin/ruby
class Foo
def initialize(hash)
for key, value in hash do
eval "@#{key}=#{value.inspect}"
end
end
end
p Foo.new({"i"=>1, "s"=>"blah"})
wconrad@pluto:~/lab/foo1$ ./foo.rb
#<Foo:0x402568ec @s="blah", @i=1>
Wayne Conrad
Someone else will probably give you a better solution, but here's
something I wrote that avoids a problem I see with the 'value.inspect'
method. The issue is that often the string version of an object cannot
be used to assign to a variable and end up with the same type as the
original.
With the inspect version, I don't think, for example, that assigning
a time value would work. Anyway, have a play with this one if you're
interested. It also has the advantage that it uses method_missing to
provide setters and getters for all the instance variables.
class HashStruct
def initialize(hash)
hash.each_pair do |key, value|
instance_eval "@#{key} = value"
end
end
def method_missing(name, *args)
if name.to_s =~ /(.*)=/
self.instance_eval "@#{$1} = args[0]"
else
self.instance_eval "@#{name}"
end
end
end
hs = HashStruct.new({"text" => "hello", "time" => Time.now})
$stderr.puts hs.text
$stderr.puts hs.time
sleep 2
hs.time = Time.now
$stderr.puts hs.time
At 13:30 04/05/2002 +0900, you wrote:
>Anyway, have a play with this one if you're
>interested. It also has the advantage that it uses method_missing to
>provide setters and getters for all the instance variables.
>
>class HashStruct
>
> def initialize(hash)
> hash.each_pair do |key, value|
> instance_eval "@#{key} = value"
> end
> end
>
> def method_missing(name, *args)
> if name.to_s =~ /(.*)=/
> self.instance_eval "@#{$1} = args[0]"
> else
> self.instance_eval "@#{name}"
> end
> end
>
>end Harry Ohlsen
This nice piece of code is solving an issue I was dealing with, about "copy
constructors". This is a C++ idiom and I suspect there could be a
better/different one in Ruby. However, for those interested, here is the code:
# value.rb
# Module that helps handling 'Value' objects and C++ style Copy Constructors
#
# 02/05/04, JHR, Creation
# Typical synopsis:
# class MyClassWithCopyConstructor
# include CanValue
# def initialize( some_stuff )
# return self if initValue( some_stuff)
# ... init somehow differently here ...
# end
# ...
# end
# The module basically makes it easy to write copy constructors. Nota: the new
# object is not a deep copy of the original, its instance variables have the
# values of the original object's ones.
module CanValue
class Value < Hash
def toValue() self end
end
def initValue( a_value )
return nil unless a_value.respond_to? :toValue
a_value.toValue().each_pair do | var_name, value |
instance_eval "#{var_name}= value"
# p "Setting #{var_name} instance variable"
end
self
end
def toValue()
a_value = Value.new
instance_variables().each do | var_name |
a_value[var_name.intern()] = instance_eval( var_name)
end
a_value
end
end # CanValue
# An example, using the CanValue module.
class MyClass
include CanValue
attr_accessor :myVar
def initialize( a_value = nil )
return self if initValue( a_value)
@myVar = "Hello"
end
def print() p @myVar; end
end
a = MyClass.new
b = MyClass.new( a)
c = a.clone()
a.print; b.print; c.print
a.myVar = "I am a"; b.myVar = "I am b"; c.myVar = "I am c"
a.print; b.print; c.print
# Apparently .clone() does a similar job: making a "copy" of a object.
# However it does not work well with .new(), as can be seen here:
class MyClass2
attr_accessor :myVar
def initialize( some_stuff = nil )
return some_stuff.clone() if some_stuff.kind_of? self.class()
@myVar = "Hello"
end
def print() p @myVar; end
end
a = MyClass2.new
b = MyClass2.new( a) # Does not work
c = a.clone()
a.print; b.print; c.print
a.myVar = "I am a"; b.myVar = "I am b"; c.myVar = "I am c"
a.print; b.print; c.print
# A solution is to use a MyClass.create instead of MyClass.new. This could be
# another example for those who believe in "'new' considered harmful"...
class MyClass3
attr_accessor :myVar
def initialize()
@myVar = "Hello"
end
def self.create( some_stuff = nil )
return some_stuff.clone() if some_stuff.kind_of? self
new
end
def print() p @myVar; end
end
a = MyClass3.create
b = MyClass3.create( a)
c = a.clone()
a.print; b.print; c.print
a.myVar = "I am a"; b.myVar = "I am b"; c.myVar = "I am c"
a.print; b.print; c.print
Question: What the preferred idiom for creating objects in Ruby ?
The C++ idioms ('new' & overloading) seems rather unpractical to me, due
to the inability to overload new (or any other method, for that matter)
based on the parameters.
Yours,
Jean-Hugues
-------------------------------------------------------------------------
Web: http://hdl.handle.net/1030.37/1.1
Phone: +33 (0) 4 92 27 74 17
I'll add something to pile, here is something I used as a mixin where ever i
needed to this.
#
# Builds object attributes from a supplied Hash
#
module HashAttrBuilder
#
# Builds the object attributes from the supplied Hash.
#
# attributes: A Hash with each key being a Symbol
#
def build_instance_vars(attributes)
attributes.each do |k, v|
name = k.id2name
value = v.type == String ? "'" + v + "'" : v
instance_eval(<<-EOS)
class << self
attr_accessor :#{name}
end
EOS
send("#{name}=", v)
end
end
end
- --
Signed,
Holden Glova
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.6 (GNU/Linux)
Comment: For info see http://www.gnupg.org
iD8DBQE808X1+mF116Lw2cQRAk7+AKCETHtHfNCnj5/T+xrPF2w9Of63pgCfTRmE
Ovta6egpqYRQFresjVRZPIQ=
=kBeb
-----END PGP SIGNATURE-----
Hello,
Modules are very convenient to add "instance methods" to a class. Is there
a way a Module can also add "class methods" ?
Something like:
module MyModule
def self.foo() # not sure of what this does, but not what I want
print "foo"
end
end
class MyClass
include MyModule
end
MyClass.foo() # Does not work, foo is not defined for MyClass
Thanks,
Jean-Hugues
J> module MyModule
J> def self.foo() # not sure of what this does, but not what I want
J> print "foo"
J> end
J> end
J> class MyClass
J> include MyModule
J> end
J> MyClass.foo() # Does not work, foo is not defined for MyClass
module MyModule
def foo()
print "foo"
end
end
class MyClass
extend MyModule
end
MyClass.foo() # work :-)
Guy Decoux
On Sat, 4 May 2002, Jean-Hugues ROBERT wrote:
> Question: What the preferred idiom for creating objects in Ruby ?
> The C++ idioms ('new' & overloading) seems rather unpractical to me, due
> to the inability to overload new (or any other method, for that matter)
> based on the parameters.
I'm not sure what you mean. Isn't new() the usual way to create
objects?
David
--
David Alan Black
home: dbl...@candle.superlink.net
work: blac...@shu.edu
Web: http://pirate.shu.edu/~blackdav
H> instance_eval(<<-EOS)
[...]
H> send("#{name}=", v)
eval is evil, and it's still exist a stupid way to write it :-)
pigeon% cat b.rb
#!/usr/bin/ruby
module Hab
module Tmp
def defin(a)
attr_accessor a
end
end
def self.append_features(c)
super
c.extend Tmp
end
def biv(attributes)
attributes.each do |k, v|
type.send(:defin, k)
send(k.to_s + "=", v)
end
end
end
class A
include Hab
end
a = A.new
a.biv("aaa" => [1, 2], :bbb => [3, 4])
p a.aaa
p a.bbb
pigeon%
pigeon% b.rb
[1, 2]
[3, 4]
pigeon%
Guy Decoux
J> The C++ idioms ('new' & overloading) seems rather unpractical to me, due
It's normal, ruby is not C++ :-)))
Guy Decoux
Let me clarify. The way you create objects using MyClass.new() is similar
to the way you do it in C++. With one major limitation: There can be only
one initialize() method. Whereas in C++ you can have multiple ones. The
difference being what type/number of parameters each one gets (in C++ the
"initialize()" method is not called "initialize()", it is called after the
name of the class).
So, in C++, based on what parameters you create a new object, you define
multiple "constructors".
The way to do it in Ruby is not natural/direct. Because you have only one
initialize() method, you need to determine yourself what the caller
intention was, based on the supplied parameters.
def initialize( param = nil )
case param
when nil
# no parameter constructor
...
when self.class
# Copy constructor
when xxx
# Whatever other constructor
end
Some other languages will create objects based on prototypes:
def initialize()
proto = @@proto
proto.instance_variables.each do | var |
instance_eval( "#{var} = #{proto.instance_eval( #{var})}")
end
end
Some other languages will have no "constructor" or "initialize()" specific,
language defined, construct:
def MyClass.create()
obj = Object.new()
obj.class = MyClass # Not ruby: Smalltalk's style .becomes() missing
obj.body = { :@myVar1 => "Hello", :@myVar2 => "world" } # Not ruby
end
Some other languages just create objects, without having the method that
create objects beeing named in any specific way:
def MyClass.createFromInt( ii )
obj = MyClass.new()
obj.foo = ii
return obj
end
def doubleIt()
obj = MyClass.new()
obj.foo = self.foo * 2
end
. an so on ..
The basic OO equation is: an_object is a_id + a_value + a_behaviour.
There are many idioms to create objects. Appararently the Ruby one is to
use SomeClass.new( some_param) that leads to: A) the creation of the object
ii with no initial value and the behaviour of its class, followed by B) a
call to ii.initialize( some_param).
Hope this is clearer.
Thanks. However I would like to *include* the module, not extending it.
JHR
J> Thanks. However I would like to *include* the module, not extending it.
See [ruby-talk:39574], normally the method #biv can be written
def biv(attributes)
attributes.each do |k, v|
type.send(:attr_accessor, k)
send(k.to_s + "=", v)
end
end
I've just used a complex scheme to give you an example how to add class
methods and instance methods with a module
Guy Decoux
You aren't extending the Module.
You are extending MyClass with the contents of the module.
Sounds like you need to split up your module in a part you want to
include (instance methods) and a part you want to extend your class with
(class methods).
--
(\[ Kent Dahl ]/)_ _~_ __[ http://www.stud.ntnu.no/~kentda/ ]___/~
))\_student_/(( \__d L b__/ NTNU - graduate engineering - 4. year )
( \__\_鮸鮛/__/ ) _)Industrial economics and technological management(
\____/_鯻\____/ (____engineering.discipline_=_Computer::Technology___)
J> So, in C++, based on what parameters you create a new object, you define
J> multiple "constructors".
J> The way to do it in Ruby is not natural/direct.
ruby is not C++, don't try to mimic C++ with ruby (it's perhaps best to
forget it).
J> There are many idioms to create objects. Appararently the Ruby one is to
J> use SomeClass.new( some_param) that leads to: A) the creation of the object
J> ii with no initial value and the behaviour of its class, followed by B) a
J> call to ii.initialize( some_param).
No. What you describe is the new scheme allocate/initialize
Guy decoux
> Thanks. However I would like to *include* the module, not extending it.
See the recepie at
http://www.rubycookbook.org/showrecipe.rb?recipeID=166
// Niklas
True, sorry for the confusion.
>Sounds like you need to split up your module in a part you want to
>include (instance methods) and a part you want to extend your class with
>(class methods).
Yes, that would do the job. Maybe there is a way to get by with a single
module. I was wondering if some "included()" hook existed (similar to
inherited() for classes), and I found that "append_features()" is the hook
called when a module is included.
So: What I intend (to include class methods defined in a module)
module MyModule
def MyModule.foo()
end
end
class MyClass
include MyModule # I want to include both class & instance methods
end
MyClass.foo() # Calls foo() class method defined in module, broken.
Could be written:
module MyModule
def append_features( includer )
super( includer)
includer.instance_eval( "
# Here comes the definitions of included class methods
def foo() end
")
end
end
class MyClass
include MyModule
end
MyClass.foo()
However this does not work either... apparently append_features() is not
called. Additionnal 1) the construct is ugly, relies on eval and 2) there
is no way to later on introspect MyClass and know that foo() class method
was defined in MyModule.
Any hints ?
Jean-Hugues
module MyModule
module ClassMethods
def foo()
end
end
def self.append_features( includer )
super( includer)
includer.extend ClassMethods
end
end
class MyClass
include MyModule
end
MyClass.foo()
Not as direct as:
module MyModule
def self.foo()
end
class MyClass
include MyModule
end
MyClass.foo()
..But much better than my ugly .instance_eval() stuff. Thanks.
Jean-Hugues
Thanks. I am using it in my CanValue module. When that module is included
by YourClass, YourClass.create( an_obj ) will create a new object using
an_obj's instance variables (or .clone() when appropriate). This is the
equivalent of C++'s Copy Constructor (a Constructor that create an object
whose "value" is the same as the original).
Yours,
Jean-Hugues
# Typical synopsis:
# class MyClassWithCopyConstructor
# include CanValue
# def initialize( some_stuff )
# return self if initValue( some_stuff)
# ... init somehow differently here ...
# end
# ...
# end
# The module basically makes it easy to write copy constructors. Nota: the new
# object is not a deep copy of the original, its instance variables have the
# values of the original object's ones.
# The module also defines a class.create() method that behaves like the
# class's .new() method but will act as a copy constructor if called with
# a parameter that is of the same class (or of a class derived from self).
#
# Bugs: The copy gets the instance variables of the source, including the ones
# the ones that were added by derived class. The .create() does not handle
# blocks the way .new() does.
module CanValue
class Value < Hash
def toValue() self end
end
def initValue( a_value )
return nil unless a_value.respond_to? :toValue
a_value.toValue().each_pair do | var_name, value |
instance_eval "#{var_name}= value"
# p "Setting #{var_name} instance variable"
end
self
end
def toValue()
a_value = Value.new
instance_variables().each do | var_name |
a_value[var_name.intern()] = instance_eval( var_name)
end
a_value
end
# Defines a create() class method that will act as a copy constructor if
# called with a parameter that is of the same kind.
module ClassMethods
def create( *param )
# p "self #{self} is a #{self.class()}"
if param.length != 1 then
self.new( *param)
else
if (src = param[1]).kind_of? self then
# Use faster .clone() if source object's class is the good one
return src.clone() if src.class() == self
obj = self.new()
src.toValue().each_pair do | var_name, value |
obj.instance_eval "#{var_name}= value"
end
obj
else
self.new( param[1])
end
end
end
end
def self.append_features( includer )
super( includer)
includer.extend ClassMethods
end
end # CanValue
J> def create( *param )
J> # p "self #{self} is a #{self.class()}"
J> if param.length != 1 then
J> self.new( *param)
J> else
J> if (src = param[1]).kind_of? self then
pigeon% cat b.rb
#!/usr/bin/ruby
def create(*param)
if param.length == 1
puts param[1]
end
end
create(12)
pigeon%
pigeon% b.rb
nil
pigeon%
Guy Decoux
Very true. Should have been be src = param[0], not src = param[1]. Thanks.
I get to look at this eXtreme stuff and all their unit tests I guess.
BTW: Any idea for optimizing this code so that build a copy of an object ?
I guess something faster would be using .clone() but then the class of the
object need to be fixed and I am yet to find what is the Ruby's equivalent
to Smalltalk's "become" (that will change the class of an object).
Yours,
JHR
I definately have a long way to go with Ruby - solutions like this do not
come naturally to me and only make sense after much examination. *drifts off
into a dream job of useing Ruby*.
Thanks for the tip.
- --
Signed,
Holden Glova
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.6 (GNU/Linux)
Comment: For info see http://www.gnupg.org
iD8DBQE81G5N+mF116Lw2cQRAhBoAJ9uqAkq4GcvJvUV6ozrYwtyFdlvwQCgjD0B
Q+l7R/Y9kEVfrkf7CS4WOzE=
=lEFw
-----END PGP SIGNATURE-----
Thanks Niklas, that has further helped me understand Guy Decoux's example.
- --
Signed,
Holden Glova
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.6 (GNU/Linux)
Comment: For info see http://www.gnupg.org
iD8DBQE81G58+mF116Lw2cQRAqfHAJ9t22y8wz4OR8koJZqCWPGQIcQm6gCdHtPx
qgQ5jjpxcBuZSJ9O9Vn2uc8=
=WUXU
-----END PGP SIGNATURE-----
Interesting. I dare say solutions like this don't come naturally to
most people doing Ruby (yet). In fact, I still don't completely
understand it, though I get parts of it. :(
If you're like me, it takes time to wrap your brain around a language.
Oh, and thanks Guy. Perhaps a cookbook entry?
Regards,
Dan
D> Oh, and thanks Guy. Perhaps a cookbook entry?
Not a cookbook entry but I'll say you why in this case eval is evil
Imagine that you have this module
pigeon% cat hab.rb
module Hab
def biv(attributes)
attributes.each do |k, v|
name = k.id2name
instance_eval(<<-EOS)
class << self
attr_accessor :#{name}
end
EOS
send("#{name}=", v)
end
end
end
pigeon%
because you find it usefull, you always use it and one day you'll write
pigeon% cat b.rb
#!/usr/bin/ruby
require 'hab'
class A
include Hab
end
Thread.new do
$SAFE = 3
a = A.new
a.biv(:aaa => 12)
p a.aaa
end.join
pigeon%
pigeon% b.rb
./hab.rb:5:in `instance_eval': Insecure operation - instance_eval (SecurityError)
from ./b.rb:7:in `join'
from ./b.rb:7
pigeon%
You have an usefull module, but this module is just useless because it
can't be used with $SAFE = 3.
Now rewrite it
pigeon% cat hab.rb
module Hab
def biv(attributes)
attributes.each do |k, v|
type.send(:attr_accessor, k)
send("#{k}=", v)
end
end
end
pigeon%
pigeon% cat b.rb
#!/usr/bin/ruby
require 'hab'
class A
include Hab
end
Thread.new do
$SAFE = 3
a = A.new
a.biv(:aaa => 12)
p a.aaa
end.join
pigeon%
pigeon% b.rb
12
pigeon%
This module do the same thing and it still work with $SAFE = 3. Each time
that you use #eval means that you'll not be able to use your module with
$SAFE = 3
Guy Decoux
> pigeon% cat hab.rb
> module Hab
> def biv(attributes)
> attributes.each do |k, v|
> type.send(:attr_accessor, k)
> send("#{k}=", v)
> end
> end
Ah! It suddenly clicked into place what type.send was doing. Very neat.
--
Martin DeMello
I think this idiom ought to be added to Hal's "Ruby for Perl Programmers"
talk. Named arguments are one of those things that Perl hackers have come
to get used to, and are extremely handy in certain situations (e.g. Tk/GUI
programming). Until this thread, I had thought it required an anonymous
hash.
I'm glad I asked. :)
Regards,
Dan