OpenStruct respond_to? problem

J

Jamis Buck

I blindly assumed that OpenStruct would handle respond_to? properly, but
I'm seeing the following behavior:

o = OpenStruct.new( :type_id => "string" )
p o.type_id #-> "string"
p o.respond_to?( :type_id ) #-> false

Is this the correct behavior for OpenStruct, or should this be reported
as a bug?

- Jamis

--
Jamis Buck
(e-mail address removed)
http://www.jamisbuck.org/jamis

"I use octal until I get to 8, and then I switch to decimal."
 
Z

Zach Dennis

I am looking for a structure like:

my_obj = boxWidget.new
with my_obj do
x = 0
y = 10
width = 100
height = 100
end

instead of having to say

my_obj = boxWidget.new
my_obj.x = 0
my_obj.y = 10
my_obj.width = 100
my_obj.height = 100



Can you do this already? If not....anyone support an RCR for it besides me?

Zach
 
J

Jamis Buck

Zach said:
I am looking for a structure like:

my_obj = boxWidget.new
with my_obj do
x = 0
y = 10
width = 100
height = 100
end

instead of having to say

my_obj = boxWidget.new
my_obj.x = 0
my_obj.y = 10
my_obj.width = 100
my_obj.height = 100



Can you do this already? If not....anyone support an RCR for it besides me?

No such beast "out of the box," but there have been various discussions
on the list about this feature. I believe one implementation presented was:

class Object
def with(obj, &block)
obj.instance_eval &block
end
end

with "hello" do
puts length
puts reverse
end

- Jamis

--
Jamis Buck
(e-mail address removed)
http://www.jamisbuck.org/jamis

"I use octal until I get to 8, and then I switch to decimal."
 
T

T. Onoma

my_obj = boxWidget.new
with my_obj do
x = 0
y = 10
width = 100
height = 100
end

Sure. This has been talked about before. Unfortunately I can't seem to find
anything on it. Ruby-talk searches really blow (sorry, but its true).

Someone wrote the code for it before, (anyone?) but in my experience it wasn't
very useful. It prevents easy code refactoring.
 
D

David A. Black

Hi --

I am looking for a structure like:

my_obj = boxWidget.new
with my_obj do
x = 0
y = 10
width = 100
height = 100
end

instead of having to say

my_obj = boxWidget.new
my_obj.x = 0
my_obj.y = 10
my_obj.width = 100
my_obj.height = 100



Can you do this already? If not....anyone support an RCR for it besides me?

Every assignment to a bare identifier (x = 0) is parsed as an
assignment to a local variable. If you want to call a "="-style
method, you have to give the receiver explicitly. So I don't think a
"with" keyword would add much to what you can already do with
instance_eval:

my_obj = BoxWidget.new
my_obj.instance_eval do
self.x = 0
self.y = 0
...
end

which then isn't a whole lot better than my_obj.x = 0 and so on.

Another thing people sometimes do is:

my_obj = BoxWidget.new do |bw|
bw.x = 0
bw.y = 10
...
end

which can be nice because it packages the initialization with the
creation of the object, but of course it only works if
BoxWidget#initialize yields the new object.


David
 
Z

zuzu

Hi --





Every assignment to a bare identifier (x = 0) is parsed as an
assignment to a local variable. If you want to call a "="-style
method, you have to give the receiver explicitly. So I don't think a
"with" keyword would add much to what you can already do with
instance_eval:

my_obj = BoxWidget.new
my_obj.instance_eval do
self.x = 0
self.y = 0
...
end

which then isn't a whole lot better than my_obj.x = 0 and so on.

Another thing people sometimes do is:

my_obj = BoxWidget.new do |bw|
bw.x = 0
bw.y = 10
...
end

which can be nice because it packages the initialization with the
creation of the object, but of course it only works if
BoxWidget#initialize yields the new object.

David

my very first thought was of .class_eval, but "obviously"
instance_eval is correct.

i find this interesting because this seems to me to be the way to do
prototype-based objects rather than class-based objects in ruby;
similar to the Self language w/r/t smalltalk. (though offhand i'm not
sure how an object would get cloned before doing this modifying.)

peace,
-z
 
A

Austin Ziegler

I blindly assumed that OpenStruct would handle respond_to? properly, but
I'm seeing the following behavior:

o = OpenStruct.new( :type_id => "string" )
p o.type_id #-> "string"
p o.respond_to?( :type_id ) #-> false

Is this the correct behavior for OpenStruct, or should this be reported
as a bug?

I think that OpenStruct uses #method_missing.

-austin
 
D

David A. Black

Hi --

my very first thought was of .class_eval, but "obviously"
.instance_eval is correct.

i find this interesting because this seems to me to be the way to do
prototype-based objects rather than class-based objects in ruby;
similar to the Self language w/r/t smalltalk. (though offhand i'm not
sure how an object would get cloned before doing this modifying.)

