My favorites | Sign in
Project Home Wiki Issues Source
READ-ONLY: This project has been archived. For more information see this post.
Search
for
MyFirstRailsProject  
*MOVED TO WIKI summary My First Rails Project -- building a blog in 15 minutes
Updated Apr 13, 2008 by medea...@gmail.com

Introduction

We're going to make a super-simple blog. Our end product is going to look like this:

The blog:

And a single blog post:

This whole thing is mostly taken from Akita on Rails' tutorial, except this is designed to be done with Aptana IDE and aimed to be more of a first introduction to Ruby on Rails, as opposed to an intro to Rails 2.0 for former users of Rails 1.2.

Please note I'm simplifying a WHOLE lot here in explanations, trying to aim this for the Padawan level -- feel free to go straight for the original tutorial if you've been playing around with sample Rails projects some already.

Details

Basic Setup

You'll start with some version of these next three steps for any Rails app you ever create.

1. Create new Rails project called "blog"

2. Edit config/database.yml

defaults: &defaults
  adapter: mysql
  encoding: utf8
  username: root
  password: 

development:
  database: blog_development
  <<: *defaults

# Warning: The database defined as 'test' will be erased and
# re-generated from your development database when you run 'rake'.
# Do not set this db to the same as development or production.
test:
  database: blog_test
  <<: *defaults

production:
  database: blog_production
  <<: *defaults

3. In Rake Tasks tab:

  • choose db:create:all from pulldown
  • Go

Designing Our Blog

Ruby is an object-oriented programming language. Even a single integer like 4 or 923 is an object in Ruby. Every action that happens in Ruby is somehow attached to an object. So what this means is, designing a Ruby program is all about figuring out how to break your program up into objects, and what actions should go with which object.

What Rails does that is so amazing is that if you're making a web application (more specifically, a program that stores its data in a database and uses a web browser as its front end) it makes a whole lot of these decisions for us. Normally, you would have to figure out -- okay, how am I going to represent the database? How will I write my stuff to the database? How am I going to set up the front end? What files do I put this code in and how do I organize them? How do I test it? -- etc etc -- a ton of decisions that all take time and mental energy.

Rails takes all of this out of our hands. All we need to worry about is the big picture of our own particular application, and not the generic stuff that happens with every web application. Writing to the database and pumping out of the database to the web has already been mostly set up for us, and so is the organization of the files and the data.

So now we can think about our application. A blog is really just a collection of posts and comments on those posts. So the objects we want to create are posts and comments.

Creating Posts

1. In Generators tab:

  • choose scaffold from pulldown
  • type in: Post title:string body:text
  • Go

2. Open up each of the files created and see what they are:

  • app/models/post.rb: models define a class of objects, in this case a Post object.

  • app/controllers/posts_controller.rb: controllers manage how a user can interact with the object. The methods in this file are the actions you can perform on a post object -- "edit", "create", etc
  • app/views: views define what a user sees (the front end)
    • .html.erb files are html files with Embedded RuBy (ERB)
    • contents can be split up (so you can reuse a lot of code)
    • files in the layouts/ subfolder are the top-level layout for a given object -- notice the yield in the middle of posts.html.erb
    • files in the folder with the same name as the model (in this case posts/) go into the guts of the layout -- where the yield is
  • notice how several of the view files match up with the actions in the controller (index, show, new, edit)! RAILS MAGIC -- when you go to the appropriate page, it automatically goes to the appropriate action in the controller. \o/
  • db/migrate/001_create_posts.rb: this is a database migration that handle creating a table in the database that matches up to our model.
  • Note about database migrations: The super-nice thing about db migrations is that they don't rely on one type of database software. So if we ever want to switch from MySQL to PostgreSQL, for instance, we pretty much don't have to rewrite any code. They also are great because they can be rolled back and forth really easily -- notice the "up" and "down" sections. The "up" section is what you want the migration to do. The "down" section is what the database software would have to do to undo the up section.

6. Now that we've generated all this code, we need to update the database server and make it actually run the migration.

In Rake Tasks tab:
  • choose db:migrate from pulldown
  • Go

7. Try it out.

On the Servers tab:
  • right-click on blogServer and start it
  • right-click and choose Launch Browser
  • in the URL line at the top of the browser tack on "posts"
  • click New post and put in whatever you want and click Create.
  • click Back and you now see the new post.

Destroying All Our Work

Before we move on, here's how to undo creating the Post object:

