Sunday 26 May 2013

Ruby: doing a second tutorial @ codeschool.com

G'day:
OK, I'm coffee`d up and am gonna do the next Ruby tutorial. Yesterday I did "Try Ruby", and now I'm on the second leg of the Ruby path "Ruby Bits". As a recap: I'm suddenly doing these Ruby courses because codeschool.com is offering their services for free this weekend. I have 16hrs of freeness to go, so I hope to get through two courses.


But first, a bit more about yesterday. After I finished my article y/day and went to the pub for a brain-cleansing pint (strangely: just one pint). Then I settled in to watch some telly and potter around. I figured I'd be getting on towards finishing the vanilla Ruby courses today, so would probably want to have a look at Ruby on Rails too at some stage, so set about installing that. Now you'll remember I'd installed Ruby stand-alone from the RubyInstaller that Aptana recommended I use. That worked fine, but try as I might - and I spent about 4h on it amidst watching various TV shows - I could not get all the dependencies RoR needed to run also installed, and just could not get RoR working. I spent all bloody evening googling and sifting through - mostly incomprehensible Stack Overflow questions/answers, blog posts, and various other forum messages. Some helped me progress slightly, some helped me progress a lot, but none actually got me there. It was all down to Ruby not liking the SQLite install I had, or something like that.

This morning I threw my hands up in despair and removed what I had installed, and googled "ruby on rails windows installer", and located RailsInstaller, which sounded like the ticket, so installed that and everything seems fine now. And that took 5min, and went mostly without a hitch. Something didn't quite work but the error message said what I needed to do (run some script), and having done that, things worked. That's better. But I am now back to Ruby 1.9.3. I dunno if that matters too much: at least it works.

However I'm not doing "~ on Rails" yet, I'm just focusing on the language.



Hmmm. Today's tutorial already annoys me. One issue I have with these online teaching sites is that every tutorial is implemented differently. Yesterday's one was just a "here's an explanation, now type some code in and press enter". I like this approach. Today's one is styled like a video game (a really bad 1980s-style game) with levels and bonus and things to unlock.



(BLEAH)

