If you follow my feeds, you probably saw that I’ve been tweaking my website since this weekend. So I decided to undertake yet another brain surgery and learn some new stuff in the process.

You can see the result at this staging subdomain. Let me know if you find any bugs. This is a heavily customized Enki blog adapted for Rails 3.1 beta running over Ruby 1.9.2 and Nginx/Passenger.

The very first step that I recommend is David Rice’s article on how to upgrade your Rails app to 3.1. It was invaluable in my process. First and foremost, the advice that applies to any Rails upgrade:

  • Make sure your tests all pass and that you have good enough coverage to feel confident on heavy upgrades of this kind.
  • git checkout -b rails3.1 – create a new branch where all the changes will be.

I’ll go over David’s bullet points on the upgrade process and you can compare my article with his to understand a few differences and how you can adapt those steps for your own projects.

Upgrade the Gemfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
source 'http://rubygems.org'

gem 'rake', '~> 0.8.7'
gem 'rails', :git => 'git://github.com/rails/rails.git'

# Rails 3.1 - Asset Pipeline
gem 'json'
gem 'sass'
gem 'uglifier'
gem "sprockets", :git => 'git://github.com/sstephenson/sprockets.git'

# Rails 3.1 - JavaScript
gem 'jquery-rails'

gem 'mysql2', '0.3.2'

gem 'will_paginate', :git => "git://github.com/akitaonrails/will_paginate.git", :branch => "rails3.1"
gem 'paperclip', :git => "git://github.com/thoughtbot/paperclip.git"
#gem 'newrelic_rpm', '~> 2.13'
gem 'acts-as-taggable-on', :git => "git://github.com/kuldarkrabbi/acts-as-taggable-on.git"
# ... more gems

# Bundle gems for the local environment. Make sure to
# put test-only gems in this group so their generators
# and rake tasks are available in development mode:
group :test, :development, :cucumber do
  # ... more gems
  # gem 'ruby-prof'
  platforms :mri_18 do
    gem "ruby-debug"
  end
end

group :production do
  platforms :mri_19 do
    gem 'psych', :require => 'psych'
    # Bundle a Javascript runtime, the recommended one is the V8 engine
    # that comes in The Ruby Racer
    gem 'execjs'
    gem 'therubyracer'
  end
end

In case you didn’t know about that, Rake received an upgrade after more than 2 years. We were all used to version “0.8.7” and then “0.9.0” was recently released. And lots of other gems and apps that depend on Rake broke badly, Rails included. You can read more about it in other blogs such as Yehuda’s explaining the situation and what to do. For now, remember to do either of these options:

  1. declare the ‘0.8.7’ version in your Gemfile and make sure you don’t have 0.9.0 installed.
  2. don’t mind the version but in a Rails app make sure you’re using bundle exec rake … to run the tasks.

Notice I had to use several gems directly from their github repositories. That’s because we’re too bleeding edge and Rails 3.1 deprecates things that breaks some gems. You will have to test one by one and research alternatives. Sometimes it’s as easy as pointing to their master branches. I just had one case where I had to make a fork for myself and add the fix (will_paginate). That’s why your test suite is important now.

I disabled new_relic because it broke as well but it was not important for this test, so it was easier to just comment it out.

For legacy reasons I was using Active Merchant as a plugin in vendor/plugins and I had to upgrade to the latest master branch version as well. But with added tweaks, in particular:

  • vendor/plugins/active_merchant/lib/active_merchant.rb
1
2
3
#require 'active_support/core_ext/kernel/requires'
 require 'active_support/base64'
#require 'active_support/secure_random'

Ruby 1.8.7 and above comes with SecureRandom so Rails is deprecating it and kernel/requires is not available as well which requires another hack:

  • vendor/plugins/active_merchant/lib/active_merchant/billing/integrations/action_view_helper.rb
1
#require_library_or_gem 'action_pack'

I had to make changes in my code and specs for acts-as-taggable-on but those are outside of the scope of this article. No hacks required, just implementing it as the current version documents it.

And finally, when you deploy it in a production environment I recommend you run bundle —path vendor/bundler to install all the gems locally to your application. That’s because otherwise Passenger seems to not be able to recognize git based dependencies within Bundler and it will break. If you install them all isolated inside your app, Passenger can find and load them without any problems.

Config File Changes

These are copied straight from David’s blog, you can safely just copy and paste within your app:

  • config/boot.rb (replace all)
