Forum: Ruby Metaprogramming: Dynamic class generation

Ea6905beef63a84a75793f4f72cb8ff7?d=identicon&s=25 Luke Kanies (Guest)
on 2006-08-20 05:26
(Received via mailing list)
Hi all,

I finally wrote up how I'm dynamically generating classes (and why),
and I thought some of you might enjoy it:

http://madstop.com/articles/2006/08/19/my-ruby-met...

The article discusses a new method I've created in Puppet for making
dynamic class creation easier, and in the process discusses why I do
this a lot and some future directions for more abstraction.

Comments and suggestions appreciated.
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-08-20 18:10
(Received via mailing list)
On Sun, 20 Aug 2006, Luke Kanies wrote:

>
> Comments and suggestions appreciated.

first off, thanks for the article - i know this stuff is hard to write
about...

few questions/comments:


- Why I dynamically generate

   can you give a little more context for the code shown:

     package { ssh: ensure => installed, type => apt }

   is this statement part of a dsl?


- Avoiding the inherited method

   have you used delayed initialization like so:

     harp:~ > cat a.rb
     class Base
       @subs = Hash.new{|h,sub| Base.name2subs[sub].post_inherited}
       @name2subs = Hash.new

       class << self
         attr_accessor :subs
         attr_accessor :name2subs
       end

       def self.inherited sub
         Base.name2subs[sub.name] = sub
       end

       def self.post_inherited
         Base.subs[name] = self
       end
     end

     class Sub < Base
     end

     p Base.subs['Sub']
     p Base.subs


     harp:~ > ruby a.rb
     Sub
     {"Sub"=>Sub}

   i'm curious because i've faced this issue many times before too...



- i'm curious if the generated objects really need to be classes?  what
i mean
   is are there instances created later?  the reason i ask is that i've
been
   playing with prototypes lately and wondering if that might be
applicable to
   this kind of problem

     http://www.codeforpeople.com/lib/ruby/prototype/pr...


interesting stuff.

cheers.


-a
Ea6905beef63a84a75793f4f72cb8ff7?d=identicon&s=25 Luke Kanies (Guest)
on 2006-08-20 19:29
(Received via mailing list)
On Aug 20, 2006, at 11:10 AM, ara.t.howard@noaa.gov wrote:=
>
>     package { ssh: ensure => installed, type => apt }
>
>   is this statement part of a dsl?

Yes, it is; I've got a grammar and everything for it.  Before
everyone asks why I don't just use a Ruby DSL, please see http://
reductivelabs.com/projects/puppet/faq.html.  I've actually toyed
recently with creating a Ruby DSL, just to see what it would look
like, and I think it would be at least as difficult, if not more so,
because of how differently Puppet defines classes vs. Ruby.

> - Avoiding the inherited method
>
>   have you used delayed initialization like so:
>
>     harp:~ > cat a.rb
[...]
>   i'm curious because i've faced this issue many times before too...

I had code that was similar.  In thinking about the article after I
wrote it, avoiding the 'inherited' method did make my life much
easier but what really simplified my code were the class methods to
create new, related classes.  As I mentioned in my article, most of
my classes are referenced by name, and having class creation happen
in a way that inherently handles class naming and class relationships
is much, much easier.  Calling 'newclass' on the containing class is
much easier than creating the two classes and then marking them
related somehow.

I think that if I tended to refer to these classes by their constants
(i.e., their Ruby names) rather than by a human name, or if the
dynamic classes weren't always specifically associated with some kind
of containing class, the delayed initialization mechanism would work
pretty well.  As it is, I've had multiple compliments on how nice
this class creation mechanism is, and it really was striking how much
better it made my life when I made this change.  Also, your code
wouldn't work for me because the class's @name is often unrelated to
the constant -- I would need to use an array to store the subclasses
initially, and then hash after initialization.

I will say, though, that the mechanism I'm using provides a bit more
functionality than delayed initialization -- I can easily control
what happens at class creation time, before the block runs, and after
the block runs, and it wouldn't take much to add hooks for more class
customization if a specific class container needed it.  As Why
pointed out in his metaprogramming article, it's always easier when
your code makes the metaprogramming explicit -- I seem to often start
out using 'inherited', but the code always gets cleaner when I move
away from it.  I expect that your example code could be moved into a
module or something so that it made the metaprogramming explicit; it
would be interesting to compare the results of that with the system I
have.  As I mentioned I'm using this class generation facility in
about 8 different places in Puppet -- I clearly would not wnat to
duplicate your code in all 8 of these places.

> - i'm curious if the generated objects really need to be classes?
> what i mean
>   is are there instances created later?  the reason i ask is that
> i've been
>   playing with prototypes lately and wondering if that might be
> applicable to
>   this kind of problem
>
>     http://www.codeforpeople.com/lib/ruby/prototype/pr...
> README

Yes, instances of these classes are created.  The refactoring that I
asked about earlier this week (transactions and idempotency) might
result in this no longer being the case for some of these classes, in
which case prototypes would be much simpler and in some cases could
provide more functionality.
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.