Rewriting Reddit in Rails in less than 20 minutes
I recently watched Sven Van Caekenberghe's excellent video on how to write a web app like Reddit in LISP in 20 minutes.
I'd like to demonstrate here that you can do the same thing in Ruby on Rails in a lot friendlier, lot more readable way.
So, in this little tutorial, we will write an app which allows you to
I'd like to demonstrate here that you can do the same thing in Ruby on Rails in a lot friendlier, lot more readable way.
So, in this little tutorial, we will write an app which allows you to
- submit links
- view links sorted by points voted or by date/time of submission ("hot" and "new")
- vote links up or down (with AJAX, to avoid reloading the whole page)
We don't add advanced features like user accounts, or checking that users should not vote on a link more than once, because that would be out of the limits of this little tutorial.
Here's a screenshot of what we gonna do:

The basics
I won't go into details about the basics of Rails, because there are a good number of tutorials for that out there, so I'll cover the basics only in a nutshell.
- Install Rails
- Install a database server
- Start a new Rails app
- Set up database connection
- Generate a migration file
- Modify it this way:

- Run the migration
- Generate scaffold for Link
- Start the server
- Go to http://localhost:3000/links and play around with adding a few links. Note that Rails populates the created_at field automatically.
When you are finished, let's start the real development. We will leave the generated scaffolding mostly alone, because I think that's useful as an admin interface.
Defining the actions that will respond to the requests
Go to links_controller.rb and add the following two methods:

The submissions controller shows the list of links. Either "hot" or "new" links, both are handled by the same controller, as they are only different in ordering and in header text. In the first line, we extract requested ordering from the request parameters and default it to 'hot' if not supplied.Defining the actions that will respond to the requests
Go to links_controller.rb and add the following two methods:

Then we create the appropriate SQL snippet for ordering, it's worhty to note that we are using case-when as an expression, instead of as a statement, LISP style.
Then we call the Rails paginator class that pulls the data from our database into the @links instance variable, each by pages of 20.
In the next line we set the header text of our page and also save it in an instance variable.
The modify_points method handles ranking links up and down. This method expects two request parameters. An "id" parameter for the id of the link, and the amount of points to assign in the "by". We enforce the "by" parameter to be either "1", "+1" or "-1" by a regex to protect against dirty tricks, and then update the points in the database.
This is an AJAX method, therefore this method does not render a view, but sends the updated number of points back to the original (submissions) view by a render_text call.
Defining the view
Add submissions.rhtml to the link view with the following content:

In the first line, we are including the default JavaScript libraries that let us use AJAX for updating the points. Then we display the header text we set in the controller class, and add the three links for showing records ordered either by points or the date of submission, and a link fo submitting a new link. All three links point to methods defined in our controller class, the first both of the also supplying the "order" parameter required by our submissions method.

Here we are looping through the list of link objects (records) supplied by the controller class in the @links variable. For each link, we generate a table row. The first two cell contain the links for voting up or down.
The link_to_remote method called here is actually the AJAX call. What we actually do is to run the modify_points method of the controller class, supply the parameters it expects, and remotely update a part of our page without reloading it by the text rendered by our method. What we are updating is the element in the page DOM whose identifier is "link" + the id of the link, such as "link1" or "link567". This element is defined later.

And here comes our link. We also extract the domain of the link with a regex and print it in parens, just like Reddit.
The other half of the AJAX trick is in the next line. What we do is we define a span element, and give it the identifier "link" + the id of the link. This span contains the number of points - this is the DOM element what our link_to_remote will update.
In the next line, the helpful time_ago_in_words Rails method prints how long ago the link was submitted in a user-friendly way, and then we only set the previous/next paging links and we are done.
Finishing touches
We only need a few touches to finish our app. Go to _form.rhtml and delete the four lines containing "points" and "created_at" - we don't want our users to be able to edit it. Go to new.rhtml and change the "Back" link to link to "submissions" instead of "list". Go to the controller and change the redirect_to in the create controller to redirect to "submissions" instead of "list".
And now you are done - go to http://localhost:3000/links/submissions and try out your shining new Reddit!
Download the source
(Thanks to Box.net!)
Any questions?

22 Comments:
More readable? Hardly.
If readability and maintainability is your goal, use Python.
Rails magic does all the work here, so there's not much code.
That makes the code simple to read, but at the same time it makes it difficult to understand. Where are these variables comming from?
Perhaps one of the Python frameworks like TurboGears or Django would be easier to understand.
A robot said : "More readable? Hardly."
it's definitely readable if you have a basic understanding of ruby and rails, and because ruby does all the work, all you need is a very basic idea and to implement it becomes almost trivial. that's the point of ruby on rails.
Great tutorial.
I was just thinking of a similar system earlier today.
Actually, a similar system, crispynews, is written in rails.
You may be interested in checking it out.
crispynews
We enforce the "by" parameter to be either "1", "+1" or "-1" by a regex to protect against dirty tricks, and then update the points in the database.
Not exactly. That's fine until someone clever uses "by=10000". "10000" matches /[+|-]?1/. You'd need to use "^" and "$" to make sure that it contains *only* "1", "+1", or "-1".
Also, you either want "[+-]" or "(+|-)", not "[+|-]". ("[]" is a character class, not a grouping structure. In other words, /^[+|-]?1$/ will match "|1", which you probably don't want.)
Other than that, nice work.
This is great, but you've missed the whole point of using Lisp in the first place.
The whole point of using Lisp in the first place? You mean, other than pretentiousness?
Eric B said...
The whole point of using Lisp in the first place? You mean, other than pretentiousness?
Isn't that the reason for using rails?
Having never read Ruby code before today, I can say I'm impressed.
However, I have to wonder if it is powerful enough to do everything that reddit really does without becoming an ungodly mess.
ASP/VBScript was pretty easy too, but it can quickly get out of hand.
Jonathan Allen
Some people program with rocks. Others use a hammer. I prefer a contractors'-grade pneumatic nail gun with a belt-fed magazine. Have fun "becoming one with the metal", I've got real work to do.
"This is great, but you've missed the whole point of using Lisp in the first place."
AFAIK the whole point of using LISP is creating sublanguages - and Rails is also a kinda sublanguage.
While readability is subjective, lines of code are not, your example takes over 300 LoC versus 100 for the Lisp version.
How did you count that 300? It's less than 50 lines of pure Ruby in the migration and in the controller, and a few other in the view. You should not count the HTML in, neither the generated code like the scaffolding or the model. It could be as short as LISP if it was using generated HTML instead of HTML templating, but that's a wrong idea, so the HTML does not count in the game.
I counted non-comment lines in *.rb. True a lot of it is auto generated, nevertheless it hampers flexibility (i.e. things have to be done a certain way) and maintainability.
Including tests was perhaps undue, but on the other hand the list version has all the presentation (accounting for nearly a third of the code) bundled into those 100 lines.
Now it's my turn to ask how did you count 50? Just the controller and migration code amounts to nearly 90.
I counted only that I wrote by hand - the ones you can see in the pictures. I did not count the autogenerated scaffolding in the controller.
I think lines of code is misleading, it does not completely reflect the amount of work needed.
Besides, while I agree that code generation is bad, it's actually just structure generation, not logic generation.
There is a problem with Pagination in this example. When I am visiting "new" page and try to go to "previous pages", this code jumps to "hot" because it doesn't keep the state.
I had to break the submission method to two to fix this.
If I am suggest: you should use some pagination plugin other than the rails paginator (it's being dropped in Edge Rails because it's slow).
I wholeheartedly recommend the will_paginate
Plus it makes for much nicer code, to.
This post has been removed by the author.
This post has been removed by the author.
Can you reup the codes?
The link got deleted
Post a Comment
Links to this post:
Create a Link
<< Home