Chapter 15 - RESTful blogs
Introduction
RailsSpace has come a long way since we completed the login and authentication system in Chapter 7. We've added full-text search; browsing by age, sex, and location; a double- blind email interface; and customizable user profiles with avatars and friends lists. In this chapter and the next, we'll add one final feature: a simple weblog, or blog, for each of our users. Like its more full-featured cousins (such as the Rails Typo and Mephisto projects), the blog engine developed in this chapter will allow users to create, manage, and publish blog posts. In Chapter 16, we'll extend the blog engine by adding comments (with a healthy dose of Ajax).
We're going to build RailsSpace blogs using a development style called REST, which is a source of considerable excitement in the Rails community. REST support is new as of Rails 1.2, and it represents the cutting edge of Rails development. Since REST represents a marked break from traditional ways of structuring web applications, we begin this chapter with a general introduction to its core principles (Section 15.1).
REST deals with Big Ideas, so discussions about REST are often highly abstract; though we may get a bit theoretical at times, we'll focus on practical examples, with the goal of explaining what REST means for us as Rails programmers. As the chapter unfolds, our examples will become progressively more concrete, leading ultimately to a fully RESTful implementation of blogs and blog posts. (Chapter 16 continues the theme by making the blog comments RESTful as well.) As you gain more experience with the details of REST, we suggest occasionally referring back to Section 15.1 to see how the individual pieces fit into the big picture.
Table of Contents
- 15.1 We deserve a REST today 438
- 15.1.1 REST and CRUD 439
- 15.1.2 URL modifiers 441
- 15.1.3 An elephant;in the room 442
- 15.1.4 Responding to formats and a free API 444
- 15.2 Scaffolds for a RESTful blog 445
- 15.2.1 The first RESTful resource 445
- 15.2.2 Blog posts 447
- 15.2.3 The Posts controller 450
- 15.3 Building the real blog 454
- 15.3.1 Connecting the models 454
- 15.3.2 Blog and post routing 455
- 15.3.3 Posts controller, for real 456
- 15.3.4 Blog management 459
- 15.3.5 Creating posts 461
- 15.3.6 Showing posts 463
- 15.3.7 Editing posts 467
- 15.3.8 Publishing posts 468
- 15.3.9 One final niggling detail 471
- 15.4 RESTful testing 473
- 15.4.1 Default REST functional tests 474
- 15.4.2 Two custom tests 476
Source Code
- Listing 15.1 config/routes.rb
- Listing 15.2 config/routes.rb
- Listing 15.3 db/migrate/008_create_blogs.rb
- Listing 15.4 app/models/user.rb
- Listing 15.5 app/models/blog.rb
- Listing 15.6 config/routes.rb
- Listing 15.7 db/migrate/009_create_posts.rb
- Listing 15.8 app/controllers/posts_controller.rb
- Listing 15.9 app/models/post.rb
- Listing 15.10 app/models/blog.rb
- Listing 15.11 config/routes.rb
- Listing 15.11.5 app/controllers/posts_controller.rb
- Listing 15.12 app/views/user/index.rhtml
- Listing 15.13 app/controllers/user_controller.rb
- Listing 15.14 app/views/posts/index.rhtml
- Listing 15.15 app/views/posts/new.rhtml
- Listing 15.16 app/views/posts/_form.rhtml
- Listing 15.17 public/stylesheets/profile.css
- Listing 15.18 app/views/posts/show.rhtml
- Listing 15.19 app/views/posts/_post.rhtml
- Listing 15.20 app/views/posts/edit.rhtml
- Listing 15.21 app/controllers/application.rb
- Listing 15.22 app/controllers/profile_controller.rb
- Listing 15.23 app/controllers/user_controller.rb
- Listing 15.24 app/views/profile/_blog.rhtml
- Listing 15.25 app/views/profile/show.rhtml
- Listing 15.26 app/views/user/index.rhtml
- Listing 15.27 app/controllers/posts_controller.rb
- Listing 15.28 app/models/post.rb
- Listing 15.29 test/functional/posts_controller_test.rb
- Listing 15.30 test/fixtures/posts.yml
- Listing 15.31 test/fixtures/blogs.yml
- Listing 15.32 test/functional/posts_controller_test.rb
- Listing 15.33 test/functional/posts_controller_test.rb
- Listing 15.34 test/functional/posts_controller_test.rb
Errata
As of the first printing, these are the known corrections:
- p. 446. Listing 15.3 should be titled db/migrate/009_create_blogs.rb
- p. 449. The code output should have "create db/migrate/010_create_posts.rb"
- p. 448. For Rails 2.0, replace scaffold_resource with scaffold
- p. 449. Listing 15.7 should be titled db/migrate/010_create_posts.rb
- p. 454. Listing 15.9 should be titled app/models/post.rb
- p. 457. There is a missing listing number for the posts controller code on this page, it is included on the web site as Listing 15.11.5 above. Also, the second line should read
helper :profile, :avatar. - p. 458. There's a security flaw that allows users to edit others' posts as long as the URL contains their blog id; i.e., if yours is blog 2, you can edit post 17 even if it isn't yours by going to /blogs/2/posts/17;edit. The following code fixes this:
class PostsController < ApplicationController helper :profile, :avatar before_filter :protect, :protect_blog before_filter :protect_post, :only => [:show, :edit, :update, :destroy] . . . private # Ensure that user is blog owner, and create @blog. def protect_blog @blog = Blog.find(params[:blog_id]) user = User.find(session[:user_id]) unless @blog.user == user flash[:notice] = "That isn't your blog!" redirect_to hub_url return false end end def protect_post post = Post.find(params[:id]) unless post.blog == @blog flash[:notice] = "That isn't your blog post!" redirect_to hub_url return false end end end - p. 477. To test the code in the erratum on p. 458, change the Posts fixture in test/fixtures/posts.yml to
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 blog_id: 1 title: MyString body: MyText created_at: 2007-01-18 08:43:03 updated_at: 2007-01-18 08:43:03 two: id: 2 blog_id: 2 title: MyString2 body: MyText created_at: 2007-01-18 08:43:03 updated_at: 2007-01-18 08:43:03
and then add this to the Posts controller test:def test_post_blog_mismatch wrong_post = posts(:two) # Post exists, but belongs to wrong blog. get :edit, :id => wrong_post, :blog_id => @post.blog assert_response :redirect assert_redirected_to hub_url assert_equal "That isn't your blog post!", flash[:notice] end