# How to read and write 80 column punched cards with Ruby

**URL:** <https://www.ruby-forum.com/t/how-to-read-and-write-80-column-punched-cards-with-ruby/68118>\
**Category:** Ruby\
**Created:** [September 9, 2006, 10:41am UTC](https://www.ruby-forum.com/t/how-to-read-and-write-80-column-punched-cards-with-ruby/68118 "2006-09-09T10:41:48Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![christer](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/a5b964/32.png) [@christer](https://www.ruby-forum.com/u/christer)\
**Post date:** [September 9, 2006, 10:41am UTC](https://www.ruby-forum.com/t/how-to-read-and-write-80-column-punched-cards-with-ruby/68118/1 "2006-09-09T10:41:48Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![christer](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/a5b964/32.png) [@christer](https://www.ruby-forum.com/u/christer)\
**Post date:** [September 9, 2006, 1:02pm UTC](https://www.ruby-forum.com/t/how-to-read-and-write-80-column-punched-cards-with-ruby/68118/2 "2006-09-09T13:02:13Z")

</div>

On 9/9/06, Christer N. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

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

---

<div class="post-metadata">

**Author:** ![christer](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/a5b964/32.png) [@christer](https://www.ruby-forum.com/u/christer)\
**Post date:** [September 9, 2006, 1:12pm UTC](https://www.ruby-forum.com/t/how-to-read-and-write-80-column-punched-cards-with-ruby/68118/3 "2006-09-09T13:12:14Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![christer](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/a5b964/32.png) [@christer](https://www.ruby-forum.com/u/christer)\
**Post date:** [September 10, 2006, 11:40am UTC](https://www.ruby-forum.com/t/how-to-read-and-write-80-column-punched-cards-with-ruby/68118/4 "2006-09-10T11:40:27Z")

</div>

Alex F. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) 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.

---

<div class="post-metadata">

**Author:** ![christer](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/a5b964/32.png) [@christer](https://www.ruby-forum.com/u/christer)\
**Post date:** [September 9, 2006, 10:54pm UTC](https://www.ruby-forum.com/t/how-to-read-and-write-80-column-punched-cards-with-ruby/68118/5 "2006-09-09T22:54:56Z")

</div>

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