Getting to one attribute of my marshalled array of objects

B

Brian Tickler

I have looked around for an answer to this on the mailing list and
Google searches, but I am not finding anything, so sorry if this
question seems too basic but...

I am just learning Ruby, and one of my class assignments is writing a
command line app that tracks an inventory of cars, each car having 6
attributes: VIN, year, make, model, color, and price. We are supposed
to use 2 objects for this exercise, a Car class and a "List of Cars"
class. Then a menu that allows adding a car to the list, deleting a car
from the list, and showing the list.

I could probably done this assignment as-is :)...but I wanted to go a
little bit further. It did not seem to make sense that this app did not
call for any persistence and once you made your inventory of cars and
then left the app they were gone. So, armed with pickaxe, I tried to
add file handling, but...that didn't make sense either: since I was
using Car objects and a CarInventory object that is just an array of Car
objects, converting the objects all the time into a format where I could
put them into a delimited text file and then read them out again seemed
crazy.

Then I read farther in pickaxe (past what my class goes into,
unfortunately), and I found Marshalling. That seemed like exactly what
I wanted...I could just dump the CarInventory object with it's whole
array of Cars (into a file called cars.data in this case) and then read
it right back in when needed.

It is almost working, too :). But, I am having a problem figuring out
how to connect all the dots from A to D. A couple of things are
nagging:

- The first time the app is run, there is no cars.data file, so I have
to handle that, which I am doing now but it's far from elegant...the app
will decide you don't have any car inventory, initialize the inventory
with a Car that has all nil attributes, and ask you to enter the first
car, then you can Save. This has some issues in that even though I
tried to write the code to later overwrite the "nil car", it is still
there somehow :).

- More importantly, once I have a Saved cars.data file and I use
Marshal.load on it, I don't want that "enter the first car" routine to
kick in, so I try to check the first Car object in CarInventory and see
if its VIN number is nil or not. Somehow I am missing the boat here,
and I am sure it's something simple. When I try to get from Marshalled
object -> CarInventory instance -> first Car object -> first Car.vin I
get the following:

cars.rb:106: undefined method `[]' for #<CarInventory:0x3fb97c8>
(NoMethodError)

I am including the entire app below, it's not too big though I am sure
it is much bigger and clunkier than it needs to be ;)...like I said I am
jsut learning Ruby...any help would be appreciated in fixing my
immediate and dire issues, and/or anything about making the whole app
work more smoothly. You can ignore some of the other
also-not-actually-required-for-this-assignment bells and whistles I have
partially implemented for now. Sorry, I realize it's a bit messy.

----SOURCE

class Car

def initialize(vin, year, make, model, color, price)
@vin = vin
@year = year
@make = make
@model = model
@color = color
@price = price
end

def to_s
if !self.vin
@displayVIN = "(No VIN)"
else
@displayVIN = self.vin
end
if !self.color
@displayColor = ""
else
@displayColor = "(#@color)"
end
"#@displayVIN\t#@year #@make #@model #@displayColor\t#@price\n"
end

def <=> (compareThisCar)
self.year <=> compareThisCar.year
end

attr_reader :vin, :year, :make, :model, :color, :price
attr_writer :vin, :year, :make, :model, :color, :price

end

class CarInventory

@@inventoryFile = "cars.data"

def initialize
if File.exists?(@@inventoryFile)
File.open(@@inventoryFile) do |file|
@carArray = Marshal.load(file)
end
else
@carArray = Array.new
@carArray[0] = Car.new(nil, nil, nil, nil, nil, nil)
end
end

def save
File.open(@@inventoryFile, "w+") do |file|
Marshal.dump(self, file)
end
end

def addCar(car)
if car.vin == nil
@carArray[0] = car
else
@carArray << car
end
end

def updateCar(car)
updateIndex = @carArray.find {|vinMatch| car.vin == vinMatch}
if updateIndex
@carArray[updateIndex] = car
end
end

def delCar(car)
@carArray = @carArray - car
end

def to_s
@carArray.each {|car| car.to_s if car.vin}
end

def sort(sortBy)
case sortBy
when "V":
when "M":
when "P":
when "Y":
else
end
end

def filter(filterArray)

end

end

puts "Car Inventory 1.0\n"

cars = CarInventory.new
firstCar = cars.carArray
firstCar = firstCar[0]
if !firstCar.vin
puts "\nNo car inventory found, please enter the first car:"
puts "\nVIN:"
vin = gets.chomp
puts "\nYear:"
year = gets.chomp
puts "\nMake:"
make = gets.chomp
puts "\nModel:"
model = gets.chomp
puts "\nColor:"
color = gets.chomp
puts "\nPrice:"
price = gets.chomp
newCar = Car.new(vin, year, make, model, color, price)
cars.addCar(newCar)
else
cars.sort("V")
cars.filter(nil)
end

loop do
puts "\nVIN#\t\tCar:\t\t\tPrice:\n"
puts "------------------------------------------------\n"
puts cars.to_s
puts "------------------------------------------------\n"
puts "(A)dd a new car\n"
puts "(E)dit a car\n"
puts "(D)elete a car\n"
puts "(L)ist cars by VIN\n"
puts " by (M)ake and Model\n"
puts " by (P)rice\n"
puts " by (Y)ear\n"
puts "(S)ave changes\n"
puts "(Q)uit\n"
puts "\nCommand: "
command = gets.chomp.upcase
case
when command =~ /^A/ : puts "\nAdd a new car:\n"
puts "\nVIN:"
vin = gets.chomp
puts "\nYear:"
year = gets.chomp
puts "\nMake:"
make = gets.chomp
puts "\nModel:"
model = gets.chomp
puts "\nColor:"
color = gets.chomp
puts "\nPrice:"
price = gets.chomp
newCar = Car.new(vin, year, make, model, color, price)
cars.addCar(newCar)
when command =~ /^E/ : puts "\nEdit a car:\n"
when command =~ /^D/ : puts "\nDelete a car:\n"
when command =~ /^L/ : puts "\nList car inventory by VIN:\n"
when command =~ /^M/ : puts "\nList car inventory by Make and
Model:\n"
when command =~ /^P/ : puts "\nList car inventory by Price range:\n"
when command =~ /^Y/ : puts "\nList car inventory by Year\n"
when command =~ /^S/ : cars.save
when command =~ /^Q/ : puts "Are you sure? [Y/N]"
confirm = gets.chomp.upcase
exit if confirm =~ /^Y/
next
else puts "Illegal Command: #{command}"
end
end
 
F

Fabian Streitel

[Note: parts of this message were removed to make it a legal post.]

As a little off topic:

You should also read about YAML:
http://www.ruby-doc.org/core/classes/YAML.html.

I don't really know about marshalling since i serialize all my data
with YAML. It's human readable and automatically handles
dumping and loading your ruby objects.
All you need is:

c = Car.new
dumped = c.to_yaml
c = file.open('dumpfile') { |f| YAML::load(f) }

Greetz!
 
B

Brian Tickler

Fabian said:

Thanks, I did look at YAML which has about a page in pickaxe like
standard Marshalling, and I might use it later but for this assignment I
just want the simplest way to reliably write and read my object
(containing an array of objects) and read it back. The marshalling
*seems* to be working in that when I open the cars.data file it looks
like it has data on the child Car objects, but I can't tell for sure yet
until I actually figure out how to get all the way to my Car attributes
:).

Essentially I want a statement that acts like this:

cars = CarInventory.new # handle for object which is array of Cars
if !cars.car[0].vin # grab the first car from CarInventory and check if
the VIN is nil

That second statement does not fly and no matter how I deconstruct it I
can't seem to get at the array that CarInventory represents here.
 
D

Douglas Seifert

[Note: parts of this message were removed to make it a legal post.]

I won't comment on your design as it seems straightforward enough. It could
use some rethinking, but with time and experience you will come up with
better designs. If you want some pointers let me know ...

Some issues with the implementation though, my comments below ...

class Car

attr_reader :vin, :year, :make, :model, :color, :price
attr_writer :vin, :year, :make, :model, :color, :price

The above can be shorted to one line:

attr_accessor :vin, :year, :make, :model, :color, :price

end

class CarInventory

@@inventoryFile = "cars.data"

def initialize
if File.exists?(@@inventoryFile)
File.open(@@inventoryFile) do |file|
@carArray = Marshal.load(file)
end
else
@carArray = Array.new
@carArray[0] = Car.new(nil, nil, nil, nil, nil, nil)
end
end

def save
File.open(@@inventoryFile, "w+") do |file|
Marshal.dump(self, file)

Here you are dumping the CarInventory (self) object to the file. What you
really want to do is dump the @carArray to the file. This is because above
in the initialize method you are trying to read the @carArray from the
file. This is the source of the undefined method `[]' error you are getting
when you run the program after saving.

