From: Dave Thomas Date: 2002-08-13T03:08:48+09:00 Subject: Re: Handling forms on database driven websites "Hal E. Fulton" 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