20090404

Giving up on Blogspot

I just don't like the software behind Blogspot, so despite my short stay I'm moving to blog.lyte.id.au.

20090317

Recursive many-to-many association in Rails

I'm fairly new to Ruby on Rails and I needed a way to create a recursive many-to-many association, but search as I may, the best help I could find was always on RailsRocket, and frankly their article, along with a lot of rails tutorials I've been reading, all leave a lot to be desired.

So here goes, a Recursive many-to-many association in Rails tutorial by a Rails newbie.

I'm using Ruby 1.8.7, RubyGems 1.2.0, Rails 2.1.0 and Linux.

App. Description
For this example I'm going to go with the what the RailsRocket article went over breifly. That is a Student database where we are mapping Tutors.

Why is this recursive? Well a Tutor is a Student, a Tutor teaches a Student and a Student is Tutored by a Student. Get it?

Getting started
Initialise the app:
$ rails students
$ cd students

Create Student Scaffold
$ rake db:migrate
$ script/generate scaffold Student name:string

Enter some studentsStarting up the server and navigating to the students url should produce an empty listing.
$ script/server
Now just put in some students so that we have something to play with. I created two: 'Dave' and 'Frank'.

Add the Tutorship model
$ script/generate model Tutorship tutor_id:integer pupil_id:integer
$ rake db:migrate

Add Rails associations
Add belongs_to associations in app/models/tutorship.rb:
  belongs_to :tutor, :class_name => "Student", :foreign_key => "tutor_id"
belongs_to :pupil, :class_name => "Student", :foreign_key => "pupil_id"
The only way I've been able to get this to work the way I want is to add an intermediate association on Student to it's Tutorships and then the actual association we want to use to map right through, via they has_many :through functionality.

For those who can make more sense of code, add the following associations to app/model/student.rb:
  has_many :tutorship_pupils,
:foreign_key => :tutor_id,
:class_name => 'Tutorship'
has_many :pupils,
:through => :tutorship_pupils,
:foreign_key => :pupil_id,
:class_name => 'Student'

has_many :tutorship_tutors,
:foreign_key => :pupil_id,
:class_name => 'Tutorship'
has_many :tutors,
:foreign_key => :tutor_id,
:through => :tutorship_tutors,
:class_name => 'Student'