end
end

def addCar(car)
if car.vin == nil
@carArray[0] = car
else
@carArray << car
end
Not sure what you are trying to do with this implementation. I would change
it to simply

@carArray << car

end

def updateCar(car)
updateIndex = @carArray.find {|vinMatch| car.vin == vinMatch}
if updateIndex
@carArray[updateIndex] = car
end
This will not have the effect you want. I think you will have to define ==
on the Car class and implement it use vin as the equality check:

class Car
...
def ==(other)
self.vin == other.vin
end
...
end

Then, the updateCar method of the CarInventory class could be something
like:

def updateCar(car)
i = @carArray.index(car)
if i
@carArray = car
else
# TODO: Print an error message? Raise an exception?
end
end

end
def delCar(car)
@carArray = @carArray - car

Here, you need
@carArray = @carArray - [car]

and it will only work if you define Car#== as above. The Array#- method
only works on two Array objects. You can't use it to remove an object.
Alternatively, you could use Array#delete:

def delCar(car)
@carArray.delete(car)
end

puts "Car Inventory 1.0\n"

cars = CarInventory.new
firstCar = cars.carArray
firstCar = firstCar[0]

I would change the above two lines to simply
firstCar = cars.carArray.first

if !firstCar.vin

Then instead of looking for a nil vin, just check if firstCar itself is nil:

if !firstCar

puts "\nNo car inventory found, please enter the first car:"
puts "\nVIN:"
vin = gets.chomp
puts "\nYear:"
year = gets.chomp
puts "\nMake:"
make = gets.chomp
puts "\nModel:"
model = gets.chomp
puts "\nColor:"
color = gets.chomp
puts "\nPrice:"
price = gets.chomp
newCar = Car.new(vin, year, make, model, color, price)
cars.addCar(newCar)
else
cars.sort("V")
cars.filter(nil)
end

But really, I would just get rid of the entire if/else statment above and go
directly into the loop below it. The initial logic is not DRY and doesn't
add anything the loop doesn't already give you.

Hope that helps,
Doug Seifert
 
D

Douglas Seifert

[Note: parts of this message were removed to make it a legal post.]

One other thing:


def initialize
if File.exists?(@@inventoryFile)
File.open(@@inventoryFile) do |file|
@carArray = Marshal.load(file)
end
else
@carArray = Array.new
@carArray[0] = Car.new(nil, nil, nil, nil, nil, nil)
I would delete the above line, it is not needed. You should use the fact
that the array itself is empty to denote there are no cars in the inventory,
not a fictional marker car with nil VIN.

-Doug Seifert
 
B

Brian Tickler

Thanks Doug,

I implemented all those changes. I guess I just went too far down the
wrong road with trying to handle the empty inventory, so I removed that
whole mechanic. Part of that was that I tried to marshal @carArray
originally but messed it up somehow (it only dumped the object ID or
something), and that's when I tried to marshal self instead and it
worked. Not sure what I did there since when I tried again with
@carArray it worked fine.

I am having another issue, though. In order to delete a car, I need to
number the inventory when it displays so that I can ask the user to tell
me which car to delete by its number on the list (this is part of the
assignment so I can't really do it another way).

At first I thought I would modify the to_s on CarInventory which right
now just iterates through the array of Cars using .each and calls
Cars.to_s, but then I decided it would be more robust if Cars.to_s was
aware of whether it was being called repeatedly as part of a listing or
not, from anywhere.

So I added a parameter "def to_s(listPosition=0)" to Car.to_s and
changed the .each call in the CarInventory.to_s to pass a position
number. When I run the progrma now though I get a really bizarre
behavior...I was supposed to get this type of output with 2 cars in my
data file (simplified because I am also formatting it to comform to
columns, etc.):

[1] VIN# Year Make Model Color Price
[2] ...

Instead I got the same results without any bracketed list numbers...so I
added a debug counter and a puts of the relevent part of my string
output, and I got this:

Called 1 times
[1]
Called 2 times
[2]
Called 3 times
nil
VIN# [...]
Called 4 times
nil
VIN# [...]

Which is where I am stuck...after adding the parameter Ruby appears to
be calling to_s 4 times now instead of 2 times and I have no idea why.
The first 2 times it gets the first part of the string I am building set
up correctly, but loses everything else and the second 2 times it does
the whole rest of the string build correctly but ignores the first part.
I have tried everything I cna thinnk of to display the strings invloved
at every stage/state and it seems ok...I just can't figure out why the
to_s is calling four times when it should be twice.

Source below (you can run it, add a car or two, then uncomment the DEBUG
lines to see what happens):

----SOURCE

class Car

# DEBUG: @@debugCounter = 0

def initialize(vin, year, make, model, color, price)
@vin = vin
@year = year
@make = make
@model = model
@color = color
@price = price
end

def to_s(listPosition=0) # allow for a listing position to be passed
# DEBUG: @@debugCounter = @@debugCounter + 1
# DEBUG: puts "Called #@@debugCounter times"
displayString = ""
80.times {displayString << " "} # lay down a canvas of spaces to
build the full displayString
if !self.vin
displayVIN = "(No VIN)"
else
displayVIN = self.vin
end
if !self.color
displayColor = ""
else
displayColor = "(#@color)"
end
# DEBUG: debugString = "[" + listPosition.to_s + "]" if listPosition
!= 0
# DEBUG: puts debugString
displayString[0, 0] = "[" + listPosition.to_s + "]" if listPosition
!= 0
displayString[6, 0] = displayVIN
displayString[25, 0] = @year + " " + @make + " " + @model + " " +
displayColor
displayString[70, 0] = @price
displayString = displayString.strip # make the format look like a
"normal" to_s call if passed no list position
end

def <=> (compare)
self.year <=> compare.year
end

attr_accessor :vin, :year, :make, :model, :color, :price

end

class CarInventory

@@inventoryFile = "cars.data"

def initialize
if File.exists?(@@inventoryFile)
File.open(@@inventoryFile) do |file|
@carArray = Marshal.load(file)
end
else
@carArray = Array.new
end
end

def save
File.open(@@inventoryFile, "w+") do |file|
Marshal.dump(@carArray, file)
end
end

def addCar(car)
@carArray << car
end

def updateCar(car)
updateIndex = @carArray.index(car)
if updateIndex
@carArray[updateIndex] = car
else
puts "Cannot find car #car.vin\n"
end
end

def delCar(car)
@carArray.delete(car)
end

def to_s
if @carArray.first
@carArray.each {|car| car.to_s(@carArray.index(car) + 1)}
else
return "(No inventory)"
end
end

def ==(compare)
self.vin == compare.vin
end

def sort(sortBy)
case sortBy
when "V":
when "M":
when "P":
when "Y":
else
end
end

def filter(filterArray)

end

attr_reader :carArray, :inventoryFile

end

puts "Car Inventory 1.0 - Brian Tickler\n"

cars = CarInventory.new

loop do
puts "\n# VIN Car Description
Price\n"
puts
"----------------------------------------------------------------------------\n"
puts cars.to_s
puts
"----------------------------------------------------------------------------\n"
puts "(A)dd a new car\n"
puts "(E)dit a car\n"
puts "(D)elete a car\n"
puts "(S)ave changes\n"
puts "(Q)uit\n"
puts "\nCommand: "
command = gets.chomp.upcase
case
when command =~ /^A/ :
puts "\nAdd a new car:\n"
puts "\nVIN:"
vin = gets.chomp
puts "\nYear:"
year = gets.chomp
puts "\nMake:"
make = gets.chomp
puts "\nModel:"
model = gets.chomp
puts "\nColor:"
color = gets.chomp
puts "\nPrice:"
price = gets.chomp
newCar = Car.new(vin, year, make, model, color, price)
cars.addCar(newCar)
when command =~ /^E/ :
puts "\nEdit a car:\n"
when command =~ /^D/ :
puts "\nDelete a car:\n"
when command =~ /^S/ :
cars.save
puts "\nSaved to #cars.inventoryFile\n"
when command =~ /^Q/ :
puts "Are you sure? [Y/N]"
confirm = gets.chomp.upcase
exit if confirm =~ /^Y/
next
else puts "Illegal Command: #{command}"
end
end
 
D

Douglas Seifert

[Note: parts of this message were removed to make it a legal post.]

Brian,

I am having another issue, though. In order to delete a car, I need to
number the inventory when it displays so that I can ask the user to tell
me which car to delete by its number on the list (this is part of the
assignment so I can't really do it another way).

At first I thought I would modify the to_s on CarInventory which right
now just iterates through the array of Cars using .each and calls
Cars.to_s, but then I decided it would be more robust if Cars.to_s was
aware of whether it was being called repeatedly as part of a listing or
not, from anywhere.

I would not go down this road. A car should not care if it is being
displayed as part of a listing of other cars or if it is alone. It should
just know how to display itself. In addition, adding a parameter to to_s is
bad ruby style: to_s has well-defined semantics in the ruby language. I
suggest before you go any further, you pick up a copy of "Programming
Ruby". If you can't afford the money, the first edition is available for
free on line: http://www.rubycentral.com/book/. The book does a far better
job than I ever could of explaining how the to_s method works in the Ruby
world.

So I added a parameter "def to_s(listPosition=0)" to Car.to_s and
changed the .each call in the CarInventory.to_s to pass a position
number. When I run the progrma now though I get a really bizarre
behavior...I was supposed to get this type of output with 2 cars in my
data file (simplified because I am also formatting it to comform to
columns, etc.):
After looking at your implementations, I see what you are trying to
accomplish. A better idea in my opinion would be to leave the counting
logic to the CarInventory class. It is the class that knows about a list of
cars.

I noticed also that you tried to put some logic in to line up the car
attributes in a nice column layout. You are reinventing the wheel here,
unfortunately. One thing you should look up right away is is the % method
of class String:

$ ri String#%
--------------------------------------------------------------- String#%
str % arg => new_str
------------------------------------------------------------------------
Format---Uses _str_ as a format specification, and returns the
result of applying it to _arg_. If the format specification
contains more than one substitution, then _arg_ must be an +Array+
containing the values to be substituted. See +Kernel::sprintf+ for
details of the format string.

"%05d" % 123 #=> "00123"
"%-5s: %08x" % [ "ID", self.id ] #=> "ID : 200e14d6"

You should do an ri Kernel#sprintf to see all the good stuff that can go in
the format string. If you are familiar with c/c++ at all, this will be
familiar to you.

I'm not sure also in your original to_s method on the CarInventory class how
it ever worked. The each method of class Array returns the Array itself.
Your to_s method would end up returning an array of Car objects and it might
have been pure accident that it looked ok in your command loop.

Here is a revised to_s for the CarInventory class. It handles the indexing
problem and makes sure it returns a String object in all cases. I would
implement Car#to_s without a parameter and use the formatting methods above
to get a nice column layout

def to_s
if @carArray.first
counter = 0
@carArray.map {|car| counter += 1; "[%3d] %s" % ]counter,
car.to_s]}.join("\n")
else
return "(No inventory)"
end
end

