July 13th, 2007
I went to look at this famous rails tutorial by DHH, as the creator of Rails is often referred to, in which he debuts Rails by showing how to make a blog app in under 15 minutes. Watching the video (which was actually very awkward because the quicktime controls were outside of my screen area, so i couldn’t pause it), i was struck by how much i prefer tutorials when they’re written down (though having a movie as well is useful). So, i decided to do a written version of his tutorial, with extra explanation, mainly for my own benefit, even though it’s written as if i’m instructing someone. Here goes. By the way, i’m using windows, while DHH uses a mac in the demo. Rails is pretty much platform-independent but i did find a few minor differences so if you’re on windows then this tutorial may be well suited to you.
MASSIVE DISCLAIMER - I’m a noob myself so some of this so-called teaching may be wrong, or at least not properly explained. If you see any errors please let me know!
Before we start, i’m assuming that you have at least a little (like me!) programming experience in some language, and that you have a basic (like me!) knowledge of ruby. If you don’t know (for example) how a ruby hash or a ruby for loop works then go and read the ruby page on wikipedia, it’s a nice quick introduction. That reminds me - Ruby and Rails are both meant to be capitalised but i’m lazy so will tend not to bother. Just in case anyone gets bothered by this sort of thing, i apologise but i’m not actually sorry.
First, install ruby and rails, and mysql, and a web server. The guys from Onlamp explain how to do this pretty well here. I used their recommendation of InstantRails which does pretty much everything you’ll need except perhaps a mysql front end.
So, assuming you’re all set up with the above (you might need to add the ruby/bin folder to your PATH environment variable if you’re using windows), open a command window in your rails working folder (eg InstantRails/rails_apps in my case) and type
rails blog
This creates a framework structure for a rails app called blog. You should see a load of stuff get made. Next, go to the new blog folder (in a new command window) and start your web server. I do this with
mongrel_rails start
Now, your web server should be up and running. Even though we’ve not created a database yet we can still go and see our rails app running, at http://localhost:3000/ in my case (your web server should tell you at what local address it’s running). We see a screen saying “Welcome to rails!” and some other standard stuff that shows us it’s working.
Next, we generate a controller class, called Blog. Do this by typing (at the command line)
ruby script/generate controller Blog
This will make a bunch of new files - it should list them, along with some others that weren’t created because they already existed. Go and open one of them, blog\app\controllers\blog_controller.rb. It contains a single empty class, BlogController, which extends ApplicationController.
Let’s add a new method, “index”. Index is the first page called by any website, and an index method acts as a replacement for the page, which doesn’t exist yet - it will be called when we browse the site.
The method is simply “hello world” - add the following inside the class:
def index
render :text => “Hello world!”
end
Some explanation: “render” is a rails method for rendering web pages. :text is a symbol, which is like a fixed variable name, actually stored as a number (under the hood, though we can view the number as well) for efficiency. Symbols are used in a similar way to variable names, and are denoted by the colon prefix. They have properties of a string as well as the hidden number - printing them simply prints the name minus the colon for example. They’re commonly used as hash keys, and we don’t put them in quotes when we use them. There are various ‘flavours’ of render - for example, we can render a saved html file with render :file => “file path /name”. Some more on render here.
The use of => in this case refers to the creation of a hash pair, which we pass to the method: the hash key is :text and the value is “Hello World!”. In this case, we’re calling the render method, which can take a hash. It then checks the hash for the existence of a specific key or keys, like :text for example, and if they exist then it gets the value associated with the key and does something with it - in this case print to the browser. The above code would be a little more obvious if the {} around the hash and the () around the method arguments weren’t omitted (like a lot of things in ruby, the syntax is optional). In other words, the line could have been written as
render( {:text => “Hello world!”} ) #passing a hash with one k-v pair as an argument
This strategy, of having a single method that takes a hash and analyses its contents to decide what to do, seems to be the ruby alternative to having lots of overloaded methods that take different arguments, as in Java or C. I guess that because of ruby’s dynamic typing, it’s hard to differentiate methods based on arguments. BTW, the hash must be the last parameter in the list, to allow an arbitrary (including zero) number of key-value pairs to be passed to the method and treated as belonging to the same hash.
Anyway, let’s get on with it. Save blog_controller.rb, go back to the browser and go to http://localhost:3000/blog or your equivalent. The text “Hello world!” should now be there.
As DHH explains, having the controller print to screen isn’t really the right way to do things - rails uses the model-view-controller pattern, and anything to do with displaying should be done in the “view” section of the app. You probably noticed how rails created controllers, models, and views folders in the app folder (the app folder is where we do our work). Remove the hello world line from blog_controller.rb (leave the empty index action for now) Go to the views folder. Rails uses a special kind of file to generate html for the browser to read, called rhtml files. These are basically html in which we can embed chunks of ruby code inside <%= %> brackets: when rails runs the rhtml files are used to make html, with the ruby blocks filled in with the results of the ruby code.
Make a file called index.rhtml in the app/views/blog folder - this is a template. Write “Hello from the template!”, save it, then go back to the browser and refresh. Instead of the ‘index’ action, the browser is now loading the index page (we didn’t have one before), and we see our message. We can go back and delete our index action completely now - the page doesn’t need the action to work.
Now let’s get serious - let’s get some database action in here. If you look in blog\config\database.yml you can see that rails has already set up the config for connecting to a DB called blog_development (we’re just worrying about the dev version of our software for now), on a DB server. I’m using mysql like in the onlamp setup instructions, you should be able to change the details for your own DB if it’s different (enter your username and password for example, if you changed your user name or don’t just have a blank password). But, we don’t have a DB called blog_development yet, so let’s make one.
DHH does this by hand, using a DB front end, in onlamp they use sql script to create the DB and also to add the tables, columns etc. Doing it by hand might be better to understand what’s being done, if you’re not confident in SQL then you’ll need a DB front end for this. If you’re using mysql then HeidiSQL is a decent free front end. Anyway, make a new database called blog_development.
So - the purpose of the DB is to model and store our domain entities, which we’ll mirror with our software classes. The first entity we’re going to model is a post, which consists of a title, some text, and a date. Ultimately we’re going to have a class in our models folder called Post, and one of the many cool things about rails is that if we name things right, then our ruby classes automatically map onto the database structure. The way we name things right is to have the name of a database table being the plural of the class it represents. So, in this case we should call our table “posts”. (Rails is pretty clever with the pluralisation - for example it knows that ‘people’ is the plural of ‘person’. We can override this automatic mapping if we want to , by explicitly saying “this class maps to this table”. Though i’m not sure how to do this.)
(POSTSCRIPT - i’ve since learned about overriding the rails defaults for table and field names, etc. For example if we wanted our Post class to map to a table called blogposts, which had a primary key called bp_id we could have, at the top of the class definition
class Post <>My wonderful weblog
Scaffolding was also placed in the view files, making some default display code for us. Now we’re going to chop some of the scaffold-created stuff out and replace it with our own html and ruby code. First, chop out the first table row section, which is the column titles. Have a look. Next, we make each post title a header (h2), that links to the post. After
<% for post in @posts %>
add
<%= link_to post.title, :action => ’show’, :id => post %>
Let’s try and break this ruby code (in red) down a bit:
for post in @posts #for every object (called ‘post) in the instance variable @posts (which points to an array of post objects, pointing into the DB), do the following:
link_to post.title, :action => ’show’, :id => post #equivalent to
link_to(post.title, {:action => ’show’, :id => post} ) #create a link that is represented by the post title, calls the action called ’show’ when clicked, and passes the ID of the post to the DB to retrieve the body.
Note that in the first ruby block, there’s no =. This is because we only need <%= on lines that return something. Purely structural code like a for expression only needs <%. Have another look in the browser. Next, show the post body beneath each title: add this line before the closing div tag:
<%= post.body %>
It’s easy enough to see that this is literally just displaying the body of the current post (as we cycle through the full list of posts). We can also see that the show/edit/destroy links are now cut off from the posts they correspond to, so let’s get them back in the same div element as the new title and body. (Actually we only need edit and destroy, as we show a post by clicking on the title now, which is more intuitive anyway). We put edit and destroy in their own paragraph, in small text, with the time of creation as well. In addition, since we’ve now got a big continuous list of posts, there’s no need to split them onto pages, so we can delete the previous page/next page lines.
After doing all that stuff, list.rhtml should look like this:
My wonderful weblog
<% for post in @posts %>
<%= link_to post.title, :action => ’show’, :id => post %>
<%= post.body %>
<%= post.created_at.to_s %>
<%= link_to ‘Edit’, :action => ‘edit’, :id => post %>
<%= link_to ‘Destroy’, { :action => ‘destroy’, :id => post }, :confirm => ‘Are you sure?’, :method => :post %>
<% end %>
<%= link_to ‘New post’, :action => ‘new’ %>
Note - i had one problem with DHH’s code: the line where we convert the timedate object into a string, (<%= post.created_at.to_s %>) he was passing it the symbol (:long). This broke it for me, i had to take out long and just call the to_s method with no parameters. Then it was fine. I don’t know what passing :long to the to_s method is supposed to do. My code shown above has the (:long) taken out. If anyone can explain this please add a comment.
Next, DHH shows how to list the posts in reverse order, ie most recent first: simply go to the for block in list.rhtml and change posts to posts.reverse. This demonstrates (i guess) that when we get the rows out of the DB they come into ruby as array elements, where each element is another array (or maybe a hash?).
More polishing: at the moment our body is just plain text: no italics or bold. We add this functionality in via the method textilize, which uses the same rules as the common markup language textile, in which we show bold with *bold text* and italics with _italicised text_. textilize just takes any block of text, which is what we have. Note that this is happening in the view. The database doesn’t know anything about bold or italic text - the view (in list.rhtml in this case) is translating the *s into bold markups. So, where we have post.body at the moment, we pass this as an argument to textilize:
<%= textilize(post.body) %>
Having said this, rails can’t find the textilize method when i try it: after a quick look on the net, i found that it needs to be added via a ruby gem before use (it’s part of the redcloth gem, which is kind of what packages are called in Ruby). So, if this doesn’t work for you either, just change it back to how it was before. That’s what i did (gems ain’t the focus of this tutorial).
(POSTSCRIPT - It’s actually very easy to add this gem: Just type “gem install redcloth” at the command line and then restart the web server. I have had some problems with other gems, such as rmagick, though, whose windows versions are a bit complex/broken)
Next, we’re going to look at partials, which are blocks of code that can be reused by different files. Open blog_controller.rb. We’re going to change the list action to just find all posts, instead of trying to seperate them into ten per page or whatever:
def list
@posts = Post.find(:all)
end
Then, go back to list.rhtml. Cut out all the contents of the for block, and put them into a new file, called _post.rhtml. This partial is going to be our ‘post displaying’ code. Make sure the name has the underscore, as this is used to denote partials. Now, _post.rhtml is a partial that represents the display for a single post. We can now use this in various places to make sure that a) we have a consistent style and b) we don’t repeat ourselves. “Don’t Repeat Yourself”, or “DRY”, is a good design pattern for any language and is a ruby and rails mantra.
The first place we need to use it is the place we just chopped it out of! Go back to list.rhtml and remove what’s left of the for loop completely. Instead, we have a single call to the render method. As with our “hello world” code earlier we pass render a hash, but in this case the hash has two elements: one has the key :partial, and points to the partial in question, simply called ‘post’ (it knows to add the underscore when looking for a partial), and the other has the key :collection, which points to @posts.reverse. So we’re saying “do the partial for every item in that collection”.
So, list.rhtml now looks like this:
My wonderful weblog
<%= render :partial => “post”, :collection => @posts.reverse %>
<%= link_to ‘New post’, :action => ‘new’ %>
Now refresh the list page - it should look exactly the same. Of course, partials are only worth doing if we use them in more than one place, so lets do that now. In the browser, click on a item title to go to the ’show’ page. It still has the old default looking style: we’re going to fix it to use the partial as well. Go to show.rhtml, delete the for block from there and replace it with a line that’s almost the same as the one we used to replace the for block in list.rhtml. The only difference is that instead of displaying a whole collection of posts, we just want to show one, the current one, ie @post. (we don’t reverse it because it’s a single post - reversing it makes no sense). So we change :collection to :object, and @posts to @post -
<%= render :partial => “post”, :object => @post %>
Now refresh the show page in the browser - it should have the new template.
Next - adding comments. Comments are a whole new type of object, so we’re going to have to change the model and the DB. Let’s do the model first. At the command line, in the main blog folder, type
ruby script/generate model Comment
Among other things, that will have created a new model file, comment.rb. (this will map automatically onto a table called “comments” in the DB but we’ll get to that in a bit). Open comment.rb.
Next we model the relationship between our classes: a comment belongs to a particular post instance, and we inform rails of this by writing
belongs_to :post
inside the Comment class definition. This relationship is two way - a post can have many comments. So, we need to modify the Post class definition as well, with
has_many :comments
Some nice self documenting code there. Ok, now we need something to work with - let’s go to the DB and make a new table called comments. Like with posts, make the primary key field called ‘id’, set up the same way as before. Next, add a field called body, of type text. The other component is something to link a comment to a post - a foreign key in other words, a reference to an id from the posts table. As you might expect, rails has a convention for foreign key names - it’s foreigntable_id, or “post_id” in this case. If we follow this convention we don’t even need to link the tables together - rails knows what we mean.
At the moment, we don’t have an interface to add comments, so we’re going to create one in the database to use as an example/test. So, go to the data page of the database (if you’re using heidisql you might need to refresh the table view first) and write some entries into the fields: use an id of 1 and make sure you use a post_id that exists in the posts table. Swap between table and data view to make sure your data got saved ok.
Ok, now we’re going to work out how to display the comments. The main (perhaps only) place for comments to appear is on the ’show’ page, so let’s look at show.rhtml. After the post, and the edit/back links, we’re going to list all the comments. This is another for loop, along the lines of “for every comment, show the comment”. Because of the conventions we followed, rails lets us get at all the comments belonging to a post simply by writing @post.comments. We then make a for loop out of this as follows, showing the comment body for each post, inside the loop.
<% for comment in @post.comments %>
<%= comment.body %>
<% end %>
That’s worked, but the comment is simply listed on the same row as edit/back, which looks bad. So let’s add just a little html formatting: a “comments” heading before we list the comments, and a line break (
) after every comment. So, show.rhtml now looks like this:
<%= render :partial => “post”, :object => @post %>
<%= link_to ‘Edit’, :action => ‘edit’, :id => @post %> |
<%= link_to ‘Back’, :action => ‘list’ %>
Comments
<% for comment in @post.comments %>
<%= comment.body %>
<% end %>
Next, lets let the user add comments. We’re going to do this with some form components that we write straight into show.rhtml. The code we’re going to write is pretty much the same as already exists in _form.rhtml and edit.rhtml, in the views/blog folder. We call form_tag with a hash consisting of the key “:action” and the value “comment”, and the key “:id” and the value @post. In other words, when we submit, we’re making a new comment, and also passing through the id of the post we’re looking at when we write the comment, so the DB knows which post we’re commenting on.
Then we’ve just got a text area which goes into body and a submit button. Finally we close the form with an html end tag. So, the following goes at the bottom of show.rhtml:
<%= form_tag :action => “comment”, :id => @post %>
<%= text_area “comment”, “body” %>
<%= submit_tag “Comment!” %>
OK, refresh the show page in the browser, and you should see a new text box and a submit button, labelled “Comment!”. Enter a comment and hit the “Comment!” button. Whoops! (sorry, couldn’t resist it). Rails doesn’t yet know what to do with this thing we just created, and we get an error message.
To fix this, we need to make an action called “comment”. This makes sense considering above that we told the form tag that we were calling an :action called “comment”. So, we do this in the controller. Remember the rules of MVC - the model is our ‘business logic”, ie what holds what, what connects to what, etc. The view handles everything to do with displaying the data to the user. The controller handles user actions, and making a new comment is a user action (the interaction happens, ie the form is displayed, in the view, but as soon as the user submits, it’s the controller’s job to handle the action).
(Postscript - i’ve since discovered the ’skinny controller/fat model’ principle, which says that as much functionality as possible that relates to an object should be moved out of the controller into the model class for that object. The controller, then, should really just act like a manager - telling things what to do but letting them actually do the work. Under this principle, the below code is actually fine, since we’re doing about as little as we could possibly do anyway. But if the process of creating a new comment was any more complicated, then we should really call a method in the Comment class, from the controller, to do it, then, again back in the controller, decide what to do next, eg redirect to the show page for the commented-upon article. So remember - the controller’s just a manager, and it should be a lazy one at that.)
So, open blog_controller.rb. This is perhaps the most complicated bit of ruby coding we’ve done yet, so i’ll go through step by step. If you see any errors in my explanation please let me know.
Before we start, a brief diversion to talk about parameter passing in rails, and ‘params’. ‘params’ is the default way of passing through parameters to a method, and it’s a hash. That means it contains hash keys, which as we’ve seen consist of a key and a value. Usually, the key is a symbol, ie starts with a colon. We can pass any (and as many) keys we want through, but the method is normally going to be look for some specific key or keys, denoted by name, which is a symbol like i said. When we pass the key through we don’t have to explicitly put it in params, but we need to look in params for it at the other end, ie inside the method, which we do in the standard square-bracketed hash-accessing way.
So, for example, if we wanted to write a method called ‘hello’ which takes a name and returns “hello name!”, then the typical rails way would be
def hello
#in ruby the last evaluated statement is returned by default
#so we can omit ‘return’ and just have the statement
“hello #{params[:name]}!”
end
Now, the person using this method needs to know to use the symbol “:name”. If they try to call it with hello(”max”) then it won’t work. So, the name of the symbol presents a bit of inflexibility and requires a bit of knowledge of the method. Generally, though, naming conventions make it easy to remember the names of the symbols and in lots of other ways it’s great: for example, the order in which you pass the parameters in a given hash is not important - a big plus. Anyway, to call the hello method we would say
hello :name => “max”
BTW, in case you were wondering, #{} is a ruby mechanism: the contents of the brackets is evaluated and then converted to a string. They’re commonly used inside a string as an alternative to the
puts “blah ” + var1.to_s + ” blah blah ” + var2.to_s
javaesque way of doing things - we’d instead write the above as
puts “blah #{var1} blah blah #{var2}”
Anyway, that’s enough diversions into ruby. Let’s get on with our rails app!
In BlogController.rb, make a new action called comment. The first line inside the action is this:
Post.find(params[:id]).comments.create(params[:comment])
Breakdown:
Post.find(params[:id])
This is going to use the class (ie static) method find, using the :id passed through from the form (remember the second key in the hash?).
.comments
Then (all on the same line), we take that post and access the comments array associated with it.
.create(params[:comment])
In that array, we create a new comment, passing through :comment (which is the comment we just created). This is a lot of work for one line - in this respect Ruby can be a bit scary at first, like your first driving lesson.
The next line stores (not sends!) a message to the user that their comment worked.
flash[:notice] = “Added your comment.”
‘flash’ is a temporary store in rails. We can put stuff in there, and if it’s not used before anything else happens, it’s deleted. The next time the view displays anything it will check if there’s anything in the notice field and if there is, display it. This is an example of controller-view separation: the controller doesn’t directly display anything: instead it’s stuck a postit on the view’s desk.
Next line:
redirect_to :action => “show”, :id => params[:id] #equivalent to
redirect_to ( { :action => “show”, :id => params[:id] } )
By now we should be getting used to this hash-passing syntax. Without being familiar with the code of the redirect_to method, we can imagine a general logic of:
redirect to what? An action.
Which one? “show”
Do you want to pass anything along to that? yes please, the :id, so we know which post to show.
And a value for that please? the :id of the post we’re currently looking at, that was passed through to us in params from the thing that called this method.
In other words, “call show for the current post”.
(POSTSCRIPT - just an aside, but the url in a rails app is kind of a translation of the redirect_to action: after the main domain name (or localhost:3000 or whatever), we have the name of the controller we want, then the name of the action, then the id to pass to the action. so
localhost:3000/blog/show/4
is equivalent to
redirect_to :controller => “blog”, :action => “show”, :id => 4
(if we don’t specify a controller, btw, it defaults to the current controller)
That means that your choice of controller for any action will affect the url that the user sees - so if you want to have a nice bunch of intuitive urls, you might need to think about reorganising your actions between your controllers, or making a new controller for some actions, or maybe just renaming your controllers or your actions. At the very least, think about your controller and action names before you name them - they’ll show up in your url, so don’t call them foo and bar! Having said all that, there might be a way to change the url that is displayed to be something other than the controller/action names. Please comment if you know any more about this!)
Here’s the complete comment action for blog_controller.rb
def comment
Post.find(params[:id]).comments.create(params[:comment])
flash[:notice] = ‘Added your comment.”
redirect_to :action => “show”, :id => params[:id]
end
Once you’ve added those (and saved), refresh the browser and add some comments. If you get an error message about it missing an ID, go back to the list screen, select the post again, then add the comments. It should work now.
That’s our blog app! Nice work.
In the video, DHH goes on to talk about unit testing and the rails ‘console’, but if you want more practise building stuff in rails, try finishing the onlamp tutorial i referred to earlier: Rolling with Ruby on Rails Revisited. Bill Walton and Curt Hibbs’ app is a little more complicated, but the ruby code should make more sense after working through the one we just did. If you want an introduction to unit testing in Rails (and you should, since there’s no compiler safety net in Ruby, and unit testing is definitely becoming the ‘way to code’) then watch the rest of the movie, which briefly introduces rails testing (as you might be expecting by now, rails sets up a load of tests for you automatically). If you want to really learn about tests though, look at some of the links at the bottom of this post.
OK, that’s it for this tutorial, i hope it’s been as educational reading this as it was for me to write it. Please mail me with any errors or suggestions. Thanks for reading!
FROM ::
http://nubyonrails.wordpress.com/2007/07/13/david-heinemeier-hanssons-weblog-rails-tutorial-step-by-step/
No comments:
Post a Comment