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

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 ([email protected]) 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.

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

On Thu, Apr 14, 2011 at 3:11 PM, Kevin M. [email protected]
wrote:

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

Nice. Thank you!

Aaron out.

Aaron D. Gifford wrote in post #992841:

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?

class SSLObj
def talk
puts ‘hi’
end
end

class MyDecorator
def initialize(ssl_obj, val)
@ssl_obj = ssl_obj
@key = val
end

attr_reader :key

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

ssl = SSLObj.new
d = MyDecorator.new(ssl, 12345)

puts d.key
d.talk

d.do_stuff #non-existent method

–output:–
12345
hi
prog.rb:16:in method_missing': undefined methoddo_stuff’ for
#SSLObj:0x8c9a448 (NoMethodError)
from prog.rb:24:in `’

On Thu, Apr 14, 2011 at 3:11 PM, Kevin M. [email protected] wrote:

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.

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
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.

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

  1. 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 the data variable will not be shared.

  2. I don’t know if returning value/data from attach_data() makes much
    sense.

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 #(self.)attr_accessor where self=Mod

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:–
[:do_stuff]
[:data, :data=]

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.

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.

On Fri, Apr 15, 2011 at 8:35 AM, Aaron D. Gifford [email protected]
wrote:

Thanks 7stud for the food for thought. And 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 = 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

On 15.04.2011 20:40, 7stud – wrote:

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. :slight_smile:

Cheers

robert

Kevin M. 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 }

7stud – wrote in post #993115:

Kevin M. 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 }

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.

Kevin M. wrote in post #993207:

7stud – wrote in post #993115:

Kevin M. 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 }

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.

I agree.

module_eval can never be wrong or confusing. Not a hard
choice.

Similarly, I thought using module_eval on a class was confusing in your
code, and that was why I suggested class_eval(). In my opinion, if a
programmer wants to be clear they should use class_eval() on a class and
module_eval on a module. I assume that is why Matz put both in the
language.

Robert K. wrote in post #992956:

Robert K.,

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

7stud

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.

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.

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().

A class is a module.

On Sat, Apr 16, 2011 at 6:53 PM, Kevin M.
[email protected]wrote:

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().

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.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs