« Return to Thread: simple way to encapsulate class << self ; attr_accessor :xyz ; end?

Re: simple way to encapsulate class << self ; attr_accessor :xyz ; end?

by Pat Maddox :: Rate this Message:

Reply to Author | View in Thread

On 9/7/07, Giles Bowkett <gilesb@...> wrote:

> I'm writing some code which works in the context of a very popular Web
> framework and yet bumps against limitations in that framework several
> times a day. One thing I have to do **constantly** is this:
>
> def foo
>   class << bar
>     attr_accessor :baz
>   end
>   do_stuff(bar.baz)
> end
>
> or sometimes even
>
> def foo
>   instance_eval do
>     class << self
>       attr_accessor :bar
>     end
>   end
>   do_stuff(bar)
> end
>
> This pattern gets ugly fast. It would be so much easier if I could just do
>
> foo.add_accessor(:bar)
>
> and get the same functionality as
>
> class << foo
>   attr_accessor :bar
> end
>
> so I tried to graft this onto the base object:
>
> class BaseObject
>   def add_methods(methods)
>     class << self
>       attr_accessor methods
>     end
>   end
> end
>
> But that blew up on me. There's two flaws in that. The first is that
> methods is already a method name, so using it as a variable name was a
> pretty dumb idea. The second is that the arg to the method isn't
> visible once you're inside that class << self block.
>
> It doesn't seem as if there's any way to do it without using #eval,
> and frankly, using #eval is so last month. Who uses #eval any more?
> That's like Fred Flintstone style.
>
> Nonetheless, here's how you can do it with eval:
>
> class Base
>   def add_xsor(xsor)
>     eval("class << self ; attr_accessor :#{xsor} ; end")
>   end
> end
>
> class Boat < Base ; end
> boat = Boat.new
> boat.add_xsor(:need)
> boat.need = "bigger"
>
> The big flaw here, of course, is that it only works on instances, but
> in practical terms I always seem to use it in an instance context.
>
> I'm going to have to use this code for the time being but I'm
> definitely on the lookout for a better way to do it. It's clean, but
> not totally satisfying.
>
> --
> Giles Bowkett
>
> Blog: http://gilesbowkett.blogspot.com
> Portfolio: http://www.gilesgoatboy.org
> Tumblelog: http://giles.tumblr.com/
>
>

Hey,

I had a bit of trouble following along cause it's late but I hope I
got it right.

You said that

class << self
  attr_accessor :whatever
end

doesn't work.  That's because you created a new class scope.  The
simple solution is to use the (class << self; self; end) idiom with
class_eval or send.  I prefer send but whatever.

Anyway here's some mojo:

class Object
  def add_methods(*methheads)
    methheads.each do |m|
      (class << self; self; end).send(:attr_accessor, m)
    end
  end
end

class Dog
  def initialize(name, age)
    @name, @age = name, age
  end
end

irb(main):014:0> d = Dog.new "Cobi", 2
=> #<Dog:0x5cae4 @name="Cobi", @age=2>
irb(main):015:0> d.name
NoMethodError: undefined method `name' for #<Dog:0x5cae4 @name="Cobi", @age=2>
        from (irb):15
        from :0
irb(main):016:0> d.add_methods :name, :age
=> [:name, :age]
irb(main):017:0> d.name
=> "Cobi"
irb(main):018:0> d.age
=> 2

I hope I understood what you were going for.  Even if not that should
get you started.

If you want to add an attr_accessor to all instances, just use send on
the class:
Dog.send :attr_accessor, :name

Pat

 « Return to Thread: simple way to encapsulate class << self ; attr_accessor :xyz ; end?