Rails in a week — day 2

TL;DR: I have a terribly ugly first draft of the application working!

Day 0 — Day 1

Morning: spent finishing reading the Getting Started guide and beginning the Rails Tutorial.

Afternoon: so, let’s get down to maps…
What’s cool in Rails is that there are plenty of gems, and you just have to plug them in, right?

GoogleMapsForRails seems like the right tool for the job.

After trying to get my posts to be geolocalized… Success! It took some time to get a marker on the maps, because I thought the locations were created on-the-fly by a geocoding of the address returned by the model, when it actually just fishes the database for the lat/lng data. I had to display the json sent by the controller in rails to confirm that nothing was sent, then add a dummy post in the db with some lat/lng data.

So it seems that geocoding (the process of finding lat/lng coordinates from a string, e.g. “main street, san francisco” => [37.790621,-122.393355]) isn’t done by GoogleMapsForRails. What shall I use? Googling suggests Geokit, but separating the gem from its Rails counterpart sounds strange to me. And apparently it doesn’t work for Rails 3. Some more research, and the Geocoder gem turns up: looks good!

Some more time working with the bolts and nuts… And ta-dah! A first version is working.

The code is up on Github. To get the desired results:

http://localhost:3000/a/washington gives a straightforward geocoding for “washington” and displays it on a map.

http://localhost:3000/b/washington finds the opposite coordinates (i.e. the antipodes) and displays it on a map.

 

Coming up tomorrow: making things pretty, *testing*, then deploying.

Rails in a week — day 1

TL;DR: I began learning Rails this morning, and even though Rails in itself is (seems?) easy enough, setting everything up and deploying is hairier.

 

8:00: let’s get started! First step: getting vagrant up and running. We’ll hit the tutorial.
8:15: the lucid32 “box”, Vagrant’s parlance for a virtual machine image, is downloading. Time to get a cup of coffee.
8:40: box downloaded, let’s get on with the VM setup process. `vagrant ssh`… Yep, it works!
8:50: adding a few cookbooks. The Vagrantfile syntax (which is actually Ruby) isn’t recognized by vim; to fix that later.
9:00: first oops of the day:

[09:00:38] florent@Air:~ $ vagrant reload
[default] Attempting graceful shutdown of linux…
[default] Clearing any previously set forwarded ports…
[default] Forwarding ports…
[default] — ssh: 22 => 2222 (adapter 1)
[default] Cleaning previously set shared folders…
[default] Creating shared folders metadata…
[default] Running any VM customizations…
[default] Booting VM…
[default] Waiting for VM to boot. This can take a few minutes.

[default] Failed to connect to VM!
Failed to connect to VM via SSH. Please verify the VM successfully booted
by looking at the VirtualBox GUI.
[09:06:25] florent@Air:~ $

The VirtualBox GUI shows the VM running, but nothing more. Can’t force restarting the VM (or even stop it) from the GUI. Let’s start over: kill the VM process, vagrant destroy; vagrant up

Same error. Uh-oh. Is it because of the cookbooks I added? Let’s try deleting them and reverting to the original Vagrantfile. No luck.

Alright! Google to the rescue! And there we go, a Stack Overflow discussion leading to a bug report on GitHub. Well known network-related issue then, a fix seems to start the machine with GUI enabled and `/etc/init.d/networking restart` so that vagrant can SSH into the VM. Not ideal, but meh. Let’s advance!
9:45: phew! That took some time. Now let’s add back those cookbooks.
9:55: added to the Vagrantfile, the cookbooks install themselves.
10:00: port forwarding works, now’s time to go buy groceries while downloading a Debian Box for later.

