My first Ruby experience

4 05 2009

Here is my story about my first ruby script ever, actually it was the first time I writing coded in 2 years time.  So please be gentle to my awful code 🙂

Purpose was to help PO to get a complete “read me” file over every patch the “Team” created.
So why not try something new while solving the problem? I started out by “just do it” with some help of  guessing and google, Here is my first version of the code:

Round 1

def createAndWriteToFile(file, text)
 myFile =  File.new(file, "w")
 myFile.write(text)
end

def getPatchID(path)
 myRes = path.scan(/.*Patch_(.*)\//i)
 return myRes.to_s
end

def getOutputHeader(x)
 outString = "\n ----------------------------------------------------------\n"
 outString += "Patch: " + getPatchID(x) + "\n"
 outString += "\n ----------------------------------------------------------\n"
 return outString
end

def getFileContent(x)
 fileContent = ""
 file = File.new(x, "r")
 while (line = file.gets)
    fileContent+= line
 end
 file.close
 return fileContent
end

def buildFileOutput(x)
 fileContent = getOutputHeader(x)
 fileContent += getFileContent(x)
 return fileContent
end

def getAllFilesOutput(files)
 filesContent = ""
 files.each {|x|
    filesContent += buildFileOutput(x)
 }
 return filesContent
end

#Run
createAndWriteToFile("AllReadme.txt",getAllFilesOutput(Dir["./*/README.txt"]))

Round 2

After asking Niclas Nilsson to review my code (on paper) I learned some new  things.

  • “returns” are optional.
  • “()” after method name is optional.
  • In Ruby “we” don’t use camel case
  • There is easier way to open and write to a file.
  • And last but not the smallest lesson the power of “.map”

So here is my new version of the code.

def create_and_write_to_file file, text
 File.new(file, "w").write(text)
end

def get_patch_id path
 path.scan(/.*Patch_(.*)\//i).to_s
end

def get_output_header x
 out_string  = "\n ----------------------------------------------------------\n"
 out_string += "Patch: " + get_patch_id(x) + "\n"
 out_string += "\n ----------------------------------------------------------\n"
end

def build_file_output x
 get_output_header(x) + File.read(x)
end

def get_all_files_output files
 files.map{ |file|build_file_output(file) }
end

create_and_write_to_file("AllReadme.txt",get_all_files_output(Dir["./*/README.txt"]))

Round 3

Okay hang on now it was Anders Janmyr turn to take a look at it.
This is how he would like to do the code.

def get_patch_id path
 path.scan(/.*Patch_(.*)\//i).to_s
end

def get_output_header path
 out_string  = "\n ----------------------------------------------------------\n"
 out_string += "Patch: " + get_patch_id(path) + "\n"
 out_string += "\n ----------------------------------------------------------\n"
end

File.open("AllReadme.txt", "w") do |output_file|
 readme_paths = Dir["./*/README.txt"]
 readme_paths.each do |readme_path|
    output_file.write get_output_header(readme_path)
    output_file.write File.read(readme_path)
 end
end

Round 4

Okay I get it, in ruby we don’t write too many lines or what?
Here is my final version.

def get_output_header path
 out_string  = "\n ----------------------------------------------------------\n"
 out_string += "Patch: " + path.scan(/.*Patch_(.*)\//i).to_s + "\n"
 out_string += "\n ----------------------------------------------------------\n"
end

File.open("AllReadme.txt", "w") do |output_file|
 readme_paths = Dir["./*/README.txt"]
 readme_paths.each do |readme_path|
    output_file.write get_output_header(readme_path) + File.read(readme_path)
 end
end

Round 5

Now its your turn to give me feedback. Which version would you prefer  and why? Would you write the code differently?
Please feel free to comment.

Thanks to Niclas Nilsson and Anders Janmyr for taking the time to help me in my learning process.

My next project in ruby is ongoing and will be a experiment with twitter and the home made candy machine called “SlickStreamer” developed by me and Marcus Olsson . I will soon post more information about this project.

Advertisements

Actions

Information

4 responses

4 05 2009
Niclas Nilsson

Fun post!

Probably just pasting problem, but you should indent line 15-16 in Round 3 and line 10 in round 4 to avoid confusion for other newborn Rubyists.

I suspect my “final” solution would look something like this, which I suppose reflects my views on readability and terseness – when you’re used to the language idioms of course.

def header path
  patch_number = path.scan(/.*Patch_(.*)\//i)
  separator = "\n#{'-' * 58}\n" 
  separator + "Patch: #{patch_number}" + separator
end

File.open("all-readme.txt", "w") do |f|
  Dir["./*/README.txt"].each do |path| 
    f.write header(path) + File.read(path)
  end
end

(And yes, the separator line is tongue-in-cheek 🙂

On a tangent about language idioms: I once was a strong proponent of “anyone should be able to read the code regardless of which language he/she comes from, but over the years I’ve realized that it’s not a good thing. Today I think you need to get past the basic idioms of a language before you can expect to be a fluent reader.

Kind regards
Niclas

4 05 2009
Niclas Nilsson

Or I’d kept the

def patch_number path
  path.scan(/.*Patch_(.*)\//i)
end

Probably the latter in defense of separation of concerns, but I do cheat now and then on < 50 line programs.

5 05 2009
Michael Nilsson

Thanks for the response I have fixed the intend issue.
Interesting you mention “anyone should be able to read the code regardless of which language he/she comes from”.
I was reflecting on the same thing yesterday. Thinking the first versions was kind of more readable (for programmers of other language ), but now with a couple of hours experience with Ruby I think more like you it is better to use the the power of the language as mush as possible. I think your last example actually is easy to read.

15 05 2009
Michael Nilsson

As always the requirement changed even to this small script.
Now there also a Swedish description that needs to be compiled.
I did some refactoring and this is the results:

def header path
  patch_number =  path.scan(/.*Patch_(.*)\//i).to_s 
  separator = "\n#{'-' * 58}\n"
  separator + "Patch: #{patch_number}" + separator
end
def compilation source_file, compilation_file
   File.open(compilation_file, "w") do |f|
     Dir["./*/"+ source_file].each do |path| 
        f.write header(path) + File.read(path)
     end
   end
end
compilation "README.txt", "all_readme.txt"
compilation "BESKRIVNING.txt", "all_beskrivning.txt"

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: