Metaprogramming is fun!


#1

I finally decided to dive in and give metaprogramming in Ruby a shot.
I’m
not sure that my example is exactly practical, but it seemed useful at
the
time. There’s a few areas I would like to hear others suggestions on:

  1. Does the syntax appear to be in line with the community standards?

  2. Are my uses of class_eval and instance_eval okay / is there a better
    way?

  3. I am using facets because I like the Dictionary (OrderedHash), but
    I’m
    sure there are better ways to build the structure while preserving the
    field
    order. I especially don’t like the fact that I have to specify
    Dictionary[]
    in the structure of the subclass.

  4. Please comment on the code in general, I’m open to any kind of
    criticism.

I’m not sure if it’s customary to attach the code in a file or post it
in
the email. I apologize if I should have attached the code in a file.

Michael G.


require ‘rubygems’
require ‘facets’
require ‘dictionary’

class FixedLength

def self.structure(ordered_hash)

class_eval do
  @@structure = ordered_hash
end

keys = @@structure.keys
instance_eval do
  attr_accessor *keys
end

end

this entire method could probably be a lot cleaner

def self.open(file_name)
class_eval do
data = IO.read(file_name)
records = []
data.each_line do |line|
last_position = 0
record = self.new
@@structure.each_pair do |name, length|
record.instance_variable_set( “@#{name.to_s}”,
line.slice(last_position,
length.to_i).strip)
last_position += length.to_i
end
records << record
end
return records
end
end

end

class InputStructure < FixedLength

I’d like to clean this up, either removing the need for Dictionary[]

and
the separation by commas

or with something like this

column.add :id, 10

column.add :phone, 10

column.add :first_name, 25

etc. I’d love to hear some suggestions.

structure Dictionary[ :id , 10,
:phone , 10,
:first_name , 25, :middle_name , 1, :last_name , 25,
:address , 30,
:city , 25,
:state , 2,
:zip , 5, :zip4 , 4 ]

end

require ‘test/unit’

class InputStructureTest < Test::Unit::TestCase

def setup
@records = InputStructure.open(‘fixed_data.txt’)
end

def test_first
record = @records.first
assert_equal “1”, record.id
assert_equal “1234567890”, record.phone
assert_equal “SOME RANDOM”, record.first_name
assert_equal “”, record.middle_name
assert_equal “PERSON”, record.last_name
assert_equal “1234 SOME RANDOM STREET”, record.address
assert_equal “RANDOM CITY”, record.city
assert_equal “OH”, record.state
assert_equal “45219”, record.zip
assert_equal “”, record.zip4
end

end


#2

I forgot, here’s the test data in case anyone wants it.

Michael G.


#3

Hi –

On Sun, 23 Apr 2006, Michael G. wrote:

I finally decided to dive in and give metaprogramming in Ruby a shot. I’m
not sure that my example is exactly practical, but it seemed useful at the
time. There’s a few areas I would like to hear others suggestions on:

  1. Does the syntax appear to be in line with the community standards?

You’re indenting one space instead of two on the first indent, but
generally it looks good.

  1. Are my uses of class_eval and instance_eval okay / is there a better way?

See below.

end

keys = @@structure.keys
instance_eval do
attr_accessor *keys
end

I’m not sure why you’re using all these *_eval calls. Try this:

def self.structure(ordered_hash)
@@structure = ordered_hash
attr_accessor *@@structure.keys
end

end

this entire method could probably be a lot cleaner

def self.open(file_name)
class_eval do

I haven’t tested it but I don’t see any reason for that one either.

 data = IO.read(file_name)
 records = []
 data.each_line do |line|
   last_position = 0
   record = self.new
   @@structure.each_pair do |name, length|
     record.instance_variable_set( "@#{name.to_s}",

The #{} thing does an automatic to_s. Also, since you’ve gone to the
trouble of creating accessors, why not do:

record.send("#{name}="), line.slice…

end
You could tighten that method up a bit. Here’s an untested rewrite;
see if this is of any use:

def self.open(file_name)
records = []
File.open(file_name) do |fh|
fh.each_line do |line|
record = new
@@structure.each do |name,len|
record.send("#{name}=", line.slice!(0,len))
end
records << record
end
end
return records
end

(I’ll save my reflections on the likelihood of class variables being
necessary for another time :slight_smile:

David


David A. Black (removed_email_address@domain.invalid)
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

“Ruby for Rails” PDF now on sale! http://www.manning.com/black
Paper version coming in early May!


#4

Thanks David! I removed the evals, cleaned up self.open, and ran my
tests
and everything works.

I really like the fact that I don’t have to use the last_position
variable
and I can use String#slice! to incrementally chop it down.

Any time you feel like reflecting on the need for class variables, my
ears
are open.

Thanks again!

Michael G.