1. In Rake Tasks, roll the database migration back:

  • db:rollback

2. In Generators, destroy the whole scaffold thing:

  • choose scaffold from the pull-down
  • hit the Destroy radio button (be sure to change it back after!)
  • type in: Post

All the files will vanish and the database will be emptied out.

3. Once you've done this, re-create Post again so you can keep going with this tutorial:

In Generators:
  • choose scaffold and Create radio button
  • type in: Post title:string body:text
  • Go

In Rake tasks:
  • db:migrate
  • Go

Improvements

So at this point, we have a Post object and a scaffold that makes it do all sorts of useful things, if in a kind of ugly and awkward way. Now let's look at ways we can improve this before we move on.

1. Let's get rid of the default front page. Since this is a blog, our front page can just be the list of posts, so let's make the /posts page into our front page. We do that with routes. Routes are how Rails knows what the user wants based on the URL.

  • open config/routes.rb (the comments in here are useful to read)
  • at the very top add:
  • map.root :controller => 'posts'
  • save
  • now delete the file public/index.html (which is that Yay! You're Riding The Rails! page)
  • In the browser, go back to the front page and reload.

2. Try editing the way the post looks.

  • Open up app/views/posts/index.html.erb
  • Edit

    Listing posts

    to be

    My Exciting Blog

    .
  • Save, go back to the browser window, and hit the reload button

Make some changes to the other view files, and then go through the process of creating a post again to see where your changes appear. Notice what the URL looks like up at the top as you go through the process.

3. Now let's keep someone from entering an empty post, or a post with a title that is too long.

  • Open app/models/post.rb
  • To make sure the title and body are not empty, add the line:
  •     validates_presence_of   :title, :body
  • Save and go back to the browser. Try entering an empty post and see what happens.
  • Go back to post.rb
  • To make sure the title is not too long or too short, add this:
  •   TITLE_MIN_LENGTH = 3 
      TITLE_MAX_LENGTH = 30 
      TITLE_RANGE = TITLE_MIN_LENGTH..TITLE_MAX_LENGTH 
      validates_length_of :title, :within => TITLE_RANGE
  • Save and go to the browser. Try entering a title that is just one letter long, or one that is more than 30 characters long. See what happens. Yay!

You can do lots more along these lines. Try looking up some Rails tutorials online -- unfortunately most of them are still geared towards Rails 1.2, but much of it will still work the same.

Adding Comments

1. Now let's add comments. Comments are attached to posts. We're not going to bother with subject lines for these comments.

In Generators tab:

  • choose scaffold from pulldown
  • type in: Comment post:references body:text
  • Go
In Rake Tasks tab:
  • choose db:migrate
  • Go

2. We have to hook up the Comment and Post models.

  • open app/models/comment.rb and add this line at the top:
  • class Comment < ActiveRecord::Base
      belongs_to :post
  • open app/models/post.rb and add this line at the top:
  • class Post < ActiveRecord::Base
      has_many :comments

3. Look at the browser window and go to "localhost:3002/comments" in the URL. Yay! Click on New comment. Uh oh -- how do we figure out what to put in for "Post"? Comments and posts are still not totally hooked up.

4. Next we are going to set up the routes so the comments are nested inside the posts.

  • open config/routes.rb
  • replace these two lines:
  •   map.resources :comments
      map.resources :posts
  • with this one:
  •   map.resources :posts, :has_many => :comments
  • Now the URL http://localhost:3002/posts/1/comments will mean "look for the comments associated with post 1".

5. Now starts the complicated part, sorry! We have to set up the controller for the comments to handle this.

  • Open app/controllers/comments_controller.rb
  • Up at the top right after the "class" line add:
  •   before_filter :load_post  
    
      def load_post
        @post = Post.find(params[:post_id])
      end
  • Do a find and replace (Edit menu/Find and Replace) for the following four things -- make sure to check the Wrap Search button. These changes are necessary because you need a post to attach comments to, and you want to look up comments only for a single post at a time:
Comment.find --> @post.comments.find
Comment.new --> @post.comments.build
redirect_to(@comment) --> redirect_to([@post, @comment])
redirect_to(comments_url) --> redirect_to(post_comments_url(@post))

6. And now we have to change all four of the comment view .erb pages for the same reasons. Below are the new versions -- before you paste them in, compare the two to see the changes.

  • app/views/comments/index.html.erb should now be:
  • <!-- app/views/comments/index.html.erb -->
    <h1>Listing comments</h1>
    
    <table>
      <tr>
        <th>Post</th>
        <th>Body</th>
      </tr>
    
    <% for comment in @comments %>
      <tr>
        <td><%=h comment.post_id %></td>
        <td><%=h comment.body %></td>
        <td><%= link_to 'Show', [@post, comment] %></td>
        <td><%= link_to 'Edit', [:edit, @post, comment] %></td>
        <td><%= link_to 'Destroy', [@post, comment], 
          :confirm => 'Are you sure?', :method => :delete %></td>
      </tr>
    <% end %>
    </table>
    
    <br />
    
    <%= link_to 'New comment', 
      new_post_comment_path(@post) %>
  • app/views/comments/show.html.erb:
  • <!-- app/views/comments/show.html.erb -->
    <p>
      <b>Body:</b>
      <%=h @comment.body %>
    </p>
    
    
    <%= link_to 'Edit', [:edit, @post, @comment] %> |
    <%= link_to 'Back', post_comments_path(@post) %>
  • app/views/comments/new.html.erb:
  • <!-- app/views/comments/new.html.erb -->
    <h1>New comment</h1>
    
    <%= error_messages_for :comment %>
    
    <% form_for([@post, @comment]) do |f| %>
      <p>
        <b>Body</b><br />
        <%= f.text_area :body %>
      </p>
    
      <p>
        <%= f.submit "Create" %>
      </p>
    <% end %>
    
    <%= link_to 'Back', post_comments_path(@post) %>
  • app/views/comments/edit.html.erb:
  • <!-- app/views/comments/edit.html.erb -->
    <h1>Editing comment</h1>
    
    <%= error_messages_for :comment %>
    
    <% form_for([@post, @comment]) do |f| %>
      <p>
        <b>Body</b><br />
        <%= f.text_area :body %>
      </p>
    
      <p>
        <%= f.submit "Update" %>
      </p>
    <% end %>
    
    <%= link_to 'Show', [@post, @comment] %> |
    <%= link_to 'Back', post_comments_path(@post) %>
Try now to go to http://localhost:3002/posts/1/comments and create a new comment. Woo! Take a break after this section; you deserve it.

Partials

Next up on the list is partials. Partials are a great way to avoid having to write the same code over and over again in views. In this case, if you look at new.html.erb and edit.html.erb, you notice they are pretty similar there in the middle. We will now create a partial that the two of them (and other parts of the program) can share.

1. Create the partial file

  • Right-click on the folder app/views/comments and choose New/File... and name the new file _comment.html.erb -- that underscore at the start of the filename means it is a partial.
  • Cut this code out of new.html.erb and paste it into _comment.html.erb instead:
  • <% form_for([@post, @comment]) do |f| %>
      <p>
        <b>Body</b><br />
        <%= f.text_area :body %>
      </p>
    
      <p>
        <%= f.submit "Create" %>
      </p>
    <% end %>

2. Now we are going to call this partial from new.html.erb by replacing the code we just took out with this line:

<%= render :partial => @comment %>
so that new.html.erb should now look like this:
<!-- app/views/comments/new.html.erb -->
<h1>New comment</h1>

<%= error_messages_for :comment %>

<%= render :partial => @comment %>

<%= link_to 'Back', post_comments_path(@post) %>

3. Now the only difference between _comment.html.erb and this same section in edit.html.erb is that in edit, the button is named "Update" instead of "Create". Fortunately, partials can take arguments -- we are going to pass in the button name as an argument to this partial, so new and edit can both use it.

  • Edit _comment.html.erb and replace "Create" with button_name.
  • Edit new.html.erb and replace <%= render :partial => @comment %> with:
  • <%= render :partial => @comment, :locals => { :button_name => "Create" } %>
  • Edit edit.html.rb and replace this whole section of code:
  • <% form_for([@post, @comment]) do |f| %>
      <p>
        <b>Body</b><br />
        <%= f.text_area :body %>
      </p>
    
      <p>
        <%= f.submit "Update" %>
      </p>
    <% end %>
with:
<%= render :partial => @comment, :locals => { :button_name => "Update" } %>

Linking Posts and Comments

We could add a link from a post to a comment by editing app/views/posts/show.html.erb to add this line somewhere (try it and see!):

<%= link_to 'Comments', post_comments_path(@post) %>

But it would be nice if the comments were right on the blog post, not on a separate page.

1. Add the comments listing to the "show" view for posts.

  • open app/views/posts/show.html.erb
  • Make it look like this:
  • <!-- app/views/posts/show.html.erb -->
    <p>
      <b>Title:</b>
      <%=h @post.title %>
    </p>
    
    <p>
      <b>Body:</b>
      <%=h @post.body %>
    </p>
    
    <!-- list the existing posts -->
    <% unless @post.comments.empty? %>
      <h3>Comments</h3>
      <% @post.comments.each do |comment| %>
      <p><%=h comment.body %></p>
      <% end %>
    <% end %>
    
    <!-- allow creating a new comment right here -->
    <h3>New Comment</h3>
    <%= render :partial => @comment = Comment.new, :locals => { :button_name => 'Create'}%>
    
    <%= link_to 'Edit', edit_post_path(@post) %> |
    <%= link_to 'Back', posts_path %>

2. Notice we are using our partial in there to create a new comment! Except we are doing something new:

<%= render :partial => @comment = Comment.new, :locals => { :button_name => 'Create'}%>
This is a bit of Ruby that can be confusing. When you see variables with a single @ symbol, they are class instance variables. (Try googling for "ruby class instance variables" and you will find many useful explanations -- see which one helps you understand.) What you want to know right here though, is that when you set a class instance variable in one location, that variable stays available through other parts of the program. In a view, a lot of times when you see a @class-instance-variable, it has been set inside the controller.
If you look at the comments_controller.rb file, that load_post function we wrote a while earlier is setting the @post variable for all the other functions. If you look at the show function there, you can see it is setting the @comment variable there. That is why you can use @post and @comment in the comments/show.html.erb file. In the new function, a new @comment variable is created -- that is why you can call the _comment.html.erb partial and pass in the @comment.
Here, on the other hand, this view is the posts/show.html.erb view. That means it is being called by the posts_controller.rb functions instead. And the posts controller doesn't have an @comment object being created or set anywhere. So we have to manually create a new comment object before we can use the partial.
Don't worry about this too much if you don't understand -- just mimic for now and understanding will gradually come. :)

