What do you do when you need to attach data to an object instance?

A

Aaron D. Gifford

What do you do when you see a need to be able to attach some data to
an object instance for later use somewhere else in a body of code?
Lately I've resorted to this:

## Generic utility to allow one to attach data with a getter/setter to
## any instance of any object so long as there isn't a method name
## collision:
def attach_data(obj, name, data)
getter = name.to_sym
setter = (name.to_s + '=').to_sym
raise "method name collision for #{obj.class} instance" if
obj.respond_to?(getter) || obj.respond_to?(setter)
## The 'value' local variable will remain in existence in the lambda
closures below:
value = data
meta = class << obj ; self ; end
meta.send:)define_method, getter, lambda { value }) ##
Getter closure
meta.send:)define_method, setter, lambda {|val| value = val }) ##
Setter closure
value
end

For example, in an application using SSH keys, I didn't want to create
a new subclass, nor use an array or hash container instance just to
carry an OpenSSL::pKey::RSA object instance around the code. But I
needed to associate a user ID (user@host) to a key so it could be
accessed somewhere else. I figured it was easiest to just attach it
to the OpenSSL::pKey::RSA instance (see code above) directly. That
made the code cleaner, portions that only required the RSA key
directly, yet still gave the benefit of the key instance containing
the additional meta data I required.

What do you do when you need stuff like that? Monkey patch? Use a
container and pass it around instead? Or?

Is there a module version of OpenStruct that one can just include in
whatever class one wants to attach additional arbitrary data to? So I
could have done this instead:

require 'ostructmod'
class OpenSSL::pKey
include OpenStructModule
end

???

Aaron out.
 
K

Kevin Mahler

Aaron D. Gifford wrote in post #992841:
What do you do when you see a need to be able to attach some data to
an object instance for later use somewhere else in a body of code? ...
require 'ostructmod'
class OpenSSL::pKey
include OpenStructModule
end

You probably wouldn't want OpenStructModule even if it did exist because
you wouldn't want NoMethodError to be suppressed. Perhaps you're looking
for Object#extend.

module UserId
attr_accessor :user_id
end
thing = Object.new # whatever the thing is
thing.extend(UserId).user_id = 123456
p thing.user_id #=>123456
 
A

Aaron D. Gifford

You probably wouldn't want OpenStructModule even if it did exist because
you wouldn't want NoMethodError to be suppressed. Perhaps you're looking
for Object#extend.

=A0module UserId
=A0 =A0attr_accessor :user_id
=A0end
=A0thing =3D Object.new =A0# whatever the thing is
=A0thing.extend(UserId).user_id =3D 123456
=A0p thing.user_id =A0#=3D>123456

Nice. Thank you!

Aaron out.
 
7

7stud --

Aaron D. Gifford wrote in post #992841:
What do you do when you see a need to be able to attach some data to
an object instance for later use somewhere else in a body of code?
Lately I've resorted to this:

## Generic utility to allow one to attach data with a getter/setter to
## any instance of any object so long as there isn't a method name
## collision:
def attach_data(obj, name, data)
getter = name.to_sym
setter = (name.to_s + '=').to_sym
raise "method name collision for #{obj.class} instance" if
obj.respond_to?(getter) || obj.respond_to?(setter)
## The 'value' local variable will remain in existence in the lambda
closures below:
value = data
meta = class << obj ; self ; end
meta.send:)define_method, getter, lambda { value }) ##
Getter closure
meta.send:)define_method, setter, lambda {|val| value = val }) ##
Setter closure
value
end

For example, in an application using SSH keys, I didn't want to create
a new subclass, nor use an array or hash container instance just to
carry an OpenSSL::pKey::RSA object instance around the code. But I
needed to associate a user ID (user@host) to a key so it could be
accessed somewhere else. I figured it was easiest to just attach it
to the OpenSSL::pKey::RSA instance (see code above) directly. That
made the code cleaner, portions that only required the RSA key
directly, yet still gave the benefit of the key instance containing
the additional meta data I required.

What do you do when you need stuff like that? Monkey patch? Use a
container and pass it around instead? Or?

How about a decorator pattern?

Is there a module version of OpenStruct that one can just include in
whatever class one wants to attach additional arbitrary data to? So I
could have done this instead:

require 'ostructmod'
class OpenSSL::pKey
include OpenStructModule
end

???

Aaron out.

class SSL
def talk
puts 'hi'
end
end

class MyWrapper
def initialize(ssl_obj, key)
@ssl_obj = ssl_obj
@key = key
end

attr_accessor :ssl_obj, :key

def method_missing(name, *args)
@ssl_obj.send(name, *args)
end
end
 