Test the models
Fire up script/console and create a new Tutorship:
>> t = Tutorship.new
=> #<tutorship nil="">
>> t.tutor = Student.find_by_name('Dave')
=> #<student dave="" 17="" 19="">
>> t.pupil = Student.find_by_name('Frank')
=> #<student frank="" 26="" 17="" 33="">
>> t.save!
=> true
Now check that you can get pupils/tutors on a student:
>> s = Student.find_by_name('Dave')
=> #<student dave="" 19="" 17="" 14="">
>> s.pupils
=> [#<student frank="" 26="" 17="" 14="">]
>> s = Student.find_by_name('Frank')
=> #<student frank="" 26="" 17="" 14="">
>> s.tutors
=> [#<student dave="" 19="" 17="" 14="">]

Editing interface
To make this all editable in the front end I added some data gathering code in app/controllers/students_controller.rb:
  def edit
@student = Student.find(params[:id])
@possible_tutors = @possible_pupils = Student.all
@selected_tutors = @student.tutors
@selected_pupils = @student.pupils
end
I also add some view code in app/views/students/edit.html.erb:
  <b>:Tutors:</b>:
<p>:
<%=
select_tag(
'student[tutor_ids][]',
options_for_select(
@possible_tutors.collect { |s| [s.name, s.id] },
@selected_tutors.collect { |s| s.id }
),
:multiple =>: true
)
%>:
</p>:
<b>:Pupils:</b>:
<p>:
<%=
select_tag(
'student[pupil_ids][]',
options_for_select(
@possible_pupils.collect { |s| [s.name, s.id] },
@selected_pupils.collect { |s| s.id }
),
:multiple =>: true
)
%>:
</p>:
It should now be possible to edit tutor associations via the student edit action. The code for the student new action is fairly similar.

I was going to attach a zip file with all the code and full files for each snippet I mentioned but it seems this blog software doesn't really give me that functionality. If you'd like the code send me a message or leave a comment.

20081218

Test Driven Learning

Everyone already knows about Test Driven Design, but today I happened to come across a clever idea, from someone called Mike Clark, about using exactly the same process for learning a new programming language. This is quite simply brilliance.

Astoundingly Awesome!

Yes that's right, it's astounding. Why? Because now when I learn something new, I'll write some code about it, and as Mike pointed out if you write it down you are so much more likely remember it. But that's not even scratching the surface, not only will I now have a great way of forcing otherwise unmemorable knowledge in to my brain, I'll also have documentation for everything I've already used. Documentation for everything I've already used, documentation that I've written! Seriously think for a moment about how good that is, no more searching through APIs and obtuse JavaDoc output trying to find that function/method I once used and now can't find just because I've forgotten the name of it and where I last used it.

You seriously think that's enough of a reason to justify wasting so much time writing code you're never actually going to use? Yes! Not only is it enough reason, there are others, but I'll come back to those later. For now let me tell you why it doesn't actually take any more time.

Every time I go to use a new library I find myself writing a file named "debug.ext", this file is roughly the same in every language I've played with, it will have the appropriate require statements, some initialization and then a few print/dump type statements testing how the thing works. Up till now this has always been code that I've written and then never used, that's right never! Often once I've actually implemented the new feature using delete the file all together, what a waste! It takes absolutely no more time to write exactly the same thing in to a test case file.

Ok now that I've convinced you it's not going to hurt much (I have convinced you haven't I? if you answer no to this, please just stop reading now) to switch to using this genius idea, lets go back and briefly discuss the remaining good points.

I can reuse the same Test Cases I write while learning how to use a library over and over again. So even though I'm still writing about the same amount of code while learning a new library, I'm no longer throwing it away at the end of that process, meaning I already have the basis for a Test Case for my app when it comes time to write one.

Sometimes libraries just change a little. I'm not refering to big things like the way objects changed between PHP 4 and 5, I mean subtler things, sometimes even bugs. Library maintainers have a picture in their mind of the way everyone uses their library, and it's possible that if I'm not using it in just the way they pictured that one day I'll have a piece of code that will stop working for no other reason than the API has changed slightly. Sure I should be able to find that out from a changelog, but some libs don't have changelogs, and lets be honest, I usually don't even read them.

Couldn't you just catch changes to the Language and Libraries you use in the normal test cases for your app? Probably, but maybe I don't want to. The current app I work with (at work), is written in PHP 4 and has an insane number of dependencies and things that can go wrong at install. At some point we will need to upgrade to PHP 5. Why would I want to go through all the hassle of configuring up a full install with PHP 5 just to start finding out how my assumptions of the way Class Constructors have changed? I wouldn't, that's why. Much easier would be to take a set of PHP 4 "learning test cases" (which unfortunately I don't have :( ) and run them against an out-of-the-box PHP 5 install, I could then modify them to be working PHP 5 test cases and have a great heads up for all the things I'm going to have to change in the rest of my code, possibly even giving me great data to use to make a script that will automate all the changes between the two versions of PHP :)

Boring! Gimme example!

Ok. Today I was trying to figure out how to unit test a web crawler app written in Ruby. I decided I would use Mongrel. I had never used Mongrel before and I had no idea how to use it at first. Rather then trying to integrate it straight into my Unit Test Suite and risk confusing my self, I needed to learn to use Mongrel separately from any of the code I was actually writing. Normally I would just write a debug file and be done with it, but not today! Today I wrote a Test Case. The best part was that it didn't turn out to be significantly longer then a debug.rb file woudl have, and now rather than throwing it away I can keep it forever and even reuse the same Test Case in my web crawler app.

For those who actually want at least a little proof that I have actually written something today, here it is:
require 'rubygems'
require 'mongrel'
require 'net/http'

require 'test/unit'

# special class
class TC_MongrelHandler < Mongrel::HttpHandler
def process(request, response)
response.start(200) do |head,out|
head['Content-Type'] = 'text/plain'
out.write 'hello'
end
end
end

class TC_Mongrel < Test::Unit::TestCase

def setup
@http_port = 12345

# start mongrel web server, try default port increment on failure to bind
begin
@http_server = Mongrel::HttpServer.new('127.0.0.1', @http_port)
rescue Errno::EPERM => e
# todo: is there a better way of checking the exception type?
if e.message == 'Operation not permitted - bind(2)' then
@http_port += 1
retry
else
raise
end
end

@http_server.register('/', TC_MongrelHandler.new)

# join the thread but wait 0 seconds for it to return
@http_server.run.join 0
end

def teardown
# kill off mongrel web server
@http_server.stop
end

def test_hello_world
res = Net::HTTP.get_response(URI.parse("http://localhost:#{@http_port.to_s}/"))
assert_equal 'hello', res.body
end
end
It's perfectly possible I'm not using the libraries involved exactly as they were designed, but it doesn't matter much because if they ever break on me I'll know!

20081129

Aoeu

Right for some stupid reason I thought that maybe I should relearn Dvorak... you know it's not like I set myself up with enough stuff to learn, I thought I would add Dvorak to Lisp and Emacs (and all the other stuff I'm trying to pretend that one day I might come back too). Yay.

So after taking about 3 minutes j...(wow "j" is well freaking hidden down here between "k" and "q", I had to stop and print out a key guide just because the "j" key seemed to have gone missing... ARGH!!!!) just to stumble through the above paragraph (if you can call it that, I know it is quite short) I've already decided that I have to drop one, you know just for today. Bye bye Lisp, I will visit you again very soon. In the mean time I'm growing some really nice flowers that I'm sure you'll like as soon as they are ready.

Today I made a decent attempt to round my bank account down to the ever illustrious single figure territory, I was however thwarted by my banks cunning "limit" system, sneaky so-and-sos that they are. Despite my money problems I will shortly be the owner of the biggest and most comfortable bed in the shop :D ... for those of you that don't know I frequently suffer from stupid levels of Insomnia. Normally I just try to to spend my wakeful energy on something useful (even if it's just something that pretends to be useful like learning Lisp) but by the end of last week (uhm yesterday to be precise, but it feels like so much more time has passed), I felt like death, my blood pressure shot up and I think something exploded in my head because all I was able to do was lie on the couch in the fetal position holding my head and waiting for the axe that had cleaved my poor skull to gradually be removed.

This I blamed on lack of sleep, so on a spur of the moment decision I've at least made a nice little commission for the sales man at the store. Despite his commision I still can't wait till Monday night when it arrives.

After typing that tiny amount of text I've quite successfully used up all of my learning energy (as in learning Dvorak, not that you need reminding I'm sure as it probably hasn't taken you quite as insanely long to get to this point as it has me...) I set aside for today, and a whoping hour of my time and still haven't managed to write anything worthwhile... but maybe you disagree if you're still reading.

20081122

First Post

I thought I would create a Blog just in case I suddenly had something to say. Plus I just noticed how stupidly easy it's suddenly become.

I just figured out how to make the singularly best seed raising planter pot "thing" ever. You take a toilet roll, the ones you were throwing out anyway, cut 6 tabs out of the bottom that are slightly longer than the radius of the roll and just kind of fold them in. Why is this so great? Because they are free, quick and easy to make and fairly environmentally friendly :)

Completely unrelated, I want to learn CLisp in the next few weeks, if I have any success I intend to document it here. Why would I want to learn a 50 year old language? Dunno, something different. But realistically so many really smart people keep telling me how much work they are doing in it and how good it is. Not the kind of "smart" people who like Java or Perl, but the ones who actually like things because it solves a problem in some unique and efficient way.