Essentials • 1:05:35
Discover why Mac OS X is the dream development platform for Ruby on Rails, a powerful and agile web development framework that comes bundled with Mac OS X Leopard. Learn to harness the power of Ruby on Rails to create unified, first-class web applications that are simple to deploy on Mac OS X Server and that display and perform exceptionally in Safari on Mac OS X, Windows, and iPhone.
Speakers: Luke Burton, Joel Young
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Welcome everyone to creating Ruby on Rails Applications for Safari on iPhone and the Desktop. My name's Luke Burton. Are you all excited to be in the Ruby on Rails session? Yes! Developers, developers, developers! Yes, I know. So, you know, you'll have to forgive me because, first of all, I'm Australian, so you're probably not going to understand a lot of what I'm saying, but if you don't understand something, just look around. There's probably an Australian sitting next to you who can do the translation. There's a lot of us here.
Another reason I'm not really feeling myself is normally during WWDC, I would see a lot of the sessions through the bottom of a pint glass, which is, I'm sure, how some of you guys have seen the sessions, but I'm very tired at the moment because on Monday, my wife gave birth to a little baby girl. Thank you.
This is being recorded, so in the year 2020, I'm going to bring up this session and embarrass her mightily at one of her birthdays. And I haven't had much sleep, so forgive me if I start going off the rails. Yes. All right, so let's go on to our agenda.
What are we going to talk about in this session? We're going to, first of all, have a look at a little bit of a fairy tale on Rails. Then we're going to move on to a discussion of inside Ruby on Rails, talk about the technology in a little bit more detail.
Then we're going to get our feet wet with actually coding a real Ruby on Rails application. Then we're going to learn about how we can ask some sophisticated questions about data using Ruby on Rails. After that, we'll learn how to present the answers that we got from asking those questions. And then we'll talk about how Rails faces the world. Deployment.
So are you in the right session? Well, there's no such thing as a wrong session at WWDC, but you might take more out of this session than others if you've never heard of Ruby on Rails, or if you've only dabbled with Rails but you haven't really dived in and done a lot with the technology, or perhaps you're a Rails developer that's new to Mac OS X. If you're a more advanced developer, we hope you'll take something out of this session as well, and we've got some great Q&A lined up at the end, so you can ask some very hairy questions then and get all your answers hopefully satisfied.
So let's talk about our fairy tale on Rails. Now, Apple has a lot of money. We've got $19 billion in the bank. It's probably like $22 billion now, based on the number of MacBook Airs I've seen in the audience. So... We need to sort of keep track of all this money, and Joel and I, my co-presenter, who I'll introduce shortly, were approached to sort of write a really simple, small website that was going to help keep track of a very important area where some of this money is spent at Apple.
And we had to develop it quickly. It had to be accessible on Safari on the Desktop, and it had to be accessible on the iPhone. And it was going to be a very, you know, basically a business-critical, mission-critical financial application for Apple. We were rewarded very handsomely out of that pool of $19 billion. Both Joel and I received $10 gift vouchers to Cafe Max.
And I sort of skimped and got the $4 salad and pocketed the $6 to spend on other things. We had a choice of languages that we could develop this application in, naturally. We don't have to develop it in Ruby on Rails, but, you know, Perl? We're old-time Perl developers. We're not necessarily ragging on Perl here, but we're smart guys as well, if I don't mind saying so.
And every time we end up writing Perl, we end up writing code that looks like this, and we end up getting sort of knocks on our door years later from disgruntled people who have to maintain our code. And just as an aside, does anyone know what this code does? That's right. Wait, what was that? Something with a regular expression. Well, yeah, it's got little slashes in it. Nice one. It's got a while loop in there as well. It apparently prints out prime numbers.
So, yes, of course it prints our prime numbers. We could have used PHP as well, but, you know, it is very much everything in the kitchen sink language, grown very organically over time, and when a need comes up in PHP, they sort of put another function in, and are you looking for a haystack and a needle or a needle in a haystack? Can I remember this function? We've got something like 3,500 functions that are in the public namespace, and it makes it easy to write an application quickly but hard to maintain it later on. Much in the same way as Perl does.
J2EE, that's a heavyweight language. There's a lot of, you know, lifting there, or heavyweight framework, I should say. It's good for some things, but in our case, you know, we had this mission-critical financial thing we had to sort out, and we didn't have time to go through J2EE and skill ourselves up and go on a long development cycle. It wasn't really appropriate for our project.
I didn't really hear that. Something to do with C#. It's like one of those-- it's got something to do with developers, developers, developers, actually, that particular language. Yeah, we're not talking about C# today. Anyway, so we can talk about our secret project, though, that we're involved in. And for that, I'd like to switch to the demo machine. The demo machine.
Apple has $19 billion in the bank, and a small but significant proportion of it is spent on playing World of Warcraft. And we can't have our guildmaster in the dark about what's going on in the World of Warcraft bank. So we sat down, we selected Ruby on Rails, and we developed this kind of like online banking application for World of Warcraft as an example of what you can do.
And first thing I'll point out is we will have sample code available for this whole application. So we're not going to drill through everything piece by piece here, but we can give you the sample code for this and you'll see it and be able to have a look at it.
Everything on this page is CSS apart from the images. So all those gradients and drop shadows and reflections and all this other stuff we've implemented in CSS using WebKit. We can go across to our gold history page here and find out what's been going on in our guild bank. Have people been depositing, withdrawing things? We see here somebody made a very sizable contribution sometime along in March. So we can actually click and drag there and get it all populated below.
And we can actually scroll down and see that 2000 gold was deposited by a player called Null. And that's his real name, Null, because we've, you know, it's all about punning on Apple related things in iGuild. Now if I go across to the inventory history tab, I can see the items that have been going in and out of our guild bank.
And you'll notice that when we have a very large selection of items here that we're looking through, when you page down quickly, it's pulling in pages of data as it needs to rather than having them all loaded in to start with. So this is a very efficient way of getting the data in our nice JavaScript graphing library here.
We've got individual player reports. So our guild master who's very concerned about how each character is doing can go in and find out about Boris, the level 31 Tauren Druid, find out what he has been or she has been withdrawing and depositing into our bank. We've also got a little player rankings page so we can see aggregate information about the whole bank, which is pretty cool with lots of little graphs in there as well.
One of the cool things is we wrote an iPhone version for it as well. So this is what it looks like on the iPhone and, you know, this was my first iPhone application and it took me, you know, a couple of days to get my head around it. But once my head got around it, it was very easy to develop with.
So I can go across into the gold history here, see the same graph that I saw before. Something new you can do in the iPhone 2.0 software that you couldn't do in previous versions. I can actually tap and drag along here to select a region and have it populate. Very nice. class.
Click back. Got a nice little transition. Those are all done in CSS as well. I can also see player reports, same sort of thing. All this is Ajaxified, so there's no whole page loads going on here. Back. We've got our other graph here as well. A whole bunch of things. So that is the little demo application that we'll be taking you through parts of today. If we can switch back to the slides.
In the next section, I'm going to hand it over to my colleague Joel Young to talk about inside Ruby on Rails. Thanks, Luke. Hi, my name is Joel Young. I work on Mac OS X in the integration department. I'm going to be talking about what is inside of Ruby on Rails. You know, we saw, you saw we came up with a nice little demo application for you, and we were able to build this in a few short weeks, and So I'm going to talk about just what makes Rails so productive.
So to answer this, I'm going to address two questions here. One is just what is Ruby on Rails, and how is it structured to make your development fast yet maintainable? And why, in particular, would you want to use it on Mac OS X? So Rails promises to be web development that doesn't hurt.
It achieves this by using a few big ideas. So I'm going to talk about just two of these right now. One is the Model-View-Controller pattern, which helps you keep your display code separate from your business logic. and a little something called domain-specific language, which is a way to keep your code short and still understandable.
So first, Model-View-Controller. You might be familiar with this. The idea here is that you don't want to be making database connections inside of your templates. Similarly, you don't want to be spitting out HTML code from the bits of functions that handle your business logic. If you do this even for a medium-sized application, it's a recipe for disaster down the line.
So, Modiview Controller solves this by having a view layer, which contains everything that the user is going to see, separate from the model layer, which contains the structure of your data. So, in between them, you have the controller mediating things. It receives the user input, translates them over to requests for data from the model, and once it has that data, it then updates the view. So, how does Ruby on Rails handle this? How does Ruby on Rails structure this? So, let's look at Ruby on Rails.
and David Aron. You have ActiveRecord. That's the name Rails uses for the model layer. ActiveRecord is a thin layer around the database. Generally, you're going to have one table in your database translates to one ActiveRecord class. So now, the ActionController is the name of the controller layer. So when you get a request from the browser, it goes through the web server. The web server pushes it through to Rails, and Rails dispatches it to the proper ActionController class.
So the controller will then request the data from ActiveRecord. So then we have Action View, name of the view layer. So once the controller has the data from Active Record, it'll push it through to the correct template and Action View, where it's composited, sent back as a response to the browser.
Next big idea here is domain-specific language. So it's an optimized vocabulary for talking about some particular problem set. You could think of this as jargon. Except in a good way, right? So the classic example of this is making a complicated order at Starbucks. I'm not a big coffee person myself, but I gather that Grande 2-Pump Vanilla Non-Fat Extra Hot Latte is about the most efficient way that you could express that specific drink of coffee.
So how does Ruby on Rails do this? Here's a few examples real quickly. So let's say you want to find an author named George. You would call simply author.findByFirstName George. In your model, let's say you want to define a relationship to another publications model. So you might say that it has many publications. Or, let's say, before you show any page on the site, you want to authenticate users, unless, of course, they're on the login page already. You would say, before filter, authenticate, accept login. Very clear, very easy to understand just what these lines of code are doing.
So Ruby on Rails was developed using Mac OS X. This is David Heinemeier Hansen, the creator of Ruby on Rails, and when he talks about his motivation for building a framework that is meant to increase programmer joy, he says this. For all the time I've been building Ruby on Rails, I've been using a Mac. I wouldn't have probably built it if I didn't enjoy my computer. And you can see this reflected in the Rails community. It's very Mac heavy.
And Apple has recognized this by bundling it into Mac OS X as of Leopard. So that means that for development, you have everything you need to very quickly build your application. You have the Ruby language, which comes bundled on Mac OS X with a large number of useful Ruby libraries. You have the Ruby on Rails framework itself.
Now, for development, you don't need a heavyweight database application. And SQLite, which comes bundled as well, is perfect for quickly bringing up your applications. And now we have, as Luke showed you, the iPhone simulator. which will allow you to make sure that the iPhone version of your website looks great.
Similarly, for deployment, Mac OS X Server is a great platform for deploying your Rails applications. It comes with Ruby and the Rails framework. And since you're going to be receiving many requests from different users potentially at the same time, you'll want a more heavyweight database server, and it comes with MySQL installed.
Then for managing the many Rails applications that you might have running, the server admin tools give you a nice GUI way to handle all these things. So, for a little taste of what it's like to actually build a Rails application, I'm going to hand it back to Luke.
I'd just like to point out that's the first time Joel's ever done a public presentation, and you wouldn't really know it, would you? He's good. All right, can we please switch back? Oh, no, no, don't switch the demo machine. Ignore what I just said. All right, so talking about how we exactly designed this particular Ruby on Rails application, we started off with a data source, in this case, the World of Warcraft Armory site, which you can go to to get all sorts of information for your World of Warcraft guild.
Unfortunately, the data is structured in a very tabular sort of format, so if you want to find out information about trends or aggregate information or sort of do those kind of more complex queries, you're sort of a little bit out of luck, which frustrated our guild master no end with his iron rule that he exerts over our guild.
Luckily, behind the scenes, when you view the code, it's actually pretty much all XML. So the way they've structured the site is they give you XML and a style sheet, and there's an in-browser. So what you can do to harvest the data from this site is just pull it down, and you've got all this lovely XML that you can use to aggregate things and repurpose this website for other purposes.
Naturally, this means you've got to import the data somehow. So we had a simple script-based process here. We had the Blizzard Armory making HTTP requests to it using our import script, then populating some simple active record tables. In this case, we partitioned things off into an inventory log, an item, and a player, an inventory log being one transaction in the guild bank, so like a withdrawal or a deposit. And items and players are fairly self-explanatory.
And backing all that up, we just chose a simple SQLite database. Now, to get the XML data in, a really cool thing you can use is this great little gem called HBricot. It's pronounced HBricot. We actually had to ask around to someone who had seen the creator, WhyTheLuckyStiff, to find out how this was pronounced. And I don't know if that's wise. I don't know if Y's name on his birth certificate is WhyTheLuckyStiff, but it would be pretty cool if it was.
And then we have the parsing joy. So it allows you to parse XML using XPath or CSS-style selectors, so similar to jQuery, if you've ever done any JavaScript work with that. and it's pretty cool because it's bundled with Mac OS X, so you don't need to go hunting for this library. It's already available on your system right now.
TextMate. We love TextMate. Everyone in the Rails community loves TextMate. The only people who don't love TextMate are people on Windows because they can't use it. It's available from Macromates.com. And it's really an editor that's grown up and around Rails, I think. It kind of comes from the Emacs school of, you know, provide plug-ins and flexible architectures so that third-party developers can add absolutely whatever they want. And that's, you know, the Rails community has really taken this editor to heart. Flexible Plugin Architecture. And it's exclusive to Mac OS X. So if you want this joy, this wonderful work of TextMate, then you are going to have to develop on our platform.
Next up, Rails. Obviously, to develop a Rails app, we need Rails. We need version 2.1 in this case. The demo code we can give you, and I'm going to regret saying this, but if you want the sample code, then please feel free to write down our email address at the end of the presentation, and we'd be happy to share the sample code with you.
We didn't write that in Rails 2.1, but the demo that I'm going to code up here will be written in Rails 2.1. So we'll talk a little bit about the new features that are in Rails 2.1. If you haven't got Rails 2.1 on your system, it's very easy. You just need to sudo gem install rails, and that's it. You've now got Rails 2.1. Now I'd like to switch to the demo machine.
Hello, demo machine. All right, so what we're going to do is take a very small part of this project and sort of create it from scratch as we go. So I'm going to take the player information, that is the list of people who are in our guild, and I'm going to build some simple tables and write through to a view and demonstrate how we sort of display this data. So to get going, I'm just going to start a simple Rails app called demo.
And you can see here, if you haven't generated Rails app before, it does all this fantastic code generation for you. It gives you the skeletons to work with without sort of, you know, making it hard to work with. So what we're going to do is the very next thing, going to our Rails application, and I'm going to generate a model.
I'm going to call this model Player Class. So this is the class of the player. You might be a mage or a warlock or a warrior or something. That's the first model that I'm actually going to generate. It's going to have one attribute, which is a name, and it's going to be of type string. So if I just go, Shazam.
It will generate all the necessary things I need. In particular, you'll notice the migration down the bottom here, which generates the schema for our database table. In Rails 2.1, this is a timestamp, which is a big benefit if you're working in an application with many developers, because before it was an incrementing sort of integer, and if two people generated a migration with the same number and then tried to merge, it was a pain, whereas now it's a timestamp, so you probably won't get quite as bad conflicts, which is pretty cool. So now I'm going to generate another model, this time called Player, and this is obviously the player itself. So players have names, which are of type string. They have a level. I still haven't got to level 70.
which I'm embarrassed about. They have a race, which is also a string. They have a gender, which is also a string. They also have a player class ID, which is our key into the player class table, and that is an integer. Did I spell all that correctly? I think I did.
Now we've generated our second model, which is the player. We just need to run these migrations to actually create our database. So the way you do that is just rake db migrate. That'll go away. This is a little MacBook Air, by the way. So if it takes a little bit, it's chugging along, doing its best, I assure you. So it has gone away and created these tables for us. One cool thing that you can do in Rails 2.1 is there's actually a db console.
So you can do script db console, and it will drop you into the SQLite prompt of whatever Rails environment you're currently in. And you can see the schema, for instance, see what's actually being created there, which is pretty cool. Now, at this point, I'm going to fire up TextMate.
Let me grab everything here. Can you see that OK? OK, maybe not. On the left-hand side, there's actually the demo app. So what I'm going to do now is drill into my model classes. I'm going to start with the player class. Now, this is all the skeleton code that's been generated for you.
So if you come from other generated code backgrounds, you might be pleasantly surprised to not see 400 lines of crazy code in here. This is the generated code that was actually put for you. All the magic is in the background. So for that particular player, we're going to say this belongs to a player class.
And when you're saying belongs to, it's kind of establishing a one-to-one sort of relationship here. And you use belongs to when the class that you're actually working with, or the table that you're working with, is the table that contains the foreign key. So player contains the player class ID. Therefore, we're using belongs to here, as opposed to has one, for instance. I'm going to save that. Player class, in this case, I'm going to say it as many playas.
And I'm going to add an additional thing here. So whenever we query for multiple players through the player class, I'm going to specify an order, which is of name. I'm going to order everything by name. Nice and easy. So, now that we've got our database set up, we've got a simple model system set up, what we need to do now is actually write a script to import data. So, I'm going to just touch a little import script here, import.rb.
And I'm going to, first of all, I'll show you the XML that we'll actually be importing. So this is kind of cut down a little bit from what's on the Blizzard website, but it's this big glob of XML, and it's essentially just a page root element and then multiple character elements underneath that, and they contain the attributes encoded as sort of, you know, key values in there. So we're going to work with that particular class, and it's pretty easy.
What I'm going to start with is require hbricot. Make sure that's in there. Then we're going to start a document. It's going to be a new hbricot XML document. And I'm going to open that file of players, which is located... Just there. So now we've got a hbrecord document we can query.
The query we want to make, we want to search across page, across character. For each of those characters, we will do something. And each character inside this block will be referenced by C. So we're just going to create a player each time we encounter one of these characters. The naming is slightly sort of different here. We're calling them players.
Blizzard calls them characters. It doesn't really matter. So I'm going to start with a name. And I'm going to specify a name such as that. So essentially, I'm starting a hash here, creating each name, the name column that belongs to my player. And I'm just pulling out the attribute name from the character element that I'm iterating over. And we're going to do four of those. So the next one is going to be the race, followed by the level. followed by the gender.
Race, level, gender. I don't need to worry about types. Rails will recognise that, for instance, the level is an integer and will massage the type into an integer for me, even though from an XML document, naturally it's going to look like a string. And that last thing is I want to associate it with the right player class.
Now, the player class is just a string, like mage or whatever, that's in the character element. What I can do is use a bit of Rails trickery here and say associate to the player class, find or create by name. I love it. Class. So the class is going to be mage, and if mage exists, it will supply the ID for mage and associate it. If mage doesn't exist, mage will exist after this happens. Simple.
Now, to actually run this script in the context of our Rails application, what we use is the scriptrunner command. So I'm going to use scriptrunner, and I'm going to say import.rb. and it's gonna horribly blow up in my face. No, it doesn't. Fantastic. Yes, woo! This is basically, I am poking the demo gods with a large stick here by trying to do so much code, but you know, somebody needs to do it.
Now I'm gonna drop into the console and just verify that something is actually in my database. So let's find the first player. And I believe this is new for Rails 2.1. If I say player.first, I should get the first player in there. This makes me happy. If I say player.class, I can actually ask for the last player class.
And that works out okay. Last player class that was loaded in was rogue. Our association should work, so based on the last player class, I should be able to ask for all the players that are in that player class. So this should list all the rogues. There's a bunch of rogues. Now I can actually specify .count to find out how many of them there are. And that's actually doing a select count star from in the database behind the scenes.
I can... What else can we do here? We can ask for player first, the player class, for instance, to find out what player class the first player is. In that case, it was a mage. So our associations are all set up, and they seem to be working OK, which is pretty good. Oh, another thing I like about TextMate is this feature. I love that. Makes my code nice and readable. Now it's going to execute more quickly as well.
I'm going to go on believing that. All right. So we've created our database, and we've imported some data in a very quickly, in a fairly straightforward manner. So the next thing we need to do is actually create a controller. So I'm going to generate a controller, and I'm going to just call this one player list, just like so. That goes away, and it dumps all the files we need. In particular, it's created the player list controller, which is that guy there. So we're going to... Well, let's just mock up the query that we're going to write here on the console first, before we do anything.
What I'd like to do is have kind of like a table that says the player class, the number of players in that class, and then optionally allow me to see those players. So I'll have like multiple headings, Mage, Warlock, Warrior, with the counts, and then between those, the actual players that are in them. So what I can do here is ask for player.count, one of these aggregate functions. I can say count all, count all the players, And then I can say group, group them by player class.
If I run that, it's going to dump a bunch of stuff in there. Now, what exactly has this dumped? Well, let's just look at the first thing that it dumped here, the first element. It's actually dumped us a simple array. The first object in the array is a player class describing mage, and the second object is the count, 14, 14 mages down there.
So, that's very convenient. We haven't had to touch any SQL yet or worry about anything like that. It's all happening for us behind the scenes. And in Rails 2.1, you can trust that a lot of these queries have been very nicely optimized, so we should be able to use this, you know, without ending up in a total meltdown. So, I'm going to go now into my controller.
My player list controller. I'm going to start by defining an index method. We'll just call an index. And I'm going to call this player by class and then that same query. Player count all group by the player class. This is what Joel was referring to in domain-specific languages.
This almost reads like a sentence that describes exactly what you want to have happen. It's almost not even jargon. You could show that to a non-programmer and they would probably be able to figure out what is going on, more or less, which is exciting. If you're the kind of guy who gets excited about those things, which is me.
So I'm going to start a new index view layer now. Like, we need a template to actually-- you know, now that we've got the query result, we need to present it. So we're going to need to write, like, a little bit of HTML. So I'm going to write an index.html.erb, just like that. Here's my index.html. Now I'm going to flagrantly cheat and paste some code in.
simple code. We'll go through briefly what I'm doing here. Obviously, I'm saying this is the iGuild roster. I'm iterating through that players by class. I'm actually calling sort. And this is the most kind of pearlish thing that I've done here, which is I'm sorting and I'm sorting by the second element in the array, which is the count. And I'm sorting in descending order.
So I could have probably optimized that out, but I sort of fell back onto my sort of early Paul Pearl education there and just wrote it all in line. And once we've sorted all this out, we're going to look at player class and count. So inside this block, we're going to be presented with those two objects.
I'm just presenting them here, player class.name and the count. And then down here, I'm saying in a slightly different way of iterating, for player, in player class.players. So for the given class, mage or whatever, I'm pulling out all the players and then I'm just printing their name, their level and their rank. And then I'm going to run the server, the built-in web server, which will come up.
In I will start a new window here and visit localhost:3000. Play a list. See what happens. I've seen that before. Oops. What did we do wrong? You know that bit where I said I was flagrantly poking the demo gods? They've been flagrantly poked. It's a typo. Come to WebKit. No, not you. Damn spaces. Yeah, you all know my pain. That's better. So, you can applaud if you want.
That was a deliberate typo, just to get a round of applause, because it makes me feel great. All right, so, you know, wow, we've got this, you know, simple list here, iGuild roster, and everything worked out as we planned. We've got the warlock, we've got the number of warlocks, and then we've got a list of all the warlocks. And so have we got hunters and rogues and other things like that.
But this is kind of boring, and we can wrap it up really quickly, sort of demonstrating a little bit of the power you get from working with WebKit on our platform. By doing some extra things here. What extra things could we do? Well, let's go into public style sheets. Let's create a style sheet.
called demo.css. I'm gonna grab my CSS code. I've written about 60 lines of CSS here. I'm not lying, there it is, 59 lines. There's no extra magic behind the scenes here. Paste in that CSS, all pretty standard stuff. And then I am going to grab some JavaScript. Now, normally you wouldn't do this. Normally you would put this in a document load and sort of put it off in its own JavaScript file. But today I'm just going to put it at the end of our file, just so we can see everything on the screen at once.
And basically what this JavaScript is going to do is set up an event handler such that when you click one of the titles, it toggles the unordered list element underneath that and grows its height from zero to some other value. So it's toggling the value. It might be zero pixels or it might be 300 pixels. And it would oscillate between those two, depending on how many elements are in there. So I put in my JavaScript. And you'll notice it's all using prototypes. So what I've got to do is say JIT to get all of my JavaScript stuff.
And then I'm going to get a style sheet as well that's cool TextMate stuff, all these little macros. So I've included the JavaScript. I've included the style sheet. I've got my seven or eight lines of JavaScript in there just to set up the handlers. Let's see how it looks now.
Ooh, that looks better. Yes, wow. The cool thing is I've also used animations and transformations here. So when you click on one of these things, you get this nice transformation. Everything grows down there like that, round the corners on the boxes, very nice and easy, only using a few lines of code.
And this isn't one of those hacky kind of JavaScript animations. This is all CSS-based animation. The exact same stuff will work on the iPhone as well. So when you go to extend this into the iPhone, you're not going to be rewriting a whole bunch of that code either. So that is that, pretty much. I think that's the end of our demo. Yes, that's the end of our demo.
So we're going back to the slides, and I'm handing back over to Joel, who's going to take you through asking questions about data. Thank you very much. Luke is a wizard on the demo machine. And so you can see just how quickly you can bring up a Rails application. But for our project here from our guild master, we had a very particular application in mind.
We wanted to be able to build this quickly, and we wanted it to be maintainable. And so for that, we need to ask some questions about our data. So in this section, I'm going to talk a little bit about how Rails makes test-driven development extremely easy, and it helps guide your implementations. And then I'm going to show some tools and techniques for tightening up your code.
So the question that our guild master wants to know is, where's our stuff? To answer this question as quickly as possible while still keeping the code maintainable, we need a tool. We need something to keep us on track, keep us going in the right direction, not waste any time.
One proven tool for this is test-driven development. So you might be familiar with writing unit tests. You have a suite of tests that verify the behavior and the correctness of your code. But with test-driven development, you write the test first, you specify the way you want your code to work, and then in effect, the test itself is the specification for how your code should work. This keeps the temptation away of writing all those features that you think you might need somewhere in the future, but you don't actually. You just write the code to the specification. This also gives you the ability to refactor without fear.
If you have this raft of unit tests and you change the underlying implementation of some method, you don't have to worry that you're missing some edge cases and you might be breaking something. Well, you just run your unit tests, see what breaks, fix it. Ruby on Rails makes this extremely simple. You have no excuse, really, not to be doing test-driven development with Rails. When you, I don't know if you noticed, when Luke was generating all that code, it was building the skeletons for your unit tests right there.
So let's look back at our question here. Where's our stuff? We can rephrase this as, how do items trend over all time? So to do this with a test-driven approach, we need a story, right? We need a simple story, and we need to be able to verify it in code. So we know basically what we want our end product to look like. We want this time series graph.
But writing a test to verify this data set would take possibly thousands of lines for all the thousands of data points there. So this is not what we want. We need something simpler. So let's try this on for size. We make up this story. We start out with no elements. On Monday, someone deposits five items. And on Tuesday, someone else withdraws two of them. That's a simple enough story. And we can now verify this with just a few lines of code.
So first, you have to define the data here. When the model is created, it gives you a file in test slash fixtures called inventory logs dot yaml. This is just, yaml is just a simple format, simple and human readable format for specifying structured data. So we name each of our transactions here, deposit elixir and withdraw elixir. And then for each line, we'll write an attribute of this. inventory log entry.
So, player Jane was defined in when players.yaml, and instead of having to look up the player ID, we can simply reference the player by her name. But really, for the purposes of this test that we're writing now, all we want to know is that there are two entries, two item entries in our inventory log.
And to make sure that it's in chronological order, we're going to test that the balance on the first one is 100. Okay, so let's go over to our unit test file, test units, inventory log test.rb. And we're going to start by adding a line here, fixtures, inventory logs, and that will load in the fixtures file that we just defined.
and it will make it available to all of our test cases. So how do you write a test case? You simply define a method that starts with test_. Now, the purpose of this is to say how we want this code to work. So we define a method that we want to exist, inventory log.items.
Going back to our example, we know that the length of the item's collection should be two and that the balance on the first item should be 100. So when we go ahead and run this unit test from the command line, We're going to get about what we would expect. We get a single E there, which indicates that there was one test and that it bailed out with an exception. So what's the exception? Simple enough, undefined method items. Well, let's go and define that method. So we go over to our model file, @models.inventorylog.rb.
And we start by defining, okay, what are going to be -- what are the log entry types that refer to items, deposit item and withdraw item? Then this class less than less than self. If you're not familiar with Ruby very much, this will just say that anything inside of this block is a class method instead of the normal instance method.
So in our items method here, our items class method, we're going to use the ActiveRecordSupplied find method, and we're going to find all entries that match these conditions, that the activity type is deposit item or withdraw item, and then we're going to order them by activity timestamp. So let's go back and run our test. We get a little dot there, which says that it passed with flying colors. And we have some functionality here.
So if we look back at our simple story here, if we were to present this graph to our guild master, he'll know how items trend over all time. After that, he's going to want to know, well, what happened on Tuesday? There's that dip there. Whose fault was that? Once we have that, we're going to want to answer some questions about money. We'll have similar related questions to that, such as how does money trend over all time, and again, what happened on Tuesday once we have that picture there.
So let's break it down. We've written the items method. And we could answer the question of what happened to the items on Tuesday by giving it an optional date range parameter. Then we can write a money method that's going to look very similar to our items. And then to answer the question of what happened to the money on Tuesday, we could add another optional date range parameter, but at that point, We are repeating this date range parameter. We're handling it the same way. We're adding it to our find options in the same way. We're repeating ourselves. This violates the DRY principle. What is DRY? Well, this is DRY.
Joy also means don't repeat yourself. The reason you don't want to repeat yourself is the more code you write, the more mistakes you're likely to make. And when you make those mistakes, if you've repeated yourself, you're going to have to fix them in multiple places. So we don't like this. This gets old real fast.
So there's a few ways to solve this, but in the middle of writing our little demo app, Rails 2.1 came out, and it gives us this named scope method that we can use in our models. And named scopes, you just define a scope, and it acts a lot like a class method, except that you can chain them together in interesting ways. So just to take a quick example here, let's say you have some marbles. You define a blue scope in your marbles, and that'll return-- and marble.blue will return all of your blue marbles.
You define a small scope here so that marble.small will return all your small marbles. But then you can combine them together, call marble.blue.small, and with a single call into your database, it will give you all the small blue marbles. So let's apply this back to our inventory log model. Here's the code that we had, a class method called items. We rip that out and we replace it with a named scope called items. And we simply repeat those same conditions and ordering that we had before.
So breaking it down once again, we have redefined this items method. And we can define another name scope called nRange that will take our date range, string them together like that, and it'll answer the question of what happened to the items on Tuesday. We still have to write another money scope that looks similar to the items one we just wrote. But then we can reuse the nRange scope, combine it with money, and that'll answer what happened to the money on Tuesday.
All right, so I'm just going to take a quick look here at items.inrange and write a test method for this case. So we're going to test items with date range. We define our date range here as just two strings separated by two dots, and that'll create a range object in Ruby. And then we say we want to be able to call inventory log.items.inrange with that range element. And we would expect from our scenario that there would be one item in that range.
So simple enough, we define a name scope in range. And since it takes a parameter, we need to use this funny lambda thing. A lambda function is an anonymous function. It's known in other languages as a closure. Really, it's just a block of code here, and it accepts an argument range, and we can then use that range when we specify the conditions. So we run our unit test again from the command line. It gives us two little dots this time, and that will let us know that we have our two test cases, and they both passed with flying colors.
So that's just a little taste of the kind of power that Rails gives you when you need to do this structured development and ask questions about your data. It makes it easy to do test-driven development to keep your implementations going in the right direction, and then there are tools such as named scope that help you tighten up your code and keep it dry. So once we have those questions formulated, once we understand how our models should work, we need to present those answers.
So for that, we built our demo website. Luke showed you this just to review. We have this web page here. There's a graph inside. You click and drag. It gives you a table of data below. Then we also have the iPhone version, which is showing the same data, but it's just in a different format, and it works slightly differently.
So we have this problem where we have the same data. We want to present it in different ways, in different formats. To solve this, Rails gives you, sorry, we have, so I'm going to take a look at three of these different formats. You have, most obviously, the HTML. This is the page container for International Bank of iGuild. We have something called JSON.
Now, that graph that you saw isn't an image generated on the server side. There's a JavaScript library that makes a request from the client side for structured data. And this is, and it asks for a JavaScript object notation. And then it dynamically builds the graph. And then, of course, we're going to look at the iPhone version. So the tool for managing this is RespondTo. It's a method and action controller, and it lets you alter your response based on the requested format. You can think of it as kind of a telephone switch operator.
And so to use respond to, we set up a history controller to show the item history and the gold history. And if we define an item's action, we'll just say what types of formats that it should respond to. So we're going to say that we're responding to HTML, JavaScript, and iPhone formats. So first, the HTML. This is the overall page. We don't actually have any data on this page because the data is requested after the page load by the JavaScript library. So we're going to say that we're responding to HTML, JavaScript, and iPhone formats.
So it's very simple here. Each of these format.whateverformat declarations can be followed by an optional block of code, but we don't need any of that. All we do is declare format.html, and if it matches that format, it'll go out and fetch the items.html.erb template and display that.
[Transcript missing]
Really all at once is a form of serialization of your data. So we're going to send that back as serialization.
So it couldn't be easier to do that in Rails because you don't need to build any templates. You don't need to have any kind of library that spits out your objects in this object notation. You simply supply a block that says, render JSON arrow, and then the collection of data that you have. and it will implicitly call .2 JSON on it. Rails knows how to spit out the JSON format for a large number of data types.
Now, the iPhone. We had to make this iPhone version of this, because what web app is complete without one? It turns out that we can use the same content and the same structure that we had it before, and all we have to do is define new behavior. So we keep our HTML templates the same with a few one-line changes here and there. We defined a new JavaScript file for the behavior for all those fancy slides and the back buttons and the like. And then, of course, a new CSS file to define the new iPhone look stripped down.
So I'm not going to go through all the code for this. I'm just going to talk about, in general terms, how the JavaScript works. We'll have the sample code available afterward if you want to get down into the nitty gritty here. So from the front page, when you click on one of these-- When you click on one of these categories, the JavaScript code is going to intercept that. It's going to talk to our controller. It's going to receive some bare-bones HTML back and then apply the transition to get our new page.
So we have the same structure here with new behavior, but as far as the Ruby code is concerned, it's, again, pretty simple. For the iPhone format, we give it a block, and we just tell it to render our layout, or render without any layout, without any of the backgrounds, the headers, any of the Chrome on the page. JavaScript handles everything for us, displays it with a nice transition.
So this is just a little flavor of the kind of problems we ran into and how we were able to present these answers in a nice, pretty-looking website. So we used the respondTo method, which allows you to respond with different formats to requests for the same data. We used object serialization with extreme ease. And we built an iPhone website where we were able to keep the same structure and define some new behavior for it. So once we have the application built, we need to have it face the world. And for that, I'm going to hand it back to Luke.
All right. Did I mention before that my daughter was born right in the middle of the keynote? And do you know how hard it is to actually keep doing that stuff while tracking the keynote on your iPhone? It's hard, because if you get it wrong, you are going to be removing a surgically implanted iPhone in your head if you get found out. Apparently, we released a new phone or something.
So sooner or later your Rails application is going to get popular. And I wanted a picture here of like a crowded train station or something, but instead this is sort of the only picture we had authorization to use. So what I want you to imagine is, imagine all of these people in this photo crammed into a train carriage, like really crammed in together, and there's condensation coming down the windows and everything because everyone's sort of crammed in so tightly. Then I want you to imagine these people smeared with honey.
and Peanut Butter. And then I want you to imagine somebody releases a swarm of bees into that train. And then another man opens up a briefcase and it has a whole ant's nest in it. And he kicks that briefcase as hard as he can. And then imagine that this train with the bees and the honey and the peanut butter is driving through Yosemite at like four miles an hour, say, at the start of spring. I have just described to you exactly what is going on in Twitter's data center right now.
For this reason, it's very important to get your Rails deployment strategy right, unless you want to be one of these unfortunate people crammed into the train. I'd like you to deploy on Mac OS X Server because we think it's a really nice platform to deploy on. We've got fantastic high-end hardware. It's all silver.
And it looks great in your data center. But it performs really well as well. It's not just about the looks. It's really good stuff. I'm not going to go into that too much detail. I don't want you to feel like you're in a sales and marketing sort of presentation. But OS X Server is also structured very similarly to client.
So when it comes time to work on the command line and work with the system, you're not in an alien environment. You'll feel if you've developed everything on client, you're going to be at home on server. And of course, you've got the server admin tools as well, which simplify a whole range of operations, so you can just dive into the GUI and work there. You don't need to do it all in the command line.
So deployment. What do we mean by deployment for a Rails application? Well, here's our server, a Mac OS X server, naturally. You've got Apache on there answering all of the web requests. You've got your Ruby on Rails application living on there as well, and a database, and something in the middle. Something has to be in the middle, because Ruby on Rails doesn't know about Apache, and Apache doesn't know about Ruby on Rails with something in there to route these requests around.
What can you use to route these requests around? One option is Mongrel. That's bundled on OS X. It's basically a web server that knows about Rails. So it's not an application server per se, but it's a web server that can be started up inside a Rails application and serve that Rails content out. And as I said, it's built into Mac OS X, built into Mac OS X Server.
It looks something like this. You're going to have your Apache layer or your web server, and then underneath that, mod proxy balancer, which is essentially a way of taking requests and evenly distributing them using some algorithm over multiple instances behind the scenes. In this case, you're going to have multiple mongrel instances that are running individual Ruby on Rails instances themselves and a shared database underneath all that. So you can structure your application to run all these different, as many mongrel instances as you see necessary.
To start it up, it's really simple. We've got this little command line, useful little command line called mongol-rails-persist. What that does, apart from allowing you to set things like ports and what environment you're starting in, are you starting in production or otherwise, it also creates a launch DP list in library launch daemons so that if your server gets rebooted or a swarm of bees attacks it or whatever happens, it's going to come back up again on startup.
And it's also Bonjour discoverable, which is nice because when you get into the configuration side on the GUI and you're establishing one of these, you know, sites from scratch, you go into the proxy tab, you enable reverse proxy, and then you simply specify what balancer URLs represent your mongrel instances and requests can be routed to all these.
And when you go to add one of these proxy sort of instances, it already knows about the URL because it discovers it over Bonjour. So you can start up a whole bunch of mongrels and they'll be visible in here without you needing to remember anything, which is kind of nice. A new way to deploy that works really well on our platform is Passenger, and that is their logo there.
It looks like something that was once spray-painted on my van when I lived in a rough part of town, but that's their logo. It's available at modrails.com. You can kind of think of it as Mod Rails in the same way that there is a mod.php and a mod.perl. It's very new. It's extremely easy to use. It doesn't really encapsulate how easy this is to use. And it works great on OS X. The developers are totally on board with working with OS X, and they've done a great job of optimizing it on our platform.
So what does it look like? You've got your Apache. That's still there. Underneath that, you have mod.passenger, which is the Apache module itself. Mod.passenger starts a Rails framework instance, an instance of the Rails framework. So if it's Rails 2.1, there'll be one 2.1 framework instance. So unlike the Mongrel case, you're not going to be instantiating multiple instances of framework. You'll just have one.
Underneath that, you have an application spawner, which takes care of starting multiple Rails application instances as needed. So you can start 10 Rails instances if you think you're going to need 10 different instances of it running. Installing passenger, because it doesn't come default on our platform yet, is simply a matter of saying sudo gem install passenger. and I are working on a new Apache module called Pseudo Passenger Install Apache 2, which sets you up so it's a valid Apache module.
You create a virtual host, the document root of which points to the public directory of your Rails application. And then there's nothing else to do. That's all you need to do to deploy the application. So once you've done those initial two steps of getting Passenger installed, All you need to do from that point forward, anytime you want to deploy a new Rails application, is just create a virtual host and point it to the right location. And it starts up the Rails instance and it handles everything for you. It's really cool.
Another thing you're going to think about with deployment, especially if you are an iron-fisted guildmaster and you want to keep track of everything that's going on in your World of Warcraft guild, you're going to need to pull data on a regular basis. So when you're doing something like that on Mac OS X Server, we recommend that you use LaunchD rather than anything like Cron or setting up your own process that sort of persists and lives on.
I like to use Lingon because I can't remember the XML syntax for creating LaunchDP lists, and if you can remember that, then you're doing much better than I am. Just grab Lingon, set up a LaunchD process. Just remember that you have to set the Rails env variable to specify what environment your application is starting in, whether it's production or whether it's development.
So in conclusion, what have we learned? I think you can say that building Rails apps are very easy. We built one live on stage here with hardly any effort, and we built that more sophisticated website with actually a lot more effort. But it wasn't really that bad. Test-driven development is very easy.
As I flagrantly demonstrated, I did not use test-driven development and was punished for it by the demo gods. But you really have no excuse not to use test-driven development on Ruby on Rails, because all the infrastructure is there for you. It's presented on a plate. You shouldn't ignore it.
Rails loves the iPhone. The iPhone probably loves Rails. It's really easy to write an iPhone version of your website or create a dedicated website just for the iPhone. We think Rails is best enjoyed on Mac OS X. As Joel pointed out, it was developed on Mac OS X. If you look at a screencast of Ruby on Rails anywhere on the Internet, chances are you'll be looking at TextMate on Mac OS X. It's very popular on our platform, and you'll get a lot of support there.
And I think Rails is best deployed on Mac OS X Server, but I may be sort of biased in that belief, working for Apple and all that. But we make it really easy, and hopefully in the future, especially as Snow Leopard rolls along, we hope to have even more easy ways to deploy your Rails applications. We want to make it as easy as possible.
We have some documentation for you to check out here. Recommend the Rails book from Pragmatic Programmers. We've got a website, maybe you've heard of it, called apple.com. You can go there and see a whole bunch of Rails resources as well. There's this really great PDF, which has a difficult-to-remember URL, but somebody has put together all of the Rails 2.1 changes in a really nice PDF, and if you just search for Rails 2.1 PDF, you'll probably be taken to the same site.