Assigning to dynamic class attributes

Hi!

Ruby n00b here, but experienced in Perl and Python and many others, looking for guidance in my first program.

I want to build a class mirroring a flat file record that uses column names as accessor methods, I want to dynamically assign an attribute from a parsed string and assign it from the postitional list of symbols.

   rec = QlbRecord.new(file_line)
   rec.dpend += 5
   puts rec.address

First, I Set up the class attributes in the class.
   @@cols = [ :address, :classes, :status, :dpend, :cremote ]
   @@cols.each { |a| attr a,true }

Next, I would populate the fields as such....
     arec = record_string.split(/\t/)
     @@cols.each { |c| self.c = arec.shift }
where c is the sumbol of the attribute to set. This is wrong.

Am I going in the right direction? It is like using a hash
    data[c] = arec.shift
though I want to access the columns through methods (rec.address, etc.)

Is there a class in the library I should be using instead? Or do I really just need to replicate code (ick!) for each data field?
   address = arec.shift
   classes = arec.shift
   ... etc. ...

Also, I don't completely understand how symbols work or why they are needed.

Following is my class for your inspection. I can kludge something that works, but I want to learn the *best* method. Thanks!

···

########################
class QlbRecord
   @@cols = [ :address, :classes, :status, :dpend, :cremote ]
   @@cols.each { |a| attr a,true }

   def initialize(record_string=nil)
     from_s(record_string) if record_string
   end

   def from_s(record_string)
     arec = record_string.split(/\t/)
     @@cols.each { |c| self.c = arec.shift }
##---------------------> I want it to go to @address, @classes, etc
   end

   def to_s
     str=''
     @@cols.each { |c| str += (str>'' ? "\t" : '') + self.c }
##--------------------------------------------> from @address, etc.
     str
   end
end

I'm sure you'll get better sugestions, but this works:

@@cols.each { |c| self.__send__ "#{c.to_s}=".to_sym, arec.shift }

Allen wrote:

···

Hi!

Ruby n00b here, but experienced in Perl and Python and many others,
looking for guidance in my first program.

I want to build a class mirroring a flat file record that uses column
names as accessor methods, I want to dynamically assign an attribute
from a parsed string and assign it from the postitional list of symbols.

   rec = QlbRecord.new(file_line)
   rec.dpend += 5
   puts rec.address

First, I Set up the class attributes in the class.
   @@cols = [ :address, :classes, :status, :dpend, :cremote ]
   @@cols.each { |a| attr a,true }

Next, I would populate the fields as such....
     arec = record_string.split(/\t/)
     @@cols.each { |c| self.c = arec.shift }
where c is the sumbol of the attribute to set. This is wrong.

Am I going in the right direction? It is like using a hash
    data[c] = arec.shift
though I want to access the columns through methods (rec.address, etc.)

Is there a class in the library I should be using instead? Or do I
really just need to replicate code (ick!) for each data field?
   address = arec.shift
   classes = arec.shift
   ... etc. ...

Also, I don't completely understand how symbols work or why they are
needed.

Following is my class for your inspection. I can kludge something that
works, but I want to learn the *best* method. Thanks!

########################
class QlbRecord
   @@cols = [ :address, :classes, :status, :dpend, :cremote ]
   @@cols.each { |a| attr a,true }

   def initialize(record_string=nil)
     from_s(record_string) if record_string
   end

   def from_s(record_string)
     arec = record_string.split(/\t/)
     @@cols.each { |c| self.c = arec.shift }
##---------------------> I want it to go to @address, @classes, etc
   end

   def to_s
     str=''
     @@cols.each { |c| str += (str>'' ? "\t" : '') + self.c }
##--------------------------------------------> from @address, etc.
     str
   end
end

Allen wrote:

Hi!

[...]
Following is my class for your inspection. I can kludge something that
works, but I want to learn the *best* method. Thanks!

########################
class QlbRecord
   @@cols = [ :address, :classes, :status, :dpend, :cremote ]
   @@cols.each { |a| attr a,true }

   def initialize(record_string=nil)
     from_s(record_string) if record_string
   end

   def from_s(record_string)
     arec = record_string.split(/\t/)
     @@cols.each { |c| self.c = arec.shift }
##---------------------> I want it to go to @address, @classes, etc
   end

   def to_s
     str=''
     @@cols.each { |c| str += (str>'' ? "\t" : '') + self.c }
##--------------------------------------------> from @address, etc.
     str
   end
end

If you're going dynamic, the need for attribute accessors is reduced.
Here, instance_variable_get and ..._set are used:

class QlbRecord
   @@cols = [ :@address, :@classes, :@status, :@dpend, :@cremote ]

   def initialize(record_string=nil)
     from_s(record_string) if record_string
   end

   def from_s(record_string)
     record_string.split(/\t/).each_with_index do |c, ix|
       instance_variable_set(@@cols[ix], c)
     end
   end

   def to_s
     @@cols.map do |col|
       instance_variable_get(col)
     end.join("\t")
   end
end

qr1 = QlbRecord.new("1 addr aaa\tclasses aa\tst1\tdp1\tcr1")
p qr1
  #-> #<QlbRecord:0x2c8b9e8 @address="1 addr aaa", @dpend="dp1",
  #-> @status="st1", @classes="classes aa", @cremote="cr1">
p qr1.to_s
  #-> "1 addr aaa\tclasses aa\tst1\tdp1\tcr1"

Also, I don't completely understand how symbols work or
why they are needed.

If you use strings to identify fields, each time you write the
field name, a new string is created. Symbols are unique and
memory-efficient.

p 'cremote'.object_id #-> 23356052
p 'cremote'.object_id #-> 23356016
p 'cremote'.object_id #-> 23355992
puts
p :@cremote.object_id #-> 3504654
p :@cremote.object_id #-> 3504654
p :@cremote.object_id #-> 3504654

Hope that's of use.

daz

While :@cremote is a valid symbol, it sort of implies that the @ symbol is somewhow special for symbols. The corresponding symbol to "cremote" is :cremote.

irb(main):001:0> a = 'cremote'
=> "cremote"

irb(main):002:0> a.to_sym
=> :cremote

irb(main):003:0> a.to_sym.to_s
=> "cremote"

Note also that symbols are immutable (like a large integer), while strings are mutable (like an Array). Think of symbols as a unique identifier that you can pass around in a lightweight and unambiguous way. For example, symbols are often used for the value of constants:

class Person
     MALE = :male
     FEMALE = :female
     attr_reader :sex
     def initialize( sex )
         @sex = sex
     end
end

pat = Person.new( Person::MALE )
jim = Person.new( Person::MALE )
if jim.sex == Person::FEMALE
     # This won't occur
     raise "Something went bad!"
end

Compare to this pathological case:

class Person
     MALE = 'male'
     FEMALE = 'female'
     attr_reader :sex
     def initialize( sex )
         @sex = sex
     end
end

pat = Person.new( Person::MALE )
jim = Person.new( Person::MALE )

# Trying to give JUST pat a sex change, the wrong way
pat.sex.sub!( /^/, 'fe' )

if jim.sex == Person::FEMALE
     # This will occur!
     raise "Something went bad!"
end

···

On Sep 13, 2005, at 2:36 PM, daz wrote:

Allen wrote:

Also, I don't completely understand how symbols work or
why they are needed.

If you use strings to identify fields, each time you write the
field name, a new string is created. Symbols are unique and
memory-efficient.

p 'cremote'.object_id #-> 23356052
p 'cremote'.object_id #-> 23356016
p 'cremote'.object_id #-> 23355992
puts
p :@cremote.object_id #-> 3504654