I am not sure how a beginner goes about accumulating the knowlege necessary
to make implementing this method easy. There is a lot of knowledge bundled
up in this implementation of to_s: use of Array#map and Array#join.
Knowledge of String#% and by implication, Kernel#sprintf. I have done a lot
of reading I guess.

This whole thread is reminding me of a recent blog post by Jamis Buck, a guy
who I think it is fair to say is a Ruby community luminary:

http://weblog.jamisbuck.org/2009/9/17/there-is-no-magic-there-is-only-awesome-part-1

If you ever want to get good with Ruby, you need to apply his four rules:

1. *Know thy tools.*
2. *Know thy languages.*
3. *Know thy libraries.*
4. *Know thy communities.*

Applying these rules, you would know about the % method of the String class
and about Kernel#sprintf and Array#map and Array#join.

So keep studying! It is a high hill to climb, but once you get over the
top, your programs will reach the level of conciseness that Ruby is famous
for.

-Doug Seifert
 
B

Brian Tickler

Douglas said:
I would not go down this road. A car should not care if it is being
displayed as part of a listing of other cars or if it is alone. It
should
just know how to display itself. In addition, adding a parameter to
to_s is
bad ruby style: to_s has well-defined semantics in the ruby language.

Ok, I will stay away from that. I normally stay pretty hands off on
things like that, but I have seen a lot of examples of modifying really
important things like Object and Integer in Ruby, so I thought it was
just more normal to do this and that because it's interpreted it would
not be permanent. I did try to leave the functioning of to_s untouched
if the parameter was not passed by defaulting the parameter and making
sure the output when not in a list was as expected, but maybe that only
works with the *p array method? Anyway, I see the point, so I took it
out.
I
suggest before you go any further, you pick up a copy of "Programming
Ruby". If you can't afford the money, the first edition is available
for
free on line: http://www.rubycentral.com/book/. The book does a far
better
job than I ever could of explaining how the to_s method works in the
Ruby
world.

