Metaprogramming is fun!

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 Guterl

···

----------------------------------------------

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

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

Michael Guterl

fixed_data.txt (138 Bytes)

···

On 4/23/06, Michael Guterl <mguterl@gmail.com> 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?

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 Guterl

----------------------------------------------

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

Hi --

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.

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

See below.

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

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...

line.slice(last_position,
length.to_i).strip)
         last_position += length.to_i
       end
       records << record
     end
     return records
   end
end

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

···

On Sun, 23 Apr 2006, Michael Guterl wrote:

--
David A. Black (dblack@wobblini.net)
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

"Ruby for Rails" PDF now on sale! Ruby for Rails
Paper version coming in early May!

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 Guterl

···

On 4/23/06, dblack@wobblini.net <dblack@wobblini.net> wrote:

Hi --

On Sun, 23 Apr 2006, Michael Guterl 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.

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

See below.

> 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

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...

> line.slice(last_position,
> length.to_i).strip)
> last_position += length.to_i
> end
> records << record
> end
> return records
> end
> end
>
> 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 (dblack@wobblini.net)
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

"Ruby for Rails" PDF now on sale! Ruby for Rails
Paper version coming in early May!