#227 Upgrading to Rails 3 Part 3
In this episode we’re going to finish our series on upgrading an Rails 2 application to Rails 3. We’ve made good progress so far; we have all of the application’s tests passing and have used the Rails upgrade plugin to find and fix most of the areas that need upgrading to Rails 3. There are still things that need to be fixed in the view layer, however, and we’ll focus on those in this episode.
Removing Deprecation Warnings
Before we start fixing the view layer we’ll take another look at the tests. By the end of the last episode all of the application’s tests passed but there were a large number of deprecation warnings shown as they ran. We’ll run the tests again now to see if we can reduce the number of warnings.
$ rake spec # large amount of output snipped. .DEPRECATION WARNING: error_messages_for was removed from Rails and is now available as a plugin. Please install it with `rails plugin install git://github.com/rails/dynamic_form.git`. (called from _app_views_sponsors__form_html_erb___2363957037552137609_2171491000_4161310651677273387 at /Users/eifion/rails/apps_for_asciicasts/ep227/railscasts/app/views/sponsors/_form.html.erb:2) <span class="passed">...................................................</span> <span class="passed">Finished in 3.24 seconds</span> <span class="passed">152 examples, 0 failures</span>
The tests still pass, but there are still a large number of warnings. Fortunately a large number of them are duplicates so it shouldn’t take a lot of work to remove them.
We’ve cut out most of the output from rake spec above but left the final warning in. The error_messages_for
and error_messages_on
methods have been deprecated in Rails 3 and we could replace them with custom code that loops through the model’s errors.full_messages
to display the errors. The old methods are still available as a plugin, however, so we’ll take the easier option and use that instead. The instructions for installing it are shown in the deprecation warning above.
$ rails plugin install git://github.com/rails/dynamic_form.git Initialized empty Git repository in /Users/eifion/rails/apps_for_asciicasts/ep227/railscasts/vendor/plugins/dynamic_form/.git/ remote: Counting objects: 22, done. remote: Compressing objects: 100% (17/17), done. remote: Total 22 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (22/22), done. From git://github.com/rails/dynamic_form * branch HEAD -> FETCH_HEAD
With the plugin installed when we run rake spec
again and the warning has gone. There are other errors, though, and one that appears frequently is this:
DEPRECATION WARNING: subclasses is deprecated and will be removed from Rails 3.0 (use descendants instead). (called from block (2 levels) in load_models at /Users/eifion/.rvm/gems/ruby-1.9.2-rc2/gems/thinking-sphinx-2.0.0.rc1/lib/thinking_sphinx/context.rb:58)
This error is thrown by the Thinking Sphinx library. If you’re getting errors from a plugin it’s worth checking the issue tracker for that plugin to see if there are any issues listed related to the error you’re seeing. If not then we can add one and maybe it will be fixed for the next release. (Alternatively you can always fork the code and fix the error yourself.)
In the case of Thinking Sphinx this problem has already been reported and fixed but hasn’t been included in the latest gem release at the time of writing. To remove this warning from our application we can tell bundler to get the Thinking Sphinx code from a git repository instead of from a gem.
So, in our Gemfile
instead of including Thinking Sphinx this way:
gem 'thinking-sphinx', '>=2.0.0.rc1', :require => 'thinking_sphinx'
we’ll include it like this:
gem 'thinking-sphinx', :require => 'thinking_sphinx', :git => "git://github.com/freelancing-god/thinking-sphinx.git", :branch => "rails3"
We have to run bundle install
again so that it downloads Thinking Sphinx from its git repository. Our application will now use this version instead of the latest gem. When we run our specs now they all pass without warnings.
$ rake spec <span class="passed">........................................................................................................................................................</span> <span class="passed">Finished in 3.3 seconds</span> <span class="passed">152 examples, 0 failures</span>
Fixing The Views
With the deprecation warnings out of the way we can focus on the views. When we run the application the first error that stands out is that the sidebar is missing from the episodes page.
A part of the page as large as this should be covered by a test, if only to test for its existence. If we were upgrading this application for production then we’d write a test to cover this functionality but we won’t do that here.
If we take a look inside the layout file, the code that generates the side bar is this:
<%= yield(:side) || render(:partial => 'shared/side') %>
This code will yield
the side content and if that returns nil
it will render the side partial instead. The problem here is that in earlier versions of Rails yield
would return nil
if the appropriate content_for
call was missing on the page whereas in Rails 3 it will return an empty string so the second part of the code will never be called.
To fix this we can use the content_for?
method which will return true
if that content area has been defined for a page. If it has we’ll display that content, otherwise we’ll render the sidebar.
<%= content_for?(:side) ? yield(:side) : render(:partial=> 'shared/side') %>
When we reload the page now the sidebar is displayed.
There are still other problems in the view to be fixed. If we view the page for a single episode we’ll see that the show notes part of the page is being escaped.
The content for that part of the page goes through a textilize
method that is defined in the ApplicationHelper
module.
module ApplicationHelper def textilize(text) Textilizer.new(text).to_html unless text.blank? end end
This method returns some HTML as a string. In Rails 3 if you’re going to pass a string of HTML to a view to be displayed the string will need to be marked as HTML-safe, otherwise it will be automatically escaped. To mark the string as safe we just need to call the html_safe
method on it.
module ApplicationHelper def textilize(text) Textilizer.new(text).to_html.html_safe unless text.blank? end end
Now the string has been marked as safe it will no longer be escaped. This is covered in more detail in episode 204 [watch, read]. When we reload the page again the show notes are rendered correctly.
There’s still a small problem left on this page: the titlebar should include the name of the episode. This problem is specific to the way that titles are handled in this application; a title
helper method is used which sets a instance variable rather than using content_for
to set the title.
module LayoutHelper def title(page_title, show_title = true) @content_for_title = page_title.to_s @show_title = show_title end end
To fix this we should set the content for the title using content_for
, like this:
module LayoutHelper def title(page_title, show_title = true) content_for(:title, page_title.to_s) @show_title = show_title end end
Now when we call yield(:title)
in the layout the title will be set properly. We can pass the content itself in as a block or, as we have here, as a second argument.
Fixing Destroy Links
The next error is in the admin section of the application. On the page below is a list of model objects, each with an “edit” and “destroy” link.
The problem here lies with the “destroy” links. When we click one of them we’ll be taken to the show
action for the question rather than destroy
. The reason that the delete
action isn’t being called is that the JavaScript used for destroy links has been made unobtrusive in Rails 3. If we look at the HTML for one of the questions we’ll see that the JavaScript is no longer embedded in the delete link’s tag. Instead there are two custom HTML 5 attributes that begin with data-
and there should be some JavaScript that will detect these attributes and change the request to a DELETE request.
<tr> <td>What does the M stand for in MVC?</td> <td>model</td> <td><a href="/spam_questions/1/edit">Edit</a></td> <td><a href="/spam_questions/1" data-confirm="Are you sure?" data-method="delete" rel="nofollow">Destroy</a></td> </tr>
As this application uses jQuery we need to download a custom rails.js file from the jquery-ujs project. If we were using Prototype then we wouldn’t need to do this as the correct file is already included in the /public/javascripts
directory. When we’ve downloaded the file and copied it into the javascripts
directory we’ll need to add a reference to it in the head section of our application’s layout file. We also need to add a csrf meta tag to prevent cross-site request forgeries.
<%= javascript_include_tag 'jquery', 'rails', 'application' %> <%= csrf_meta_tag %>
The use of unobtrusive JavaScript in Rails 3 was covered in episode 205 [watch, read].
While we’re in the layout file we’ll update the application to use HTML 5 by changing the DOCTYPE and the opening html
tag.
<!DOCTYPE html> <html>
There will be other things that we need to do to make the application fully HTML 5 valid and we can run the code through a validator to find out what has to be changed.
With the new rails.js
file in place when we reload the spam questions page and try to delete one of the questions the JavaScript confirmation will be shown and the question will be deleted if we click OK.
The final change we can make to the view code is to remove any calls to the h
method. In Rails 2 views any output that we wanted to be HTML-escaped had to be wrapped in the h
method. In Rails 3 all output is escaped by default so we can tidy up the views by going through them and removing those h
methods. This was covered in episode 204 [watch, read]. Leaving the method in won’t cause any problems but the view code will look cleaner without it.
Removing Obsolete Files
We’re coming to the end of the upgrade now; everything is working but we need to clean up some of the files that we no longer need. When we generated the new Rails 3 application it will have created a new welcome page and this will need to be removed.
$ rm public/index.html
The same applies to the Rails image file that’s shown on the welcome page.
$ rm public/images/rails.png
There are also files in the /script
directory that are no longer used by Rails 3 and which can be removed.
$ ls script about destroy process setup autospec generate rails setup_test console performance runner spec dbconsole plugin server spec_server
Only the rails script is used by Rails 3 and so we can remove the others, such as console
, server
and so on.
That’s it for this series on upgrading a Rails 2 application to Rails 3. Keep in mind that a lot of what we’ve covered is specific to the Railscasts application and there’s no way that we could cover everything that might need to be done to upgrade an application to Rails 3. If you see deprecation warnings or error messages then the chances are that someone else has already had the same problem and a Google search will bring a solution. It’s also worth checking out the other episodes that cover Rails 3.
A large number of the problems that you’ll come across when upgrading an application will be caused by plugins. the Rails Plugins9 site has a great list of plugins and lists whether they are compatible with Rails 3 or not. If you do have trouble with a specific plug in it’s also worth checking it’s Github repository to see if a Rails 3 compatible version is available.