Subclassing Array

E

El Gato

I'm sure I'm just being an idiot here... my mind is a little foggy this
morning, but I'm having a hard time understanding how to accomplish
this. I've written a class (I'll just put some snippets in for
understanding) in which I'd like to be able to use the following
behavior:

irb> columns = [[1, "Hostname"], [2, "Model"], [5, "OS Version"]]
irb> report = InventoryReport.new("HARDWARE_QUERY", columns)
irb> report.query.map {|a| a.first.downcase!; a}.save "/tmp/test.xls"

Where query() returns an array like [["host1", "Dell", "Windows"],
["host2", "Hitachi", "Windows"], ...]

However, throwing a map (or select or whatever) in there (obviously)
ends up returning an Array. How would I handle this so that it works as
I would like? Do I need to move my save(), to_csv(), and to_xls() stuff
into the Array class?

Sorry if this is a dumb question.

class InventoryReport < Array
def initialize(query_name, fields)
@query_name = query_name
@fields = fields
super()
end

def query
cols = @fields.map {|a| a.first - 1}
%x{runquery #{@query_name}}.each_line do |l|
t = l.chomp.split
tmp = []
cols.each do |a|
tmp << t[a].strip
end
self << tmp
end
self
end

def save(filename)
case filename
when /.*\.csv/ then to_csv(filename)
when /.*\.xls/ then
to_csv(filename.gsub(/xls$/, "csv"))
to_xls(filename, filename.gsub(/xls$/, "csv")
end
end

def to_csv(...)
end

def to_xls(...)
end
end
 
I

Ilan Berci

El said:
I'm sure I'm just being an idiot here... my mind is a little foggy this
morning, but I'm having a hard time understanding how to accomplish
this. I've written a class (I'll just put some snippets in for
understanding) in which I'd like to be able to use the following
behavior:

El Gato,

My suggestion is to take a good look at Ruby's incredibly powerfull use
of delegates
http://www.ruby-doc.org/stdlib/libdoc/delegate/rdoc/index.html
(documentation provided by James Earl Gray II)

Secondly, there is a great book entitled "Ruby Recipes", which discusses
your question in great detail and I higly recommend that you pick it
up..

Hope this helps and this is only a suggestion as there exists 100 ways
to do what you are asking.. :)

ilan
 
M

Matt Lawrence

I have become a tea.

So what did you do to get yourself in such hot water?

-- Matt
It's not what I know that counts.
It's what I can remember in time to use.
 
I

Ilan Berci

James said:
I have become a tea.

James *Edward* Gray II

Ouch!!! Apologies.. Not enough coffee this morning and guess what my
wife's favourite is.. Also forgot to mention Gavin as well.. :(

I will keep quiet for the remainder of the day.. :)

ilan
 
J

James Edward Gray II

Ouch!!! Apologies.. Not enough coffee this morning and guess what my
wife's favourite is..

No worries. Hal listed me as James Edward Gray III in the second
edition of The Ruby Way. Good to know he gives me the +1 I guess... ;)

James Edward Gray II
 
E

El Gato

Ilan said:
El Gato,

My suggestion is to take a good look at Ruby's incredibly powerfull use
of delegates
http://www.ruby-doc.org/stdlib/libdoc/delegate/rdoc/index.html
(documentation provided by James Earl Gray II)

Secondly, there is a great book entitled "Ruby Recipes", which discusses
your question in great detail and I higly recommend that you pick it
up..

Hope this helps and this is only a suggestion as there exists 100 ways
to do what you are asking.. :)

ilan


I'm not sure if I'm missing something, but it appears that calling map()
on a DelegateClass still returns an Array object. I guess I would need
map() and friends to return an InventoryReport class (i.e., Array
doesn't have a save method, whereas InventoryReport does, so calling
report.query.map {...}.save "/tmp/filename.xls" fails)
 
I

Ilan Berci

El said:
I'm not sure if I'm missing something, but it appears that calling map()
on a DelegateClass still returns an Array object. I guess I would need
map() and friends to return an InventoryReport class (i.e., Array
doesn't have a save method, whereas InventoryReport does, so calling
report.query.map {...}.save "/tmp/filename.xls" fails)