13:45: back! Stomach full and coffee by my side.
14:30: that whole Vagrant/Chef thing is a bit strange. There seem to be a recipe for Rails on Opscode’s (the company behind Chef) GitHub account, but it seems to also installs a bunch of Java stuff. Anyway… We’ll get to the bottom of provisioning later, the goals here is to learn Rails, right? Let’s just install what’s needed by hand.
15:00: new Debian Vagrant box set up. Installing rvm to get ruby 1.9.2.
15:20: `rvm install 1.9.2` then `rvm use 1.9.2`: Ruby all set. Good.
15:22: `gem install rails` let’s go!

(Starting Rails for Zombies on the side)
Lesson 1: okay, so there’s a built-in ORM in Rails. That seems to be ActiveRecord if I understand correctly. Gives you methods like Tweets.find(id), etc.
Lesson 2: models. Models are the O in ORM, and the M in MVC.
Lesson 3: erb. Built-in templating. The V in MVC.
Lesson 4: controllers. The C in MVC.
Lesson 5: routes. The mapping between URLs and actual code.

17:00: alright! Rails for Zombies is done, I feel ready to start a real Rails project.
`rails new sample`
Bunch of stuff getting created… All done. Let’s launch!
`cd sample && scripts/rails server` … Crashes. Says it needs a Javascript runtime. Why? No idea. But here’s the fix.
“Still pretty lame that rails 3.1 is “broken” out of the box.”, says wonderfulthunk. Quite true. :|

17:20: gem added, bundler works (`bundle install` manages dependencies and puts the needed gem into the “/vendor” folder), `script/rails server` works! Let’s plug the host’s port 3000 to the VM’s one and set up a shared folder so that we can develop and test from the host machine while running everything in the VM. This is all done in the Vagrantfile.
17:30: setting up the shared folder ate the Rails project. Fun times. Re-create it, re-add the gem, re-bundle, re-start the server…
17:40: it works! I can see Rails’ welcome page.
17:41: so, what now? “1. Use rails generate to create your models and controllers” Okay, sure.
Let’s try and create something simple, say a blog. It need posts. `script/rails generate model post`: it creates the model I want, some migration (?) stuff and some testing stuff. But wait, it doesn’t create any view, or controller… There is a better way: scaffold. `script/rails generate scaffold` gives us an example of how the command works, by suggesting to create… A blog post. Great minds think alike, I guess. ;D [/narcissism]
Scaffolding creates another bunch of stuff. Looking at config/routes.rb, there is now a resources :posts. So I guess going to http://localhost:3000/posts should work?
=> Could not find table ‘posts’. Oh.
Let’s see what’s in the db then. I remember an option on the rails script about that: yep, `script/rails db`
It gives an SQLite shell:

sqlite> show tables;
Error: unable to open database “db/development.sqlite3”: unable to open database file

Ah! So there’s no database. Time to learn a bit more about that migration stuff.
18:15: So I checked the official Getting Started guide on RubyonRails.org, recommended by orta on HN, and it’s really well written and comprehensive. I should have started here actually; it’s exactly the right amount of conciseness and straightforwardness. That’ll teach me not listening to others.
The database is created with `rake db:migrate`.
18:20: http://localhost:3000/posts is now a fully functioning CRUD app. Is it supposed to be that easy? It really feels like cheating.
The example on the aforementioned Getting Started guide is a blog, so I’ll piggy-back on it for the rest of the day.
20:00: hmm. Learned about automatic code generation, configuration over convention (it’s all automatic! The error messages look like they might be a bit cryptic from time to time, though), migrations, and partials. Lots of nifty features indeed. And all is quite simple. So simple that I’m actually going to try and quickly write that antipodes application tomorrow, then practice TDD and stop worrying about deploying before I actually have an app to deploy.

 

On a side note, yesterday’s “day 0” post has been viewed nearly 1200 times thanks to a (brief) appearance on HN’s front page. That helps building up some pressure, I hope not to disappoint. :)

 

Come back tomorrow for more bug-fighting, stumbling in the dark and unstructured write-ups!

Rails in a week — day 0

Tl;dr: I’m learning Rails in a week! And I’ll blog about it all along. Any tips?

 

