44: Debugging RJS
(view original Railscast)
RJS makes writing AJAX-enabled Rails applications easy, but bugs still happen. In this episode we’re going to show how to debug both server-side and client-side errors. The application we wrote in the last episode let customers add reviews of products via a form that used AJAX, but it’s not working now. When we click the button to try to add a review, nothing happens. This means that there is an error somewhere in the lifetime of the AJAX call, either the request is not being sent correctly or an error on the server is causing the response to be invalid or missing. Bugs like this can be difficult to track down as the browser doesn’t show an error message so there’s no obvious place to start looking for the line of code that needs fixing.
Our broken product page.
Tracing The Call
The first step towards debugging our problem is to look at the source code for the page. We’ll look in the <head>
section to make sure that the required JavaScript files have been referenced.
<head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <link href="/stylesheets/ep44.css?1237493400" media="screen" rel="stylesheet" type="text/css" /> <script src="/javascripts/prototype.js?1237114650" type="text/javascript"></script> <script src="/javascripts/effects.js?1237114650" type="text/javascript"></script> <script src="/javascripts/dragdrop.js?1237114650" type="text/javascript"></script> <script src="/javascripts/controls.js?1237114650" type="text/javascript"></script> <script src="/javascripts/application.js?1237114650" type="text/javascript"></script> <title>Episode 44: Debugging RJS</title> </head>
The JavaScript files referenced in the <head> section of our page.
The Prototype and script.aculo.us libraries are correctly referenced in our page. Next we’ll look at the opening form tag to make sure that the AJAX request that is made when the form is submitted is correct.
<form action="/products/1/reviews" class="new_review" id="new_review" method="post" onsubmit="new Ajax.Request('/products/1/reviews', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
What we want to check for here is that the AJAX request is made and that the URL it calls is correct. Our code looks right; the JavaScript in the onsubmit attribute creates a new Ajax.Request and the URL is the correct one to create a new review.
Given that the code on the page looks correct, the next place to check is on the server. The development log will be able to tell us if the AJAX call has been made. We’ll run tail
on the log and try submitting another request.
Processing ReviewsController#create (for 127.0.0.1 at 2009-03-19 21:17:50) [POST] Parameters: {"commit"=>"Add comment", "review"=>{"name"=>"Bob", "content"=>"Wow!"}, "product_id"=>"1", "authenticity_token"=>"4cncFdRcYuinYVNWj3uGZTPuvx4VXM7gBCRCMOLt4fw=", "_"=>""} Review Create (0.4ms) INSERT INTO "reviews" ("name", "updated_at", "product_id", "content", "created_at") VALUES('Bob', '2009-03-19 21:17:50', 1, 'Wow!', '2009-03-19 21:17:50') Rendering reviews/create Rendered reviews/_review (1.3ms) Product Load (0.3ms) SELECT * FROM "products" WHERE ("products"."id" = 1) SQL (0.2ms) SELECT count(*) AS count_all FROM "reviews" WHERE ("reviews".product_id = 1) ActionView::TemplateError (undefined method `pluralized' for #<ActionView::Base:0x221b66c>) on line #2 of app/views/reviews/create.rjs: 1: page.insert_html :bottom, :reviews, :partial => 'review', :object => @review 2: page.replace_html :reviews_count, pluralized(@review.product.reviews.size, 'Review') 3: page[:new_review].reset 4: page.replace_html :notice, flash[:notice] 5: flash.discard
The RJS error in the development log.
When we click the button an error message appears in the development log. The correct action (ReviewsController#create
) is called and the review is inserted into the reviews table in the database. Then the RJS file is called and it’s here where the error occurs. The log shows that a call to an undefined method, pluralized
, is made in our RJS file.
If we look in our RJS file we’ll find the line of code that causes the error. The method should be called pluralize rather than pluralized. We’ll change it and see if it fixes the error.
page.replace_html :reviews_count, pluralize(@review.product.reviews.size, 'Review')
The review is now added.
Our change has fixed the error and now our form is submitted correctly. Just to make sure we’ll take another look at the end of the development log and we should see that there are no errors and that the request has completed successfully.
Processing ReviewsController#create (for 127.0.0.1 at 2009-03-19 22:06:40) [POST] Parameters: {"commit"=>"Add comment", "review"=>{"name"=>"Fry", "content"=>"This is great!"}, "product_id"=>"1", "authenticity_token"=>"4cncFdRcYuinYVNWj3uGZTPuvx4VXM7gBCRCMOLt4fw=", "_"=>""} Review Create (0.4ms) INSERT INTO "reviews" ("name", "updated_at", "product_id", "content", "created_at") VALUES('Fry', '2009-03-19 22:06:40', 1, 'This is great!', '2009-03-19 22:06:40') Rendering reviews/create Rendered reviews/_review (0.1ms) Product Load (0.3ms) SELECT * FROM "products" WHERE ("products"."id" = 1) SQL (0.2ms) SELECT count(*) AS count_all FROM "reviews" WHERE ("reviews".product_id = 1) Completed in 22ms (View: 10, DB: 1) | 200 OK [http://localhost/products/1/reviews]
This was a fairly straightforward bug to find and fix, but it has demonstrated how to step through the stages of an AJAX call and locate an error.
Client-side Errors
Sometimes an AJAX request is broken but the log file shows that the code has executed correctly on the server. In these cases we need a good JavaScript debugger, and a really good one is Firebug which works with Firefox. Firebug can be installed from the Firebug website in the same way as any other Firefox extension. Once installed it can be used to debug HTML, CSS and JavaScript, including AJAX requests.
Firebug is disabled by default.
Firebug is activated by clicking the bug icon in the status bar. By default it will be disabled for the current server. To enable it we’ll check the three checkboxes and then the “Apply” button. Once enabled we’ll add another review to our page and see what appears in the Firebox console.
The console shows us that our AJAX request was made and returned 200, so was successful. If it had failed we’d generally see the top line in red and a 500 status. Below that are three tabs that show was was sent in the headers, in the body of the request and what came back in the response. In the tab above we can see the contents of the form fields that were sent.
In the ‘Response’ tab is the JavaScript that our RJS file generated. Our Ajax call will evaulate this script when it’s returned from the server and it’s this that causes the page to be updated and the form to be reset. Being able to see the JavaScript that’s going to be run is incredibly useful as we’ll be able to debug it much more easily than we could back in the bad old days of debugging by alert
statements.
That said, alert messages do still have their uses. If an error is thrown by the JavaScript that is returned from the server, it’ll be caught by the try
/ catch
block in the code and an alert
message shown. We’ll show this by deliberately causing an error by changing the line
of RJS that resets the form so that it has the wrong id
.
Adding a review now throws up the following two alerts, the first showing the error and the second the code that caused it.
Thankfully the error message clearly explains that the error occurred as a result of failing to find an element on the page with an id
of new_reviewXXX
, so we’ve got a good starting point to try and track the error down.
Hopefully, we’ve covered enough in this episode to give you a good idea as to where to start tracking down bugs in your RJS code. With a combination of the development log for checking server-side code and Firebug for checking client-side code you’ll have enough information to quickly fix any bugs that arise.