I do have it, and I have read the entire book ;). That's how I deciding
on marshalling, etc. I will say the book is great, but if you don't
have a C background or something similar (I do, but only at a basic
level and dating from the 1980s ;)...), it's hard to figure some things
out. I ended up googling a lot of stuff to get sample code that I could
understand readily for things that are not covered in depth.
After looking at your implementations, I see what you are trying to
accomplish. A better idea in my opinion would be to leave the counting
logic to the CarInventory class. It is the class that knows about a
list of
cars.
Fixed.

I noticed also that you tried to put some logic in to line up the car
attributes in a nice column layout. You are reinventing the wheel here,
unfortunately. One thing you should look up right away is is the %
method
of class String: [snip...]
You should do an ri Kernel#sprintf to see all the good stuff that can go
in
the format string. If you are familiar with c/c++ at all, this will be
familiar to you.

I remembered this from C after you talked about it. Also fixed now, but
I did end up having to Google around to find an example since the ones
in the book don't cover many of hte possible scenarios/uses for this.
I'm not sure also in your original to_s method on the CarInventory class
how
it ever worked. The each method of class Array returns the Array
itself.
Your to_s method would end up returning an array of Car objects and it
might
have been pure accident that it looked ok in your command loop.

Hmmm...ok, I am confused on this one. I thought the whole point of
each was to iterate through the elements of arrays and hashes and
perform actions on each one...like a for or while loop without the fuss
and muss of counters, etc.
Here is a revised to_s for the CarInventory class. It handles the
indexing
problem and makes sure it returns a String object in all cases. I would
implement Car#to_s without a parameter and use the formatting methods
above
to get a nice column layout

def to_s
if @carArray.first
counter = 0
@carArray.map {|car| counter += 1; "[%3d] %s" % ]counter,
car.to_s]}.join("\n")
else
return "(No inventory)"
end
end

Done, as you'll see below.
I am not sure how a beginner goes about accumulating the knowlege
necessary
to make implementing this method easy. There is a lot of knowledge
bundled
up in this implementation of to_s: use of Array#map and Array#join.
Knowledge of String#% and by implication, Kernel#sprintf. I have done a
lot
of reading I guess.

One of the issues I am having in looking up what I need is that there's
no master hierarchy/chart/graph of Ruby objects *and* mixins together in
the book...and in this case, with Array, map is actually from the
Enumerable mixin, although there is a connect! (which I guess is the
same as map); but as a neophyte I was generally staying away from any
commands that modify objects in-place and so I did not really look
closely at what connect! was. Once I saw your code I was able to figure
it out, though.
This whole thread is reminding me of a recent blog post by Jamis Buck, a
guy
who I think it is fair to say is a Ruby community luminary:

http://weblog.jamisbuck.org/2009/9/17/there-is-no-magic-there-is-only-awesome-part-1

I read this and I like the ideas. Hopefully the next installment is
coming soon.
If you ever want to get good with Ruby, you need to apply his four
rules:

