How to read and write 80 column punched cards with Ruby

  • Thread starter Christer Nilsson
  • Start date
C

Christer Nilsson

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
 
M

Martin DeMello

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.

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
 
A

Alex Fenton

Christer said:
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
 
C

Christer Nilsson

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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,151
Latest member
JaclynMarl
Top