El Gato,

No, you didn't miss anything, your delegate class would return and
InventoryReport on map() (on a side note, I dislike the re-usage of the
"map" name for alternate functionality but that's another discussion)
which would have the save() method. The delegate pattern allows you to
"intercept" selectively in order to accomplish the task at hand.


ilan
 
C

Chad Perrin

No worries. Hal listed me as James Edward Gray III in the second
edition of The Ruby Way. Good to know he gives me the +1 I guess... ;)

James Edward Gray II

James Edward Gray II ++
 
B

Brian Candler

I'm sure I'm just being an idiot here... my mind is a little foggy this
morning, but I'm having a hard time understanding how to accomplish
this. I've written a class (I'll just put some snippets in for
understanding) in which I'd like to be able to use the following
behavior:

irb> columns = [[1, "Hostname"], [2, "Model"], [5, "OS Version"]]
irb> report = InventoryReport.new("HARDWARE_QUERY", columns)
irb> report.query.map {|a| a.first.downcase!; a}.save "/tmp/test.xls"

Where query() returns an array like [["host1", "Dell", "Windows"],
["host2", "Hitachi", "Windows"], ...]

However, throwing a map (or select or whatever) in there (obviously)
ends up returning an Array. How would I handle this so that it works as
I would like? Do I need to move my save(), to_csv(), and to_xls() stuff
into the Array class?

It was a Moment of Enlightenment for me when I realised that all this stuff
they teach you about OO, class hierarchies and inheritance is actually
irrelevant. It's mainly there just to keep C++ and Java compilers happy.

A more flexible approach is composition and delegation. (Object A *has a* B,
not Object A *is a* B)

In this model, what you'd get is something like this:

class InventoryReport
def initialize(query_name, fields, data = [])
@query_name = query_name
@fields = fields
@data = data
end

def query
ans = ...do database query...
self.class.new(@query_name, @fields, ans)
end

def map(&blk)
self.class.new(@query_name, @fields, @data.map(&blk))
end

... etc
end

But in that case, InventoryReport#query could modify its own @data instance
in-place, in which case you'd have

def query
@data = ...do database query...
end

def map(&blk)
@data = @data.map(&blk)
end

(Then you can argue about whether the methods should be called 'query!' and
'map!' but I won't go there)

Anyway, I find this approach is much more powerful. For example, your object
A can contain instances of B, C and D. When an incoming method call arrives,
it can forward to one of these, or use them in sequence to perform whatever
task is required.

If you find you are writing lots of explicit delegation, then use one of the
delegation patterns to help you - or just write a method_missing() function.
And if you're using inheritance as a form of implementation code sharing
between your own objects, then just use mixins for the common code.

Going down this route, a lot of the OO conundrums simply disappear - such as
"is a circle an oval, or is an oval a circle?"

I notice that your InventoryReport is already composed of three elements: a
query name, a field array, and a data array. If you use inheritance, you
have to worry about which of these elements is the "primary" one which
inherits from some other class, and deal with all the nits of inherited
methods (such as Array#map always returning an Array, as you discovered).
However if you use composition, all the elements are equal, and you can pick
whatever behaviour you want from all three.

Regards,

Brian.
 
G

Giles Bowkett

(documentation provided by James Earl Gray II)
LOL

Awesome :)

I love it. I get these images of the Lord of the Sith relaxing with a
nice McVittie's digestive biscuit. "The Force is strong in this cup."
 
P

Peña, Botp

From: _why [mailto:[email protected]] :
# fr: James Edward Gray II wrote:
# > >(documentation provided by James Earl Gray II)
# > I have become a tea.
#=20
# Not just any tea. A tea that speaks in the voice of Darth Vader.

can't resist my stomach, sorry list, but i really love this list! rotfl =
:)))
 

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,999
Messages
2,570,243
Members
46,836
Latest member
login dogas

Latest Threads

Top