There's not really much object modifying here, compared to what you
*can* do :) (adding methods to individual objects, etc.) In general
I tend to think of Ruby objects as not very class-based: once an
object has been launched into objectspace by its class, it follows a
path of its own, in which the class of origin may play a role but
which is not constrained by it. (And of course Class objects
themselves can change.)


David
 
G

Gavin Sinclair

I think that OpenStruct uses #method_missing.

Indeed it does. If respond_to? is to work, you'd need to implement
it. Utterly untested:

class OpenStruct
def respond_to?(sym)
super or @table.key?(sym.to_sym)
end
end

There's probably several problems with OpenStruct along these lines.
I don't think #hash is implemented, for example.

Gavin
 
J

Jamis Buck

Austin said:
I think that OpenStruct uses #method_missing.

I think you're right. However, that doesn't preclude the possibility of
OpenStruct also implementing a custom "respond_to?" method... Would this
be desirable, or would it have other (negative) consequences?

- Jamis

--
Jamis Buck
(e-mail address removed)
http://www.jamisbuck.org/jamis

"I use octal until I get to 8, and then I switch to decimal."
 
R

Robert Klemme

Jamis Buck said:
Zach said:
I am looking for a structure like:

my_obj = boxWidget.new
with my_obj do
x = 0
y = 10
width = 100
height = 100
end

instead of having to say

my_obj = boxWidget.new
my_obj.x = 0
my_obj.y = 10
my_obj.width = 100
my_obj.height = 100



Can you do this already? If not....anyone support an RCR for it besides
me?

No such beast "out of the box," but there have been various discussions
class Object
def with(obj, &block)
obj.instance_eval &block
end
end

with "hello" do
puts length
puts reverse
end

Doesn't work for member init since all statements of the form "x=y" create
local variables in the block. I'd prefer something like:

class Object
def member_init(map)
map.each do |name, val|
send "#{name}=", val
end
self
end
end

ost = OpenStruct.new.member_init(
:name => "foo",
:age => 10
)

Kind regards

robert
 
Z

Zach Dennis

Thanks everyone who replied....I like the instance_eval solution,
although I think it would be nice if *with* were on a Object level, like
Jamis Buck first posted...

class Object
def with( obj , &block )
obj.instance_eval &block
end
end

class Test
def initialize
@x = 5
end
def x
return x
end
end

t = Test.new
with t do
puts @x
x = 35
@x = 35
end

puts t.x

I think the *with* block acts as expected as far as instance variables
and local variables are conerned. When you initialize an object you have
to specify the '@' or 'self.' before the variable name. So it makes
sense that in the *block* it would be treated the same.

I think it is so much cleaner to say:

with obj do
...
end

rather then:

obj.instance_eval do

end

I guess there isn't a huge difference and I might just be being picky,
but I totally think the *with* block is a much cleaner solution.

Zach
 
Z

Zach Dennis

Also with the with block being how Jamis Buck and David A. Black posted
it works nice with methods as well.

def talk
puts "i'm talking"
end

def Test
def test
puts "i'm testing"
end
end

t = Test.new
with t do
talk
test
end

'talk' is called correctly because it is in the current scope of the
*with* block and 'test' is called correctly because it exists on object
't'.

I think this would be expected behavior as well. If someone was worried
about method confusion, they could simply say "self." like David A.
Black suggested in one of his original posts on this thread.

And it doesn't add complexity to how things work in ruby. It just
appears to be a more elegant solution, syntactically.

Zach
 
J

Jim Weirich

Zach Dennis said:
Also with the with block being how Jamis Buck and David A. Black posted
it works nice with methods as well.

def talk
puts "i'm talking"
end

def Test
def test
puts "i'm testing"
end
end

t = Test.new
with t do
talk
test
end

'talk' is called correctly because it is in the current scope of the
*with* block and 'test' is called correctly because it exists on object
't'.

The above works, but some very similar code is broken ... For example:
class Test
def test
puts "i'm testing"
end
end

class Dog
def talk
puts "i'm talking"
end

def testing_dog
talk # Class Dog#talk
t = Test.new
with t do
test # Calls Test#test
talk # ERROR: undefined local variable or method `talk'
end
end
end

This is because the second call to talk is not evaluated in the lexical
scope of the calling object Dog, but within the scope of the Test object.
Dog#talk exists, but Test#talk does not. (Your example missed the problem
by putting +talk+ into the global scope.

There is an awkward work around. First write +with+ like this:

module Kernel
def with(obj, &block)
obj.instance_eval { @self = eval("self", block) }
obj.instance_eval &block
end
end

Then reference Dog#talk like this ...

class Dog
def talk
puts "i'm talking"
end

def testing_dog
talk # Class Dog#talk
t = Test.new
with t do
test # Calls Test#test
@self.talk # Calls Dog#talk
end
end
end
 
J

Jim Weirich

Zach Dennis said:
Also with the with block being how Jamis Buck and David A. Black posted
it works nice with methods as well.

def talk
puts "i'm talking"
end

