Te has preguntado alguna vez ¿Existe vida después de ActiveRecord::Base? si es así, en esta charla vamos descubrir que Rails es algo más que un simple MVC, es un framework repleto de herramientas cuyo conocimiento nos va a facilitar enormemente la vida.
Con estas herramientas vas a poder extender Rails de una forma que no habías imaginado hasta ahora: crearas tus propios validadores, responders y renderers; serás capaz de enviar datos por streaming, de interceptar mails o añadir un nuevo middleware a tu stack.
Queremos mostrar 10 de estas herramientas junto con ejemplos de uso de cada una de ellas, para que las puedas incorporar poco a poco en tu día a día y descubras el mundo de posibilidades que realmente tienes en tus manos.
El conjunto de herramientas que vamos a mostrar es:
ActiveModel::Model
ActiveModel::Validator
ActiveSupport::Concern
ActionSupport::Notifications
ActionController::Renderers
ActionController::Responder
ActionController::Live
ActionView::Resolvers
ActionMailer Interceptors
Rack Middleware
1. 10 cosas de Rails que
deberías saber
Carlos Sánchez & Gabriel Ortuño
MADRID · NOV 21-22 · 2014
2. MADRID · NOV 21-22 · 2014
Carlos Sánchez Pérez
Person.new(
name: "Carlos Sánchez Pérez",
job: "ASPgems",
twitter: "carlossanchezp",
github: "carlossanchezp",
Blog: carlossanchezperez.wordpress.com")
3. MADRID · NOV 21-22 · 2014
Gabriel Ortuño
Person.new(
name: "Gabriel Ortuño",
job: "jobandtalent",
web: "arctarus.com",
twitter: "arctarus",
github: "arctarus")
4. MADRID · NOV 21-22 · 2014
Madrid.rb
Group.new(
name: "Madrid.rb",
google_group: "madrid-rb",
twitter: "madridrb",
vimeo: "madridrb")
¡El último jueves de cada mes en el Irish Rover!
7. MADRID · NOV 21-22 · 2014
Hoja de Ruta
1. ActiveSupport::Concern
2. ActiveModel::Validator
3. ActiveModel::Model
4. ActiveSupport::Notifications
5. ActionController::Renderer
6. ActionController::Responder
7. ActionController::Live
8. ActionView::Resolver
9. ActionMailer::Interceptor
10. RackMiddleware
8. 1. ActiveSupport::Concern
¿Qué es?
➔ Simple Syntactic Sugar sobre modulos de Ruby
➔ Forma estándar de extender clases desde Rails 4.0
¿Para qué se usa?
➔ Agrupa métodos que definen una responsabilidad
➔ Mejora cohesión
➔ Permite reutilización
MADRID · NOV 21-22 · 2014
9. # app/models/message.rb
class Message < ActiveRecord::Base
default_scope -> { where(trashed: false) }
scope :trashed, -> { where(trashed: true) }
MADRID · NOV 21-22 · 2014
def trash
update_attribute :trashed, true
end
end
10. MADRID · NOV 21-22 · 2014
# app/models/concerns/trashable.rb
module Trashable
extend ActiveSupport::Concern
included do
default_scope -> { where(trashed: false) }
scope :trashed, -> { where(trashed: true) }
end
def trash
update_attribute :trashed, true
end
end
11. MADRID · NOV 21-22 · 2014
# app/models/message.rb
class Message < ActiveRecord::Base
include Trashable, Subscribable, Commentable
end
# app/models/post.rb
class Post < ActiveRecord::Base
include Trashable, Commentable
end
12. 2. ActiveModel::Validator
¿Qué es?
➔ Es una clase base para implementar
validadores que pueden ser usados por
ActiveModel
¿Para qué sirve?
➔ Permite extraer lógica de validación de una
MADRID · NOV 21-22 · 2014
forma sencilla y reutilizable
13. MADRID · NOV 21-22 · 2014
# app/models/user.rb
class User < ActiveRecord::Base
validates_format_of :email, with:
A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]+z/
end
14. # lib/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
MADRID · NOV 21-22 · 2014
def validate_each(record, attribute, value)
message = options.fetch :message, I18n.t("validators.email")
unless value =~ /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})z/i
record.errors[attribute] << message
end
end
end
15. class Person < ActiveRecord::Base
validates :email, presence: true, email: true
end
MADRID · NOV 21-22 · 2014
16. person = Person.new(email: 'user@gmail.com')
person.valid?
# ActiveModel ejecuta algo como....
validator = EmailValidator.new
validator.validate_each(person, :email, 'user@gmail.com')
MADRID · NOV 21-22 · 2014
17. 3. ActiveModel::Model
¿Qué es?
➔ Incluir funcionalidades de ActiveModel::Model sobre
MADRID · NOV 21-22 · 2014
clases de Ruby
➔ Naming, Translation, Validation, Conversions.
¿Para qué se usa?
➔ Crear una clase con validaciones
➔ Utilizarla en las vistas
18. MADRID · NOV 21-22 · 2014
class Contact
include ActiveModel::Model
attr_accessor :name, :email, :message
validates :name, presence: true
validates :email, presence: true, email: true
validates :message, presence: true
end
19. class ContactsController < ApplicationController
def new...
def create
@contact = Contact.new(params[:contact])
if @contact.valid?
ContactMailer.new_contact(@contact).deliver
redirect_to root_path
MADRID · NOV 21-22 · 2014
else
render :new
end
end
end
21. 4. ActiveSupport::Notifications
¿Qué es?
➔ Envio de notificaciones cuando se producen acciones
MADRID · NOV 21-22 · 2014
estándar.
¿Para qué se usa?
➔ Imagina que nos preguntamos “¿Por qué nuestra
página es tan lenta?"
➔ Cómo podemos utilizar las notificaciones para obtener
las métricas.
22. # config/initializers/notification.rb
ActiveSupport::Notifications.subscribe "process_action.
action_controller" do|name,start,finish,id,payload|
MADRID · NOV 21-22 · 2014
PageRequest.create! do |page_request|
page_request.path = payload[:path]
page_request.page_duration = (finish - start) * 1000
page_request.view_duration = payload[:view_runtime]
page_request.db_duration = payload[:db_runtime]
end
end
24. MADRID · NOV 21-22 · 2014
class Product < ActiveRecord::Base
def self.search_by_name(text)
ActiveSupport::Notifications.instrument(
"products.search_by_name",
search: text)
where("name LIKE ?", "%#{text}%")
end
end
25. MADRID · NOV 21-22 · 2014
# config/initializers/notification.rb
ActiveSupport::Notifications.subscribe "products.search_by_name"
do |name, start, finish, id, payload|
Rails.logger.debug "SEARCH: #{payload[:search]}"
end
26. 5. ActionController::Renderer
¿Qué es?
➔ Un hook que expone el método render para
MADRID · NOV 21-22 · 2014
personalizar su comportamiento.
¿Para qué se usa?
➔ Nos permite devolver respuestas con formatos
personalizados como csv, pdf, zip, etc.
27. MADRID · NOV 21-22 · 2014
# app/controllers/csvable_controller.rb
def show
@csvable = Csvable.find(params[:id])
respond_to do |format|
format.html
format.csv { render csv: @csvable, filename: @csvable.name }
end
end
28. # lib/renderers/csv_renderer.rb
ActionController::Renderers.add :csv do |obj, options|
filename = options.fetch(:filename, 'data')
str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
send_data str, type: Mime::CSV,
disposition: "attachment; filename=#{filename}.csv"
MADRID · NOV 21-22 · 2014
end
# config/initializer/mime_types.rb
Mime::Type.register "text/csv", :csv
29. 6. ActionController::Responder
¿Qué es?
➔ Abstrae como funciona los controllers según:
◆ Tipo de request: HTML, XML, JSON
◆ Verbo http usando: GET, POST, PUT, PATCH, DELETE
◆ Estado del recurso: válido, con errores
¿Para qué se usa?
➔ Evita que repitamos lógica entre controladores
➔ FlashResponder, HttpCacheResponder...
MADRID · NOV 21-22 · 2014
30. MADRID · NOV 21-22 · 2014
def create
@user = User.new(params[:user])
respond_to do |format|
if @user.save
flash[:notice] = 'User was successfully created.'
format.html { redirect_to @user }
format.xml { render xml: @user, status: :created,
location: @user }
else
format.html { render action: "new" }
format.xml { render xml: @user.errors,
status: :unprocessable_entity }
end
end
end
31. MADRID · NOV 21-22 · 2014
class UsersController < ApplicationController
respond_to :html, :xml
def create
@user = User.new(params[:user])
flash[:notice] = 'User was successfully created.' if @user.save
respond_with @user
end
end
32. # lib/flash_responder.rb
class FlashResponder < ActionController::Responder
def to_html
MADRID · NOV 21-22 · 2014
set_flash_message! unless get?
super
end
private
def set_flash_message!
status = has_errors? ? :alert : :notice
return if controller.flash[status].present?
message = i18n_lookup(status)
controller.flash[status] = message if message.present?
end
end
34. 7. ActionController::Live
¿Qué es?
➔ Módulo que permite el envío datos en streaming.
➔ Requiere el uso un servidor como de Puma o Thin
➔ ActionController::Live::SSE (Server Side Events)
¿Para qué se usa?
➔ Envio de datos al clientes sin necesidad de iniciar una
MADRID · NOV 21-22 · 2014
nueva request.
ActionController::Live::SS
35. class MessagesController < ApplicationController
MADRID · NOV 21-22 · 2014
include ActionController::Live
def events
3.times do |n|
response.stream.write "#{n}...nn"
sleep 2
end
ensure
response.stream.close
end
end
36. class MessagesController < ApplicationController
include ActionController::Live
MADRID · NOV 21-22 · 2014
def create
attributes = params.require(:message).permit(:content, :name)
@message = Message.create!(attributes)
$redis.publish('messages.create', @message.to_json)
end
end
37. MADRID · NOV 21-22 · 2014
# app/controllers/messages_controller.rb
def events
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream, event: "live.messages")
$redis.psubscribe('messages.create') do |on|
on.pmessage do |pattern, event, data|
sse.write(data, event: event, retry: 500)
end
end
ensure
sse.close
end
38. # app/assets/javascript/messages.js.coffee
source = new EventSource('/messages/events')
source.addEventListener 'live.messages', (e) ->
message = $.parseJSON(e.data).message
$('#chat').append($('<li>').
text("#{message.name}: #{message.content}"))
MADRID · NOV 21-22 · 2014
39. 8. ActionView::Resolver
¿Qué es?
➔ Es el responsable de encontrar el template que debe
MADRID · NOV 21-22 · 2014
ser renderizado.
¿Para qué se usa?
➔ Obtener templates directamente de la base de datos.
➔ Modificar el directorio de vistas a renderizar en caso de
no existir para el controlador actual.
ActionController::Live::SS
40. # lib/admin/resolver.rb
class Admin::Resolver < ::ActionView::FileSystemResolver
def initialize
MADRID · NOV 21-22 · 2014
super("app/views")
end
def find_templates(name, prefix, partial, details)
super(name, "admin/defaults", partial, details)
end
end
42. class SqlResolver < ActionView::Resolver
def find_templates(name, prefix, partial, details)
MADRID · NOV 21-22 · 2014
conditions = {
path: normalize_path(name, prefix, partial),
locale: normalize_array(details[:locale]).first,
format: normalize_array(details[:formats]).first,
handler: normalize_array(details[:handlers]),
partial: partial || false
}
::SqlTemplate.where(conditions).map do |record|
initialize_template(record)
end
end
end
43. 9. ActionMailer::Interceptor
¿Qué es?
➔ Son hooks en el proceso de enviar emails
➔ Podríamos decir que ActionMailer ofrece una forma de
manipular los atributos de nuestros emails justo antes de su
envío.
¿Para qué se usa?
➔ Un ejemplo sería si tenemos correos electrónicos de usuarios
reales, modificar el destinatario y el asunto de un email de
desarrollo.
MADRID · NOV 21-22 · 2014
44. MADRID · NOV 21-22 · 2014
class EnvironmentInterceptor
def self.delivering_email(message)
message.to = "myemail@example.com"
message.subject = "#{Rails.env} #{message.subject}"
end
end
45. MADRID · NOV 21-22 · 2014
# Ahora registramos nuestro interceptor:
# config/initializers/mail.rb
require 'environment_interceptor'
unless Rails.env.production?
ActionMailer::Base.register_interceptor(EnvironmentInterceptor)
end
46. 10. Rack Middleware
¿Qué es?
➔ Clase ruby que intercepta una request dentro de una
Rack app y la procesa para acabar devolviendo una
response.
➔ Es una implementación de Pipeline Design Pattern
¿Para que se usa?
➔ Modificar cabeceras de la respuesta, caching, manage
MADRID · NOV 21-22 · 2014
exceptions, almacenar cookies, etc
47. MADRID · NOV 21-22 · 2014
Rack
Rack proporciona una interfaz
mínima entre servidores web
y frameworks Ruby
48. $ bin/rake middleware
use Rack::Lock
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
...
MADRID · NOV 21-22 · 2014
49. MADRID · NOV 21-22 · 2014
Simplest Rack Middleware
class RackMiddleware
def initialize(app)
@app = app # Rack app
end
def call(env)
status, headers, response = @app.call(env)
...
[status, headers, response]
end
end
50. require 'digest/md5'
module Rack
# Automatically sets the ETag header on all String bodies
class ETag
MADRID · NOV 21-22 · 2014
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
if !headers.has_key?('ETag')
parts = []
body.each { |part| parts << part.to_s }
headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}")
[status, headers, parts]
else
[status, headers, body]
end
end
end
end
51. MADRID · NOV 21-22 · 2014
# Rack::Etag
def call(env)
status, headers, body = @app.call(env)
if !headers.has_key?('ETag')
parts = []
body.each { |part| parts << part.to_s }
etag = %("#{Digest::MD5.hexdigest(parts.join(""))}")
headers['ETag'] = etag
[status, headers, parts]
else
[status, headers, body]
end
end
52. MADRID · NOV 21-22 · 2014
# config/application.rb
# Push Rack::ETag at the bottom
config.middleware.use Rack::ETag
# Add Rack::ETag after ActiveRecord::QueryCache.
config.middleware.insert_after ActiveRecord::QueryCache,
Rack::ETag