Your SlideShare is downloading. ×
0
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
My First Ruby
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

My First Ruby

5,984

Published on

A talk I gave at the June 2010 meeting of the London Ruby User Group. It's about the first bit of ruby I ever wrote, way back in 2003. A little bit of personal history, a little bit of ruby history, …

A talk I gave at the June 2010 meeting of the London Ruby User Group. It's about the first bit of ruby I ever wrote, way back in 2003. A little bit of personal history, a little bit of ruby history, a whole lot of terrible code for you to learn from.

Published in: Technology, Education
1 Comment
0 Likes
Statistics
Notes
  • Be the first to like this

No Downloads
Views
Total Views
5,984
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
4
Comments
1
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide
  • wont’ all be about me - but enough to say what kind of programmer i am

    will focus on code






    I'm going to talk to you about the first ruby script I ever wrote. It's called *MY* first ruby, but I promise it won't all be about me. There'll be a little bit of personal history, just so you can get an idea of what sort of programmer I was at the time, but most of the talk will focus the script itself.


  • me now.

    but, my first code was written in 2003 so....




    First the personal history which I promise won't take up much time. This is me now. But I wrote this script in 2003. not 2010. So we have to go back in time. So step into my time portal and come with me to the late summer of 2003....

    [doodley doo]

    [doodley doo]

  • 2003. different.

    just leaving apr. hired as java, did c++, vb, python as well.
    not very oo python - gui based + tuple heavy
    no ruby, just a tase for dynamic via python
    still a java - starting at empower (sms place) in a few mnths


    --

    Cast your mind back. Things were different then. Summers were longer, the sun was brighter, I had more hair and ruby 1.8.0 (the grandfather of the version you all know and love) had only just been released that August.

    I was in the dying stages of my first job out of university. I was interviewed to work primarily as a Java programmer; but I ended up doing a load of C++, a bit of VB and some Python GUI development. It was a great company, but the management decided to move the company offices to Cambridge, which I felt meant I was living the wrong way around; you commute *into* London, not out of it. I'd never done any Ruby programming, but I did have a taste for dynamic languages having done a lot of (not very OO) Python programming (we used tuples a lot). I still considered myself a Java programmer, not least because I'd just accepted a job at a Java-based SMS gateway company.


  • James Adam. PhD not job. toyed java + python. settled on ruby 2001.
    wouldn’t stop going on about it.
    actual quote from mailing list.



    -----



    I heard of Ruby because my friend, James Adam (pictured), had been doing a PhD instead of working and while he flirted with Java and Python for a couple of early installments of his thesis code, he finally settled in about 2001 on Ruby for the majority of his code, and wouldn't stop going on about it to anyone that would listen.

    The prime vehicle for James' evangelism of Ruby was to send messages a mailing list that our university classmates had set up to keep in touch after we graduated.
  • mailing list on egroups. swanky for 2000. (files, polls, etc...)
    *click*
    bought by yahoo. got rid of the yellow.
    started dropping mails or taking days to deliver them.
    *click*
    write our own.
    James said “ruby”. for no reason we agreed.


    --




    This mailing list was originally running on egroups. Which was fine. It hosted files and polls and had a web interface. It was pretty sweet for the year 2000.

    *click*

    Then they got bought by Yahoo! and it became Yahoo! Groups! Basically the same service, but it had a red and blue logo instead of a purple one, and yellow was banished from the UI.

    However, and here's where we finally get to the ruby script, Yahoo! Groups! started dropping emails intermittently, or taking several days for emails to show up.

    As you can imagine, this sort of delay in the insane ramblings of a group of 20-somethings debating the merits of their first jobs during the dot-com era was TOO. MUCH. TO. BEAR!

    *click*

    So despite thousands of available choices, and even a single click install of mailman on our shared hosting, we decided to write our own replacement.

    James somehow convinced us that we should write it in ruby even though he was the only one who knew any ruby. James knocked up a simple test script as a proof of concept and we decided to go ahead.
  • mailing list on egroups. swanky for 2000. (files, polls, etc...)
    *click*
    bought by yahoo. got rid of the yellow.
    started dropping mails or taking days to deliver them.
    *click*
    write our own.
    James said “ruby”. for no reason we agreed.


    --




    This mailing list was originally running on egroups. Which was fine. It hosted files and polls and had a web interface. It was pretty sweet for the year 2000.

    *click*

    Then they got bought by Yahoo! and it became Yahoo! Groups! Basically the same service, but it had a red and blue logo instead of a purple one, and yellow was banished from the UI.

    However, and here's where we finally get to the ruby script, Yahoo! Groups! started dropping emails intermittently, or taking several days for emails to show up.

    As you can imagine, this sort of delay in the insane ramblings of a group of 20-somethings debating the merits of their first jobs during the dot-com era was TOO. MUCH. TO. BEAR!

    *click*

    So despite thousands of available choices, and even a single click install of mailman on our shared hosting, we decided to write our own replacement.

    James somehow convinced us that we should write it in ruby even though he was the only one who knew any ruby. James knocked up a simple test script as a proof of concept and we decided to go ahead.
  • mailing list on egroups. swanky for 2000. (files, polls, etc...)
    *click*
    bought by yahoo. got rid of the yellow.
    started dropping mails or taking days to deliver them.
    *click*
    write our own.
    James said “ruby”. for no reason we agreed.


    --




    This mailing list was originally running on egroups. Which was fine. It hosted files and polls and had a web interface. It was pretty sweet for the year 2000.

    *click*

    Then they got bought by Yahoo! and it became Yahoo! Groups! Basically the same service, but it had a red and blue logo instead of a purple one, and yellow was banished from the UI.

    However, and here's where we finally get to the ruby script, Yahoo! Groups! started dropping emails intermittently, or taking several days for emails to show up.

    As you can imagine, this sort of delay in the insane ramblings of a group of 20-somethings debating the merits of their first jobs during the dot-com era was TOO. MUCH. TO. BEAR!

    *click*

    So despite thousands of available choices, and even a single click install of mailman on our shared hosting, we decided to write our own replacement.

    James somehow convinced us that we should write it in ruby even though he was the only one who knew any ruby. James knocked up a simple test script as a proof of concept and we decided to go ahead.
  • Thus, my first ruby.
    but, James wrote initial prototype
    and James and I got together 13th Sept 2003 to write it

    *click*


    --

    And so, this talk is called “My First Ruby”, and while it’s true that it’s my first ruby. It’s not like I wrote it alone. After James wrote the prototype we both had a free weeken on the 13th September, 2003 and knocked up the initial version together.

    *click*

  • Not all my own work.
    Had guidance.
    Much like most of you. github, peers, lrug, colleagues.
    pair programming.
    let other people look at your code.


    My first ruby script was written under the guidance of someone who had been using it for a couple of years. This probably isn't that different to many of you though. Any code you've written has probably had the benefit of other people at your job working on the same project, if you're lucky you've even been pair programming with them. Even if it's code you've written at home, chances are you've put it up on github and have the chance of thousands of rubyists looking at it. Don't be scared of letting other people look at your code.

  • aside:

    Chris Lowis - blog post blog.chrislowis.co.uk rails apps to read
    read code = learn from the author.
    read code = teach the author.
    read code = good.

    --

    Speaking of reading code, Chris Lowis, LRUG's resident podcaster, recently wrote a great blog post about open source rails projects and what you can learn from reading the source of them. I think it goes both ways, if you read other people's code you learn a lot, if you let other people read your code, they can learn, but so can you when they critique it, or suggest patches for edge-cases you didn't cover or even a neat re-factoring. It's also a nice confidence boost when you read some code for say, gemcutter, and you notice something you think could be improved.

    Anyway, aside over.
  • my first commit.
    (dated lunchtime on sat - so probably not first ruby - ethernet cable).
    CVS!

    why this change? empty constructor for YAML? Just error checking?
    But, could just be excitement at ruby.

    Compare with:

    --

    So. Here’s the first code I commited to the project.

    This probably isn't the actual first bit of ruby I ever wrote. As I already mentioned, James had hacked up a prototype and the first commit to our source control was timed at around lunchtime on the Saturday. It’s 7 years ago so while I can’t remember exactly what we did that day, and I do recall spending a lot of the morning hunting around for a spare ethernet cable. I'm pretty sure we did some hacking on the code before we decided that CVS might be a good idea.

    Anyway, I think there's plenty in here that's worth talking about. Why did I add this "empty constructor". The commit message says it's to allow YAML to make the object good. I'm not sure I know what that means, and on the face of it, it looks like I'm just stamping some error checking on here. I suspect however I was just experimenting with all the fun new things that Ruby can do.

  • Java programmer in 2003. vs Ruby.
    (return arrow to indicate line break)
    LESS CODE!
    if statement as modifier (love it!)
    (also, less {} syntax)
    be fair - Java syntax not as bad as now (generics, annotations, etc...)

    (finally, the current version of this code no modifier, unnecessary)

    first committed ruby -> onto system as a whole


    --
    To compare what this felt like to me in 2003 it'd help to compare the final code to how I'd do the same thing in Java.

    (I think, my Java is rusty). This was pretty amazing! So much less code! First, there's the fact that by allowing default values in method signatures I can get rid of that entire 2nd constructor. Then there's using the if statement as a statement modifier, by placing it at the end. I don't know why, but I'm a massive fan of this format, and I think it's one of the reasons that I get ruby. It just reads so neatly. Finally, there's a lack of extraneous syntax.

    But, you didn't come here to listen to a talk about why ruby syntax is better than java's. And to be fair to Java in 2003, the syntax is nothing like the mess it is now with generics and annotations.

    I think having shown my first "committed" ruby, it's time to talk about the system as a whole rather than go through it and pick holes in every commit of mine.

  • breif warning.
    named after favourite insult. possibly placeholder.
    have to say it + show it on screen a few times.

    I give you....


    --

    Now I have to warn those of a sensitive nature, for reasons best left unexplored we decided to call our new mailing list software after a favourite insult from our university days. And I’m not going to be able to avoid saying it or showing it on screen, so I have to warn you;

    * click *
  • Fucknut.

    what is it? less good version of mailman.
    (someone keeps asking on twitter, mailinglist = python? why?)

    2 parts - mail processor + web front end


    --

    We called it

    Fucknut

    Fucknut is a mailing list with an attached web front-end for viewing the archived messages and attachments and managing your user account. Basically, it's a less accomplished version of mailman.

  • talk about mail processor.

    mostly based on james initial prototype.
    procmail -> unix tool
    .procmailrc -> regexp match run a script (+ stop, or continue)
    kinda like routes.rb
    pipes mail via STDIN to script.
    ours match on TO or CC addr for list, run slim handler script for that list.
    handler sets up env, parses mail using Rmail (not tmail or mail)
    (at the time, tmail not as good apparently)
    yaml::syck (now default?) for config + users db
    net::smtp sending mail

    get’s mail as rmail obj
    does some stuff to it
    archives it
    sends to users.

    simple.

    lets look at some code.
    --





    The main component of fucknut is the part that processes mail, and this is also the oldest part of it as it's based on an organic growth out of james' inital prototype.

    It starts with a .procmailrc file. For those that don't know, procmail is a UNIX tool that you can get to run against every mail that is delivered to your shell account and the .procmailrc file controls it. You can think of it like a rails routes.rb file for mail (except it doesn't use ruby or have a nice dsl).

    [ SHOW .procmailrc HERE ]

    You define a regexp to match against some part of the incoming mail and if it matches you can decide what to do, for example forward the mail to /dev/null, or invoke a script on it (it passes the mail in via STDIN). You also decide if you want to stop processing or continue to see if it matches other rules.

    For fucknut we have a rule that matches against the TO (or CC) address, and if it matches the list address we ask procmail to invoke a list handler script for that list.

    These handler scripts are slim wrappers that set up the environment for the mailing list processor and then pass it the mail as a ruby object. We use Rmail for this (not Tmail or Mail which you may be more famlilar with)).

    This mail part of fucknut also uses YAML::Syck (which I think is now default in 1.8.x so you’re just using YAML now) to deal with some configuration stuff and Net::Smtp to send out email.

    We’ll cover this in detail later, but having received an email it stores it in an archive db and then sends it on to the other users on the list.
  • the main method of the script.
    route of a mail object through the system
    Just talk, briefly, about each method.

    now, some bits of the system in detail.
    --

    This is the main method from the mail processing script and it described the route that the mail takes through the system.

    First thing it does it make sure that the sender of the mail is one of the users. Because we're all digital natives a user is allowed to have several email addresses attached to their account and can post from any of them. (@users is our list of users)

    If it's not a valid user the mail is discarded. If it is a valid user we continue processing it.

    The next thing we do is set the from address to the user's preferred posting address. I might send email from my work account, but I don't want people using that account to mail me (and this was important at the time because my work email address changed about 4 times as the company underwent furious rebranding every few months at the whim of our VCs) so we tell fucknut to make it seem like all my mail comes from my personal account.

    We then massage the subject of the mail to add our list identifier and keep “re:s” down to a minimum

    Then we do various things to the headers, mostly required of us by the 12 hundred RFCs that there are about mailing lists.

    Then we process any attachments to save them to a separate data store. And, because we wrote this in 2003 when many people were still on dialup, remove any attachments over a certain limit.

    Then we archive the message to our database (for which read dump the raw mail to disk).

    Finally, we go through the complete user list and send the mail out to all the users. Including the sender.

    And that’s it. That’s Fucknut at a glance. Now I’ve described the system I’ll go over some of the code that I think is particularly terrible.
  • first code show = subject massaging.
    first an aside.
    asked about the whole idea of new list = not much response.
    asked about position of listname + re:’s in subj = 3 days of furious debate
    don’t bikeshed if you don’t have to. just do.

    --

    Originally we were just going to add the list name in square brackets to the start of the mail, but then we realised we had to do something to prevent various mail clients messing up the re: re: re: re: stuff. After a tortuous requirements gathering

    [ SHOW SCREEN SHOT OF re: DISCUSSION ]

    thread we decided to settle on [list name] <original subject> and Re: [list name] <original subject without any re:>. As you can might be able to see, this took 3 days and 23 messages to argue about and decide to do the thing we were going to do anyway. A further argument, if you ever needed it, for not starting a bikeshed discussion if you can possibly get away with it. There was nothing else about this app that involved this level of debate.

    *click*

  • code for adding listname in []
    + removing all re: and adding one at start if any found
    painful. use regex, not slice.
    Something I was doing 2-3 years later when working in ruby professionally. (Jon Lim)

    Probably java. strings = immutable, work with stringbuffers to avoid memory
    regexp new / bad.

    --


    This code is pretty bad. I'm massaging a string, I should be using regexp here. I don't necessarily agree with using regexp for everything but clearly doing all this string manipulation here it would be much better done with regexp. It wasn't until a few months into doing ruby professionally (2005-ish I think) that Jon Lim (who I was working with at the time) asked my why I kept using .slice and [] all the time instead of .gsub with a simple regexp. I think this was a hangover from my Java days where regexp's were (percieved?) as slow and crappy, and strings were immutable so you did everything with stringbuffers.


  • I’m pretty sure, that even despite using regexps, this is easier to read and understand what’s going on.


  • Next thing - add headers from list config.
    object extended from Hash? Why?
    more convenient? huh? hash is pretty convenient.

    should just have a hash inside + delegate the specific bits of api we want.
    as an aside, I made this same mistake at uni. first project with java.
    clearly not learned.




    The next thing we do is add list headers. We store those as part of the system config and have this object ListConfiguration, “an extended Hash with some little things to make it’s use more convenient”.

    As a comment at the top. You know what, I've never *ever* used a hash in ruby and thought, "Gee, I wish this was more convenient", I can't say I even really think that the new 1.9 hash syntax is that much better. It also shouldn't extend Hash, it should contain a Hash and delegate the bits of the Hash API that I want and then provide it's own methods where I want more convience.

  • one of those methods.

    clearly premature optimisation.
    hash is yaml loaded.
    maybe misunderstood?

    still. don’t optimize if you don’t have too.

    also, what would be more convenient is this:

    --

    Clearly there’s some insane premature optimisation going on here. Maybe I misunderstood yaml backed Hashes and though it would always be hitting the YAML file. Even if I did, don’t optimize until you have to.

    Given all that, you know what would be more convenient than having to write that method in the first place:

    *click*
  • we mostly assigned instances of ListConfiguration to a @config variable when we use it.


    Or, if I wanted to save 1 char typing whenever I accessed the list headers.

    *click*

    But, really, the first thing is better, I’ve genuinely no idea what was going on here.

  • go back to existing method.

    using constants as keys? all defined at top of config module

    *click*

    why? again, java hangover? “magic” strings create only once
    as public final static String.

    but ruby == symbols? distinct lack of symbols in here, probably didn’t know about ‘em

    or maybe, want error if typo? constant missing vs. nil down the road?


    --

    The final weird thing about this ListConfiguration object is, if we look back at that listHeaders method you'll notice, we don't use symbols or strings as the keys into the hash. We use Constants, which are defined at the top of the Config module. For example:

    *click*

    Why? I don't know. I just doesn't make any sense, until you remember my Java routes where these sorts of "magic" strings would be defined as public static Strings because you'd only want to create that object once (woe betide the Java programmer in the early 2000's who went around creating more objects than he strictly needed to). Thing is, Ruby has symbols which save one char on typing a string and are more idiomatic. I can only assume I didn't know about symbols when I wrote this. I probably wanted some comfort that I wasn't spelling a config key wrong and causing a nil error so shied away from using strings. Using Constants means I'd get a runtime error saying there's a missing constant instead of a nil error somewhere down the line.

  • fragment from attachment processing.
    message is multipart, do this on each part.
    code actually not bad.
    seen most work as we see new messages and clients (richer + richer).
    loads of edgecases.

    ‘cept a bug in it I fixed 2 weeks ago.

    multipart/alternative means nesting multipart messages within parts.
    so the multipart/alternative part doesn’t have a filename

    no filename log error then *CONTINUE TO USE*

    highlights 2 things:

    1. TDD should have caught it?
    2. real world data hates you.


    --

    The next bit of code to talk about is this, it’s a chunk of the attachment processing method.

    We get here if the message is multipart, and this fragment is run on each part.

    we extract and store all the attachments, but we also remove them entirely if they are over a certain size

    This code, is actually not too shabby. It's quite long because there are loads of edge-cases. And over the years, this is the part of the code that's seen a lot of changes. Turns out multipart mime messages are hard and you can nest things and it gets weird. Whatever your naive approach is, it's going to crumble as people send richer and richer messages from more and more esoteric mail clients. In fact I had to fix a bug in it only a month ago, someone's mail client started sending nested multipart messages with multipart/alternative.

    What you’re looking at is what the code used to look like. Some of you may already have noticed the bug.

    If the part of the mail we're dealing with didn't have a filename (such as multipart/alternative which is effectively nesting another set of parts), then our code logs the error but then blindly continued on assuming that it has a filename it can do something with. It never came to bite us until someone sent an multipart/alternative message.

    This is something I hope would have been fixed by TDD. With TDD I'd probably have mocked the logging or file.size calls and built the function up slowly. But, I may not have caught it with TDD because I might never have thought to try out a nested multipart file.

    The other thing it shows is that the real world will always conspire to break your code. If I had tests though I probably would have been able to feed the mail that broke it into the test suite and find out what tests suddenly failed.
  • I've covered a few of the little refactorings I'd do to individual methods, but I think the whole thing could do with an overhaul. It's pretty much one class and it does everything. I think something like this:

    *click*


  • it’s a pipeline. one thing shouldn’t have all those responsibilities.
    each thing takes a mail message and passes it on.
    like rack, but for mail. Even have simple API like Rack.
    test in isolation. add new ones. etc...
    --

    might work. It's really a pipeline and, like Rack, it might make sense to have a chain of smaller classes linked together, they all take in a mail message and do something to or with it and then pass it on. That way everything can be tested properly in isolation and it reduces the coupling between things.

  • that’s the mail.
    now for the webhead.

    2003 -> web dev in ruby a pain. little libs, nothing great.
    also, no gems or (not sure, rubyforge).
    used raa (hangs off ruby-lang.org, so pretty official)
    no hosting, just pointers to stuff.
    all browser, except, raa-install (quite new) would scrap (api?) and install (no deps)
    most libs used ruby setup.rb so was some standard there.

    not sure why gems came along. probably interesting story there.
    aside: some gem hate right now. not really a problem. it replaced this, so if something replaces it, whatevs.


    --

    That's the first part of fucknut covered, so now we're onto the 2nd part, the web front-end.

    2003 was a dark time for web development in ruby. There were lots of little libraries, but nothing as comprehensive as Rails, certainly not the rails we have now, but not even the rails we got in 2005. The thing that may surprise you is that we didn't even have gems back in 2003. The first release of gems was in March 2004. To find ruby stuff we scoured something called the Ruby Application Archive which was a website similar to what rubyforge is now, where you could list ruby projects and categorise them. Except it did no hosting, you just pointed the links to where the data was.

    On top of this, someone wrote a program called raa-install, which would go and find projects on RAA, download them and install them. At this point most libraries, if they had any installation, used the ruby setup.rb incantations, and raa-install would run those for you too. It didn't do dependency graph information, but that's because this info wasn't on RAA. The thing that's not clear to me looking back, is why gems and rubyforge came along when there was already this in place. I've not looked into it nor looked at the code. There's probably an interesting story there.

    So, as an aside, I know there's a bit of hate for gems right now, mostly about issues over dependencies and keeping applications and system stuff independent. I wouldn't worry about it though, gems is not the first ruby code distribution and management system that's existed, so maybe it won't be the last. If it's served it's purpose maybe it is time to move on; be that bundler, rip or something else.

  • chose narf.
    sound -nve, but was good at time. also
    0.3.4 went on to 0.7.3 by 2005 before death (rails?)
    docs imply good things for future versions (i’ve never used)

    came with testing framework (we didn’t use)
    surprising to me looking back as I assumed rails was the first to put it front-and-center
    but narf does it too.

    --

    Anyway, after a couple of false starts we settled on something called narf, which appeared to be the most high-level thing at the time. Now, remember this is me talking about narf as it was in September 2003. It was version 0.3.4 then, and it got up to 0.7.3 before development appeared to stop in 2005, and some of the docs imply it was headed in a direction that would abstract things further.
  • weird == not just lib, is executable too
    don’t use ruby, use narf.
    (actually docs say just to help with funnelling errors back up cgi to webserver)
    also, loadpath massaging, probably not many in the room have done this.


    --

    So, the first thing that seems weird to me, is that narf isn't just a library that you include into your scripts. It also comes with an executable. As it turns out this executable is just to redirect stderr into the cgi environment properly and redirect any exceptions and errors back into the cgi environment. But this fact is buried as an aside in the docs. It's weird.

  • first missing - no routes
    not high level. abstracts CGI only.
    run as cgi script (can do fastcgi if you build/compile/install that too)
    or via mod_ruby (original, not passenger - flawed though)

    want nice urls or routing, do it with webserver config.

    this is ours.
    it’s a pain
    so we didn’t do it much.

    (think early versions of rails did this too)


    --

    There's no nice abstraction of routes, but then that's not surprising, it's not a higher-level MVC framework. It's a web framework. It abstracts the first level of CGI interaction, it doesn't build on top of it to give you what rails or sinatra gives you.

    So, you run it as a CGI script, although you could have used mod_ruby (no, not passenger, the mod_ruby that existed ages ago that no-body used). And if you've installed ruby_fastcgi you can run it as fastcgi. So, it's very bare bones, if you want fancy urls you have to write them yourselves using RewriteRule directives. This is ours:

    We clearly got bored, as there are a few more URLs in the webhead other than looking at the archives, but we clearly couldn’t be bothered with making them pretty. ‘cos we’d have to write them in this non-expressive regex format.

    That said, I'm pretty sure early versions of rails asked you to do the same thing. I could be wrong though. Already it should be clear we are working at a lower level of abstraction. We're close to the metal here.
  • fragment of main cgi handler.

    my naive stylings (4 spaces, collect not map) + a lot of narf api

    narf gives Web object.
    Web[] = params
    Web.print_template = render templates
    Web.flush = send it to the browser

    --

    Having asked apache to invoke your script, you require the narf libraries and this gives you a Web object. This is what you interact with to communicate with the webserver. This is a fragment of our main cgi script

    Apart from showing off my naive ruby stylings (4 space indent! collect instead of map!) this explains a lot of the narf api. Web[] to get params that were sent with the request, and Web.print_template to invoke some template processing on a file providing a list of variables and Web.flush to send everything back to the webserver.This is what that template looks like:

  • an example narf template

    2 ways of rendering data
    {} -> moustache style eval expressions
    hash param to print_template -> $vars for {}’s

    <narf:> tags for more complex stuff
    this kind of thing comes around alot
    view code == code (erb style)
    view code == markup (this style)

    only radius does this for rails (I think).
    seen it alot in other langs though.

    some narf tags emit things (<narf:foreach>) for use in {}
    probably could write own for more complex views.

    that’s narf. now back to fucknut (and my code).

    --


    As you can see here, there are 2 ways of rendering data in these templates.

    The first is that, moustache style, it’ll evalutate and render the results of any expressions within {} braces. The key/values in the hash you provide to the print_template are available as $vars for evaluation. Much like the :locals hash when rendering a rails partial.

    The other way of interacting is to use these <narf:> prefixed tags. Think of them like rails helpers, except instead of looking like code, they look like html. The web community swings back and forth on this sort of thing every so often, should the code in our views look like code (front-enders keep your hands off!) or should it look like markup (front-enders get stuck in!). I can think of only one templating engine for rails (radius, comes with radiant) that does it this way though.

    Some of the narf tags would emit things, like <narf:foreach> so you could use what that emits in the curly braces. But as far as I can tell, it was just expressions, no logic. If you wanted logic you used the narftags.

    Anyway, that's a whistlestop tour of narf as it was. To be honest it's clearly early days and some of the things littered in the documentation suggest it was pointed in the right direction (it came with a testing framework and the docs suggested building the app test first using that framework).

  • fragment of the login handler.

    showLogin is render action for the login command.
    not logged in show this.
    strings as constants, for no reason.

    abstracted .print_template into calling + args.
    can’t say why. never call .loginTemplateArgs elsewhere.
    probably just excitement about splat.

    maybe this makes sense: *click*
    --

    showLogin is effectively the render action for the "login" command. If the user isn't logged in we want to show them the login page, we'll call .showLogin on the LoginHandler. So, what's going on here? Again I've defined strings as constants when they really didn't need to be, it's all internal. I've also, for no reason abstracted the call to print_template out into calling it and the args I'd want to pass to it, I think it was just excitement at using the splat operator. Nowhere in the code do I ever call loginTemplateArgs except in this method, and I can't think where I'd want to given that the showLogin method is simply a pass through to print_template. The only way this might make sense were if it was like this:

    *click*
  • if show login ui in other page + want params for it?

    but I don’t. pointless complication before I need it.

    should be: *click*

    --

    Maybe somewhere else (and I use this form for other handlers and their "actions") I might want to render a template that shows a fragment of the login ui, and so grabbing the params that it needs from the Login Handler might make sense.

    But I don't. This just makes it more complex, and it should really be:

    *click*
  • It’s just simpler.
  • talked about loginhandler.
    narf not mvc. no controller framework.
    we rolled our own (ish).

    main handler looks at ‘cmd’ param and forwards to one of:

    login, user, archive.

    similar
    .new takes Web object
    .do method looks at ‘cmd’ to work what todo.
    e.g. user -> ‘update’ changes current user via POST params
    archive -> ‘showmsg’ looks for and displays single message
    login -> ‘logout’ terminates session

    differences
    User + Archive .do is render endpoint.

    LoginHandler .do isn’t an endpoint. it returns session
    .showLogin or .showLogout does render

    could make them same, but I didn’t.
    have something else deal with is user logged in.

    we .do loginhandler at the start to get session,
    then look at ‘cmd’

    framework = rules.
    go it alone = inconsistency without thought.

    finally, surpised by how much code == routing.
    something not seen in rails (it does it for you).

    --


    So. You'll have noticed that I talk about LoginHandler. Narf doesn't give you any controller framework, so we came up with our own. There are 3 handlers: LoginHandler (deals with login), UserUpdater (deals with letting the user manipulate their details from the user database) and ArchiveDisplayer (deals with showing archived messages). They all have the same constructor: takes the Web object and the path to the fucknut root. They all have a .do() method. This do method looks at the 'cmd' param of the Web object and acts accordingly, for example if the 'cmd' is 'showmsg' in the ArchiveDisplayer, it finds the requested msg and displays it. If the 'cmd' is 'update' in the UserUpdater it fetches the current user's details and updates them based on POST'ed params.

    That's where consistency ends though. LoginHandler returns a session object (our own wrapper to a couple of methods on Web) and has other methods for rendering templates like showLogin above. For the other 2 calling .do is effectively an action endpoint and that handler will do everything from then on.

    It's clear that Login and the other 2 aren't really the same sort of thing, and yet I've made them look the same, or, the fact that I was using them differently meant I should have realised that they were different things, or that I was doing something else wrong. A LoginHandler could easily have acted like the other 2 (where .do() is a render endpoint) if I'd had some other object that deals with is the user logged in or not. Frameworks give you rules and consistency, when you go it alone without much thought you end up with messes like this.

    Also, looking at the code for the main handler script and these other 'cmd' handlers I'm amazed at how much plumbing has gone into *my* code to determine what to do based on the params, as opposed to actually doing it. With a higher level abstraction (like rails or sinatra routing) I just get on with saying 'this url => this code gets run'.

  • fragment from when ‘update’ cmd

    all code in .do method!
    not even refactored out to method for update or show
    2010 me reels! not 2003 though + more:

    view code!
    model code!
    - data conversion
    - data validation

    ugh! should have a model for this!
    we even have a User class. but it doesn’t abstract this.
    if you save it with bad data, you have bad data.

    --


    Let’s look at some code in one of those handlers. UserUpdater.

    So, this is some of the code that is run when the ‘update’ command is sent to the handler.
    This code is directly inside the UserUpdater.do method. It’s not even refactored out into it’s own method! My mind reels at this nowadays, but clearly not in 2003. We've got:

    view code - I'm building html fragments that later I send to a template.

    model code -
    data conversion - converting params from strings into objects
    data validation - checking that the data isn't nil or invalid

    Clearly there's the shock that I've had to hand code all this, then there's the shock that it's all there mixed up in one method and finally that although we have a User class it didn't even cross our minds to keep this logic inside there. There's nothing inside that User object that deals with validation. If I didn't check that the sentTo mail address wasn't valid here, it would be saved by default by the User when I asked it to later in the method.
  • finally, archive displayer.

    wrote this in jan 2004 (week between jobs)
    bit better. more refactored.
    but it’s pretty simple
    provides web view of a directory structure on disk
    same flaws as other code:
    view code, bad styling, etc...
    but some code could be a view helper in a rails app

    (actually, one bug surfaced recently
    Contract of Ruby’s Dir.open is that the files come in
    disk order, but on a 64bit server (with ruby 1.8.4 at least)
    random order... still need to fix that)

    --

    Finally, I want to show some code from the Archive Displayer. The code in here isn't actually so bad. Maybe it's because I wrote this in January 2004 in the week I had off between jobs. I was clearly more learned, or maybe it's because one you get past the web stuff, what the code is doing is fairly workmanlike. It just has to go through our disk based archive structure:

    archive/<year>/<momnth>/message_id.msg

    And display them, it has some of the flaws already discussed in that everything happens in one class even when it probably could be decomposed more, it has a mix of view code and model code. But actually, looking back at it, although the framework is unfamiliar, some of the methods look not too far from what I'd write as view helpers today. Apart from this one:

    *click*
  • fragment for page navigation
    this gets link for going to the previous year.
    nothing *so* bad about that
    although, using the same var for a date + the output string is odd
    except: repeats this *exact* code for getting
    prev month, next month, next year.

    exactly same logic, just different vars!
    prolog example form uni
    "Surely we put this sort of copy and paste behind us in CS1001?"

    apparently not.


    --



    That doesn't look *too* bad, until you look at the rest of the method (this is just a fragment). That last if statement (which gets the navigation link or placeholder for taking you from the page you are on to the page for the previous year) is repeated almost in it's entirety to get the navigation links for the previous month, the next month and the next year.

    I remember during my final year in university doing a prolog exercise and getting a good mark for it because somehow I'd managed to bend my mind into making it do a reasonable job at playing noughts and crosses (for a partial board) without using all the memory on the planet. However one of the negative remarks was for a section of code where I'd repeated some lines without abstracting them into another method call. "Surely we put this sort of copy and paste behind us in CS1001?"

    Apparently almost 4 years later I was still doing it.
  • before I go: why

    get up and show off your code.
    as I said, good to show code, you learn, we learn.

    hopefully inspired you to do so:
    either:
    not embarrassed it’s better than this
    scared I’ll do a series on every bit of ruby I’ve ever written

    cheerz


    --

    So that's my first ruby. And before I go, I just want to explain why I gave this talk. Mostly it's because I hope that after having me come up here and show you this code from 7 years ago, and how pretty bad it is more people will want to get up and show off their code in future meetings. Either, as I've intended, by showing that everyone writes bad code and you needn't be worried about it. Or, unintentionally, because you're worried that if you don't I'll turn this into a series of lectures where I talk you through every piece of ruby code I've ever written.

    Thanks.

  • Transcript

    • 1. My First Ruby by Murray Steele (aged 31 1/2)
    • 2. Me in 2010 Passport photo: Employed by: Codes mostly in:
    • 3. Me in 2003 Passport photo: Employed by: Codes mostly in:
    • 4. I FORSAKE JAVA! I RENOUNCE C++! ALL HAIL the Mighty RUBY!! Riddle: 2.upto(100) { |num| print "ruby is #{num} times better that java or c++" } Work it out... .... if you dare.
    • 5. L LED N CE C A
    • 6. I am all dressed up ready to code me some Ruby. YEAH!!!
    • 7. You don’t even know Ruby, I’ll make sure you do it right.
    • 8. http://www.flickr.com/photos/ajstarks/4196202909/
    • 9. @ -24,8 +24,8 @@ class Config < Hash } end - def initialize(name) - self[Config::Name] = name + def initialize(name=nil) + self[Config::Name] = name if name != nil end end
    • 10. public Config(String name) { if (name != null) { this.put(Config.NAME, name) } } public Config() { super(null); } def initialize(name=nil) self[Config::Name] = name↩ if name != nil end
    • 11. For frequent strong language
    • 12. FUCKNUT
    • 13. def handleMessage(message) sender = @users.findByEmail(message.header.from[0]) if (sender == nil) return end message.header.delete('From') message.header['From'] = sender.preferredFromAddress processSubject(message) # modify the subject addListHeaders(message) # add the list headers archive(message) # archive the mail processAttachments(message) # modify attachments sendToMembers(message) # send it out. end
    • 14. def processSubject(message) sub = message.header.subject.dup prefix = "[" + @config.listHeaders["List"] + "]" i = sub.index(prefix) sub.slice!(i..(i+prefix.length)) if (i != nil) found = 0 re_str = "re: " i = sub.downcase().index(re_str) while (i != nil) sub.slice!(i,re_str.length) i = sub.downcase().index(re_str) found = 1 end prefix = "Re: "+prefix if (found == 1) message.header.subject = prefix + " " + sub end
    • 15. def processSubject(message) sub = message.header.subject.dup prefix = "[" + @config.listHeaders["List"] + "]" sub.gsub!(/#{Regexp.escape(prefix)}/,'') if sub =~ /re: /i sub.gsub!(/re: /i, '') prefix = "Re: "+prefix end message.header.subject = prefix + " " + sub end
    • 16. # this is a loosely extended Hash with some little # things to make it's use more convenient class ListConfiguration < Hash
    • 17. # this method is a bit overkill, but by caching # the actual list header object instead of having # to get it from the main config hash each time # we should save some overhead. the ListHeaders # part is the most frequently accessed, because # it contains a lot of varied list info. def listHeaders if @listHeaders.id != self[ListHeaders].id puts "recaching" @listHeaders = self[ListHeaders] end @listHeaders end
    • 18. @config[ListHeaders] def listHeaders self[ListHeaders] end
    • 19. def listHeaders if @listHeaders.id != self[ListHeaders].id puts "recaching" @listHeaders = self[ListHeaders] end @listHeaders end # these just resolve to unique strings, # used as keys within the config hash Name = "name" ListHeaders = "listheaders" # this is the root directory BaseDir = "basedir"
    • 20. log("looking at "#{part.header.media_type}"") if !TypesToKeep.include?(part.header.media_type) md = name_regex.match(part.header["Content-Type"]) filename = "null" if md != nil filename = newUniqueFile(AttachmentDir, md[3]) { |f| f.write(part.decode()) } saveAttachmentMetaData(filename, message) else log("error getting filename from "#{part.header["Content-Type"]}"") end size = File.size(File.join( base_dir, @config[AttachmentDir], filename ))
    • 21. def handleMessage(message) sender = @users.findByEmail(message.header.from[0]) if (sender == nil) return end message.header.delete('From') message.header['From'] = sender.preferredFromAddress processSubject(message) # modify the subject addListHeaders(message) # add the list headers archive(message) # archive the mail processAttachments(message) # modify attachments sendToMembers(message) # send it out. end
    • 22. #!/home/room206/bin/narf fucknut_home = '/home/room206/fucknut' $: << fucknut_home # add our library path to the search path require 'web'
    • 23. AddHandler cgi-script .rb RewriteEngine on RewriteRule ^(.*)/archives/(.*)$ /handler.rb?↩ listname=$1&mode=archive&$2
    • 24. listname = Web['listname'] if listname != nil and listname != "" # main processing when we have a listname param else lists = Config.possible lists = lists.collect { |i| {'name' => i, 'value' => i} } if lists != [] Web.print_template("what_list.html", {'listname' => listname, 'listnamevalues' => lists}) else Web.print_template("generalerror.html", {'error' => "No lists defined, get a better admin"}) end end Web.flush
    • 25. <html> <head> <title>Login to {$listname}</title> <link href="/arse.css" rel="stylesheet" type="text/css"> </head> <body> <h1>Error during login to {$listname}</h1> <p>{$error}</p> <narf:form formname="tryagain" method="POST"> <narf:hidden name="listname"> <input type="submit" value="Try Again"> </narf:form> </body> </html>
    • 26. class LoginHandler LoginTemplate = "login.html" LogoutTemplate = "login.html" LoginCommand = "login" LogoutCommand = "logout" def showLogin() @web.print_template( *loginTemplateArgs() ) end def loginTemplateArgs() [LoginTemplate, {"listname" => @web['listname'], "cmd" => LoginCommand}] end end
    • 27. def showLogin() @web.print_template(LoginTemplate, loginTemplateParams() ) end def loginTemplateParams() {"listname" => @web['listname'], "cmd" => LoginCommand} end
    • 28. def showLogin() @web.print_template(LoginTemplate, {"listname" => @web['listname'], "cmd" => LoginCommand} end
    • 29. errors = "" commalist = @web['addresses'].gsub(/n/, ",") list = RMail::Address.parse(commalist) if list.empty? errors += "<br><h2>no valid email addresses given in list</h2>" end user.addresses = list sendTo = RMail::Address.parse(@web['sendto'])[0] sendToString = @web['sendto'] if nil == sendTo errors += "<br><h2>Send To Address is not valid</h2>" sendToString = "INVALID: " + sendToString else sendToString = sendTo.format.to_s user.sendTo = sendTo end
    • 30. /archives/<year>/<month>/<message_id>.msg
    • 31. if @config[StartYear] < lastyear.year lastyear = "<a href= "#{baseurl}year=#{lastyear.year}&month=#{lastyear .month}"><<</a>" elsif @config[StartYear] == lastyear.year if @config[StartMonth] <= lastyear.month lastyear = "<a href= "#{baseurl}year=#{lastyear.year}&month=#{lastyear .month}"><<</a>" else lastyear = "<<" end else lastyear = "<<" end
    • 32. The End

    ×