From: Brian Tickler Date: 2009-09-22T11:57:30+09:00 Subject: Re: Getting to one attribute of my marshalled array of objects Douglas Seifert wrote: > 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 -- Posted via http://www.ruby-forum.com/.