A

Aaron D. Gifford

So using Kevin's suggestion:

def extend_accessor(obj, name)
mod = Module.new
mod.send:)public).send:)attr_accessor, name.to_sym)
obj.extend(mod)
obj
end

That's a cleaner way to attach data:

irb(main):001:0> def extend_accessor(obj, name)
irb(main):002:1> mod = Module.new
irb(main):003:1> mod.send:)public).send:)attr_accessor, name.to_sym)
irb(main):004:1> obj.extend(mod)
irb(main):005:1> obj
irb(main):006:1> end
=> nil
irb(main):007:0> a = "this is a string"
=> "this is a string"
irb(main):008:0> extend_accessor(a, :bar)
=> "this is a string"
irb(main):009:0> a.bar
=> nil
irb(main):010:0> a.bar = "hi there"
=> "hi there"

Now another question. I noticed that if I don't include the
send:)public) bit up there, that the send:)attr_accessor) message
delivered to the anonymous module ends up creating private accessor
methods, not public.

I find that puzzling:

irb(main):001:0> def extend_accessor(obj, name)
irb(main):002:1> mod = Module.new
irb(main):003:1> mod.send:)attr_accessor, name.to_sym)
irb(main):004:1> obj.extend(mod)
irb(main):005:1> obj
irb(main):006:1> end
=> nil
irb(main):007:0> a = "this is a string"
=> "this is a string"
irb(main):008:0> extend_accessor(a, :bar)
=> "this is a string"
irb(main):009:0> a.bar = "hi there"
NoMethodError: private method `bar=' called for "this is a string":String
from (irb):9
from /opt/local/bin/irb:12:in `<main>'
irb(main):010:0>

Any ideas why sending :attr_accessor to a module would create
accessors as private?

I would think that attr_accessor implies public, but I guess if by
default an anonymous module is in "private" mode, that might explain
it.

However, if that's the case, why does this work to create public methods:
def extend_method(obj, name, &block)
mod = Module.new
mod.send:)define_method, name, &block)
obj.extend(mod)
obj
end

Sending define_method (at least for me) to an anonymous Module seems
to default to "public" mode. This seems a tad inconsistent.

All this was on Ruby 1.9.2 on a FreeBSD box.

Aaron out.
 
7

7stud --

Aaron D. Gifford wrote in post #992841:
What do you do when you see a need to be able to attach some data to
an object instance for later use somewhere else in a body of code?
Lately I've resorted to this:

## Generic utility to allow one to attach data with a getter/setter to
## any instance of any object so long as there isn't a method name
## collision:
def attach_data(obj, name, data)
getter = name.to_sym
setter = (name.to_s + '=').to_sym
raise "method name collision for #{obj.class} instance" if
obj.respond_to?(getter) || obj.respond_to?(setter)
## The 'value' local variable will remain in existence in the lambda
closures below:
value = data
meta = class << obj ; self ; end
meta.send:)define_method, getter, lambda { value })
meta.send:)define_method, setter, lambda {|val| value = val })

1) Note that you don't need to use send() there -- but maybe that's your
preferred closure? Once you have the singleton class, you can define
methods on it like this:

def attach_data(obj, name, data)
getter = name.to_sym
setter = (name.to_s + '=').to_sym

raise "method name collision for #{obj.class} instance" if
obj.respond_to?(getter) || obj.respond_to?(setter)

#value = data

singleton = class <<obj
self
end

singleton.class_eval do
define_method(getter) do
data
end

define_method(setter) do |x|
data = x
end
end


#meta.send:)define_method, getter, lambda { value })
#meta.send:)define_method, setter, lambda {|val| value = val })

#value
obj
end


2) I don't understand why you are creating a new local variable called
value and closing over that? data is also a local variable and you can
create a closure over that. Additional calls to attach_data() will
create new local variables--including data, so if you call attach_data()
twice data will not be shared.
 
7

7stud --

Aaron D. Gifford wrote in post #992887:
Now another question. I noticed that if I don't include the
send:)public) bit up there, that the send:)attr_accessor) message
delivered to the anonymous module ends up creating private accessor
methods, not public.

I find that puzzling:

Me too:

module Mod
attr_accessor :data

def do_stuff
end
end

p Mod.public_instance_methods.grep(/^d/)

--output:--
[:data, :data=, :do_stuff]


Test = Module.new
Test.send:)attr_accessor, :data)
Test.send:)define_method, :do_stuff, Proc.new {puts 'hi'})
p Test.public_instance_methods.grep(/^d/)
p Test.private_instance_methods.grep(/^d/)

--output:--
 
K

Kevin Mahler

