"Hal E. Fulton" <hal9000 / hypermetrics.com> writes:

> What I've thought of is a little code generator
> that would take a simple data description and
> generate code from it.
> 
> Does that approach interest you? Would you want 
> to collaborate on it?

I'll be giving a short talk at RubyConf on just that. For my current
web project I have a set of classes that let me specify my tables like
this:

     class ChallengeDescTable < Table
       table "challenge_desc" do
         field autoinc,       :chd_id,           pk
         field int,           :chd_season,       references(SeasonTable)
         field boolean,       :chd_primary_only
         field int,           :chd_levels        # bitmask
         field varchar(100),  :chd_name
         field varchar(10000),:chd_desc
         field varchar(200),  :chd_file_path
         field varchar(200),  :chd_icon_url
       end
     end

     # The challenges for a season

     class ChallengeTable < Table
       table "challenge" do
         field autoinc,       :cha_id,           pk
         field int,           :cha_aff_id,       references(AffiliateTable)
         field int,           :cha_chd_id,       references(ChallengeDescTable)
         field int,           :cha_levels        # bitmask
       end
     end

     # and a convenient view
     class ChallengeViewTable < Table
       view "challenge_view",
         [ChallengeTable, ChallengeDescTable],
         "cha_chd_id = chd_id"
     end

From this, the code generates

1. The SQL DDL to create and drop the schema

2. A DOT diagram of the schema

3. A set of accessor classes that work alongside a simple persitence
   layer. Using these, I can do things like

      challenge = store.select_one(ChallengeTable, "cha_id=?", id)
      puts challenge.cha_levels

The basic accessor objects are never used stand-alone: instead they
are wrapped in business objects that give them meaningful
behavior. Thus, the Challenge business object contains code such as

     class Challenge < BusinessObject

       def Challenge.for_affiliate(aff_id)
         $store.select(ChallengeTable, "cha_aff_id=?", aff_id).map do |c|
           new(c)
         end
       end

       def Challenge.delete(cha_id)
         $store.delete_where(ChallengeTable, "cha_id=?", cha_id)
       end


       def Challenge.with_id(cha_id)
         maybe_return($store.select_one(ChallengeTable, "cha_id=?", cha_id))
       end

   etc...

The business objects also provide behavior to let them play with web
forms. I use the RDoc template code (with extensions to handle form
controls tidily), so the method that fetches a database about and uses
it to populate a form would look like:

   def fetch_and_display(id)
      @session.cha = Challenge.with_id(id)
      display_common
   end

   def display_common
      form_data = { 'action_url' = url(:handle_form) }
      @session.cha.add_to_hash(form_data)
      standard_page("Edit Challenge", form_data, EDIT_CHALLENGE)
   end

The code that then reads back the results looks like this:

   def handle_form
     form_data = hash_form_cgi
     @session.cha.from_hash(form_data)
     errors = @cha.error_list
     if errors.empty?
       @session.cha.save
       main_menu
     else
       error_list(errors)
       display_common
     end
   end


This approach also lets business objects handle nested objects any way
they want: it's pleasantly transparent to the code that uses them.


Cheers


Dave