And I have to watch an explanatory video before I get to the code. I hate "learning" by video, as I'm forever having to pause and back-up and listen/watch again. And what I'm watching is still just code on the screen, it just disappears whilst I'm still digesting it (or reading the docs on it). But anyway, I guess the video already has me thinking "hey, [that thing] is cool, I'm looking fwd to trying that out (and writing it up here", so I guess it's not entirely bad. I think a uniform UX on these sites would go a long way though.

So I've finished the first video. The first section of this course is about expressions and - seemingly - control statements. I'm now gonna click "Start Challenges".

Expressions

Unless

First up we look at the unless conditional. This works in place of a negative expression, eg:

# unless.rb

name = "Zachary"

if !name.empty?
    puts "Hi " + name
end

puts "\n"

unless name.empty?
    puts "Hi " + name
end

Above we see a standard if statement with a negative expression, and then an unless statement with a positive condition. The plain-language of the unless is easier to brain-parse than the negative condition with the if, I think.

We can even expedite that code further but doing the unless expression inline:

# unlessInline.rb
name = "Zachary"
puts "Hi " + name unless name.empty?

Actually I don't think that's a very clear example as the association between "Hi " + name is muddied, and could be read as name unless [etc]. But you get the drift.


||=

That's all very parquet-tiles-looking, but it's also a conditional assignment operator. And works like <cfparam> does. Except elegantly:

# conditionalAssignment.rb

number ||= "tahi"
puts number

puts "\n"

number ||= "rua"
puts number

The output for this is:

tahi
tahi

This is just shorthand for:

# conditionalAssignmentLonghand.rb

number = number || "tahi"
puts number

puts "\n"

number = number || "rua"
puts number

The thing to remember here is that all expressions in Ruby return something, so it's OK to use number on the right-hand-side of an expression before it's set: because as an expression it returns nil (Ruby's take on null). So given the first usage of number is nil (false) the other side of the || is evaluated, and that's the value that is then returned from the right-hand-side of the assignment, and the value that gets assigned to the variable. I think the ||= operator is pretty cool.

I could not find the "official" document page for ||=, hence no link to it.

Conditional expressions

Like I said before, everything in Ruby is an expression, and has a value. Even the results of an if clause:

# ifExpression.rb

score = 75

result = if score >= 50
    :Pass
else
    :Fail
end

puts result.to_s

I'm just using symbols here because I can (although it's a reasonable use case). The point is we're assigning the value of result based on the value of the if expression.

This is a contrived example, and one would probably do this:

result = score >= 50 ? :Pass : :Fail

But bear in mind the if/else blocks could have more than a single statement in them.

The tutorial also reminds us that functions in Ruby just return the last expression processed, so we can do this in a function:

# ifReturn.rb

def markExam score
    result = if score >= 50
        :Pass
    else
        :Fail
    end
end

puts markExam(49).to_s

This outputs "Fail". Note that there's no return statement, just the result of the else block, in this case.

OK, so now I've passed that "level" and are progressing to "Level 2". Spare me.



Methods and Classes

Optional arguments

First up the tutorial discusses how Ruby deals with optional method arguments. There are a coupla... err... options... which one can use:

# optionalArgs.rb

def gatherTranslations1(english, maori="", japanese="", german="")
    puts english
    puts maori unless maori.empty?
    puts japanese unless japanese.empty?
    puts german unless german.empty?
end

gatherTranslations1("one", "tahi", "", "eins") 

puts "\n"

def gatherTranslations2(english, others={})
    puts english
    puts others[:maori] unless others[:maori].nil?
    puts others[:japanese] unless others[:japanese].nil?
    puts others[:german] unless others[:german].nil?
end

gatherTranslations2(
    "two",
    :maori        => "rua",
    :japanese    => "ni"
)

The first function, gatherTranslations1(), is pretty predictable and perhaps how one would do it in CFML. In the second function, gatherTranslations2(), we use an optional argument hash, and then when calling the function we use symbol/value pairs. We could do this in CFML as well, the chief difference is a trivial one in that we still pass the symbol/value pairs as discrete arguments, rather than as key/values in a struct like we'd need to in CFML. Trivial.

Exceptions

It's a bit of a strange time to throw this into the mix, but this was next in the tutorial.

# exception.rb

class SomeException < StandardError
end

begin
    raise SomeException.new
rescue SomeException => e
    puts e
end

This is much the same as we'd do in CFML, except we  need to define the Exception before we can raise it. Here our SomeException exception extends the inbuilt StandardError. This is just standard class inheritance like we'd do with a CFC in CFML. It's good that in Ruby an Exception is  a fully-fledged class, rather than basically just a label as it is in CFML.

After that instead of try/catch Ruby has begin/rescue/end, and uses raise rather than throw. Otherwise it's the same as CFML.

Variable-length argument support

Ruby has quite a neat way of implementing a function which accepts - by design - an undefined number of arguments.

# encoding: utf-8

def joinEm(*args)
    puts args.join(" ")
end

joinEm "tahi", "rua", "toru", "wha"
joinEm "whero", "karaka", "kowhai", "kakariki", "kikorangi", "tūāuri", "tawatawa"

This is a really trivial example, but it shows the technique: one prefixes an argument name with a *, then all corresponding passed-in values get put in an array of that name. Googling suggests this is called a variadic function. That fussy-sounding name is offset in Ruby by the fact the * is known colloquially as the "splat operator".

One interesting thing that made me go "how?!?" initially was that an argument marked as variable-length doesn't need to be the last one in the argument list (which I initially assumed it would need to be). For example this works fine:

# encoding: utf-8

def joinEm(firstOne, *middleOnes, lastOne)
    puts "First one: " + firstOne
    puts "Middle ones: " + middleOnes.join(" ")
    puts "Last one: " + lastOne
end

joinEm "tahi", "rua", "toru", "wha"
joinEm "whero", "karaka", "kowhai", "kakariki", "kikorangi", "tūāuri", "tawatawa"

Outputting:

First one: tahi
Middle ones: rua toru
Last one: wha

First one: whero
Middle ones: karaka kowhai kakariki kikorangi tuauri
Last one: tawatawa


Cool.

Oh, you'll also note that I've had to stick a directive at the top of the file so that it supports UTF-8-encoded text (to accommodate the macrons in "tūāuri"):

# encoding: utf-8

For this to work, I had to hastily install a new "gem", "magic_encoding". This was as simply as typing this:

gem install magic_encoding

Ruby whirred away for a while and said "yup" (figuratively), and then the functionality worked.

I didn't need to do this with my code yesterday, so I guess this is something that was automated in Ruby 2.0.0, but needs specific dealing-with in 1.9.3.


I'm also using the Array join method in there. Although you can probably guess what that does, if you don't know already.

Classes


The next bit treads some ground I'd already trod yesterday, so I'll just refer you back to that.

The one new thing it does mention is that you know the attr_accessor method I touched on yesterday, that sets a property to have an implicit getter and setter? Well there's another method attr_reader that only creates a getter for the property. One would use that if one had like a "created" timestamp on an object or something: something that can be read but not changed.

That's the end of that section. The next section is still entitled "Classes".



Classes

Hmm. A lot of the first part of this section is just OO theory: methods, encapsulation, etc. I won't bore you with that.

One thing I meant to observe before but didn't as I was wishing it would go away is that Ruby seems to be one of these languages that is in love with the underscore character. I think it's a bit of an eyesore. Methods seem to be called my_method rather than myMethod, and variables my_variable rather than myVariable. Bleah. I am going to resist this whilst writing my notes here, but it's perhaps worth bearing in mind when writing "real world" code. I wonder what the hell they were thinking when adopting that as a practice?

arrayAppend

Unrelated to the notion of "classes", one of my exercises was to append an element to an array. I hopefully tried this:

numbers = ["tahi", "rua", "toru"] + "wha"

But that gives a type conversion error.

Then I tried append and add, and those didn't exist. And finally settled on push which worked fine:

numbers = ["tahi", "rua", "toru"].push "wha"

However on the next step of the tutorial, I see there's a << method which also does the trick.

numbers = ["tahi", "rua", "toru"] << "wha"

private

Private methods are implemented by adding the word private to the method definition in a way similar to CFML. however the keyword is not linked the directly to the method following it, it seems to be kind of a flag which says "methods from here on are private". That's quite neat. EG:

# private.rb

class Accessibility

    def myPublicMethod
    end
    
    private
    def myFirstPrivateMethod
    end
    
    def myOtherPrivateMethod
    end

end


test = Accessibility.new

test.myPublicMethod
test.myOtherPrivateMethod

This errors on the last line with

private.rb:21:in `<main>': private method `myOtherPrivateMethod' called for #<Accessibility:0x25d7528> (NoMethodError)

super

The super keyword (in Ruby's case: method) works much the same as in CFML, except one cool thing: if one doesn't pass any arguments to the call to super, then Ruby assumes that all of them ought to be passed. This makes a kind of sense.

And that was that for that section.



activesupport

Well this section is clearly going to have something new... as I have no idea what "activesupport" is. [watches excruciating video]. Ah, OK, activesupport is basically an extension library, adding a bunch of stuff onto the core of Ruby (eg: more methods on arrays, dates, integers, etc). It seems like it's stuff originally written for Rails, but back-ported as a library for the Ruby language. Okey doke. There'll be some interesting stuff in here by the sounds of it. It requires installation, so I've needed to do this:

gem install activesupport

And they recommended installing the i18n one as well:

gem install i18n

This one has given me an error:

 C:\src\ruby\misc\rubyBits1\classes>gem install i18n
Fetching: i18n-0.6.4.gem (100%)
Successfully installed i18n-0.6.4
1 gem installed
Installing ri documentation for i18n-0.6.4...
Installing RDoc documentation for i18n-0.6.4...
ERROR:  While generating documentation for i18n-0.6.4
... MESSAGE:   error generating I18n.html: incompatible encoding regexp match (UTF-8 regexp with CP850 string) (Encoding::CompatibilityError)
... RDOC args: --op C:/apps/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/doc/i18n-0.6.4/rdoc lib --title i18n-0.6.4 Documentation --quiet

I'm buggered if I know what that means, but I'll deal with it later. Or if I need to in the course of writing this article.

Arrays

First up, they show the from and to methods:

# encoding: utf-8
require "active_support/all" 

puts ["tahi", "rua", "toru", "wha"].from(2)
puts "\n"
puts ["whero", "karaka", "kowhai", "kakariki", "kikorangi", "tūāuri", "tawatawa"].to(3)

puts "\n"
puts ["Pipiri", "Hōngongoi", "Here-turi-kōkā", "Mahuru", "Whiringa-ā-nuku", "Whiringa-ā-rangi", "Hakihea", "Kohi-tātea", "Hui-tanguru", "Poutū-te-rangi", "Paenga-whāwhā", "Haratua"].from(6).to(2)


Output:
toru
wha

whero
karaka
kowhai
kakariki

Hakihea
Kohi-tatea
Hui-tanguru


So you can probably tell what's going on here: from returns the part of the array from the given point, to returns the part of the array up to the given point. And remember Ruby arrays start from index 0. And as per the last example, because each method returns an array, they methods can be chained (to extract the summer months in New Zealand).

Also note how I pull in the activesupport stuff here, with the require method. BTW: this really slows down the processing of the code doing this. Having had a flutter around in the docs, I have found I can simply require "active_support/core_ext/array/access.rb" to get this example working. Which causes it to run much faster (but still visibly slower to start up than any of the other code thusfar used).

Hashes, dates, strings, etc

The rest of this section just had me trying out some of the methods on hashes, dates and strings. I'm not going to elaborate on them here as the exercises were basically just calling methods and seeing what the results were. However there's some really interesting functionality in there, so I recommend browsing through them. There's a lot of stuff CFML ought to do.



Now. I'd working my way through the next section "Modules", and written most of it up. However on review of what I'd written I'm clearly running out of steam, and indeed a couple of the exercises are doing my head in. Not that they're tricky - I get the underlying techniques - I just am not connecting with the tutorial. There's a facility to get "hints" towards the solutions and I've just looked at the hints for one of the questions and my reaction was "oh FFS, was that what you were wanting me to do? Why the hell didn't you say?". I suspect it's partly the paucity of narrative explaining what they want me to do for a given exercise, but also partly cos I'm stuffed.

Anyway, I'm gonna leave this article here, and put the rest of the stuff I've written up in another article tomorrow, along with the rest of this second course. The modules stuff is interesting, and the last section is about blocks, which also sounds like interesting stuff. I'm gonna watch the video now whilst I have dinner, but I'll leave the exercises for tomorrow (it's a bank holiday in the UK tomorrow, so other than a lunch appointment, I'll be in a position to "hit the books" some more without my job... um... getting in the way). But I just can't do the write-up justice just now, so I'll do it tomorrow.

The "free weekend" on Code School is going to finish before I come back to this stuff... I wonder if - given I've started this course - whether it'll let me finish it for free? I hope so.

That said, the amount of stuff I've learned this weekend has been excellent, so I think I'm happy to pay them for the subsequent courses anyhow. Depending on how much they cost.

Anyway... I've got dinner to finish preparing. And a glass of wine to pour. See ya tomorrow then!

--
Adam