1
2
3
4
5
6
require 'rubygems'

# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE']   = File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
  • config/application.rb (append within the Application block)
1
2
# Enable the asset pipeline
config.assets.enabled = true
  • config/environments/development.rb (remove or comment out the following line)
1
#config.action_view.debug_rjs             = true
  • config/environments/production.rb (add these lines)
1
2
3
# Compress both stylesheets and JavaScripts
config.assets.js_compressor  = :uglifier
config.assets.css_compressor = :scss

Remember that you can change the Javascript compressor to YUI or something else, but I’d just stick to the defaults for now and tweak those later when your app is already up and running properly.

Move Assets

David instructs the following:

1
2
3
4
mkdir app/uploads
git mv public/images app/uploads/images
git mv public/javascripts app/uploads/javascripts
git mv public/stylesheets app/uploads/stylesheets

And then to judiciously using your tool of choice to search within all your front-end files (views, layouts, css, javascripts) and replace for the new locations:

1
2
find: /images/
replace: /uploads/

Now I will add this: you are not required to move everything from the public folder to the new app/uploads location, only those that you want or need to go through the Sprockets asset pipeline. For example, if you want to mix and compress all your CSS files than by all means move them to app/uploads/stylesheets/. But let’s say you have just one print.css that is only available for media=“print”. You can maintain that at its original public location and link to it directly.

For those Stylesheets and Javascripts that you do want within the pipeline, you will need to create manifest files. The default manifest file names are:

  • app/uploads/javascripts/application.js
1
2
3
4
5
6
7
8
9
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/uploads/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require jquery-1.3.2.min
//= require jquery-ui-1.7.2.min
//= require rails
  • app/uploads/stylesheets/application.css
1
2
3
4
5
6
7
8
/*
 * This is a manifest file that'll automatically include all the stylesheets available in this directory
 * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
 * the top of the compiled file, but it's generally better to create a new file per style scope.
 *= require akitaonrails/coderay
 *= require akitaonrails/style
 *= require akitaonrails/clearfix
*/

Most tutorials stop here. But my application has a separated Administration module with different stylesheets and javascripts, so I’ve create the following additional manifest files:

  • app/uploads/javascripts/admin/application.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/uploads/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require ../jquery-1.3.2.min
//= require ../jquery-ui-1.7.2.min
//= require ../jquery.livequery
//= require ../jquery.form
//= require ../jquery.easing.1.3
//= require ../humanmsg
//= require ./shortcut
//= require ./common
//= require ../rails
  • app/uploads/stylesheets/admin/application.css
1
2
3
4
5
6
7
8
/*
 * This is a manifest file that'll automatically include all the stylesheets available in this directory
 * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
 * the top of the compiled file, but it's generally better to create a new file per style scope.
 *= require ../formtastic
 *= require ../humanmsg
 *= require ./admin
*/

The paths are always relative to the application.[js css] file. If you have any problems, try to use “./” to point to the same folder you are. For the default manifests I didn’t have to resort to this trick but for some unknown reason I had to do that for the administration manifests.

But that is not all! Rails knows nothing about those other manifest files, so you have to declare them in your config/application.rb like this:

1
2
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
config.assets.precompile += %w( admin/application.js admin/application.css )

Just so you can follow each path, my assets tree structure looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 ~app/
   ~assets/
     +images/
     ~javascripts/
       ~admin/
         -actions.js
         -application.js
         -common.js
         -dashboard.js
         -edit-preview.js
         -pages.js
         -posts.js
        `-shortcut.js
       -application.js
       -common.js
       -humanmsg.js*
       -jquery-1.3.2.min.js*
       -jquery-ui-1.7.2.min.js
       -jquery.easing.1.3.js
       -jquery.form.js
       -jquery.jfeed.js
       -jquery.livequery.js
      `-rails.js
    `~stylesheets/
       ~admin/
         -admin.css
        `-application.css
       ~akitaonrails/
         -clearfix.css
         -coderay.css
         -print.css
         -style-ie.css*
         -style-ie6.css*
        `-style.css*
       -application.css
       -formtastic.css
       -formtastic_changes.css
       -humanmsg.css*
      `-iepngfix.htc*

Now, your layouts views can just point to the manifest file, like this:

  • app/views/layouts/application.html.erb
1
2
3
4
<%= stylesheet_link_tag 'application' %>
<%= stylesheet_link_tag "akitaonrails/print", :media => "print" %>
...
<%= javascript_include_tag 'application' %>
  • app/views/layouts/admin.html.erb