So. Learning Rails.

As a recent graduate in CS, moving to a new country in two weeks and looking forward to expand my skills, Rails looks like a good fit: it’s in demand, it focuses on developer happiness, vibrant ecosystem yadda yadda. You probably already know this so let’s cut the marketing speak!

Methodology

My main aim is to get familiar with the Rails ecosystem, but also to learn better processes in the way. This includes a more thorough use of version control, testing, server provisioning and deployment.

I plan on using the following tools:

  • Git (I already know a bit of git, but basically just add/commit/push to Github. We’ll see if I need more)
  • Vagrant & Chef to provision the VM
  • Capistrano to deploy
  • Test::Unit
  • Cucumber
  • RailsForZombies as a very first introduction

The plan is to spend about 8 hours a day for 7 days, including a day off on Sunday and time off when Rugby World Cup matches are on the telly (I’m not a bot!).

I’m not yet settled on using the Pomodoro technique, we’ll see how things work out.

Goal

My objective at the end of this week is to create a small webapp that will give you a map showing the antipodes of something the user entered (a city, a country, etc). The logic behind this isn’t very complicated at first glance: plug into Google Maps and revert GPS coordinates? Which makes it a good learning project.

Caveat/Gotchas

I already know Ruby (not inside and out, but a fair bit of it), which will undoubtedly make the learning process easier. I also wrote a few websites using Sinatra, so I have a basic awareness of what Rack is, how HAML works, etc — it’s not as if I was beginning from zero.

Of course, I won’t have time to learn all there is to know about Rails in a mere seven days. :) But I have to get started somewhere, get something done, and I hope I’ll be able to reduce the number of unknown unknowns I have about Rails as much as possible in that timeframe.

I have listed more tools centered around ops than in Rails itself because I don’t know much about what will take time right now. That’s where I’m looking for your help: do you have pointers for things I should learn? Things I should absolutely do/absolutely not do? Feel free to post them here, on Hacker News, or fire me an email.

 

Learning begins tomorrow at 8!

An interesting CSS hack for highlighting S-expressions

The Community-Scheme-Wiki has a pretty interesting way of highlighting lispy code

Scheme being a Lisp dialect, it makes sense to highlight the S-expressions, i.e. “things between parenthesis”. The Community Scheme Wiki does exactly that. As you move your mouse over the code, it will highlight the s-expression you’re in and the ones around in different colors, allowing you to quickly make sense of the code.

An example can be found on this page, which renders like this as you move your mouse:

basic highlighting

half highlighting

full highlighting

How do they do this, you ask? Let’s check the HTML:

<span class="comment">;;; SAFE? tests if the queen in a file is safe from attack.</span>
<span class="paren">(<span class="keyword">define</span> <span class="paren">(safe? file board)</span>
    <span class="paren">(<span class="keyword">define</span> <span class="paren">(get-queen-by-file file board)</span>
        <span class="paren">(find-first <span class="paren">(<span class="keyword">lambda</span> <span class="paren">(queen)</span>
                <span class="paren">(= <span class="paren">(queen-file queen)</span> file)</span>)</span>
            board)</span>)</span>

(Tags linking to documentation were removed for clarity’s sake)

Nothing particularly fishy going on here. We can only see that each S-expression is wrapped into a span of the paren class.

So what is that class? Let’s dig into the CSS:

/* The paren stuff */

/* Top level */
PRE.scheme > SPAN.paren:hover { background-color: #FFCFCF }

/* Paren level 1 */
PRE.scheme > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFCF }

/* Paren level 2 */
PRE.scheme > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFCFFF }

/* Paren level 3 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFFF }

/* Paren level 4 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #FFCFFF }

/* Paren level 5 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #FFFFCF }

/* Paren level 6 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren
> SPAN.paren:hover { background-color: #B4E1EA }

/* Paren level 7 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #BDEAB4 }

/* Paren level 8 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #EAD4B4 }

/* Paren level 9 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #F4D0EC }

/* Paren level 10 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #D0D9F4 }

/* Paren level 11 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren
> SPAN.paren:hover { background-color: #FFCFCF }

/* Paren level 12 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFCF }

/* Paren level 13 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFCFFF }

/* Paren level 14 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFFF }

/* Paren level 15 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #FFCFFF }

/* Paren level 16 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren
> SPAN.paren:hover { background-color: #FFFFCF }

/* Paren level 17 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #BDEAB4 }

/* Paren level 18 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #EAD4B4 }

/* Paren level 19 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #F4D0EC }

/* Paren level 20 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #D0D9F4 }

/* Paren level 21 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren
> SPAN.paren:hover { background-color: #FFCFCF }

/* Paren level 22 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFCF }

/* Paren level 23 */
PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:hover { background-color: #CFFFCF }

PRE.scheme > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren > SPAN.paren
           > SPAN.paren > SPAN.paren > SPAN.paren
> SPAN.paren:before { content: "{{23 levels of indentation?! Yiakes!}}" }

/* extend here if more nestings are needed */

So they are actually using the fact that CSS can recognize whether a class is nested into the same class, and display it in a different manner for each level when you :hover over that span. The interesting part is that the browser also keeps the S-expressions under the current hovered one highlighted in their specific color.

Hats off!

So you want to do the SICP…

That’s awesome! But maybe you don’t know where to start.

So here we go!

Wait, the what?

Structure and Interpretation of Computer Programs, a.k.a. the SICP, is an MIT class teaching computer languages turned into a book.

Why should you care? I’ll let Stack Overflow answer:

Some classics […] teach you the effective working habits and the painstaking details of the trade. Others […] delve into the psychosocial aspects of software development. […] These books all have their place.

SICP, however, is in a different league. It is a book that will enlighten you. It will evoke in you a passion for writing beautiful programs. Moreover, it will teach you to recognize and appreciate that very beauty. It will leave you with a state of awe and an unquenchable thirst to learn more. Other books may make you a better programmer; this book will make you a programmer.

If you’re not sold on the SICP by this point, I’m afraid I can’t help you.

 

The book

The full material for the book, including lessons and the exercises, is available on the MIT website:

http://mitpress.mit.edu/sicp/full-text/book/book.html

Of course, you will need to do the exercises to fully grasp the SICP. The exercises are easy at first and get progressively more challenging. You’ll find a lot to make you think, and some concepts are guaranteed to make your head spin.

The language used to teach the SICP is Scheme, a Lisp dialect. This makes the SICP a gentle introduction to functional programming, made trendy again by the likes of Erlang, Haskell, Scala, Clojure, and even Arc if you’re an HN reader.

Okay, but doing the exercises without ever testing is not that motivating. What you need is a Scheme REPL!

 

A REPL

REPL stands for Read-Eval-Print-Loop. For example, bash could be considered a REPL: enter lines of code, they’re interpreted and results are printed out. Repeat.

Several good Scheme REPL exist, such as Gambit Scheme. If you’re on OS X with Homebrew, just type brew install gambit-scheme and launch it with scheme-r5rs in command-line. You’re good to go!

If you don’t really want to mess with Scheme locally, you can use the awesome online REPL here:

http://sisc-scheme.org/sisc-online.php

(NB: copy and paste doesn’t work on OS X, and apparently it doesn’t work with Firefox 4 on either OS X or Windows. Can anyone confirm?)

This is all good and well, but what if you’re stuck?

 

The answers

Numerous people over the web have posted their take on the SICP exercises. One of the best resources is the Scheme community wiki:

http://community.schemewiki.org/?sicp-solutions

If you trust me enough, you can see my take on the exercises on Github.

 

There it is. You don’t have any excuse not to do the SICP now, you lazy bum.

Happy hacking!

 

Edit: as always, the Hacker News discussion is very insightful.