def Test
def test
puts "i'm testing"
end
end

t = Test.new
with t do
talk
test
end

'talk' is called correctly because it is in the current scope of the
*with* block and 'test' is called correctly because it exists on object
't'.

The above works, but some very similar code is broken ... For example:
class Test
def test
puts "i'm testing"
end
end

class Dog
def talk
puts "i'm talking"
end

def testing_dog
talk # Class Dog#talk
t = Test.new
with t do
test # Calls Test#test
talk # ERROR: undefined local variable or method `talk'
end
end
end

This is because the second call to talk is not evaluated in the lexical
scope of the calling object Dog, but within the scope of the Test object.
Dog#talk exists, but Test#talk does not. (Your example missed the problem
by putting +talk+ into the global scope.

There is an awkward work around. First write +with+ like this:

module Kernel
def with(obj, &block)
obj.instance_eval { @self = eval("self", block) }
obj.instance_eval &block
end
end

Then reference Dog#talk like this ...

class Dog
def talk
puts "i'm talking"
end

def testing_dog
talk # Class Dog#talk
t = Test.new
with t do
test # Calls Test#test
@self.talk # Calls Dog#talk
end
end
end
 
Z

Zach Dennis

Jim said:
Zach Dennis said:



The above works, but some very similar code is broken ... For example:
class Test
def test
puts "i'm testing"
end
end

class Dog
def talk
puts "i'm talking"
end

def testing_dog
talk # Class Dog#talk
t = Test.new
with t do
test # Calls Test#test
talk # ERROR: undefined local variable or method `talk'
end
end
end

This is because the second call to talk is not evaluated in the lexical
scope of the calling object Dog, but within the scope of the Test object.
Dog#talk exists, but Test#talk does not. (Your example missed the problem
by putting +talk+ into the global scope.

There is an awkward work around. First write +with+ like this:

module Kernel
def with(obj, &block)
obj.instance_eval { @self = eval("self", block) }
obj.instance_eval &block
end
end

Then reference Dog#talk like this ...

class Dog
def talk
puts "i'm talking"
end

def testing_dog
talk # Class Dog#talk
t = Test.new
with t do
test # Calls Test#test
@self.talk # Calls Dog#talk
end
end
end
I see what you mean, I like your solution. It seems quite elegant! What
are your thoughts on an RCR for this functionality Jim?

Zach
 
D

David A. Black

Hi --

The above works, but some very similar code is broken ... For example:
class Test
def test
puts "i'm testing"
end
end

class Dog
def talk
puts "i'm talking"
end

def testing_dog
talk # Class Dog#talk
t = Test.new
with t do
test # Calls Test#test
talk # ERROR: undefined local variable or method `talk'
end
end
end

This is because the second call to talk is not evaluated in the lexical
scope of the calling object Dog, but within the scope of the Test object.
Dog#talk exists, but Test#talk does not. (Your example missed the problem
by putting +talk+ into the global scope.

There is an awkward work around. First write +with+ like this:

module Kernel
def with(obj, &block)
obj.instance_eval { @self = eval("self", block) }
obj.instance_eval &block
end
end

Then reference Dog#talk like this ...

class Dog
def talk
puts "i'm talking"
end

def testing_dog
talk # Class Dog#talk
t = Test.new
with t do
test # Calls Test#test
@self.talk # Calls Dog#talk
end
end
end

You could also do:

def testing_dog
s = self
t = Test.new
talk
with t do
test
s.talk
end
end

(i.e., put the burden of providing a reference to self-the-Dog to on
the method, rather than on 'with')


David
 
M

Mauricio Fernández

There is an awkward work around. First write +with+ like this:

module Kernel
def with(obj, &block)
obj.instance_eval { @self = eval("self", block) }
obj.instance_eval &block
end
end
[...]
You could also do:

def testing_dog
s = self
t = Test.new
talk
with t do
test
s.talk
end
end

(i.e., put the burden of providing a reference to self-the-Dog to on
the method, rather than on 'with')

It also has the additional benefit of being thread-safe, or more
precisely not as thread-unsafe :p
Also, polluting the original object with another i.v. feels very wrong.

You could also do

batsman@tux-chan:/tmp$ cat fdgdfg.rb
def with(obj, &block)
o = Object.new
s = self
# I'd use define_method but {|*a,&b| ...} is 1.9 only
o.instance_eval{ @self = s; @obj = obj }
class << o
def method_missing(meth,*a,&b)
@obj.send(meth, *a, &b)
end
end
o.instance_eval(&block)
end

class Foo
def foo
puts "Foo#foo"
with(Bar.new){ bar; @self.babar{ puts "foo" } }
end
def babar
puts "1931"
yield
end
end

class Bar; def bar; puts "Bar#bar" end end

Foo.new.foo


batsman@tux-chan:/tmp$ ruby fdgdfg.rb
Foo#foo
Bar#bar
1931
foo

But I don't really like it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,139
Latest member
JamaalCald
Top