1
2
<%= javascript_include_tag "admin/application" %>
<%= stylesheet_link_tag    "admin/application" %>

Finally, in your production environment, don’t forget to statically generate your compiled assets with this Rake task:

1
RAILS_ENV=production bundle exec rake assets:precompile

It will create files such as these:

1
public/uploads/application-2a8947193a591b79c885c52fbc6b01d3.css

And your final HTML output will be like this:

1
<link href="/uploads/application-2a8947193a591b79c885c52fbc6b01d3.css" media="screen" rel="stylesheet" type="text/css" />

If one of your app/uploads files change, the pre-compiled version will also change and hence the md5 hash changes too, making it safe to put cache systems such as Varnish in front of your application, because your users will not receive old versions of your assets.

Another important thing: I maintained my static images in the public/images folder so I can just keep the same urls inside the stylesheets, such as these:

  • app/uploads/stylesheets/akitaonrails/style.css
1
.main { background:url(/images/akitaonrails/header_bg.gif) no-repeat center top;}

If I wanted to keep the images at app/uploads/images I would have to change the file to use ERB and add the following:

  • app/uploads/stylesheets/akitaonrails/style.css.erb
1
.main { background:url(<%= asset_path("akitaonrails/header_bg.gif")%>) no-repeat center top;}

That’s because in production even the images will be pre-compiled with their md5 hashes appended to their names. Check out this ticket to understand the issue and the best usage practice.

Conclusion

I had to tweak a few specs to make them pass because somethings changed (for example changing ActiveSupport::SecureRandom for just SecureRandom) but other than that the upgrade went pretty smoothly, nothing like upgrading from Rails 2.3 to Rails 3. Even then its not just a simple point release update of gems. Right now its pretty bleeding edge and several gems will break. The fixes are not complicated but unless there’s a feature you really need you should wait a few days after the official 3.1 release to let the gems maintainers add new versions as well.

On the other hand, there’s nothing wrong to anticipate the upgrade hassles and start now with the Edge version and at least check what breaks so you can monitor the required fixes being added to Github. The ideal situation is that you don’t need to point your Gemfile directly to the git repositories.

Again, benchmarks are not statements carved in stone but I just ran passenger-memory-stats and I got the following results:

1
2
3
4
5
6
7
     Passenger processes
PID    VMSize   Private  Name

30383  63.6 MB  34.9 MB  Rack: /var/webapps/akitaonrails
32433  68.5 MB  34.2 MB  Rack: /var/webapps/edge
### Processes: 7
### Total private dirty RSS: 88.97 MB

The Edge upgrade of the same application, running on the same patch version of Ruby 1.9.2, consumes a bit more memory even without the new_relic and ruby-prof gems that I’ve commented out.

I also ran a very simple Apache Bench cycle from my notebook, through my office wifi, into my production server (cross country, by the way, which is why times may not be reliable):

  • ab -c 1 -n 10 http://www.akitaonrails.com/
1
2
3
4
5
6
Total transferred:      268650 bytes
HTML transferred:       261030 bytes
Requests per second:    1.27 [#/sec] (mean)
Time per request:       786.051 [ms] (mean)
Time per request:       786.051 [ms] (mean, across all concurrent requests)
Transfer rate:          33.38 [Kbytes/sec] received
  • ab -c 1 -n 10 http://edge.akitaonrails.com/
1
2
3
4
5
6
Total transferred:      264410 bytes
HTML transferred:       257750 bytes
Requests per second:    0.70 [#/sec] (mean)
Time per request:       1433.169 [ms] (mean)
Time per request:       1433.169 [ms] (mean, across all concurrent requests)
Transfer rate:          18.02 [Kbytes/sec] received

So, Rails 3.1 Edge, right now seems to consume a little bit more memory than 3.0.5 (which is my current production version) and may probably be a bit slower. Again, take these numbers more as a curiosity for comparisons sake. Test in your own environment, because those behaviors can change considerably depending on how your application is built.

All in all, I like the new Assets Pipeline which is the largest visible change. It has created new grounds for evolution of the assets management. Be aware of the deprecations from Rails 3 to 3.1 because they will break a few gems. Overall, not a difficult upgrade, but as usual, do it with care.

There’s another article on Rails 3.1 deployment from Richard Taylor that I also recommend reading.

comentários deste blog disponibilizados por Disqus