Aaron D. Gifford wrote in post #992887:
def extend_accessor(obj, name)
mod = Module.new
mod.send:)public).send:)attr_accessor, name.to_sym)
obj.extend(mod)
obj
end

Or more compactly,

def extend_accessor(obj, name)
obj.extend Module.new { attr_accessor name }
obj
end

But that's an odd maneuver anyway. My original example extended an
object with a specified, named mixin. For ad hoc methods the singleton
class is more natural.

def extend_accessor(obj, name)
obj.singleton_class.module_eval { attr_accessor name }
obj
end

You probably found a bug with attr_accessor; it should produce public
methods in any context, I think. Nobody has encountered the bug
because Module.new { } and module_eval { } are commonly used to do
those things, and those behave correctly.
 
A

Aaron D. Gifford

Thanks 7stud for the food for thought. And thanks again, Kevin, for
pointing me to some much easier ways to do what I want.

Aaron out.
 
R

Robert Klemme

Thanks 7stud for the food for thought. =A0And thanks again, Kevin, for
pointing me to some much easier ways to do what I want.

I'd still like to muse a bit about the wisdom of doing this. The main
problem that can arise from the approach to modify an existing class
or instance belonging to another component is a possible name clash.
Even if you know you are safe with the current version, it may happen
that an updated version later will introduce the exact attribute that
you are adding on the fly now which will likely have bad effects.

I do not know the specifics of your use case but of course using
delegation does not have this name clash issue. Your solution might
be as easy as

SSLContext =3D Struct.new :key, :user_id

You could also add more functionality to this class but as said, that
totally depends on your use case. I tend to favor composition over
inheritance (or modification) nowadays since it is often more modular.
Granted, in some places you need more boilerplate code (e.g. reading
an attribute before invoking the method that you really want) but you
do not end stuck with tightly coupled (e.g. via inheritance) classes
that you cannot easily untangle.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
7

7stud --

Robert K. wrote in post #992956:
Robert K.,

Any ideas why attr_accessor() creates public methods when placed
directly in a module, but if you send() :attr_accessor to a module, it
creats private methods?
 
R

Robert Klemme

Robert K. wrote in post #992956:

Robert K.,

Any ideas why attr_accessor() creates public methods when placed
directly in a module, but if you send() :attr_accessor to a module, it
creats private methods?

No. I wasn't even aware of the fact. :)

Cheers

robert
 
7

7stud --

Kevin Mahler wrote in post #992907:
def extend_accessor(obj, name)
obj.singleton_class.module_eval { attr_accessor name }
obj
end

Hey, now. obj.singleton_class is a class so how about using the synonym
class_eval():

obj.singleton_class.class_eval { attr_accessor name }
 
K

Kevin Mahler

7stud -- wrote in post #993115:
Kevin Mahler wrote in post #992907:

Hey, now. obj.singleton_class is a class, so how about using the
synonym class_eval():

obj.singleton_class.class_eval { attr_accessor name }

A class is a module, but a module is not a class.

Class.new.is_a? Module #=>true
Module.new.is_a? Class #=>false

Because module_eval and class_eval are aliases, class_eval can be
called on a module, a situation which is at worst wrong and at best
confusing. module_eval can never be wrong or confusing. Not a hard
choice.
 
7

7stud --

Kevin Mahler wrote in post #993207:
7stud -- wrote in post #993115:

A class is a module, but a module is not a class.

Yes, of course.

Class.new.is_a? Module #=>true
Module.new.is_a? Class #=>false

Because module_eval and class_eval are aliases, class_eval can be
called on a module, a situation which is at worst wrong

It certainly isn't "wrong"--ruby allows it.
and at best
confusing. module_eval can never be wrong or confusing. Not a hard
choice.

I thought using module_eval was confusing in your code, and that was why
I suggested class_eval().
 
K

Kevin Mahler

7stud
It certainly isn't "wrong"--ruby allows it.

Wrong is not synonymous with illegal. One could easily argue that the
alias is a mistake. class_eval, as the name suggests, should have been
a more restricted version of module_eval. Allowing class_eval to have
a module receiver is wrong because a module is not a class. I
presented this as the "at worst" case--one end of the spectrum.
I thought using module_eval was confusing in your code, and that was why
I suggested class_eval().

A class is a module.
 
A

Adam Prescott

[Note: parts of this message were removed to make it a legal post.]

A class is a module.


I think the point is that classes and modules, while related in the way
you've described, are still different. You don't instantiate or subclass
modules, and you don't include classes. I too found module_eval less
intuitive than class_eval, knowing that the receiver is a class, even
knowing that a class "is a" module.
 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top