#265 Rails 3.1 Overview
Ya ha sido publicada la primera versión Beta de Rails 3.1 y dedicaremos los próximos episodios a repasar las novedades que trae. Esta vez empezaremos mostrando su proceso de instalación y dando un repaso general a su funcionalidad.
Instalación de Rails 3.1
Antes de instalar la gema beta de Rails 3.1 utilizaremos rvm para crear un gemset que nos sirva para aislar esta instalación del resto de nuestro entorno y así poder utilizar la versión beta sin que afecte a nuestra instalación de Rails 3.0. Podemos crear un nuevo juego de gemas llamado railspre
lanzando esta orden:
$ rvm 1.9.2@railspre --create
Con esto ya podemos instalar la versión beta en este juego de gemas utilizando
$ gem install rails --pre
Esto instalará varias gemas, una vez que se haya completado dicha instalación podemos comprobar nuestra versión de Rails para asegurarnos de que todo ha quedado bien instalado:
$ rails -v Rails 3.1.0.beta1
Con la beta ya instalada podemos pasar a crear nuestra primera aplicación Rails 3.1.
$ rails new todo
Por defecto Rails 3.1 utiliza jQuery como librería JavaScript. Para crear una nueva aplicación que utilice Prototype podemos utilizar la opción --j
.
$ rails new todo -j prototype
Antes de inspeccionar el contenido de la nueva aplicación cambiaremos al directorio de la aplicación con cd
y ejecutaremos la orden bundle
para instalar las gemas necesarias para la aplicación tras lo cual podemos estudiar su estructura.
Este es el aspecto de la estructura de directorios en TextMate (estamos utilizando el plugin MissingDrawer para mostrar la estructura de carpetas en una barra lateral en lugar del cajón por defecto)
Primero empezaremos viendo el Gemfile
. Aquí está:
source 'http://rubygems.org' gem 'rails', '3.1.0.beta1' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'sqlite3' # Asset template engines gem 'sass' gem 'coffee-script' gem 'uglifier' gem 'jquery-rails' # Use unicorn as the web server # gem 'unicorn' # Deploy with Capistrano # gem 'capistrano' # To use debugger # gem 'ruby-debug19', :require => 'ruby-debug' group :test do # Pretty printed test output gem 'turn', :require => false end
No hay ninguna sorpresa en las primeras líneas del archivo, pero más adelante llegamos a la sección marcada “Asset template engines”. Ahora Rails incluye por defecto SASS y CoffeeScript, que serán objeto de estudio en futuras entregas. Esta sección contiene una gema llamada Uglifier que minimiza el código JavaScript de la aplicación en producción y la gema jquery-rails. Si usásemos Prototype la gema que aparecería aquí sería prototype-rails.
Al final del archivo aparece una referencia a la gema turn que hace que la salida de los tests quede más bonita.
Cambios en las plantillas
El mayor cambio que ha ocurrido en este apartado es cómo los motores de plantillas gestionan los recursos estáticos. Si abrimos el directorio /public
de la aplicación veremos que ya no incluye los directorios javascripts
o stylesheets
.
Si estos directorios han desaparecido, entonces ¿dónde debemos poner nuestro código JavaScript y CSS? La respuesta es que ahora van en el nuevo directorio /app/assets
. Si abrimos el directorio javascripts
veremos un fichero application.js
pero si lo examinamos veremos que nos dice que aquí no es donde deberíamos poner el código JavaScript de la aplicación.
// FIXME: Tell people that this is a manifest file, real code should go into discrete files // FIXME: Tell people how Sprockets and CoffeeScript works // //= require jquery //= require jquery_ujs //= require_tree .
(N del T: El texto dice "FIXME Contar que este fichero es un mero manifiesto, el código real debería ir en ficheros concretos. FIXME Contar cómo funciona Sprockets y CoffeScript")
Este fichero está diseñado para servir como manifiesto. La idea es que en lugar de escribir código JavaScript aquí deberíamos ponerlo en ficheros dentro del mismo directorio. Para hacernos una idea de cómo funciona esto, generaremos un andamiaje en nuestra aplicación para un Project
.
$ rails g scaffold project name:string
Si examinamos la salida de esta orden veremos que algunos de los archivos generados han acabado en el directorio app/assets
.
create app/assets/stylesheets/scaffold.css.scss invoke assets create app/assets/javascripts/projects.js.coffee create app/assets/stylesheets/projects.css.scss
Se incluye un fichero CSS, pero que posee la extensión .scss
y un fichero JavaScript con la extensión .coffee
. Le vamos a echar un vistazo a estos ficheros pero antes debemos migrar la base de datos para que se cree la nueva tabla projects
.
$ rake db:migrate
Los ficheros SCSS tienen a primera vista el aspecto de ficheros CSS estándar, pero si miramos al final veremos algunas diferencias, incluyendo reglas anidadas. Esto es una funcionalidad específica de SASS que veremos en algún episodio futuro.
#error_explanation { width: 450px; border: 2px solid red; padding: 7px; padding-bottom: 0; margin-bottom: 20px; background-color: #f0f0f0; h2 { text-align: left; font-weight: bold; padding: 5px 5px 5px 15px; font-size: 12px; margin: -7px; margin-bottom: 0px; background-color: #c00; color: #fff; } ul li { font-size: 12px; list-style: square; } }
El andamiaje también ha generado un fichero projects.css.scss
para incluir CSS específico a las páginas de Projects
y un fichero projects.js.coffee
para el JavaScript relacionado con dicho modelo. Obsérvese que se trata sólo de una recomendación, todos los ficheros CSS y JavaScript se cargan en todas las páginas porque se fusionan en un único archivo.
Si ahora arrancamos el servidor de nuestra aplicación y visitamos la página de proyectos veremos que en el código fuente de la página aparecen incluidos dos ficheros.
<link href="/assets/application.css" media="screen" rel="stylesheet" type="text/css" /> <script src="/assets/application.js" type="text/javascript"> </script>
Dichos ficheros contienen versiones empaquetadas del JavaScript y CSS de la aplicación. Los contenidos de estos archivos vendrán determinados por las líneas require
que aparecen en applicaton.js
y application.css
. Dichas líneas indican qué ficheros se deben incluir así como el orden de inclusión en el fichero combinado.
//= require jquery //= require jquery_ujs //= require_tree .
Entre bambalinas Rails hace todo esto utilizando Sprockets, que recibe todo los ficheros JavaScript o CSS necesarios en la aplicación y los combina en un único archivo que es el que se entrega al cliente. Esto significa que podemos organizar los ficheros en nuestra aplicación como queramos, que serán finalmente combinados en un único archivo. En modo de producción los ficheros también serán compactados para que se descarguen de la manera más eficiente posible.
En modo de desarrollo los ficheros application.js
y application.css
se recargan automáticamente mientras que en modo de producción se cachean de forma que no haya una penalización de rendimiento durante la compilación de archivos CoffeScript y SASS.
Cambios en las migraciones
Veamos los cambios en otras funcionalidades de Rails 3.1, empezando con el fichero de migración de base de datos que se generó con el andamiaje anterior:
class CreateProjects < ActiveRecord::Migration def change create_table :projects do |t| t.string :name t.timestamps end end end
Esta migración es distinta a las que estamos acostumbrados: en lugar de los métodos up
y down
sólo hay uno llamado change
que sirve para hacer las migraciones hacia adelante y hacia atrás. En Rails 3.1 no hace falta escribir los dos métodos siempre que su comportamiento pueda deducirse desde el método change
. Se trata de una nueva funcionalidad de ActiveRecord que nos ahorrará tiempo cuando escribamos código de migraciones.
Mapas de identidad
Otra interesantísima novedad de ActiveRecord son los Mapas de Identidad. Si miramos el fichero de configuración application.rb
veremos que están activados por defecto.
# Enable IdentityMap for Active Record, to disable set to false or remove the line below. config.active_record.identity_map = true
En esta primera beta de Rails 3.1 probablemente nos haga falta tocar un poco el fichero de configuración añadiendo otra línea de código:
# Enable IdentityMap for Active Record, to disable set to false or remove the line below. config.active_record.identity_map = true ActiveRecord::IdentityMap.enabled = true
¿Qué hacen los Mapas de Identidad? Utilicémoslos en la consola como demostración. Primero crearemos un nuevo Project
:
> p = Project.create!(:name => "Yardwork") SQL (81.4ms) INSERT INTO "projects" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Fri, 13 May 2011 18:41:25 UTC +00:00], ["name", "Yardwork"], ["updated_at", Fri, 13 May 2011 18:41:25 UTC +00:00]] => #<Project id: 1, name: "Yardwork", created_at: "2011-05-13 18:41:25", updated_at: "2011-05-13 18:41:25">
Lo primero que debemos notar es que el log aparece directamente en la consola de forma que sabemos exactamente el SQL que se está ejecutando en cada momento.
A continuación recuperaremos el objeto recién creado por su id
.
ruby-1.9.2-p180 :002 > p1 = Project.find(1) Project Loaded From Identity Map (id: 1) => #<Project id: 1, name: "Yardwork", created_at: "2011-05-13 18:41:25", updated_at: "2011-05-13 18:41:25">
Nótese que la consola nos dice que el proyecto ha sido cargado a partir del Mapa de Identidad (id:1). No se ha ejecutado nincuna consulta SQL para recuperar el proyecto de la base de datos porque Rails ya sabe que el proyecto está cargado en memoria y utilizará dicha instancia. Podemos confirmarlo comprobando que los dos proyectos tienen el mismo object_id
.
ruby-1.9.2-p180 :006 > p.object_id == p1.object_id => true
Esto significa que no tendremos el problema de establecer los atributos de un registro y pensar que se trata del mismo cuando lo cargamos a través de una asociación. Ahora se nos garantiza que ambos serán la misma instancia.
Asociaciones has_many :through anidadas
Hay otras novedades en ActiveRecord, tales como las asociaciones has_many :through
anidadas. Veamos el siguiente código de modelo.
class Project < ActiveRecord::Base has_many :tasks has_many :assignments, :through => :tasks has_many :users, :through => :assignments end
En las versiones anteriores de Rails no podíamos hacerlo, pero a partir de la 3.1 podemos anidar las asocaciones has_many :through
lo cual nos puede ser útil en muchas situaciones.
Otra nueva prestación es la posibilidad de asignar roles en las llamadas a attr_accesible
. Por ejemplo podríamos hacer que el atributo name
fuese accessible
, pero sólo para los usuarios que tuviesen el rol admin
.
class Project < ActiveRecord::Base attr_accessible :name, :as => :admin end
Si intentamos modificar el Project
que hemos creado en consola con el navegador cambiando su nombre “Yardwork” por “Housework”, dicho atributo no cambiará porque no hemos iniciado sesión como administrador.
Los roles se pasan a través del controlador, modificando la llamada a update_attributes
con la opción :as
.
def update @project = Project.find(params[:id]) respond_to do |format| if @project.update_attributes(params[:project], :as => :admin) format.html { redirect_to @project, notice: 'Project was successfully updated.' } format.json { head :ok } else format.html { render action: "edit" } format.json { render json: @project.errors, ↵ status: :unprocessable_entity } end end end
Si ahora intentamos actualizar el Project
con el formulario veremos que funciona porque ya estamos pasando el rol admin
.
Cambios en la capa de las vistas
Terminaremos viendo algunos cambios en las vistas. El primero es pequeño pero útil. Hasta ahora, si teníamos un file_field
en un formulario teníamos que añadir :html => { :multipart => :true }
al form_for
para que los ficheros se enviasen correctamente. Esta ya no hace falta En Rails 3.1 porque se pone automáticamente el atributo enctype
correcto en la etiqueta form
cuando dicho formulario contiene un file_field
.
Hay otro cambio que afecta a la generación de enlaces: los helpers de URLs ya admiten opciones de dominio y subdominio. Supongamos que tenemos el siguiente código para un enlace:
<%= link_to 'Edit', edit_project_path(@project) %>
Si queremos que estos enlaces apunten a un subdominio diferente podemos cambiar el código de la siguiente manera:
<%= link_to 'Edit', edit_project_url(@project, :subdomain => 'foo') %>
El enlace ahora apuntará a la misma página en el subdominio foo
.
Salida de tests mejorada
Por último le echaremos un vistazo a las mejoras en la salida de los tests. Si ejecutamos rake test
la salida quedará más clara y bonita porque utiliza la gema turn que antes vimos en el Gemfile
.
$ rake test (in /Users/eifion/todo) Loaded suite /Users/eifion/.rvm/gems/ruby-1.9.2-p180@global/gems/rake-0.8.7/lib/rake/rake_test_loader Started Finished in 0.003966 seconds. 0 tests, 0 assertions, 0 failures, 0 errors, 0 skips Loaded suite /Users/eifion/.rvm/gems/ruby-1.9.2-p180@global/gems/rake-0.8.7/lib/rake/rake_test_loader Started ProjectsControllerTest: PASS should create project (0.16s) PASS should destroy project (0.01s) PASS should get edit (0.08s) PASS should get index (0.01s) PASS should get new (0.01s) PASS should show project (0.01s) PASS should update project (0.01s) Finished in 0.307078 seconds. 7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
Rails 3.1 trae muchas más novedades que se nos han quedado pendientes de estudiar. Emisión continua de HTTP, herencia de vistas, engines montables y mucho más que veremos en episodios futuros.
Este gist enumera todos los cambios que ha habido hasta ahora en Rails 3.1