#318 Upgrading to Rails 3.2
Rails 3.2 has just been released and there’s a great post on the official blog that explains some of the changes. The more significant of these include a faster development mode which now only reloads the files which have changed since the last request. There’s also an entirely new routing engine called Journey which is also much quicker. In this episode we’ll show you how to upgrade a Rails 3.1 application to Rails 3.2 and some of its new features.
Upgrading an Application
To upgrade an existing Rails 3.1 app to Rails 3.2 the first thing we need to do is to modify its gemfile.
source 'http://rubygems.org' gem 'rails', '3.2.0' gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', " ~> 3.2.3" gem 'coffee-rails', "~> 3.2.1" gem 'uglifier', '>=1.0.3' end gem 'jquery-rails'
We’ve upgraded the rails gem to version 3.2.0
and we’ve also changed the gems in the assets group so that the version numbers match those used in a new Rails 3.2 application. To update everything to the latest version we’ll need to run bundle update
.
As well as modifying the gemfile there’s some additional environment configuration that we can set to make our application match a new Rails 3.2 application. In the development config file we’ll add these lines.
# Raise exception on mass assignment protection for Active Record models config.active_record.mass_assignment_sanitizer = :strict # Log the query plan for queries taking more than this (works # with SQLite, MySQL, and PostgreSQL) config.active_record.auto_explain_threshold_in_seconds = 0.5
The first of these sets mass_assignment_sanitizer
to :strict
which will raise an exception if the mass assignment goes against the protection. The second line sets the auto-explain threshold to 0.5
. This means that any database queries that take more than half a second to process will have an EXPLAIN query run against them and the output from that command will be written to the log. It’s a good idea to copy the mass_assignment_sanitizer
option to the test environment too to match new Rails 3.2 apps and also to remove the following line as it’s no longer generated by default.
# Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets config.assets.allow_debugging = true
EXPLAIN Queries
Now that our application has been upgraded to Rails 3.2 we’ll show some of its features in the Rails console. We’ve already mentioned that long-running database queries will automatically have an EXPLAIN query run against them, but we can also trigger this in the console by running explain
against an Active Record query.
1.9.2-p290 :001 > Product.order(:name).explain Product Load (0.6ms) SELECT "products".* FROM "products" ORDER BY name EXPLAIN (0.1ms) EXPLAIN QUERY PLAN SELECT "products".* FROM "products" ORDER BY name => "EXPLAIN for: SELECT \"products\".* FROM \"products\" ORDER BY name\n0|0|0|SCAN TABLE products (~1000000 rows)\n0|0|0|USE TEMP B-TREE FOR ORDER BY\n"
We can print out the output in a cleaner way with puts _
.
1.9.2-p290 :002 > puts _ EXPLAIN for: SELECT "products".* FROM "products" ORDER BY name 0|0|0|SCAN TABLE products (~1000000 rows) 0|0|0|USE TEMP B-TREE FOR ORDER BY
This EXPLAIN query isn’t very interesting as it’s only for a simple query but this feature is much more useful for complex JOIN queries. If there’s a place in our application that we know runs a slow database query and we don’t want an EXPLAIN query generated each time we can wrap the query in a silence_auto_explain
block like this:
1.9.2-p290 :003 > ActiveRecord::Base.silence_auto_explain { Product.order(:name) }
The query inside the block won’t generate an EXPLAIN query now no matter how long it takes to run.
New ActiveRecord Methods
Another useful new feature of Rails 3.2 is the pluck method. This allows us to pass in a column name and will return all the values for that column.
1.9.2-p290 :005 > Product.pluck(:name) (0.2ms) SELECT name FROM "products" => ["Settlers of Catan", "Flux Capacitor", "Technodrome", "Agricola", "Millennium Falcon", "Ryan's Cheesecake", "Answer to Everything"]
We can do this for any column and it will return all of its values in an array of the appropriate data type.
1.9.2-p290 :007 > Product.pluck(:id) (0.2ms) SELECT id FROM "products" => [1, 2, 3, 4, 5, 6, 7]
On a similar note if we use the select clause to restrict the columns that are returned there’s a new uniq method that we can call. We can use this, for example, to return one product for each unique name.
1.9.2-p290 :009 > Product.select(:name).uniq Product Load (0.3ms) SELECT DISTINCT name FROM "products" => [#<Product name: "Agricola">, #<Product name: "Answer to Everything">, #<Product name: "Flux Capacitor">, #<Product name: "Millennium Falcon">, #<Product name: "Ryan's Cheesecake">, #<Product name: "Settlers of Catan">, #<Product name: "Technodrome">]
Another related new feature, first_or_create, is designed to work with a where
clause.
1.9.2-p290 :010 > Product.where(name: "Foo").first_or_create!
We’ve used the optional bang here so that an exception will be raised if there are any validation errors. The code above works in a similar way to find_or_create_by_name
. The first time we run it it will create a new Product
with that name
(assuming there isn’t already an existing one); when we run it again it will return the matching product that’s found. What’s nice about this approach is that we can pass additional attributes to the create
call like this:
1.9.2-p290 :011 > Product.where(name: "Foo").first_or_create!(price: 5)
Any newly-created records will inherit these attributes but they won’t be taken into account when searching for a Product
.
The final new method we’ll show you is called safe_constantize
and is called against a string.
1.9.2-p290 :007 > "Product".safe_constantize => Product(id: integer, name: string, price: decimal, description: text, discontinued: boolean, created_at: datetime, updated_at: datetime)
This will return a matching constant if one exists. The difference between this and the normal constantize
method is that it returns nil
if no matching constant is found instead of throwing an exception.
New Migration Features
Next we’ll show some new options that we can use when we generate a migration. Let’s say that we’re generating a new ProductVariation
model. Here’s our migration.
$ rails g model product_variation product_id:integer:index name 'price:decimal{7,2}'
This migration uses three new features. We want a ProductVariation
to belong to a Product
and so we’ve specified product_id
as one of the columns. In Rails 3.2 we can specify a third index
option and this will automatically add an index to the column.
We want our ProductVariation
model to have a string name
column. Normally we’d use name:string
in our migration to do this but string
is now the default so we don’t need to specify a datatype for a string column. Finally if we’re defining a decimal
field we can pass in options to specify the precision
and scale
of that datatype. Note that it’s necessary to quote this part of the migration so that the shell doesn’t try to modify it.
Here’s what the generated migration looks like.
class CreateProductVariations < ActiveRecord::Migration def change create_table :product_variations do |t| t.integer :product_id t.string :name t.decimal :price, :precision => 7, :scale => 2 t.timestamps end add_index :product_variations, :product_id end end
We can see the products we specified in this migration and in it the product_id
field has an index defined, the name
field has defaulted to string
and the price
field has precision
and scale
options defined.
Speaking of generators you might have some options that you always use when you’re generating a new Rails app such as specifying a specific database or excluding unit tests. We can make these options the default by storing them in a .railsrc
file in our home directory.
$ echo -d postgresql -T > ~/.railsrc
When we create a new Rails app now those options will be automatically added.
$ rails new blog Using -d postgresql -T from /Users/eifion/.railsrc
Key-Value Storage
ActiveRecord now comes with a new way to define key-value storage in models. For example lets say that in our ProductVariation
model we have some attributes such as colour
and size
and we don’t want separate database columns for each one. We can use the new store
method to store these values in a single column.
class ProductVariation < ActiveRecord::Base store :properties, accessors: [:colour, :size] end
The first argument here contains the name of the column that the values will be stored in and the second defines the properties we want to store. We’ll still need to create this database column so we’ll need a database migration as well.
$ rails g migration add_properties_to_product_variations properties:text
Don’t forget to run rake db:migrate
to add the column to the database.
We’ll demonstrate this in the console. We can create a new ProductVariation
and give it colour
and size
attributes in the same way we would for normal attributes.
Loading development environment (Rails 3.2.0) 1.9.2-p290 :001 > p = ProductVariation.new(colour: 'blue', size: 3) => #<ProductVariation id: nil, product_id: nil, name: nil, price: nil, created_at: nil, updated_at: nil, properties: {:colour=>"blue", :size=>3}>
We can access these attributes just as we would any others.
1.9.2-p290 :003 > p.colour => "blue"
We can access them through the properties
hash too.
1.9.2-p290 :004 > p.properties[:colour] => "blue"
As these attributes aren’t actual database columns we can’t search by them but this is a convenient way to access a hash of attributes.
Tagged Logging
There’s one more feature that we’ll show you, tagged logging. This is something that’s normally only done in production mode but we’ll add to our development environment so that we can demonstrate it. We do this by setting the log_tags
configuration option and we set it to an array of attributes that are the names of methods that will be called against request.
config.log_tags = [:uuid, :remote_ip]
We’ve used uuid which is a new method in Rails 3.2 which returns a unique request id and which lets us determine which request the log came from. The other is the remote IP address. When we start the server up now and make a request each line in the log is tagged with those values.
[ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Started GET "/" for 127.0.0.1 at 2012-01-27 21:52:58 +0000 [ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Processing by ProductsController#index as HTML [ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Product Load (0.3ms) SELECT "products".* FROM "products" [ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Rendered products/index.html.erb within layouts/application (22.0ms) [ab939dfca5d57843ea4c695cab6f721d] [127.0.0.1] Completed 200 OK in 81ms (Views: 73.1ms | ActiveRecord: 0.3ms) [98eec5f8976586c1165b981797086b6a] [127.0.0.1]
This becomes very useful in production when there are multiple Rails instances writing to the log files. This will enable us to separate out the log entries that are associated with a specific request.