How to read and write 80 column punched cards with Ruby

I'm trying to communicate with a bank. Banks are still using the punched
card layout in their files.

Example: 001234567800023420060909
(account=12345678, amount=234 and day=20060909)

I'm trying to make some nice Ruby classes to handle this.

I would like to define the class like this (compare attr)

class Trans < Record
  field :account, 10
  field :amount, 6
  field :lastday, 8
end

This is what I've been able to do:
(There are several problems: As the fields have to be in a certain order
a hash will not work. On the other hand, looping through an array takes
time. Another is the problem of setting an attribute to nil,
trans.account=nil)

class Field
  attr_accessor :name, :size, :pad, :value
  def initialize(name,size,pad)
    @name=name
    @size=size
    @pad=pad
  end
end

class Record
  def initialize
    @fields = []
  end
  def field name, size, pad
    @fields << Field.new(name.to_s, size, pad)
  end
  def save s
    p=0
    @fields.each do |f|
      f.value = s[p,f.size]
      p += f.size
    end
  end
  def to_s
    s=""
    @fields.each do |f|
      s += f.value.to_s
    end
    s
  end
  def method_missing(id, value=nil)
    if value==nil then
      @fields.each do |f|
        return f.value if f.name == id.to_s
      end
    else
      x = id.to_s.gsub("=", "")
      @fields.each do |f|
        f.value = value if f.name == x
      end
    end
  end
end

class Trans < Record
  def initialize
    super
    field :account, 10
    field :amount, 6
    field :lastday, 8
  end
end

trans = Trans.new
trans.save "001234567800023420060909"

puts trans.account
trans.amount = 120
puts trans

/Christer

···

--
Posted via http://www.ruby-forum.com/.

Could you expand on that a bit further? What are you trying to
optimise for? If you want an O(1) accessor for say, [:account] you
could use one of the several OrderedHash implementations posted here
recently (check google groups - there was a long thread), or simply
maintain a separate hash { :account => 1, :amount => 2, :lastday => 3
} and use that to index into the array of fields.

It seems to me that you could get away with a hash of the form :field
=> [start, end], coupled with an array giving the ordering of the
fields (for when you want to iterate over all the fields), and store
Then you could access an individual field thus:
class Record
  def initialize(str)
    @record = str
    @field_limits = {}
    @fields =
  end

  def (field)
    start, end = @field_limits[field]
    @record[start..end]
  end

  def =(field, value)
    start, end = @field_limits[field]
    value = pad(value, end-start)
    @record[start..end] = value
  end
end

martin

···

On 9/9/06, Christer Nilsson <janchrister.nilsson@gmail.com> wrote:

I'm trying to communicate with a bank. Banks are still using the punched
card layout in their files.

Example: 001234567800023420060909
(account=12345678, amount=234 and day=20060909)

I'm trying to make some nice Ruby classes to handle this.

I would like to define the class like this (compare attr)

class Trans < Record
  field :account, 10
  field :amount, 6
  field :lastday, 8
end

This is what I've been able to do:
There are several problems: As the fields have to be in a certain order
a hash will not work. On the other hand, looping through an array takes
time.

Christer Nilsson wrote:

I'm trying to communicate with a bank. Banks are still using the punched card layout in their files.

Example: 001234567800023420060909
(account=12345678, amount=234 and day=20060909)

I would like to define the class like this (compare attr)

class Trans < Record
  field :account, 10
  field :amount, 6
  ...

You're on the right lines, but in your example you're working with instance methods when you want to work with class methods. Define a 'field' *class* method in the Record class, which defines reader and writer *instance* methods. Something like below.

Tip - don't use method_missing unless you really have to; there is often another way, e.g. define_method. Some well-known libraries do it but it really screws up debugging and reflection, and makes method dispatch even slower.

hth
alex

···

___

class Record
  class Field
    attr_accessor :name, :length, :position
    def initialize
      yield self
    end
  end

  def Record.field(field_name, field_length)
    @fields ||=
    position = @fields.inject(0) { | sum, f | sum += f.length }
    @fields << Field.new do | f | f.name, f.length, f.position = field_name, field_length, position
    end
    attr_reader field_name
    define_method("#{field_name}=") do | value |
      value = value.to_s.rjust(field_length, '0')
      instance_variable_set("@#{field_name}", value)
    end
  end

  def Record.fields
    @fields
  end

  def initialize(str)
    self.class.fields.each do | field |
      send("#{field.name}=", str[field.position, field.length])
    end
  end

  def to_s
    self.class.fields.inject('') { | str, f | str << send(f.name) }
  end
end

class Transaction < Record
  field :account, 10
  field :amount, 6
  field :lastday, 8
end

trans = Transaction.new("001234567800023420060909")
puts trans # 001234567800023420060909
puts trans.amount.to_i # 234

trans.amount = 120
puts trans # 001234567800012020060909

Thank you Martin and Alex, your suggestions were really helpful!
/Christer

···

--
Posted via http://www.ruby-forum.com/.

Alex Fenton <alex@deleteme.pressure.to> writes:

Tip - don't use method_missing unless you really have to; there is
often another way, e.g. define_method. Some well-known libraries do
it but it really screws up debugging and reflection, and makes
method dispatch even slower.

It's true that method_missing often is a source of problems and should
be avoided if possible, but with regards to speed, you should
benchmark first before making such as statement. I was surprised when
I benchmarked it some time ago.

···

alex

--
Christian Neukirchen <chneukirchen@gmail.com> http://chneukirchen.org