3. Finally, if you now try and create a new comment, you end up not at the post but at the list of comments again. Let's change that. We do this in the comments controller again.

  • Open app/controllers/comments_controller.rb
  • Look in the create and update functions. You will see this bit of code:
  • redirect_to([@post, @comment])
  • This line redirects the user to a new view when the work of the function is done. That version currently is redirecting the user to the comments/index view. Instead we want to go to just the posts/show view for this post. Change the line to:
  • redirect_to(@post)

Improvements Round Two

At this point, you have had an intro to some basic Rails. Try and see if you can't improve the appearance and functionality of this blog some more. Ideas:

  • Make the comment body field smaller.
  • Add a way to delete comments from right on the blog post page. (hint: look at the Show Edit Destroy links on the posts/index view).
  • Play around with the formatting.

You can also try continuing on with the rest of the original tutorial here:

http://www.akitaonrails.com/2007/12/12/rolling-with-rails-2-0-the-first-full-tutorial

Search for the "Namespaced Routes" header and you can pretty much go on from there. Don't hesitate to ask questions in the chatroom!

Comment by noliving...@gmail.com, Feb 10, 2008

Step 5 in Adding Comments - it's a little hard to parse as-is. You might want to put each on its own line:

Comment.find --> @post.comments.find Comment.new --> @post.comments.build redirect_to(@comment) --> redirect_to([@post, @comment]) redirect_to(comments_url) --> redirect_to(post_comments_url(@post))

Comment by sarkymoo...@gmail.com, Feb 10, 2008

Step 2 in Partials - there appears to be a missing % in the line to be pasted into new.html.erb. It's correct in the example for the whole file given immediately afterwards. It should be <%= render :partial => @comment %>

Comment by project member shal...@gmail.com, Feb 10, 2008

Both done, thanks! :D

Comment by afunamat...@gmail.com, Feb 12, 2008

I found the built-in diff/history function of Aptana useful in comparing the old version of the code (generated by rails) with the new version (from the wiki). What I did was the following:

1. Paste the code from the wiki into the relevant file and save the changes 2. Right click on the filename (from the explorer on the left) 3. Select Compare with>>Local History

That showed a side-by-side colorized comparison of the code from the wiki and the original code; I found it easier than flipping back and forth between the browser and Aptana.

Powered by Google Project Hosting