1. *Know thy tools.*
2. *Know thy languages.*
3. *Know thy libraries.*
4. *Know thy communities.*

Applying these rules, you would know about the % method of the String
class
and about Kernel#sprintf and Array#map and Array#join.

Well, I have to be honest here, I will never be a master of Ruby. I am
a jack-of-all-trades in general, plus I left my programming days behind
me long ago. I am evaluating a bunch of languages/technologies right
now for my website/garage startup (I am also taking more classes...in
PHP, Python, C#, Ajax, Perl, and I have been going to Drupal meetups
since I did not see any classes on it). Right now, I like Ruby the best
(more of a gut feeling than anything else), and I have not even looked
at Rails yet ;). I have not really touched the .NET side of the house
yet, though, to be fair.

Once I do have my chosen technology, though, I will probably end up
having to code the first mockup of the site myself, so I do need to know
as much as I can.

I want to thank you for all your help, Doug, it was very much
appreciated. You mentioned in an earlier post that if I wanted to know
how to design this app better you could give me some tips...up to you,
but I am all ears. Frankly, when I have been doing these Ruby
assignments, they seem more like generic "learn this language's syntax"
exercises and don't seem to be asking me to do things that will show me
the power of Ruby.
So keep studying! It is a high hill to climb, but once you get over the
top, your programs will reach the level of conciseness that Ruby is
famous
for.

I have finishing pickaxe/Programming Ruby, and I also read a certain
Ruby guide involving chunky bacon. I pretty much had to, my class is
online-only and I have had to learn everything on my own.

As far as conciseness goes, I'm sure this does not qualify...but here's
the final source for my assignment (in case anyone is looking at this
thread for answers somewhere down the road and needs to see it):

----SOURCE

# Car Inventory 1.0
# Brian Tickler
# 9/2009

INVENTORY_FILE = "cars.data"

class Car

def initialize(vin, year, make, model, color, price)
@vin = vin
@year = year
@make = make
@model = model
@color = color
@price = price
end

def to_s
if !self.vin or self.vin == ""
displayVIN = "(No VIN)"
else
displayVIN = self.vin
end
if !self.color or self.color == ""
displayColor = ""
else
displayColor = "(#{self.color})"
end
displayString = "%-20s %-40s %10s" % [
displayVIN,
(@year + " " + @make + " " + @model + " " + displayColor),
("$" + @price.to_s) ]
end

def <=> (compare)
self.year <=> compare.year
end

attr_accessor :vin, :year, :make, :model, :color, :price

end

class CarInventory

def initialize
if File.exists?(INVENTORY_FILE)
File.open(INVENTORY_FILE) do |file|
@carArray = Marshal.load(file)
end
else
@carArray = Array.new
end
end

def save
File.open(INVENTORY_FILE, "w+") do |file|
Marshal.dump(@carArray, file)
end
end

def addCar(car)
@carArray << car
end

def updateCar(car)
updateIndex = @carArray.index(car)
if updateIndex
@carArray[updateIndex] = car
else
puts "Error: Cannot find car #{car.vin}\n"
end
end

def deleteCar(car)
@carArray.delete(car)
end

def getCar(listPosition)
@carArray[listPosition - 1]
end

def to_s
if @carArray.first
displayString = ""
counter = 0
priceTotal = 0
displayString << "\n# VIN Car Description
Price\n"
displayString <<
"------------------------------------------------------------------------------\n"
displayString << @carArray.map {|car| counter += 1 ; \
priceTotal += car.price.to_i; \
"[%3d] %s" % [counter, car.to_s]}.join("\n")
displayString <<
"\n------------------------------------------------------------------------------\n"
displayString << "%78s" % ("Total Inventory Value: $" +
priceTotal.to_s)
else
return "(No inventory)"
end
end

def ==(compare)
self.vin == compare.vin
end

end

def inputValue(inputLabel, inputRegexp, inputWarningString,
allowBlankValue=false)
loop do
puts inputLabel
input = gets.chomp
return "" if allowBlankValue and input == ""
if input =~ inputRegexp
return input
else
puts inputWarningString
end
end
end

