Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > What do you do when you need to attach data to an object instance?

Reply
Thread Tools

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

 
 
Aaron D. Gifford
Guest
Posts: n/a
 
      04-14-2011
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:Key::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:Key::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:Key
include OpenStructModule
end

???

Aaron out.

 
Reply With Quote
 
 
 
 
Kevin Mahler
Guest
Posts: n/a
 
      04-14-2011
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:Key
> 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

--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
 
 
 
Aaron D. Gifford
Guest
Posts: n/a
 
      04-14-2011
On Thu, Apr 14, 2011 at 3:11 PM, Kevin Mahler <(E-Mail Removed)> wrot=
e:
> 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.

 
Reply With Quote
 
7stud --
Guest
Posts: n/a
 
      04-14-2011
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:Key::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:Key::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:Key
> 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

--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Aaron D. Gifford
Guest
Posts: n/a
 
      04-14-2011
> On Thu, Apr 14, 2011 at 3:11 PM, Kevin Mahler <(E-Mail Removed)> 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(ublic).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(ublic).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(ublic) 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.

 
Reply With Quote
 
7stud --
Guest
Posts: n/a
 
      04-15-2011
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.

--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
7stud --
Guest
Posts: n/a
 
      04-15-2011
Aaron D. Gifford wrote in post #992887:
>
> Now another question. I noticed that if I don't include the
> send(ublic) 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:--

--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Kevin Mahler
Guest
Posts: n/a
 
      04-15-2011
Aaron D. Gifford wrote in post #992887:
> def extend_accessor(obj, name)
> mod = Module.new
> mod.send(ublic).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.

--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Aaron D. Gifford
Guest
Posts: n/a
 
      04-15-2011
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.

 
Reply With Quote
 
Robert Klemme
Guest
Posts: n/a
 
      04-15-2011
On Fri, Apr 15, 2011 at 8:35 AM, Aaron D. Gifford <(E-Mail Removed)> wr=
ote:
> 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/

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
pry - attach an irb-like session to any object at runtime - 0.6.0released John Mair Ruby 0 02-22-2011 03:43 AM
Is there any shortcut for "Debug > Attach > Attach to aspnet_wp.exe"? Warren Tang ASP .Net 1 09-17-2008 03:46 PM
attach agent to applet running in firefox (attach api) craiget@gmail.com Java 0 06-14-2007 12:28 AM
Javascript Object Attach event not working bhatiaajay@gmail.com Javascript 4 10-24-2005 08:01 PM
Object creation - Do we really need to create a parent for a derieved object - can't the base object just point to an already created base object jon wayne C++ 9 09-22-2005 02:06 AM



Advertisments