Magic at class-definition time

In an interview on http://www.codegeneration.net/>, Pragmatic Dave
showed some interesting tricks. The article has a long URL, so use
http://tinyurl.com/fek8. He says:

on a recent project, I decided to wrap access to the underlying
database scheme in a set of persistence objects. Ruby let me
construct > a library which effectively extended the Ruby language:

I could write:

class RegionTable < Table
 table "region" do
   field autoinc, :reg_id,        pimary_key
   field int,     :reg_affiliate, references(AffiliateTable, :aff_id)
   field varchar(100), :reg_name
 end
end

I have a related need, to reflect an SQL schema in Ruby objects,
and thought that Ruby classes like this would be cool, so I tried
and succeeded in doing some of what Dave did - for an example, I
can do:

class InventoryReport < Table
field :ComputerName, varchar(256)
etc …
end

There’s a few things I don’t get about Dave’s example though, so
perhaps he or someone else would explain?

Firstly, I can see that he wanted the class name and the table name
to differ, so used a class method Table.table() - and that’s fine.

The method takes a block however, and the block contains a series
of calls to a field method, presumably Table.field(type, symbol, *args).

I’m guessing that “field” defines methods for the current class
instance. In this case does “field” have to eval a string with
the method definition in it or is there a better way?

Why did Dave call “field” from a block attached to “table”, instead
of just from the class RegionTable?

Clifford Heath.

Clifford Heath wrote:

There’s a few things I don’t get about Dave’s example though, so
perhaps he or someone else would explain?

I’m guessing that “field” defines methods for the current class
instance. In this case does “field” have to eval a string with
the method definition in it or is there a better way?

That’s how I did it:

def Table.field(type, name, *options)
f = Field.new(name, type, options)

 @fields << f

 @primary_key = f if f.primary_key?

 create_accessors_for(f)
 create_writer_for(f)

end

def Table.create_accessors_for(field)
name = field.name
class_eval <<-EOS
def #{name}
@#{name}
end
def #{field.setter_name}(val)
@#{name} = val#{field.type.conversion_function}
end
EOS
end

def Table.create_writer_for(field)
name = field.name
class_eval <<-EOS
def #{name}=(val)
set_changed(:#{name}) if val != @#{name}
@#{name} = val
end
EOS
end

Why did Dave call “field” from a block attached to “table”, instead
of just from the class RegionTable?

It gives me some encapsulation of the generation of the table-specific
stuff, and it seemed neat at the time. For example, more complex table
classes have extra code in there:

class StateNameTable < Table
Suspended = ‘SUSPND’
WaitPayment = ‘WTPAY’
Active = ‘ACTIVE’

 table "state_name" do
   field char(6),       :stt_id,            pk
   field varchar(30),   :stt_desc

   initial_values([Suspended,   "Inactive"],
                  [WaitPayment, "Not paid"],
                  [Active,      "Active"]
                  )
 end

end

In the end, I could do it without the yield, but I like the way that
doing it allows me to know when the definition is finished, and hence
generate all the accessors.

I can see I’m going to have to write this up at some point… :slight_smile:

Cheers

Dave