How build a blogging service

build a blogging service
Dr.MohitBansal Profile Pic
Dr.MohitBansal,Canada,Teacher
Published Date:25-10-2017
Your Website URL(Optional)
Comment
Building a Blogging Engine with Web Services Support In this chapter, we will build a blogging service for the site, allowing each user to create a blog (a reverse chronological list of journal entries). Each blog entry allows comments to be left by other members of the community. In the future, this could be extended to allow guest visitors to the site to leave comments, too. A large number of bloggers use desktop blogging tools rather than an in-browser form, making the writing and editing of blog posts easier and faster than a web application. These desktop blogging clients use one of a number of established blog APIs. Our blog service will implement some of the features of these APIs, making it possible to use a desktop application to add blog entries. Specifying the Blog Engine Requirements The blogging system will allow each user to create a number of blog entries and each entry can have a number of comments. We will create two models, Entry and Comment. Attributes common to an entire blog, such as its name and whether commenting should be enabled, will belong to the User model. A user’s profile should show the three latest blog entries that a user has made, along with a direct link to the user’s blog. The Entry Model Each blog entry will belong to a user, with each entry consisting of a title and the body text. There will be a flag defining whether the post has been published or if it is just a draft. The creation and last-update time of the entry will also be stored. As we saw with the forum feature, we can use a counter cache to keep a record of how many objects belong to another object. In this case, since we know that eachentry object can have many comments, we will use a counter cache to keep track of how many comments there are for each entry. The database fields necessary for the entry model are shown in Table 6-1. 153 The Database Fields Required for the Entry Model Field Name Field Type Description id integer The primary key user_id integer Theid of the user to whom the entry belongs title string The title of the blog entry body text The body text of the blog entry comments_count integer The counter cache of the number of comments for this entry created_at datetime The date and time this entry was created updated_at datetime The date and time this entry was last updated The Comment Model The Comment model simply holds the details of the comments left for each blog entry. Since we only allow registered users of the site to leave comments, we simply store theuser_id of the user who left the comment along with details of the comment including which entry the comment refers to, the body text, and when it was created. The fields required for this model are shown in Table 6-2. Table 6-2. The Database Fields Required for the Comment Model Field Name Field Type Description id integer The primary key entry_id integer Theid of the entry that this comment belongs to user_id integer Theid of the user who created this comment body text The body text of the comment created_at datetime The date and time that this comment was created The User Model We also need to update the User model to add a number of fields to support the blogging features. Users can set titles for their blogs and enable or disable commenting on their blogs. Also, we will add a counter cache to keep track of the number of entries that a user has created in the blog. The additional fields required for the existing user model are shown in Table 6-3. C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 155 Table 6-3. The Additional Database Fields Required for the User Model Field Name Field Type Description entries_count integer The counter cache of the number of entries created by this user. blog_title string The title of the user’s blog. enable_comments boolean Are blog comments enabled or not? The Entries Controller The entries controller provides access to the user’s blog. Since each collection of entries belongs to a specific user, this will be nested within the users resource via URLs such as/users/8/entries and/users/8/entries/12. We will, therefore, use a standard REST-style controller. Theindex method will provide the standard blog view, and theshow method will display a specific entry along with all comments left for that entry. Thenew,create,edit,update, anddestroy methods will only be accessible by the owner of the blog, allowing that user to maintain and post to the personal blog. We will only implement the HTML-accessible version for the moment. We will not provide a REST XML interface, since we will be creating an API using one of the standardized blogging APIs. The Comments Controller Since the entries controller’sshow method will display all of the comments for a specified entry, and we do not have a requirement to just show one specific comment, we do not have to imple- ment theindex orshow methods. Also, we do not have any support to allow users to edit their comments, so we do not need theedit orupdate methods. The new comment form will be displayed on the entry show view, so we do not require a new method, but we do require acreate method to actually save a new comment. We must also implement thedestroy method to allow the owner of a blog to delete any comments if he or she wishes. The Blogs Controller To provide an entry portal to the blogs hosted by RailsCoders, we will create a blogs controller. This will implement just one method,index. This method will list the ten most recently updated blogs. Blogging APIs While it would be much simpler for us to define a new API for our blogging system using the RESTful resources, there are a number of established APIs used for posting to blogs; for example, Blogger, MetaWeblog, Movable Type, LiveJournal, and the Atom API. Some of these APIs have been around since the first blogging communities started. 156 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T The Blogger API was the first standardized API but is lacking in many features, so it has been mostly replaced by a combination of the MetaWeblog and Movable Type APIs. These three APIs are generally complementary, and each provides a set of method calls to add certain extra features. All three are implemented as XML-RPC (XML-Remote Procedure Call protocol) web services. Since these APIs have been around for a number of years and are very well established, we should not define our own REST API; we’ll choose, instead, to implement one or more of the existing APIs to allow existing blogging tools to work with our site’s blogs. Our site will implement a subset of the Blogger API, providing the basics of creating, editing, and publishing blog entries to the site. To do this, we will use part of Rails known as the ActiveWebService. These libraries enable us to create or use SOAP or XML-RPC web services. Building the Blogging System We can use thegenerate script to build scaffolding code for the resources required for the blog entries and comments. We will then build our blogging system on top of the generated code. Before we add web service support, we should build the standard feature to be accessed through the web or via our own REST API. This is not too different from the other resources that we have built using resource scaffolding. Generating the Blogging Scaffolding Code Open a terminal window for your application, and run the generation script for each resource. For theEntry resource, run the following: ruby script/generate scaffold_resource Entry exists app/models/ exists app/controllers/ exists app/helpers/ create app/views/entries exists test/functional/ exists test/unit/ create app/views/entries/index.rhtml create app/views/entries/show.rhtml create app/views/entries/new.rhtml create app/views/entries/edit.rhtml create app/views/layouts/entries.rhtml identical public/stylesheets/scaffold.css create app/models/entry.rb create app/controllers/entries_controller.rb create test/functional/entries_controller_test.rb create app/helpers/entries_helper.rb create test/unit/entry_test.rb create test/fixtures/entries.yml exists db/migrate create db/migrate/013_create_entries.rb route map.resources :entries C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 157 For the comments resource, run this one: ruby script/generate scaffold_resource Comment exists app/models/ exists app/controllers/ exists app/helpers/ create app/views/comments exists test/functional/ exists test/unit/ create app/views/comments/index.rhtml create app/views/comments/show.rhtml create app/views/comments/new.rhtml create app/views/comments/edit.rhtml create app/views/layouts/comments.rhtml identical public/stylesheets/scaffold.css create app/models/comment.rb create app/controllers/comments_controller.rb create test/functional/comments_controller_test.rb create app/helpers/comments_helper.rb create test/unit/comment_test.rb create test/fixtures/comments.yml exists db/migrate create db/migrate/014_create_comments.rb route map.resources :comments We also need to add a migration to add the blog-specific settings— the blog title, a counter cache field for the number of entries, and a flag to set whether the user allows comments—to the User model. Create a new migration to create these attributes in the User table: ruby script/generate migration AddBlogSettingsToUser exists db/migrate create db/migrate/015_add_blog_settings_to_user.rb We can also create the files for the blogs controller: ruby script/generate controller Blogs exists app/controllers/ exists app/helpers/ create app/views/blogs exists test/functional/ create app/controllers/blogs_controller.rb create test/functional/blogs_controller_test.rb create app/helpers/blogs_helper.rb 158 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T Writing the Migrations We can now write the database migrations to match the specification defined earlier. The Entries Table To edit theentries table, opendb/migrate/013_create_entries.rb, and change theup method as shown in Listing 6-1. Listing 6-1. The Migration File to Create the Entries Table class CreateEntries ActiveRecord::Migration def self.up create_table :entries do t t.column :user_id, :integer t.column :title, :string t.column :body, :text t.column :comments_count, :integer, :null = false, :default = 0 t.column :created_at, :datetime t.column :updated_at, :datetime end add_index :entries, :user_id end def self.down drop_table :entries end end This provides the database fields necessary according to the feature specification. Since we will be accessing blog entries for a specific user, it is a good idea to create a database index for this table on theuser_id field. This will speed up the database queries when retrieving a user’s blog entries. The Comments Table Thecomments table is defined indb/migrate/014_create_comments.rb. Edit this file as shown in Listing 6-2. Listing 6-2. The Migration File to Create the Comments Table class CreateComments ActiveRecord::Migration def self.up create_table :comments do t t.column :entry_id, :integer t.column :user_id, :integer t.column :guest_name, :string t.column :guest_email, :string t.column :guest_url, :string C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 159 t.column :body, :text t.column :created_at, :datetime end add_index :comments, :entry_id end def self.down drop_table :comments end end Again, this migration adds an index, this time on theentry_id column. Comments will always be tied to a particular entry, so this will help speed up the database queries for retrieving comments. The Blog Settings Migration The settings for the blog will be stored within the User model. These columns are added through the migrationdb/migrate/015_add_blog_settings_to_user.rb. Open the migration file, and replace the generated code with theup anddown migrations shown in Listing 6-3. Listing 6-3. The Migration to Add the Necessary Fields to the User Table class AddBlogSettingsToUser ActiveRecord::Migration def self.up add_column :users, :entries_count, :integer, :null = false, :default = 0 add_column :users, :blog_title, :string add_column :users, :enable_comments, :boolean, :default = true end def self.down remove_column :users, :entries_count remove_column :users, :blog_title remove_column :users, :enable_comments end end You can now run the database migration, creating the necessary tables, indexes, and addi- tional columns by entering the following command: rake db:migrate == CreateEntries: migrating =================================================== create_table(:entries) - 0.0041s add_index(:entries, :user_id) - 0.1199s == CreateEntries: migrated (0.1243s) ========================================== 160 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T == CreateComments: migrating ================================================== create_table(:comments) - 0.0044s add_index(:comments, :entry_id) - 0.0087s == CreateComments: migrated (0.0134s) ========================================= == AddBlogSettingsToUser: migrating =========================================== add_column(:users, :entries_count, :integer, :default=0, :null=false) - 0.0079s add_column(:users, :blog_title, :string) - 0.0088s add_column(:users, :enable_comments, :boolean) - 0.0085s == AddBlogSettingsToUser: migrated (0.0257s) ================================== The Models’ Relationships and Validations We now need to define the relationships and validations for each of the new models that we have created. First of all, add the followinghas_many relationships to the list of relationships already present in theapp/models/user.rb model file: class User ActiveRecord::Base ... has_and_belongs_to_many :roles has_many :articles has_many :entries has_many :comments ... Now add the reciprocal relationship and the comments relationship to theapp/models/ entry.rb file. We can also add the validations for thetitle andbody attributes. class Entry ActiveRecord::Base belongs_to :user, :counter_cache = true has_many :comments validates_length_of :title, :maximum = 255 validates_length_of :body, :maximum = 10000 end This also declares that the number of entries per user should use a counter cache. Edit the Comment model at app/models/comment.rb, and add the relationships with the Entry and User models, along with a validation on the length of the body attribute: C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 161 class Comment ActiveRecord::Base belongs_to :entry, :counter_cache = true belongs_to :user validates_length_of :body, :maximum = 1000 end Creating the Resource Mapping We now need to add the resource URL mappings to the routes configuration. Edit theconfig/ routes.rb file. You will need to delete the automatically generatedmap.resource statements for entries andcomments and add new mappings for both entries and comments nested under the users resource. We also need to add the mapping for theblogs resource at the root level. These changes are shown in Listing 6-4. Listing 6-4. The Mappings for the Entries, Comments, and Blogs Resources ... map.resources :pages map.resources :blogs map.resources :users, :member = :enable = :put do users users.resources :permissions users.resources :entries do entry entry.resources :comments end end ... The Blog Name Helper Method In the migration, we defined a new field for the user model for the title or name of the blog. When a new user signs up, this value is going to be null, so we need to make sure that we can provide a default value to the entry views. The simplest way to do this is to create a helper method. Since this will normally only be used by the entry views, this should be placed in theapp/helpers/entries_helper.rb file. If you wanted the helper method to be available to all views, you should place the method in the application_helper.rb file. Open theentries_helper.rb file, and add a helper method calledblog_title as shown in Listing 6-5. Listing 6-5. The Entries Helper File module EntriesHelper def blog_title(user) user.blog_title = user.username end end 162 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T This method takes auser object as a parameter and returns either theblog_title field from the database, if it is not null, or the user’s username, if theblog_title field is null. The= in the method is simply a shorthand way of specifying this. Adding the Blog Title to the Edit User Profile Page To allow users to change the titles of their blogs, we need to add the blog title attribute to the edit user profile page. Open the existing user profile, and edit the pageapp/views/users/edit.rhtml by adding the blog title field, as shown in Listing 6-6. Listing 6-6. Adding the Blog Title to the Edit User Profile Page h2Edit your account/h2 p%= link_to 'Show my profile', user_path(user) %/p %= error_messages_for :user % % form_for :user, :url = user_url(user), :html = :method = :put do f -% pEmail:br /%= f.text_field :email, :size = 60 %/p pPassword:br /%= f.password_field :password, :size = 60 %/p pPassword Confirmation:br / %= f.password_field :password_confirmation, :size = 60 %/p pBlog Title:br /%= f.text_field :blog_title, :size = 60 %/p pProfile:br /%= f.text_area :profile, :rows = 6, :cols = 60 %/p %= submit_tag 'Save' % % end -% We can try this out now by editing a user profile and ensuring that the new title is saved correctly. Open a browser, and log into the application as a regular user. Click the My Profile link in the sidebar menu, and enter a title for your blog in the relevant field, as shown in Figure 6-1. Save the changes to your profile, and check that the attribute has been changed in the database, either by checking the database using SQL, a database GUI tool, or the Rails console, as follows: ruby script/console Loading development environment. C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 163 User.find_by_username('alan').blog_title = "Alan's Rails Projects Blog" Figure 6-1. Editing your blog title The Controllers and Views Before we start thinking about the web services or custom templates, we should first build the controller code and some basic views that allow us to use the blog functionality and test that everything is working correctly. When we are happy with this, we can move on to building the web services feature. Removing the Generated Layouts First, delete the automatically generated layout templates. As you saw in the previous chapter, they allow you to define a different layout for each controller. Since we want to use the stan- dard layout file for the whole site, delete theentries.rhtml andcomments.rhtml files inapp/ views/layouts/. 164 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T The Entries Controller The entries controller is a simple nested resource. When viewing a blog, we want to be able to view any user’s blogindex action and anyshow action, showing the entries of the user specified in the URL. However, when a user creates a new blog entry, it should be saved as belonging to the logged-in user, irrespective of the userid parameter. We need to restrict all methods exceptindex andshow to logged-in users only. The code created by thescaffold_resource generator is a good starting point. Alter theapp/controllers/ entries_controller.rb file as shown in Listing 6-7, creating, showing, and editing entries based on the userid and entryid. Theindex method uses the built-in Rails paginator, showing ten entries at a time. Listing 6-7. The Entries Controller File class EntriesController ApplicationController before_filter :login_required, :except = :index, :show def index user = User.find(params:user_id) entry_pages = Paginator.new(self, user.entries_count, 10, params:page) entries = user.entries.find(:all, :order = 'created_at DESC', :limit = entry_pages.items_per_page, :offset = entry_pages.current.offset) end def show entry = Entry.find_by_id_and_user_id(params:id, params:user_id, :include = :user, :comments = :user) end def new entry = Entry.new end def edit entry = logged_in_user.entries.find(params:id) rescue ActiveRecord::RecordNotFound redirect_to :action = 'index' end def create entry = Entry.new(params:entry) C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 165 if logged_in_user.entries entry flash:notice = 'Entry was successfully created.' redirect_to entry_path(:user_id = logged_in_user, :id = entry) else render :action = "new" end end def update entry = logged_in_user.entries.find(params:id) if entry.update_attributes(params:entry) flash:notice = 'Entry was successfully updated.' redirect_to entry_path(logged_in_user.id, entry) else render :action = "edit" end rescue ActiveRecord::RecordNotFound redirect_to :action = 'index' end def destroy entry = logged_in_user.entries.find(params:id) entry.destroy redirect_to entries_path rescue ActiveRecord::RecordNotFound redirect_to :action = 'index' end end If you look in theshow method, you will see that I have specified a number of models within the:include parameter. This tells ActiveRecord that I will need to access the user that belongs to the entry, along with all of the comments and the user models that belong to these comments. This will generate a complex SQL query that retrieves all of the related models at once. You should take a look at the Rails development log that the SQL generated if you want to understand the SQL query that ActiveRecord generates. Try comparing this with a query that does not use theinclude statement to see the database access that would be made otherwise. The New Entry View When a user creates a new blog entry, we simply want to offer a very simple form to allow entry of the title and the body text. Openapp/views/entries/new.rhtml, and modify the generated view as shown in Listing 6-8. 166 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T Listing 6-8. The New Blog Entry View File h1New Blog Entry/h1 %= error_messages_for :entry % % form_for(:entry, :url = entries_path) do f % pTitle:br /%= f.text_field :title, :size = 40 -%/p pBlog Entry:br /%= f.text_area :body, :rows = 10, :cols = 60 -%/p p%= submit_tag "Create" %/p % end % %= link_to 'Back', entries_path % Before we can take a look at this, we should create theshow view, which the controller redirects the user to after creating a blog entry. The Entries Show View Theshow method should show just the one entry and list all of the comments, along with the user who left each comment, the time and date it was left, and the body text. We will also include the new comment form on this page. It allows a visitor to the blog to quickly add a comment without having to navigate to another page. Open theshow action view at app/views/entries/show.rhtml, and edit it as shown in Listing 6-9. Listing 6-9. The Show Blog Entry View File h1 %= link_to blog_title(entry.user), entries_path(:user_id = entry.user) % /h1 h2%= entry.title %/h2 p%= textilize(entry.body) %/p h3Comments/h3 % entry.comments.each do comment -% div class="comment" p class="commentfrom"At %= comment.created_at.to_s(:short) %, %= comment.user.username % said:/p p class="commentbody"%=h comment.body %/p /div % end -% C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 167 h3Leave a comment/h3 %= error_messages_for :comment % % form_for(:comment, :url = comments_path(:user_id = entry.user, :entry_id = entry)) do f -% p%= f.text_area :body, :rows = 4, :cols = 40 %/p p%= submit_tag 'Save Comment' -%/p % end -% Since we have not yet created the comments controller, we cannot actually add comments to the entry, but we can try out the entry creation. Adding a link to the sidebar menu directing users to the entry creation page will make it quick and easy for users to add blog entries. We will also add a link to the blogs controller, which we will develop later in this chapter. Open the sidebar menu partial view file,app/views/layouts/_menu.rhtml, and add the link to the section shown only to logged-in users and the link to the blogs controller as shown in Listing 6-10. Listing 6-10. The Create New Blog Post Link in the Menu Partial File ul li%= link_to 'Home', index_url %/li li%= link_to 'News', articles_path %/li li%= link_to 'Forums', forums_path %/li li%= link_to 'Blogs', blogs_path %/li lihr size="1" width="90%" align="left"//li % if is_logged_in? % liLogged in as: i%= logged_in_user.username -%/i/li li%= link_to 'My Profile', edit_user_path(logged_in_user) -%/li li %= link_to 'New Blog Post', new_entry_path( :user_id = logged_in_user) -% /li li%= link_to 'Logout', :controller = 'account', :action = 'logout', :method = :post %/li % else % li%= link_to 'Signup', :controller = 'users', :action = 'new' %/li li%= link_to 'Login', :controller = 'account', :action = 'login' %/li % end % ... 168 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T Now, we’ll try creating a blog entry using this link and ensure that it displays correctly with theshow method. Make sure that you application is running and log into the site as a regular user. Click the New Blog Post link in the sidebar menu. You will be prompted to create a new blog post, as shown in Figure 6-2. Figure 6-2. Creating a new blog post Enter the content of a new blog post, remembering that you can use Textile markup to add markup to the entry. For instance, you can markup using bold text, _italicized text_, or add links like"this":http://railscoders.net. Saving the entry will redirect you to theshow method, displaying the new blog entry along with a comment entry box, as shown in Figure 6-3. We can now add the views for the other methods in the entries controller. C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 169 Figure 6-3. A blog entry The Entries Index View Theindex action simply finds the latest ten entries for the specified user (or ten entries with an offset if a page number is specified). So for the index page, we simply want to cycle through the entries, showing the title, the body of the entry, and the number of comments that have been left. We also need to provide links to view the comments and to edit or delete the entry if the user that is logged in is viewing their own blog. Openapp/views/entries/index.rhtml, and edit it as shown in Listing 6-11. Listing 6-11. The Entries Index View h1%= link_to blog_title(user), entries_path(:user_id = user.id) %/h1 % entries.each do entry -% div class="blogentry" h2%= link_to entry.title, entry_path(:user_id = entry.user, :id = entry) %/h2 170 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T % if is_logged_in? and logged_in_user.id == user.id -% div clas="blogoptions" %= link_to 'Edit', edit_entry_path(:user_id = entry.user, :id = entry) % %= link_to 'Destroy', entry_path(:user_id = entry.user, :id = entry), :confirm = 'Are you sure?', :method = :delete % /div % end -% div class="blogentrybody" %= textilize(entry.body) % /div div class="blognumcomments" p%= link_to pluralize(entry.comments_count, 'comment'), entry_path(:user_id = entry.user, :id = entry) -%/p /div /div % end -% % if entry_pages.page_count 1 % p class="pagination"Pages: strong %= pagination_links entry_pages, :params = params % /strong/p % end % For each entry, the number of comments for that entry is also shown. This uses thepluralize helper to automatically display the plural form of the word “comment” if there is more than one comment. The number of comments is cached in thecomments_count attribute of the entry’s database record, saving us a large number of database queries. This is linked to theshow method of the entry, as this method displays the list of comments for that entry. Obviously, since we haven’t written the comments controller yet, there is no way to add comments to an entry. The pagination links are shown if there is more than one page of articles. You can now take a look at the index view by clicking on the title of the blog on the show entry page. You should try adding a few extra articles to check that all of the entries are shown correctly, along with pagination when needed. The Edit Entry View Editing an entry is almost identical to the new entry view except that the form is posted with different parameters and with an HTTPPUT rather than aPOST. Openapp/views/entries/edit.rhtml, and edit as shown in Listing 6-12. C H AP TE R 6 ■ B U IL D I N G A B L OG G I N G E N G I N E W I TH W E B S E R V I CE S S U PP O R T 171 Listing 6-12. The Entry Edit View File h1Editing entry/h1 %= error_messages_for :entry % % form_for(:entry, :url = entry_path(:user_id = logged_in_user.id, :id = entry), :html = :method = :put ) do f % pTitle:br /%= f.text_field :title, :size = 40 -%/p pBlog Entry:br /%= f.text_area :body, :rows = 10, :cols = 60 -%/p p%= submit_tag "Save" % or %= link_to 'cancel', entries_path %/p % end % You can now try editing an existing blog entry by clicking on the edit link from the index and altering the entry. Click Save Comment to save the updated entry, and check that the content has changed on the show entry page. Testing the Entries Controller At this point, we can automate the testing of the entries controller. As before, we should create a fixture to test with. Opentest/fixtures/entries.yml, remove the generated empty fixtures, and add the following entry fixture: valid_entry: id: 1 user_id: 1 title: first post body: blah blah created_at: %= 1.days.ago.to_s(:db) % updated_at: %= 1.days.ago.to_s(:db) % We simulate a login by a user where necessary to perform the relevant tests. Opentest/ functional/entries_controller_test.rb, and replace it with the code in Listing 6-13. Listing 6-13. The Entries Controller Functional Tests File require File.dirname(__FILE__) + '/../test_helper' require 'entries_controller' Re-raise errors caught by the controller. class EntriesController; def rescue_action(e) raise e end; end class EntriesControllerTest Test::Unit::TestCase fixtures :entries, :users 172 CH AP T E R 6 ■ B U I L DI N G A B L O G G IN G E N G I N E WI T H WE B SE R V IC E S SU P P OR T def setup controller = EntriesController.new request = ActionController::TestRequest.new response = ActionController::TestResponse.new end def test_should_get_index get :index, :user_id = 1 assert_response :success assert assigns(:entries) end def test_should_get_new login_as(:valid_user) get :new, :user_id = 1 assert_response :success end def test_should_create_entry login_as(:valid_user) old_count = Entry.count post :create, :entry = :title = 'test entry', :body = 'a blog entry' assert_equal old_count+1, Entry.count assert_redirected_to entry_path(:user_id = 1, :id = assigns(:entry)) end def test_should_show_entry get :show, :user_id = 1, :id = 1 assert_response :success end def test_should_get_edit login_as(:valid_user) get :edit, :user_id = 1, :id = 1 assert_response :success end def test_should_update_entry login_as(:valid_user) put :update, :user_id = 1, :id = 1, :entry = :title = 'test entry', :body = 'a blog entry' assert_redirected_to entry_path(:user_id = 1, :id = 1) end

Advise: Why You Wasting Money in Costly SEO Tools, Use World's Best Free SEO Tool Ubersuggest.