Creating sub-classes using Struct


#1

Hello,

There are two Ruby features I want to combine. One is sub-classes (or
embedded classes):

class Foo
def initialize(x)
puts “new Foo #{x}”
end

class Bar
	def initialize(y)
		puts "new Bar #{y}"
	end
end

end

joe = Foo::Bar.new(12) # prints “new Bar 12”

Another is dynamic creation of new classes using Struct:

Customer = Struct.new(:name, :address) #=> Customer
eddy = Customer.new(“Dave”, “123 Main”)

Here ‘Customer’ is a class and ‘eddy’ is an instance of the Customer
class.

Now, by combining the features, I mean creating a subclass of Foo
dynamically, using Struct. The following pseudo-code demonstates it
(pseudo because it obviously doesn’t work and only shows what I’m trying
to do):

class Foo
def initialize(x)
puts “new Foo #{x}”
end

Bar = Struct.new(:name, :address)

end

eddy = Foo::Bar.new(“Dave”, “123 Main”)

How can this be done ?

TIA
Eli


#2

2006/5/6, Eli B. removed_email_address@domain.invalid:

    class Bar

Another is dynamic creation of new classes using Struct:
to do):

How can this be done ?

You’ll have to modify Struct to be able to create sub classes of
arbitrary classes and not just Object. Btw, if feels a bit strange to
have a nested class that is a sub class of the outer class. By that
twist it contains itself - I think this is not a good idea.

Kind regards

robert


#3

On May 6, 2006, at 11:19 AM, Eli B. wrote:

class Bar
Another is dynamic creation of new classes using Struct:
trying
eddy = Foo::Bar.new(“Dave”, “123 Main”)

How can this be done ?

I’m confused. This works for me, the above code works fine for me.

Putting a class inside another class does not subclass it though (Nor
is it supposed to, you’re just setting a constant for the enclosing
class).


#4

Robert K. wrote:

2006/5/6, Eli B. removed_email_address@domain.invalid:

    class Bar

Another is dynamic creation of new classes using Struct:
to do):

How can this be done ?

You’ll have to modify Struct to be able to create sub classes of
arbitrary classes and not just Object. Btw, if feels a bit strange to
have a nested class that is a sub class of the outer class. By that
twist it contains itself - I think this is not a good idea.

Sorry, I don’t understand what you mean by “have a nested class that is
a sub class of the outer class”.

Perhaps it’s better for me to explain the motivation for what I’m trying
to do. Simplifying:

I have a flat-file full of records. The way records are stored can be
customized via a separate configuration file. I want to have a class
Database which is a container of Record classes. The Record is best
implemented as a Struct, IMHO, and I want to create the class
dynamically after reading the configuration (which says which fields
there are in a Record). I think it would be convenient for a Record to
be a subclass of Database, since there is no point having Records
without a Database.


#5

On 5/6/06, Eli B. removed_email_address@domain.invalid wrote:

I have a flat-file full of records. The way records are stored can be
customized via a separate configuration file. I want to have a class
Database which is a container of Record classes. The Record is best
implemented as a Struct, IMHO, and I want to create the class
dynamically after reading the configuration (which says which fields
there are in a Record). I think it would be convenient for a Record to
be a subclass of Database, since there is no point having Records
without a Database.

I think that what you mean by “subclass” is something different than
the traditional meaning of the term.

Subclassing establishes a “is a” relation between the two classes. To
do it in Ruby you’d write:

class Database
#code goes here
end

class Record < Database
#more code goes here
end

I don’t think this is completely correct, however, since a Record is
not a Database. The relationship you want to establish is “contains”:
a Database contains Records. To do this, you’d have to declare a
collection of Records as an attribute of Database, doing something
like this:

class Database
def initialize()
@records = []
end

def add_record(record)
@records << record
end

#et cetera

end

Defining Record within Database, as in your first example, doesn’t
necessarily do any of those things, and is more commonly known as
creating a “nested class”.

If you want to define Record dinamically, you could maybe do something
like this:

class Record
def initialize(*attribs)
@internal_class = Struct.new(*attribs)
@internal_object = @internal_class.new
end

def method_missing(name, *args)
if @internal_object.respond_to? name
@internal_object.send(name, *args)
else super(name, *args)
end
end

def inspect
@internal_object.inspect
end

end

This way, everytime you create a new Record object, you’ll need to
define what attributes it has (and these attributes would probably
come from you configuration files). You can place the code for the
Record class inside the Database class to make it a nested class, and
you can create an attribute for Database that is an Array or Hash of
Records.


#6

On May 6, 2006, at 2:55 PM, Eli B. wrote:

arbitrary classes and not just Object. Btw, if feels a bit strange to
to do. Simplifying:

I have a flat-file full of records. The way records are stored can be
customized via a separate configuration file. I want to have a class
Database which is a container of Record classes. The Record is best
implemented as a Struct, IMHO, and I want to create the class
dynamically after reading the configuration (which says which fields
there are in a Record). I think it would be convenient for a Record to
be a subclass of Database, since there is no point having Records
without a Database.

Eh, I’m not sure you get the is-a relationship. Saying that a Record
is a subclass of Database implies that anywhere you would use a
Database you could use a Record instead. That doesn’t really make
sense. It would also seem to imply that a Record could contain many
records. Instead of

class Database

end

class Record < Database

end

I would suggest

class Database
def self.new(file_containing_info)
fields = parse_field_format(file_containing_info)
Database.const_set('Record", Struct.new(*fields))
super
end

def initialize(file)
@records = []
read_each_record_in_file(file) do |record_data|
record = Database::Record.new
extract_record_data_into(record_data, record)
@records << record
end
end
end


#7

Logan C. wrote:

On May 6, 2006, at 2:55 PM, Eli B. wrote:

arbitrary classes and not just Object. Btw, if feels a bit strange to
to do. Simplifying:

I have a flat-file full of records. The way records are stored can be
customized via a separate configuration file. I want to have a class
Database which is a container of Record classes. The Record is best
implemented as a Struct, IMHO, and I want to create the class
dynamically after reading the configuration (which says which fields
there are in a Record). I think it would be convenient for a Record to
be a subclass of Database, since there is no point having Records
without a Database.

Eh, I’m not sure you get the is-a relationship. Saying that a Record
is a subclass of Database implies that anywhere you would use a
Database you could use a Record instead. That doesn’t really make
sense. It would also seem to imply that a Record could contain many
records. Instead of

class Database

end

class Record < Database

end

It seems that I managed to completely confuse everyone using an
inaccurate description. By sub-class I meant a nested class here, not a
child in is-a inheritence. I don’t know what I was thinking :slight_smile:

I would suggest

class Database
def self.new(file_containing_info)
fields = parse_field_format(file_containing_info)
Database.const_set('Record", Struct.new(*fields))
super
end

Excellent ! const_set is what I was missing to get my solution working.
No more “dynamic constant assignment” errors for me :slight_smile:

def initialize(file)
@records = []
read_each_record_in_file(file) do |record_data|
record = Database::Record.new
extract_record_data_into(record_data, record)
@records << record
end
end
end

This looks like what I need. Many thanks


#8

Eli B. wrote:

Excellent ! const_set is what I was missing to get my solution working.
No more “dynamic constant assignment” errors for me :slight_smile:

FWIW, you can look at Superstruct which I wrote awhile back.
Disclaimer: I think it has bugs not caught by the unit tests.
Also the code is probably not very pretty in places.

As I recall it’s at: http://sstruct.rubyforge.org

Cheers,
Hal