Archive for the ‘rails’ tag
Rails 3 upgrade part 4: Prototype helpers and Javascript
Rails 3 is embracing the unobtrusive Javascript (or UJS) mantra which is good because it is the right way; at the same time, it is bad because many applications will break when they upgrade to Rails 3. On the other hand, who’s expecting a smooth upgrade anyway :)
In my test application, I used jrails because I am more interested in jQuery than Prototype. But since jrails doesn’t work with Rails 3, I removed it.
When jrails was removed, I received this error:
undefined method `observe_field' for #<#<Class:0xb6867e58>:0xb6865b6c>
Install Prototype helper plugin
‘observe_field’ is a Prototype helper and Rails 3 removed the the link between its Javascript helpers and Prototype. The goal in Rails 3 is for developers to use their preferred Javascript library. Also note that remote_#{method} helpers have been removed from Rails and moved to Prototype Legacy Helper plugin . To install this plugin, just do:
rails plugin install git://github.com/rails/prototype_legacy_helper
Remove jQuery
Once the prototype_legacy_helper is installed, the missing method is gone but observe_field is not triggering. Removing jQuery fixes this problem.
Now what if you want to use jQuery instead of Prototype? It depends how dependent your application is to Prototype. I have not found a jQuery equivalent for Prototype helper plugin yet so that would be an issue like in my case. Based on this jQuery and Rails 3 tutorial, using the jQuery UJS driver looks very easy.
Previous: Rails 3 upgrade part 3: Code fixes, views, and forms
Rails 3 upgrade part 3: Code fixes, views, and forms
This is part 3 of my Rails 2 to Rails 3 upgrade experience. Part 1 is about the initial code upgrade and getting the application to boot while part 2 deals with routes. While Part 2 is mainly about routes, getting it work involved changes in other parts of the code which I’ll share this time. So while you are updating your routes, you may need to check this post in between changes.
Update ApplicationController
After regenerating your application with rails (i.e. rails new appname -d dbadapter), your ApplicationController would look like this:
class ApplicationController < ActionController::Base protect_from_forgery end
There’s no need to panic because rails:upgrade:backup made a copy of the controller to application_controller.rb.rails2.
If you have a lot of helper modules, you’ll most likely have this code in your Rails 2 ApplicationController:
helper :all
If you encounter a missing method error while monkey clicking your application, you probably forgot to update your Rails 3 ApplicationController.
Update ApplicationHelper
The ApplicationHelper module was also modified by the rails upgrde. So don’t forget to update this, too.
RAILS_* constants are deprecated is not entirely true
When you run rails:upgrade:check, it will list items you need to update including deprecated code. There is no need to change these as the word ‘deprecated’ means but I encountered several “can’t convert nil into String” errors.
rake rails:upgrade:check (in /mnt/hgfs/greg-mini/dev/projects/propsify) Deprecated constant(s) Constants like RAILS_ENV, RAILS_ROOT, and RAILS_DEFAULT_LOGGER are now deprecated. More information: http://litanyagainstfear.com/blog/2010/02/03/the-rails-module/ The culprits: ...
The weird part is some constants are just doing fine. In any case, here are the conversion:
RAILS_ROOT -> Rails.root RAILS_ENV -> Rails.env RAILS_DEFAULT_LOGGER -> Rails.logger
You can also check your environment the Ruby way:
# before if RAILS_ENV == 'production' ... # Rails 3 if Rails.env.production?
Output strings are automatically escaped
We should all be rejoicing that Rails is now serious about XSS protection except now your pages have become ugly with all those HTML tags. For example the code below will not give you a clickable link.
- signup = link_to('create one here', signup_path)
= "If you do not have an account, #{signup}."
To fix this, use the raw() helper.
= raw "If you do not have an account, #{signup}."
Too bad for me, I got tons of views that were coded like this.
Check for ‘concat’
A popular technique to simplify your view code is to use content blocks. You create a helper that takes a block and wraps it in some HTML tags. A simple implementation would look like this:
module LayoutHelper
def main_column(options={}, &block)
# calls column()
end
def column(options={}, &block)
# concat is not needed in Rails 3
concat content_tag(:div, capture(&block), options)
end
end
# in your view
- main_column do
= render 'form'
This works fine in Rails 2 but in Rails 3 the block gets outputted twice. concat is the way to output text in a non-output block (i.e. <% %> in erb) but it seems like erb blocks in Rails 3 do not need concat.
Helpers with blocks
Before Rails 3, form_for or fields_for use non-output syntax; it means no equals sign.
# erb <% form_for @offer do |f| %> # ... <% end %> # haml - form_for @offer do |f| # ...
In Rails 3, it should now be written as an output block.
# erb
<%= form_for @offer do |f| %>
# ...
<% end %>
# haml
= form_for @offer do |f|
= f.fields_for :items do |ff|
# ...
The rule is if the method is expected to return a string, it should use the output syntax. If it just buffering the returned string like content_for, it should NOT have the equals sign.
Previous: Rails 3 upgrade part 2: Routes
Next: Rails 3 upgrade part 4: Prototype helpers and Javascript
Rails 3 upgrade part 2: Routes
In the previous post, I outlined the steps I took to upgrade and boot a Rails 3 application. This time, I share my experience upgrading the routes file. By the way, I forgot to mention in the last post that I’m using Rails 3 Upgrade Handbook by Jeremy McAnally.
The task rails:upgrade:routes (comes with the rails_upgrade plugin) converts your Rails 2 routes into Rails 3 format. It handles most cases but you may still need to edit the generated routes depending on your setup.
map.root
Below, I show the old route and the generated version.
# Rails 2 map.root :controller => 'search' # Rails 3 match '/' => 'search#index'
The conversion is correct but since I use the named route ‘root_path’ in my application, I had to change it:
root :to => 'search#index'
:as, :member, :any, :path_names
# Rails 2
map.resources :workspaces, :as => 'b', :member => { :widget => :get } do |workspace|
# ...
end
# Rails 3
resources :workspaces do
# ...
end
In Rail 3, :as is for overriding the normal naming for named routes witout affecting the path. For example, the code below will recognize the path ‘/workspaces’ and the named route becomes offices_path.
resources :workspaces, :as => "offices"
In Rails 2, :as affects the path. In my example, ‘/b’ routes the request to WorkspacesController. So for Rails 3 to recognize the path ‘/b’, I need to add another route.
match 'b' => 'workspaces#index'
The rails:upgrade:routes did not convert the following member route and had to be added.
:member => { :widget => :get }
The new route becomes:
resources :workspaces do get :widget, :on => :member end
In Rails 2, you can use the :any option to define a custom route that responds to any request method.
# Rails 2
workspace.resource :twitter_account, :member => { :authorize => :any }, :path_names => { :edit => 'request_authorization' }
# generated by rails:upgrade:routes
resource :twitter_account do
member do
any :authorize
end
end
The rails:upgrade:routes converted the :any option. However, when I booted the application, it raised an exception:
undefined method `any' for #<ActionDispatch::Routing::Mapper:0xb71b6fcc> (NoMethodError)
To fix this, I replaced the offending line with a match method.
resource :twitter_account do match :authorize, :on => :member end
:path_names was also not included in the generated route so has to be added as well.
resource :twitter_account, :path_names => { :edit => 'request_authorization' } do
match :authorize, :on => :member
end
Specifying a different controller
# Rails 2
map.resource :settings, :controller => 'users' do |settings|
settings.resource :twitter_account, :name_prefix => nil, :member => { :authorize => :any }, :path_names => { :edit => 'request_authorization' }
end
# generated by rake:upgrade:routes
resource :settings do
resource :twitter_account do
member do
any :authorize
end
end
end
To fix, just specify the controller
resource :settings,:controller => :users do # ... end
Undefined named route helper
I encountered this exception while trying the application:
undefined method `edit_twitter_account_path'
In Rails 2, this is the route that created this named route:
map.resource :settings, :controller => 'users' do |settings|
settings.resource :twitter_account, :name_prefix => nil, :member => { :authorize => :any }, :path_names => { :edit => 'request_authorization' }
end
This is a bit tricky for me because I can’t remember why I nested it :) Nevertheless, to fix the Rails 3 error, I moved :twitter_account outside of :settings. The correct Rails routes now look like these:
resource :settings, :controller => :users
resource :twitter_account, :path_names => { :edit => 'request_authorization' } do
match :authorize, :on => :member
end
Custom polymorphic named route helper
A long time ago, I played around with polymorphic paths. In hindsight, that is a waste of time but back then it was fun or should I say a time well wasted. I have a named route helper that takes any object and used like this:
# in views
link_to 'invitations', invitations_path(@voteable)
# definition
module RoutesHelper
def invitations_path(voteable)
send("#{voteable.class.name.underscore}_invitations_path", voteable)
end
def workspace_invitations_path(workspace)
super(:workspace_id => workspace)
end
# ...
end
In Rails 3, my named route helper is not being called. Thus, wrong URL is generated. I know, I know it should have been a simple polymorphic_path call but I still wonder why my method is not called. Moving on, the new ruby is:
link_to 'invitations', polymorphic_path([@voteable, :invitations])
I cheated a bit here because I want this post to focus on routes. Along the way, I had to update non-route related code to discover the route problems. You can learn more about Rails 3 routes from this RailsGuides page.
There are still more updates to be done and I’ll share them in other posts. Just like your favorite late night infomercial, “Wait! There’s more”.
Rails 3 upgrade part 1: Booting the application
It’s time for another Rails upgrade! We all have our share of bad experiences and frustrations every time we upgrade a piece of software. Even for technical people who live and breath on the edge, upgrades are one of these things we try to avoid as much as possible. Still, there is always a sense of excitement in trying something new even if it adds problems to an already stable piece of code.
For a little background, I am upgrading a Rails app several of friends and I have written last year. The code is available at github.
In this post, I share the steps I did to boot the application. This doesn’t mean the upgrade went fine neither the app is ready to go. It only means all the required initialization are OK. In succeeding posts, I share my experiences in upgrading the app to a green state.
First, my environment.
greg@piccolo:~/dev/projects/propsify3$ rvm info
ruby-1.8.7-p299@propsify:
system:
uname: "Linux piccolo 2.6.31-22-generic #61-Ubuntu SMP Wed Jul 28 01:57:06 UTC 2010 i686 GNU/Linux"
shell: "bash"
version: "4.0.33(1)-release"
rvm:
version: "rvm 0.1.44 by Wayne E. Seguin (wayneeseguin@gmail.com) [http://rvm.beginrescueend.com/]"
ruby:
interpreter: "ruby"
version: "1.8.7"
date: "2010-06-23"
platform: "i686-linux"
patchlevel: "2010-06-23 patchlevel 299"
full_version: "ruby 1.8.7 (2010-06-23 patchlevel 299) [i686-linux]"
greg@piccolo:~/dev/projects/propsify3$ script/about
About your application's environment
Ruby version 1.8.7 (i686-linux)
RubyGems version 1.3.7
Rack version 1.0 bundled
Rails version 2.3.2
Active Record version 2.3.2
Action Pack version 2.3.2
Active Resource version 2.3.2
Action Mailer version 2.3.2
Active Support version 2.3.2
Application root /mnt/hgfs/greg-mini/dev/projects/propsify
Environment development
Database adapter postgresql
Database schema version 20100113032723
greg@piccolo:~/dev/projects/propsify3$ gem list
*** LOCAL GEMS ***
actionmailer (2.3.2)
actionpack (2.3.2)
activerecord (2.3.2)
activeresource (2.3.2)
activesupport (2.3.2)
geokit (1.5.0)
json (1.4.5)
mime-types (1.16)
oauth (0.4.1)
pg (0.9.0)
rails (2.3.2)
rake (0.8.7)
RedCloth (4.2.2)
twitter_oauth (0.3.2)
greg@piccolo:~/dev/projects/propsify3$ ls vendor/gems/
authlogic-2.1.3 geokit-1.5.0 haml-2.2.16 macaddr-1.0.0 twitter_oauth-0.3.2 uuid-2.1.0
greg@piccolo:~/dev/projects/propsify3$ ls vendor/plugins/
acts_as_commentable geokit-rails is_taggable thinking-sphinx will_paginate
declarative_authorization gravatar-plugin jrails validates_date_time
exception_notification haml subdomain-fu vote_fu
Step 1: Install rails 3
gem install rails --pre
Step 2: Install the plugin tool
script/plugin install git://github.com/rails/rails_upgrade.git
Step 3: Show upgrade checklist
rake rails:upgrade:check
This task lists the items you should watch out for when doing the upgrade. You don’t need to fix everything right away (some are deprecation notice) but review the checklist nevertheless.
Step 4: Generate the new routes
rake rails:upgrade:routes
This task reads the current config/routes.rb and outputs a Rails 3 version.
Don’t worry, it doesn’t override your routes file. Keep this in a safe place for later use.
IMPORTANT: I actually didn’t realize I did the right thing until after the actual code upgrade. When I tried generating the new routes after the code change, it outputted an empty block. I have no idea if this is unique to my case but just to be sure, generate the routes beforehand and keep a copy.
Step 5: Create Gemfiles
rails:upgrade:gems
Next is to generate the file ‘Gemfile’. In Rails 2, the gems you need are listed in config/environment.rb while in Rails 3 the gems are listed in the Gemfile. Gemfile is used by the program ‘bundler’ to manage the gems required by your application. Unfortunately, this task didn’t include the gems I listed in environment.rb so I have to add it later.
Step 6: Backup your files
rails:upgrade:backup
I hope you are working on another branch (or a copy) but just in case you are not, run this task to make copies of the files that will be affected during the upgrade.
Now comes the juicy part.
Step 7: Generate the Rails 3 app on top of your Rails 2 app
rails new propsify3 -d postgresql
Run this command in your app’s parent folder. In my case, my app’s name and pathname is ‘propsify3′ and I am using postgresql as my database. This command created and replaced a bunch of files. Since you’ve backed-up everything, there’s nothing to worry.
Step 8: Move code from environment.rb to application.rb
Your new config/environment.rb file looks like it went through a rigorous diet. You can leave this file for now. What is important now is you move the initializer code from your config/environment.rb.rails2 to config/application.rb. These are the config.* lines except the config.gem which goes to Gemfile.
Step 9: Convert the new routes
You can still use the existing routes until 3.1 but since there’s a tool to help you migrate, I suggest doing it. At this point, when I tried the rails:upgrade:routes, no routes were generated. So make sure you generate the routes before Step 7.
Step 10: Delete new_rails_defaults.rb
rm config/initializers/new_rails_defaults
Step 11: Upgrade the plugins and gems
Many plugins are now available as gems. Check your plugins and gems at http://railsplugins.org. In my case, the following plugins were converted to gems:
acts_as_commentable declarative_authorization haml will_paginate thinking-sphinx
Unfortunately, the plugins below are not yet ready for Rails 3. I removed them for now and all code that references them.
jrails subdomain-fu vote_fu
IMPORTANT: In your Gemfile, make sure you check specify the right version that is compatible with Rails 3. Some gems are still in the pre-release version and will not be downloaded if you don’t specify a version in your Gemfile. For example, this is a snippet from my Gemfile:
gem 'pg' gem 'acts_as_commentable' gem 'declarative_authorization' gem 'haml' gem 'thinking-sphinx', '2.0.0.rc1', :require => 'thinking_sphinx' gem 'will_paginate', '3.0.pre2' gem 'uuid' gem 'geokit'
Step 12: Update initialization code
After step 10 you are good to go, if you’re lucky. In my case, I had to remove some patches and change code to boot the application.
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!(date_time_formats)
This fails in Rails 3 because core extensions have been moved out of their modules and are now included in classes they extend. For example, to fix the date format problem do:
Date::DATE_FORMATS.merge!(date_time_formats)
Step 13: Boot the app
rails server
Yay! If you are wondering what happened to script/server command, Rails went the “Merb way” and consolidated the script/* commands into the rails script.
By now, you should see the famous Rails’ “Welcome aboard” message in your browser.
Step 14: Remove public/index.html
Now, you can try if your application is working.
There are still more work to do like moving to the ActiveRecord/ActiveRelation API and removing the deprecation notices. Before moving on, I still need to fix the problems in my routes and unsupported gems which I will tackle in my next post.
DHH’s RailsConf 2010 Keynote Video
The keynote is about Rails 3.0 and the many enhancements it bring to make web application development more fun. The improvements in writing database queries (via ActiveRelation), routes, ActionMailer are really neat and I believe would make it easier for developers to get on board with Rails.
The official release will be available in a few weeks but the current version is already good enough for production use according to DHH.