#326 ActiveAttr
- Download:
- source codeProject Files in Zip (78 KB)
- mp4Full Size H.264 Video (15.8 MB)
- m4vSmaller H.264 Video (7.53 MB)
- webmFull Size VP8 Video (7.62 MB)
- ogvFull Size Theora Video (16.7 MB)
En el episodio 219 usamos ActiveModel para crear un modelo sin persistencia en base de datos pero usando algunas funcionalidades de ActiveRecord tales como las validaciones. Sin embargo aunque ActiveModel está muy bien no resulta conveniente su uso directo de esta manera. Por ejemplo hace falta bastante código para escribir un modelo sencillo que tenga soporte de validaciones.
class Message include ActiveModel::Validations include ActiveModel::Conversion extend ActiveModel::Naming attr_accessor :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 def initialize(attributes = {}) attributes.each do |name, value| send("#{name}=", value) end end def persisted? false end end
La gema ActiveAttr nos será aquí de ayuda. Su autor Chris Greigo la describe como “lo que se le pasó a ActiveModel” que es una descripción bastante acertada de lo que hace. Con ella es mucho más fácil crear un modelo sin tablas que se comporta de forma similar a ActiveRecord, que es lo que veremos en este episodio.
Uso de ActiveAttr con un formulario de contacto
Vamos a trabajar con una aplicación que tiene un formulario de contacto. Al rellenar el formulario y enviarlo, queremos enviar el correo pero no queremos que se guarde el mensaje en la base de datos. No deseamos utilizar ActiveRecord, pero sí que queremos usar algunas de las funcionalidades que ofrece, por ejemplo las validaciones, de forma que si el usuario no rellena correctamente el formulario debería ver un mensaje de error explicando qué es lo que ha hecho mal.
Ya hemos creado el controlador y la vista que funcionan de una manera muy similar a como lo harían si usásemos el andamiaje de Rails. Vamos a darle un repaso rápido. El controlador MessagesController
tiene un par de acciones llamadas new
y create
. Cuando se active la acción new
se creará una nueva instancia de Message
y se mostrará la plantilla.
<h1>Contact Us</h1> <%= form_for @message do |f| %> <% if @message.errors.any? %> <div class="error_messages"> <h2><%= pluralize(@message.errors.count, "error") %> prohibited this message from being saved:</h2> <ul> <% @message.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :content, "Message" %><br /> <%= f.text_area :content, :rows => 5 %> </div> <div class="actions"><%= f.submit "Send Message" %></div> <% end %>
En esta vista está el código del formulario. Nótese que usamos form_for
para definir el formulario y le pasamos la instancia del objeto mensaje creada por el controlador. Los mensajes de error se muestran igual que con el código normal que se generaría con el generador de Rails, o lo que es lo mismo: esta plantilla de la vista tiene el mismo aspecto que si usásemos un modelo de ActiveRecord. Al enviar el formulario se activa la acción create
del controlador.
class MessagesController < ApplicationController def new @message = Message.new end def create @message = Message.new(params[:message]) if @message.valid? # TODO send message here redirect_to root_url, notice: "Message sent! Thank you for contacting us." else render "new" end end end
La acción genera una nueva instancia de Message
basándose en los parámetros recibidos del formulario y luego comprueba que dicho mensaje es válido. En tal caso, se emite el mensaje de correo y se redirige a la página principal. En caso de que el mensaje sea incorrecto se muestra otra vez el formulario. Es necesario crear la instancia del modelo de mensaje para replicar el funcionamiento de un modelo de ActiveRecord, con la única diferencia de que estamos sólo realizando validaciones y nunca lo vamos a guardar en una tabla de la base de datos.</ApplicationController>
El modelo Message
utiliza ActiveModel para gestionar todo este comportamiento, y puede verse el código al comienzo de este episodio. No queremos utilizar aquí este enfoque, sino que usaremos ActiveAttr. Para ello tenemos que añadir la gema al Gemfile
y ejecutar bundle
para instalarlo.
gem 'active_attr'
Ya podemos ejecutar ActiveAttr en nuestro modelo.
class Message include ActiveAttr::Model end
Nótese que Message
no hereda de otra clase, se trata de una simple clase en Ruby. Al incluir ActiveAttr::Modelo
añadimos cierta funcionalidad (basada en ActiveModel) que hace que la clase se comporte como un modelo de ActiveRecord. Podemos definir atributos para el modelo utilizando attribute
y podemos añadir validaciones igual que haríamos con una clase derivada de ActiveRecord.
class Message include ActiveAttr::Model attribute :name attribute :email attribute :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 end
Con esto ya tenemos un modelo totalmente funcional que se comporta igual que un modelo de ActiveRecord. Si visitamos otra vez el formulario y dejamos sin rellenar los campos, veremos los errores de validación igual que antes pero el código del modelo es mucho más simple.
Protección contra asignación masiva
Con ActiveAttr también tenemos protección contra asignaciones masivas. Supongamos que tenemos un atributo de prioridad en el modelo Message que no queremos establecer mediante valores del formulario. Podemos usar attr_accessible
para definir los atributos que deberían ser accesibles igual que haríamos con un modelo ActiveRecord.
class Message include ActiveAttr::Model attribute :name attribute :email attribute :content attribute :priority attr_accessible :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 end
Podemos probar este comportamiento en la consola. Si creamos un nuevo Message
e intentamos establecer su atributo priority
veremos un error, si bien podemos establecer priority
directamente.
1.9.3p0 :001 > m = Message.new(priority: 1) => #<Message content: nil, email: nil, name: nil, priority: nil> 1.9.3p0 :002 > m.priority => nil 1.9.3p0 :003 > m.priority = 1 => 1
ActiveAttr también nos permite invocar a un atributo seguido de una interrogación para forzar su valor a booleano, igual que con modelos de ActiveRecord. Las futuras versiones de ActiveAttr también nos permitirán pasar opciones a attribute
para poder especificar el tipo del atributo, así como un valor por defecto.
attribute :priority, type: Integer, default: 0
Estas opciones aún no están disponibles pero deberían salir pronto, así que merece la pena ir comprobando la página en Github de ActiveAttr.
Con esto finalizamos nuestro episodio dedicado a ActiveAttr, que es una forma muy cómoda de generar modelos sin tablas. La documentación tiene más detalles de lo qu epodemos hacer. Hemos utilizado el módulo ActiveAttr::Model
que incluye todo pero quedan por ver otros módulos que ofrecen diferentes funcionalidades que podemos usar si nos hacen falta sólo ciertas funcionalidades concretas de ActiveAttr.