Upgrading to Rails 1.2
If you have been following this weblog for a while, you have probably figured out that Spotstory is a Ruby on Rails application.
Over the past few days, I have been working to upgrade Spotstory from Rails 1.1.6 to Rails 1.2.3.
There is very little comprehensive information about some of the problems encountered when upgrading a Rails application from the 1.1.6 release to 1.2 (specifically, Rails 1.2.3).
Once You Freeze, Step Cautiously
Upgrading your Rails application is easy! Update, deploy, then go enjoy your apple pie! Right? Wrong.
% rake rake rails:freeze:edge TAG=rel_1-2-3
So far, so good. Let’s see if anything runs.
rake rails:update --trace rake aborted! uninitialized constant ActionController /usr/local/gems/rake-0.7.1/lib/rake.rb:1948:in `const_missing' config/../config/environment.rb:32
Why did this happen? Did the Rails installation get corrupted during the upgrade? No. Far, far simpler. On line 31 of config/environment.rb, I had a raw reference to ActionController. Due to major dependency changes in Rails 1.2, this no longer works.
# Rails 1.1.6 technique for setting session expiry: ActionController::Base.session_options[:session_expires] = Time.mktime(2037)
In Rails 1.2, it appears that this sort of configuration can not appear within the Initializer block. After moving session configuration out of the Initializer block, things started running.
Deprecations & Warnings
I decided that every deprecation message would be squashed and every warning removed. I worked on most of the warnings before even trying to get all our tests to pass; they were just too noisy for my aesthetics.
DEPRECATION WARNING: The :dependent => true option is deprecated and will be removed from Rails 2.0. Please use :dependent => :destroy instead. See http://www.rubyonrails.org/deprecation for details. See http://www.rubyonrails.org/deprecation for details. (called from has_many at vendor/rails/activerecord/lib/active_record/associations.rb:558)
(Yes, that link is actually printed twice!)
These deprecation messages are great — they tell me what has changed and how to improve my code, but they reference source files and line numbers within the Rails core. This is not helpful. I was able to find some instances of deprecated usage by recursively grepping our source (from within Emacs, which makes results electric). Very quickly, I turned to a heavier hammer.
ActiveSupport::Deprecation.debug = true
Toggling this debug variable exposes the full callstack which caused the deprecation message.
Here are the changes I needed to make in order to eliminate all our deprecation messages:
- Some model helpers have gone away or have changed in call syntax. Rewrite model methods in the following way:
Rails 1.1.6 Rails 1.2.3 Model.find_first Model.find(:first) Model.find_all Model.find(:all) Model.type Model.class.to_s Model.count(conditions) Model.count(:all, :conditions => conditions) - Has many associations no longer provide the has_ASSOCIATIONs? and ASSOCIATION_count methods. Rewrite:
Rails 1.1.6 Rails 1.2.3 Model.has_ASSOCIATIONs? ! Model.ASSOCIATIONs.empty? Model.ASSOCIATION_count Model.ASSOCIATIONs.size - Rewrite controller/view instance variable references:
@params, @session, @request, @response all become params, session, request, response. - Move to the new form generators.
form_tag ... end_form_tag
becomes:
form_tag do ... end
The same is true for form_remote_tag references. In addition, the form methods no longer need output-generating RHTML rules (remove the “=”).
- The link_to helper prefers a different way for specifying that a link will generate a POST operation.
link_to ... :post => true
becomes
link_to ... :method => :post
- Association dependencies now support more granularity, which means that the old-style:
has_many ... :dependent => true
becomes:
has_many ... :dependent => :destroy
- Add parentheses and remove whitespace between parens and method names to prevent ambiguity. Not clear why these issues didn’t cause issues in Rails 1.1.6.
Fixing Broken Tests
With our application beaten into quiet submission, it was time to turn to our tests. A good majority of our tests still worked, but a few changes were needed to get everything passing.
- Content-Type headers now include charset information; make test helpers resilient to the presence of charset.
- assert_tag with a string value for the :content option now performs a strict match against the content for any sub-tag. As a result, some :content options were converted to regular expressions.
Determining when a regular expression is necessary is not obvious, especially when the match text is embedded within another markup tag.
At this point, all of our tests passed.
Dependencies
Unfortunately, we weren’t done. The development server would unexpectedly give errors like:
User expected, got User
In addition, our User model would get missing_method errors for methods which clearly exist. The problem is an interaction between the Rails 1.2 dependency rewrite and plugins which have dependencies upon application models. Ramble On discusses a similar dependency issue.
We use a number of plugins in our application. One of these is a customized version of acts_as_commentable. This plugin defines the Comment model and expects a User model to be defined by your application.
In development mode, plugin models are not reloaded. The dependency infrastructure unloads the User model, but doesn’t realize that there are still live references from the plugin-defined Comment model. Subsequent references to a comment’s user information will generate an AssociationTypeMismatch exception giving the cryptic “expected User, got User” message. Because this is a reload problem, errors don’t happen on initial load. The errors start the second time a comment-displaying page is loaded.
Web searching didn’t reveal much at first. Some people recommended explicitly requiring the User model in application code. Others recommended replacing references to a User object with references to its id. Another solution called for explicit :class_name in the association declarations. Some of it worked some of the time; none of it worked consistently.
I started looking through the Rails dependency code and found that Nicholas Seckar (ulysses on IRC and in SVN) was responsible for much of the rewrite. After explaining my missing_method errors in an email, he replied:
See http://api.rubyonrails.org/files/vendor/rails/railties/CHANGELOG.html
Look under the changes for 1.2.0, 4th one down.
This references a late change to Rails 1.2 by Rick Olson:
Ensure plugins are in the Dependencies.load_once_paths collection by default. [Rick] If you really want your plugins to reload, add this to the very top of init.rb:
Dependencies.load_once_paths.delete(lib_path)
Ok. I need to reload my plugins. Bleh. Further, this has to happen in environment.rb, not in environments/development.rb. Bleh.
# Array of plugins with Application model dependencies.
reloadable_plugins = ["acts_as_commentable"]
# Force these plugins to reload, avoiding stale object references.
reloadable_plugins.each do |plugin_name|
reloadable_path = RAILS_ROOT + "/vendor/plugins/#{plugin_name}/lib"
Dependencies.load_once_paths.delete(reloadable_path)
end
None of this forced plugin reloading feels right. Rails should just figure it out, yes? More mail to Nicholas, leading to his response:
The problem is related to associations — the class object is cached and thus the class is never garbage collected, even though it is ‘removed.’ This causes all sorts of problems…
Since it only happens in development mode, it really doesn’t matter.
He’s partially right. It doesn’t affect production applications, but it really does affect peace-of-mind. I’m sure lots of people are freaked out when they bring up their upgraded app in development mode and find all sorts of things unexpectedly broken.
Update: I was wrong about the session configuration changes in Rails 1.2. I have updated this post accordingly.
12 Comments so far
Leave a reply
Upgrading to Rails 1.2
If you have been following this weblog for a while, you have probably figured out that Spotstory is a Ruby on Rails application.
Over the past few days, I have been working to upgrade Spotstory from Rails 1.1.6 to Rails 1.2.3.
There is very little comprehensive information about some of the problems encountered when upgrading a Rails application from the 1.1.6 release to 1.2 (specifically, Rails 1.2.3).
Once You Freeze, Step Cautiously
Upgrading your Rails application is easy! Update, deploy, then go enjoy your apple pie! Right? Wrong.
% rake rake rails:freeze:edge TAG=rel_1-2-3
So far, so good. Let’s see if anything runs.
rake rails:update --trace rake aborted! uninitialized constant ActionController /usr/local/gems/rake-0.7.1/lib/rake.rb:1948:in `const_missing' config/../config/environment.rb:32
Why did this happen? Did the Rails installation get corrupted during the upgrade? No. Far, far simpler. On line 31 of config/environment.rb, I had a raw reference to ActionController. Due to major dependency changes in Rails 1.2, this no longer works.
# Rails 1.1.6 technique for setting session expiry: ActionController::Base.session_options[:session_expires] = Time.mktime(2037)
In Rails 1.2, it appears that this sort of configuration can not appear within the Initializer block. After moving session configuration out of the Initializer block, things started running.
Deprecations & Warnings
I decided that every deprecation message would be squashed and every warning removed. I worked on most of the warnings before even trying to get all our tests to pass; they were just too noisy for my aesthetics.
DEPRECATION WARNING: The :dependent => true option is deprecated and will be removed from Rails 2.0. Please use :dependent => :destroy instead. See http://www.rubyonrails.org/deprecation for details. See http://www.rubyonrails.org/deprecation for details. (called from has_many at vendor/rails/activerecord/lib/active_record/associations.rb:558)
(Yes, that link is actually printed twice!)
These deprecation messages are great — they tell me what has changed and how to improve my code, but they reference source files and line numbers within the Rails core. This is not helpful. I was able to find some instances of deprecated usage by recursively grepping our source (from within Emacs, which makes results electric). Very quickly, I turned to a heavier hammer.
ActiveSupport::Deprecation.debug = true
Toggling this debug variable exposes the full callstack which caused the deprecation message.
Here are the changes I needed to make in order to eliminate all our deprecation messages:
- Some model helpers have gone away or have changed in call syntax. Rewrite model methods in the following way:
Rails 1.1.6 Rails 1.2.3 Model.find_first Model.find(:first) Model.find_all Model.find(:all) Model.type Model.class.to_s Model.count(conditions) Model.count(:all, :conditions => conditions) - Has many associations no longer provide the has_ASSOCIATIONs? and ASSOCIATION_count methods. Rewrite:
Rails 1.1.6 Rails 1.2.3 Model.has_ASSOCIATIONs? ! Model.ASSOCIATIONs.empty? Model.ASSOCIATION_count Model.ASSOCIATIONs.size - Rewrite controller/view instance variable references:
@params, @session, @request, @response all become params, session, request, response. - Move to the new form generators.
form_tag ... end_form_tag
becomes:
form_tag do ... end
The same is true for form_remote_tag references. In addition, the form methods no longer need output-generating RHTML rules (remove the “=”).
- The link_to helper prefers a different way for specifying that a link will generate a POST operation.
link_to ... :post => true
becomes
link_to ... :method => :post
- Association dependencies now support more granularity, which means that the old-style:
has_many ... :dependent => true
becomes:
has_many ... :dependent => :destroy
- Add parentheses and remove whitespace between parens and method names to prevent ambiguity. Not clear why these issues didn’t cause issues in Rails 1.1.6.
Fixing Broken Tests
With our application beaten into quiet submission, it was time to turn to our tests. A good majority of our tests still worked, but a few changes were needed to get everything passing.
- Content-Type headers now include charset information; make test helpers resilient to the presence of charset.
- assert_tag with a string value for the :content option now performs a strict match against the content for any sub-tag. As a result, some :content options were converted to regular expressions.
Determining when a regular expression is necessary is not obvious, especially when the match text is embedded within another markup tag.
At this point, all of our tests passed.
Dependencies
Unfortunately, we weren’t done. The development server would unexpectedly give errors like:
User expected, got User
In addition, our User model would get missing_method errors for methods which clearly exist. The problem is an interaction between the Rails 1.2 dependency rewrite and plugins which have dependencies upon application models. Ramble On discusses a similar dependency issue.
We use a number of plugins in our application. One of these is a customized version of acts_as_commentable. This plugin defines the Comment model and expects a User model to be defined by your application.
In development mode, plugin models are not reloaded. The dependency infrastructure unloads the User model, but doesn’t realize that there are still live references from the plugin-defined Comment model. Subsequent references to a comment’s user information will generate an AssociationTypeMismatch exception giving the cryptic “expected User, got User” message. Because this is a reload problem, errors don’t happen on initial load. The errors start the second time a comment-displaying page is loaded.
Web searching didn’t reveal much at first. Some people recommended explicitly requiring the User model in application code. Others recommended replacing references to a User object with references to its id. Another solution called for explicit :class_name in the association declarations. Some of it worked some of the time; none of it worked consistently.
I started looking through the Rails dependency code and found that Nicholas Seckar (ulysses on IRC and in SVN) was responsible for much of the rewrite. After explaining my missing_method errors in an email, he replied:
See http://api.rubyonrails.org/files/vendor/rails/railties/CHANGELOG.html
Look under the changes for 1.2.0, 4th one down.
This references a late change to Rails 1.2 by Rick Olson:
Ensure plugins are in the Dependencies.load_once_paths collection by default. [Rick] If you really want your plugins to reload, add this to the very top of init.rb:
Dependencies.load_once_paths.delete(lib_path)
Ok. I need to reload my plugins. Bleh. Further, this has to happen in environment.rb, not in environments/development.rb. Bleh.
# Array of plugins with Application model dependencies.
reloadable_plugins = ["acts_as_commentable"]
# Force these plugins to reload, avoiding stale object references.
reloadable_plugins.each do |plugin_name|
reloadable_path = RAILS_ROOT + "/vendor/plugins/#{plugin_name}/lib"
Dependencies.load_once_paths.delete(reloadable_path)
end
None of this forced plugin reloading feels right. Rails should just figure it out, yes? More mail to Nicholas, leading to his response:
The problem is related to associations — the class object is cached and thus the class is never garbage collected, even though it is ‘removed.’ This causes all sorts of problems…
Since it only happens in development mode, it really doesn’t matter.
He’s partially right. It doesn’t affect production applications, but it really does affect peace-of-mind. I’m sure lots of people are freaked out when they bring up their upgraded app in development mode and find all sorts of things unexpectedly broken.
Update: I was wrong about the session configuration changes in Rails 1.2. I have updated this post accordingly.
12 Comments so far
-
spotstory » Release: KML Support, Rails 1.2.3 April 24th, 2007 8:45 am
[…] This isn’t a change you’ll notice, but we get a lot of traffic from the Ruby on Rails community, so we thought it made sense to mention that our production environment is now using Rails 1.2.3. For details, you can see Aron’s great post about his Rails 1.2.3 upgrade travails. […]
-
Googletron::new(’en’) » Blog Archive » User expected, got User? May 7th, 2007 6:38 pm
[…] By far one of the most annoying and unhelpful messages on Rails ever. the “User expected, got User” message tends to pop up now and then when using plugins such as acts_as_commentable or acts_as_taggable, that define models on the vendor/plugins. The solution, as pointed out by the localhost:3000 blog (nice blog name, I must say ;)), is moving any models that relate to models on your application to your own /models folder. The reason behind the problem is that the libs don’t get reloaded on each server request, whereas the models are (some thoughts on things that reload here). So theoretically, plugins that define models inside them are a bad bad practice. There’s another less intrusive way to make the plugins workm though: you can add the plugins on the ‘reloadable path’, as explained on spotstory: […]
-
’s blog » links for 2007-06-01 June 1st, 2007 2:18 am
[…] spotstory » Upgrading to Rails 1.2 (tags: rails) […]
-
nick June 6th, 2007 7:25 pm
Thanks for the explanation. I’ve run into this problem now in trying to cache database ‘constants’ from a file in the lib directory. This has been the most useful description of the problem I’ve found yet.
-
Robert June 19th, 2007 4:17 pm
Very helpful.
We also got stung by a discrepancy between the way 1.1.6 and 1.2.3 handles updates to a has_one relationship. When updating the parent, 1.1.6 updates the child, while 1.2.3 does not. See example here:
-
User expected, got User? July 7th, 2007 9:31 am
[…] Sem dúvida uma das mensagens mais irritantes que já tive o desprazer de encontrar nas andanças pelo Rails, a infame “User expected, got User” tende a aparecer, quando se utilizam plugins como o acts_as_commentable ou o acts_as_taggable, que definem Models dentro do diretório vendor/plugins. A solução, como apontado pelo blog localhost:3000 blog (belo nome para um blog, diga-se de passagem ;)), é simples (apesar de meio “xunxo”): basta mover os models do plugin para o diretório apps/models de sua aplicação. A razão por trás do problema é que as libs (e o conteúdo do diretório vendor) não são carregadas a cada request, quando utilizando o modo “development”, diferentemente de todo o conteúdo do /app . Teoricamente, definir models dentro de plugins é uma má pratica, e deve ser evitado. Vale notar que o problema não acontece em modo production, onde os models não são recarregados o tempo todo. Existe ainda uma maneira menos intrusiva de adicionar os plugins ao ‘reloadable path’, fazendo com que eles sejam recarregados junto da aplicação, conforme explicado neste link: […]
-
» links for 2007-07-25 | Paul Cowles August 28th, 2007 1:58 pm
[…] spotstory » Upgrading to Rails 1.2 gotchas for the 1.1 to 1.2 rails upgrade (tags: rails ruby) […]
-
Brian McNaboe February 11th, 2008 11:00 am
I’m seeing this ‘User expected, got User’ in Rails 2.0.2 now (and wasn’t see it in 1.2.3). I also run into what appears to be the same problem when using the spec_server.
Putting the fix above in my development.rb file seems to have resolved the problem in development, but not for the spec_server (even when placed in environment.rb). I’ll need to dig deeper into that.
Thanks for saving me a ton of time!
-
skwpspace – Rails 2.0 dependency system problems February 27th, 2008 2:31 pm
-
hervalicio.us » Blog Archive » User expected, got User? May 11th, 2008 6:56 pm
[…] Sem dúvida uma das mensagens mais irritantes que já tive o desprazer de encontrar nas andanças pelo Rails, a infame “User expected, got User” tende a aparecer, quando se utilizam plugins como o acts_as_commentable ou o acts_as_taggable, que definem Models dentro do diretório vendor/plugins. A solução, como apontado pelo blog localhost:3000 blog (belo nome para um blog, diga-se de passagem ;)), é simples (apesar de meio “xunxo”): basta mover os models do plugin para o diretório apps/models de sua aplicação. A razão por trás do problema é que as libs (e o conteúdo do diretório vendor) não são carregadas a cada request, quando utilizando o modo “development”, diferentemente de todo o conteúdo do /app . Teoricamente, definir models dentro de plugins é uma má pratica, e deve ser evitado. Vale notar que o problema não acontece em modo production, onde os models não são recarregados o tempo todo. Existe ainda uma maneira menos intrusiva de adicionar os plugins ao ‘reloadable path’, fazendo com que eles sejam recarregados junto da aplicação, conforme explicado neste link: […]
-
hervalicio.us » User expected, got User? May 19th, 2008 3:03 pm
[…] By far one of the most annoying and unhelpful messages on Rails ever. the “User expected, got User” message tends to pop up now and then when using plugins such as acts_as_commentable or acts_as_taggable, that define models on the vendor/plugins. The solution, as pointed out by the localhost:3000 blog (nice blog name, I must say ;)), is moving any models that relate to models on your application to your own /models folder. The reason behind the problem is that the libs don’t get reloaded on each server request, whereas the models are (some thoughts on things that reload here). So theoretically, plugins that define models inside them are a bad bad practice. There’s another less intrusive way to make the plugins workm though: you can add the plugins on the ‘reloadable path’, as explained on spotstory: […]
-
Pwhndvve August 9th, 2008 8:52 am
Honi soit look closer buy cytotec dead hand held.
Leave a reply


