Forum: Ruby Metaprogramming is fun!

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Michael G. (Guest)
on 2006-04-23 08:08
(Received via mailing list)
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
Michael G. (Guest)
on 2006-04-23 08:11
(Received via mailing list)
I forgot, here's the test data in case anyone wants it.

Michael G.
unknown (Guest)
on 2006-04-23 16:16
(Received via mailing list)
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.

> 2. 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 :-)


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!
Michael G. (Guest)
on 2006-04-23 17:12
(Received via mailing list)
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.
This topic is locked and can not be replied to.