#318 Upgrading to Rails 3.2
Rails 3.2 está recién salido y hay una anotación muy interesante en el blog oficial que explica algunas de las novedades. Lo más importante de todo es que el entorno de desarrollo carga con mayor velocidad porque sólo recarga los archivos que han cambiado desde la última petición. También hay un nuevo motor de rutas llamado Journey que también es más rápido. En este episodio veremos cómo actualizar una aplicación de Rails 3.1 a Rails 3.2, así como algunas de las nuevas funcionalidades.
Actualización de una aplicación ya existente
Para actualizar una aplicación Rails 3.1 a 3.2 lo primero que tenemos que hacer es modificar el 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'
Hemos actualizado la versión de la gema a 3.2.0
y también hemos cambiado la gema del grupo de estáticos para que los números de versión coincidan con los que aparecerían en una aplicación Rails 3.2 nueva. Para actualizar todo a la última versión tenemos que ejecutar bundle update
.
Además de modificar el fichero de gemas hay otra configuración de entorno extra que nos queda por hacer hasta configurar nuestra aplicación igual que una nueva en 3.2. En el fichero de configuración tenemos que añadir estas dos líneas.
# 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
La primera de estas configuraciones establece mass_assignment_sanitizer
a :strict
. Esto elevará una excepción si hay una asignación masiva de atributos protegidos. La segunda línea establece el umbral de auto-explicación de consultas a 0.5
. Esto quiere decir que se ejecutará una consulta EXPLAIN contra cualquier consulta a base de datos que supere el medio segundo de tiempo, quedando la información guardada en trazas. Es buena idea copiar la opción mass_assignment_sanitizer
al entorno de pruebas y también eliminar la siguiente línea que ya no se genera por defecto.
# Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets config.assets.allow_debugging = true
Consultas EXPLAIN
Con la aplicación ya actualizada a Rails 3.2 veremos algunas de las nuevas funcionalidades en la consola de Rails. Ya hemos mencionado que a las consultas que tarden demasiado se les aplicará una consulta adicional EXPLAIN, pero podemos activar este comportamiento desde la consola ejecutando explain
contra una consulta ActiveRecord.
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"
Podemos imprimir la salida de forma más limpia con 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
En este caso la consulta EXPLAIN no es muy interesante porque se trata de una consulta muy sencilla pero esta funcionalidad es mucho más útil con consultas JOIN complicadas. Si hay algún punto en nuestra aplicación que sabemos que lanza una consulta lenta y no queremos que se haga un EXPLAIN automáticamente podemos rodear la consulta en un bloque silence_auto_explain
de la siguiente manera:
1.9.2-p290 :003 > ActiveRecord::Base.silence_auto_explain { Product.order(:name) }
La consulta dentro del bloque no generará un EXPLAIN, independientemente de lo que tarde en ejecutarse.
Nuevos métodos ActiveRecord
Otra funcionalidad muy útil de Rails 3.2 es el método pluck
, que nos permite pasar un nombre de columna y devolverá los valores de dicha columna.
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"]
Podemos hacerlo con cualquier columna para recuperar todos los valores en un array con el tipo de datos apropiado.
1.9.2-p290 :007 > Product.pluck(:id) (0.2ms) SELECT id FROM "products" => [1, 2, 3, 4, 5, 6, 7]
De forma similar si utilizamos la cláusula select
para restringir qué columnas hay que recuperar, ahora existe un nuevo método uniq
que podemos usar por ejemplo para devolver un producto para cada nombre único.
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">]
Otra nueva funcionalidad, first_or_create
está pensada para funcionar con la cláusula where
.
1.9.2-p290 :010 > Product.where(name: "Foo").first_or_create!
En este caso hemos usado el método en su versión con admiración para que se eleve una excepción si existe algún error de validación. El código anterior funciona de forma similar a find_or_create_by_name
. La primera vez que se ejecute creará un nuevo Product
con el name
correspondiente (asumiendo que no existe ya uno); cuando volvamos a ejecutarlo devolverá el producto correspondiente. Lo interesante de este enfoque es que se pueden pasar atributos adicionales a la llamada create
, de la siguiente manera:
1.9.2-p290 :011 > Product.where(name: "Foo").first_or_create!(price: 5)
Los registros que se creen tendrán estos atributos, pero dichos atributos no serán tenidos en cuenta cuando se busca el Product
.
El último método nuevo que veremos se llama safe_constantize
, que es un método implementado sobre las cadenas.
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)
El método devuelve la constante correspondiente si es que existe. La diferencia de este método y el constantize
normal es que devuelve nil
si no se encuentra la constante en lugar de lanzar una excepción.
Nuevas funcionalidades en las migraciones
Veamos ahora algunas nuevas opciones que podemos usar cuando generamos migraciones. Supongamos que vamos a generar la migración de un nuevo modelo ProductVariation
. He aquí nuestra migración.
$ rails g model product_variation product_id:integer:index name 'price:decimal{7,2}'
La migración utiliza tres nuevas funcionalidades. Queremos que una ProductVariation
pertenezca a un Product
por lo que hemos especificado product_id
como una de las columnas. En Rails 3.2 podemos añadir una nueva opción index
para generar automáticamente un índice sobre esta columna.
Queremos que la ProductVariation
disponga de una columna name
de tipo cadena. Por lo general utilizaríamos name:string
en nuestra migración pero ahora string
es el tipo por defecto por lo que no tenemos que hacerlo. Por último si estamos definiendo un campo decimal
podemos pasar opciones para especificar la precisión y la escala de dicho tipo. Nótese que es necesario entrecomillar esta parte de la migración para que la shell no intente modificarla.
He aquí el aspecto que tiene la migración generada.
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
En la migración puede verse que existe un índice definido sobre product_id
, que el campo name
ha sido asignado por defecto como cadena y que el campo price
tiene definida su precisión y escala.
Hablando de generadores puede ser que tengamos algunas opciones favoritas a la hora de generar una aplicación Rails. Por ejemplo podemos escoger siempre la misma base de datos o excluir los tests de unidad. Se puede hacer que estas sean las opciones por defecto guardándolas en un fichero .railsrc
en nuestro directorio.
$ echo -d postgresql -T > ~/.railsrc
Cuando creemos una nueva aplicación Rails estas opciones se añadirán automáticamente.
$ rails new blog Using -d postgresql -T from /Users/eifion/.railsrc
Almacenamiento clave-valor
ActiveRecord viene con una nueva manera de definir almacenamiento de pares clave-valor en los modelos. Supongamos que en nuestro modelo ProductVariation
tenemos algunos atributos como colour
y size
y no queremos columnas separadas de base de datos para cada uno de ellos. Podemos utilizar el nuevo método store
para guardar dichos valores en una única columna.
class ProductVariation < ActiveRecord::Base store :properties, accessors: [:colour, :size] end
El primer argumento que se pasa contiene el nombre de la columna sobre la que se almacenarán los valores y en el segundo se definen las propiedades que queremos guardar. Tenemos que crear la columna en base de datos por lo que nos hará falta una migración.
$ rails g migration add_properties_to_product_variations properties:text
No debemos olvidarnos de ejecutar rake db:migrate
para añadir la columna a la base de datos.
Veamos una demostración de esto en la consola. Podemos crear una nueva ProductVariation
y darle los atributos colour
y size
igual que haríamos con los atributos normales.
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}>
Podemos acceder a estos atributos igual que al resto.
1.9.2-p290 :003 > p.colour => "blue"
También podemos acceder a ellos mediante el hash properties
.
1.9.2-p290 :004 > p.properties[:colour] => "blue"
No podremos hacer búsquedas sobre estos atributos porque no mapean directamente sobre columnas de la base de datos pero se trata de una forma muy conveniente de acceder a un conjunto de atributos.
Etiquetado de trazas
Todavía nos queda una última funcionalidad por ver: las trazas etiquetadas. Se trata de algo que normalmente sólo se hace en modo de producción pero para la demostración lo vamos a incorporar a nuestro entorno de desarrollo. Esto se hace con la opción de configuración log_tags
a la que le establecemos un array de atributos que son los nombres de los métodos que se invocarán sobre la petición.
config.log_tags = [:uuid, :remote_ip]
Hemos usado uuid
que es un nuevo método en Rails 3.2 que devuelve un identificador único de petición y que nos sirve para determinar la petición a partir de la que se originó la traza. La otra es la dirección IP remota. Si ahora arrancamos el servidor de nuevo y lanzamos una petición las líneas de la traza aparecerán etiquetada con estos valores.
[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]
Esto resulta útil en producción cuando hay varias instancias de Rails escribiendo sobre los archivos de traza, ahora podremos separar las entradas del log que han sido generadas por diferentes peticiones concretas.