[…] This isn’t a change you’ll notice, but we get a lot of traffic from the Ruby on Rails community, so we thought it made sense to mention that our production environment is now using Rails 1.2.3. For details, you can see Aron’s great post about his Rails 1.2.3 upgrade travails. […]
[…] By far one of the most annoying and unhelpful messages on Rails ever. the “User expected, got User” message tends to pop up now and then when using plugins such as acts_as_commentable or acts_as_taggable, that define models on the vendor/plugins. The solution, as pointed out by the localhost:3000 blog (nice blog name, I must say ;)), is moving any models that relate to models on your application to your own /models folder. The reason behind the problem is that the libs don’t get reloaded on each server request, whereas the models are (some thoughts on things that reload here). So theoretically, plugins that define models inside them are a bad bad practice. There’s another less intrusive way to make the plugins workm though: you can add the plugins on the ‘reloadable path’, as explained on spotstory: […]
[…] spotstory » Upgrading to Rails 1.2 (tags: rails) […]
Thanks for the explanation. I’ve run into this problem now in trying to cache database ‘constants’ from a file in the lib directory. This has been the most useful description of the problem I’ve found yet.
Very helpful.
We also got stung by a discrepancy between the way 1.1.6 and 1.2.3 handles updates to a has_one relationship. When updating the parent, 1.1.6 updates the child, while 1.2.3 does not. See example here:
http://dev.rubyonrails.org/ticket/8673
[…] Sem dúvida uma das mensagens mais irritantes que já tive o desprazer de encontrar nas andanças pelo Rails, a infame “User expected, got User” tende a aparecer, quando se utilizam plugins como o acts_as_commentable ou o acts_as_taggable, que definem Models dentro do diretório vendor/plugins. A solução, como apontado pelo blog localhost:3000 blog (belo nome para um blog, diga-se de passagem ;)), é simples (apesar de meio “xunxo”): basta mover os models do plugin para o diretório apps/models de sua aplicação. A razão por trás do problema é que as libs (e o conteúdo do diretório vendor) não são carregadas a cada request, quando utilizando o modo “development”, diferentemente de todo o conteúdo do /app . Teoricamente, definir models dentro de plugins é uma má pratica, e deve ser evitado. Vale notar que o problema não acontece em modo production, onde os models não são recarregados o tempo todo. Existe ainda uma maneira menos intrusiva de adicionar os plugins ao ‘reloadable path’, fazendo com que eles sejam recarregados junto da aplicação, conforme explicado neste link: […]
[…] spotstory » Upgrading to Rails 1.2 gotchas for the 1.1 to 1.2 rails upgrade (tags: rails ruby) […]
I’m seeing this ‘User expected, got User’ in Rails 2.0.2 now (and wasn’t see it in 1.2.3). I also run into what appears to be the same problem when using the spec_server.
Putting the fix above in my development.rb file seems to have resolved the problem in development, but not for the spec_server (even when placed in environment.rb). I’ll need to dig deeper into that.
Thanks for saving me a ton of time!
[…] http://blog.spotstory.com/2007/04/19/upgrading-to-rails-12/ […]
[…] Sem dúvida uma das mensagens mais irritantes que já tive o desprazer de encontrar nas andanças pelo Rails, a infame “User expected, got User” tende a aparecer, quando se utilizam plugins como o acts_as_commentable ou o acts_as_taggable, que definem Models dentro do diretório vendor/plugins. A solução, como apontado pelo blog localhost:3000 blog (belo nome para um blog, diga-se de passagem ;)), é simples (apesar de meio “xunxo”): basta mover os models do plugin para o diretório apps/models de sua aplicação. A razão por trás do problema é que as libs (e o conteúdo do diretório vendor) não são carregadas a cada request, quando utilizando o modo “development”, diferentemente de todo o conteúdo do /app . Teoricamente, definir models dentro de plugins é uma má pratica, e deve ser evitado. Vale notar que o problema não acontece em modo production, onde os models não são recarregados o tempo todo. Existe ainda uma maneira menos intrusiva de adicionar os plugins ao ‘reloadable path’, fazendo com que eles sejam recarregados junto da aplicação, conforme explicado neste link: […]
[…] By far one of the most annoying and unhelpful messages on Rails ever. the “User expected, got User” message tends to pop up now and then when using plugins such as acts_as_commentable or acts_as_taggable, that define models on the vendor/plugins. The solution, as pointed out by the localhost:3000 blog (nice blog name, I must say ;)), is moving any models that relate to models on your application to your own /models folder. The reason behind the problem is that the libs don’t get reloaded on each server request, whereas the models are (some thoughts on things that reload here). So theoretically, plugins that define models inside them are a bad bad practice. There’s another less intrusive way to make the plugins workm though: you can add the plugins on the ‘reloadable path’, as explained on spotstory: […]
Honi soit look closer buy cytotec dead hand held.