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

Discussion in 'Ruby' started by Aaron D. Gifford, Apr 14, 2011.

  1. 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.
     
    Aaron D. Gifford, Apr 14, 2011
    #1
    1. Advertising

  2. Aaron D. Gifford

    Kevin Mahler Guest

    Re: What do you do when you need to attach data to an objectinstance?

    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

    --
    Posted via http://www.ruby-forum.com/.
     
    Kevin Mahler, Apr 14, 2011
    #2
    1. Advertising

  3. Re: What do you do when you need to attach data to an objectinstance?

    On Thu, Apr 14, 2011 at 3:11 PM, Kevin Mahler <> 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.
     
    Aaron D. Gifford, Apr 14, 2011
    #3
  4. Aaron D. Gifford

    7stud -- Guest

    Re: What do you do when you need to attach data to an objectinstance?

    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

    --
    Posted via http://www.ruby-forum.com/.
     
    7stud --, Apr 15, 2011
    #4
  5. Re: What do you do when you need to attach data to an objectinstance?

    > On Thu, Apr 14, 2011 at 3:11 PM, Kevin Mahler <> 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 `<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.
     
    Aaron D. Gifford, Apr 15, 2011
    #5
  6. Aaron D. Gifford

    7stud -- Guest

    Re: What do you do when you need to attach data to an objectinstance?

    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/.
     
    7stud --, Apr 15, 2011
    #6
  7. Aaron D. Gifford

    7stud -- Guest

    Re: What do you do when you need to attach data to an objectinstance?

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

    --
    Posted via http://www.ruby-forum.com/.
     
    7stud --, Apr 15, 2011
    #7
  8. Aaron D. Gifford

    Kevin Mahler Guest

    Re: What do you do when you need to attach data to an objectinstance?

    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.

    --
    Posted via http://www.ruby-forum.com/.
     
    Kevin Mahler, Apr 15, 2011
    #8
  9. Re: What do you do when you need to attach data to an objectinstance?

    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.
     
    Aaron D. Gifford, Apr 15, 2011
    #9
  10. Re: What do you do when you need to attach data to an objectinstance?

    On Fri, Apr 15, 2011 at 8:35 AM, Aaron D. Gifford <> 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/
     
    Robert Klemme, Apr 15, 2011
    #10
  11. Aaron D. Gifford

    7stud -- Guest

    Re: What do you do when you need to attach data to an objectinstance?

    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?

    --
    Posted via http://www.ruby-forum.com/.
     
    7stud --, Apr 15, 2011
    #11
  12. Re: What do you do when you need to attach data to an object instance?

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

    Cheers

    robert

    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Apr 15, 2011
    #12
  13. Aaron D. Gifford

    7stud -- Guest

    Re: What do you do when you need to attach data to an objectinstance?

    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 }

    --
    Posted via http://www.ruby-forum.com/.
     
    7stud --, Apr 16, 2011
    #13
  14. Aaron D. Gifford

    Kevin Mahler Guest

    Re: What do you do when you need to attach data to an objectinstance?

    7stud -- wrote in post #993115:
    > 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 }


    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.

    --
    Posted via http://www.ruby-forum.com/.
     
    Kevin Mahler, Apr 16, 2011
    #14
  15. Aaron D. Gifford

    7stud -- Guest

    Re: What do you do when you need to attach data to an objectinstance?

    Kevin Mahler wrote in post #993207:
    > 7stud -- wrote in post #993115:
    >> 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 }

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

    --
    Posted via http://www.ruby-forum.com/.
     
    7stud --, Apr 16, 2011
    #15
  16. Aaron D. Gifford

    Kevin Mahler Guest

    Re: What do you do when you need to attach data to an objectinstance?

    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.

    --
    Posted via http://www.ruby-forum.com/.
     
    Kevin Mahler, Apr 16, 2011
    #16
  17. Re: What do you do when you need to attach data to an objectinstance?

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

    On Sat, Apr 16, 2011 at 6:53 PM, Kevin Mahler <>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.
     
    Adam Prescott, Apr 18, 2011
    #17
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Marcus Leon
    Replies:
    2
    Views:
    408
    Marcus Leon
    Feb 3, 2006
  2. Gerard Flanagan
    Replies:
    3
    Views:
    477
    Terry Hancock
    Nov 19, 2005
  3. Replies:
    0
    Views:
    677
  4. Warren Tang
    Replies:
    1
    Views:
    564
    Warren Tang
    Sep 17, 2008
  5. Geoff Cheshire
    Replies:
    8
    Views:
    130
    Bret Pettichord
    Jan 26, 2007
Loading...

Share This Page