puts "Car Inventory 1.0 - Brian Tickler\n"

cars = CarInventory.new

loop do
puts cars.to_s
puts "(A)dd a new car\n"
puts "(E)dit a car\n"
puts "(D)elete a car\n"
puts "(S)ave changes\n"
puts "(Q)uit\n"
puts "\nCommand: "
command = gets.chomp.upcase
case
when command =~ /^A/ :
puts "\nAdd a new car:\n"
vin = inputValue("\nVIN:", /\w{3,17}/, "Please enter a VIN or
identifier that is 3-17 alphanumeric characters.")
year = inputValue("\nYear:", /^[12][90]\d{2}$/, "Please enter a
year between 1901-2099.")
puts "\nMake:"
make = gets.chomp
puts "\nModel:"
model = gets.chomp
puts "\nColor:"
color = gets.chomp
price = inputValue("\nPrice:", /^\d*$/, "Please enter an integer
(round to the nearest dollar).")
newCar = Car.new(vin, year, make, model, color, price)
cars.addCar(newCar)
when command =~ /^E/ :
puts "\nEdit a car:\n"
puts "\nEnter the number of the car to be edited:"
edit_selection = gets.chomp.to_i
editCar = cars.getCar(edit_selection)
puts "\nCar: " + editCar.to_s
puts "(leave fields blank if you want them to keep their current
values)"
if editCar
vin = inputValue("\nVIN:", /\w{3,17}/, "Please enter a VIN or
identifier that is 3-17 alphanumeric characters.", true)
year = inputValue("\nYear:", /^[12][90]\d{2}$/, "Please enter a
year between 1901-2099.", true)
puts "\nMake:"
make = gets.chomp
puts "\nModel:"
model = gets.chomp
puts "\nColor:"
color = gets.chomp
price = inputValue("\nPrice:", /^\d*$/, "Please enter an integer
(round to the nearest dollar).", true)
puts "\nUpdate VIN# #{editCar.vin}? [Y/N]"
confirm = gets.chomp.upcase
if confirm =~ /^Y/
editCar.vin = vin if vin != ""
editCar.year = year if year != ""
editCar.make = make if make != ""
editCar.model = model if model != ""
editCar.color = color if color != ""
editCar.price = price if price != ""
end
else
puts "Cannot access car #" + edit_selection.to_s
end
when command =~ /^D/ :
puts "\nDelete a car:\n"
puts "\nEnter the number of the car to be deleted:"
delete_selection = gets.chomp.to_i
delCar = cars.getCar(delete_selection)
puts "\nCar: " + delCar.to_s
if delCar
puts "\nDelete VIN# #{delCar.vin}? [Y/N]"
confirm = gets.chomp.upcase
cars.deleteCar(delCar) if confirm =~ /^Y/
else
puts "Cannot access car #" + delete_selection.to_s
end
when command =~ /^S/ :
cars.save
puts "\nSaved to #{INVENTORY_FILE}\n"
when command =~ /^Q/ :
puts "Are you sure? [Y/N]"
confirm = gets.chomp.upcase
exit if confirm =~ /^Y/
next
else puts "Illegal Command: #{command}"
end
end
 
F

Fabian Streitel

[Note: parts of this message were removed to make it a legal post.]
Hmmm...ok, I am confused on this one. I thought the whole point of
.each was to iterate through the elements of arrays and hashes and
perform actions on each one...like a for or while loop without the fuss
and muss of counters, etc.

That is correct. But unlike a C-style for or while loop, a call to #each()
does
have a return value. And again unlike C, a function always returns its last
value, e.g:

def foo
5
end
foo() # returns 5

And since #each() returns an array and the call to #each() was the
last thing in the #to_s() method, #to_s() would just return what
#each() returned -- an array.

What you wanted was probably #inject(). It's a very powerful method
that let's you do all kinds of neat things. See [1] and [2]

Greetz!

[1]: http://www.ruby-doc.org/core/classes/Enumerable.html#M003140
[2]: http://blog.jayfields.com/2008/03/ruby-inject.html
 

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

Forum statistics

Threads
473,968
Messages
2,570,150
Members
46,696
Latest member
BarbraOLog

Latest Threads

Top