Upload
carlos-sanchez-perez
View
249
Download
1
Embed Size (px)
DESCRIPTION
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
Citation preview
MADRID · NOV 21-22 · 2014
10 cosas de Rails que deberías saber
Carlos Sánchez & Gabriel Ortuño
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")
MADRID · NOV 21-22 · 2014
Gabriel Ortuño
Person.new(
name: "Gabriel Ortuño",
job: "jobandtalent",
web: "arctarus.com",
twitter: "arctarus",
github: "arctarus")
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!
MADRID · NOV 21-22 · 2014
Ruby on Rails
MADRID · NOV 21-22 · 2014
Rails 4 Features
MADRID · NOV 21-22 · 2014
Hoja de Ruta1. ActiveSupport::Concern2. ActiveModel::Validator3. ActiveModel::Model4. ActiveSupport::Notifications5. ActionController::Renderer6. ActionController::Responder7. ActionController::Live8. ActionView::Resolver9. ActionMailer::Interceptor
10. RackMiddleware
MADRID · NOV 21-22 · 2014
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
# app/models/message.rbclass Message < ActiveRecord::Base default_scope -> { where(trashed: false) } scope :trashed, -> { where(trashed: true) } def trash update_attribute :trashed, true endend
MADRID · NOV 21-22 · 2014
# app/models/concerns/trashable.rbmodule Trashable extend ActiveSupport::Concern
included do default_scope -> { where(trashed: false) } scope :trashed, -> { where(trashed: true) } end
def trash update_attribute :trashed, true endend
MADRID · NOV 21-22 · 2014
# app/models/message.rbclass Message < ActiveRecord::Base include Trashable, Subscribable, Commentableend
# app/models/post.rbclass Post < ActiveRecord::Base include Trashable, Commentableend
MADRID · NOV 21-22 · 2014
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
forma sencilla y reutilizable
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
MADRID · NOV 21-22 · 2014
# lib/email_validator.rbclass EmailValidator < ActiveModel::EachValidator 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 endend
MADRID · NOV 21-22 · 2014
class Person < ActiveRecord::Base validates :email, presence: true, email: trueend
MADRID · NOV 21-22 · 2014
person = Person.new(email: '[email protected]')
person.valid?
# ActiveModel ejecuta algo como....
validator = EmailValidator.new
validator.validate_each(person, :email, '[email protected]')
MADRID · NOV 21-22 · 2014
3. ActiveModel::Model
¿Qué es?➔ Incluir funcionalidades de ActiveModel::Model sobre
clases de Ruby ➔ Naming, Translation, Validation, Conversions.
¿Para qué se usa?➔ Crear una clase con validaciones➔ Utilizarla en las vistas
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: trueend
MADRID · NOV 21-22 · 2014
class ContactsController < ApplicationController def new... def create @contact = Contact.new(params[:contact]) if @contact.valid? ContactMailer.new_contact(@contact).deliver redirect_to root_path else render :new end endend
MADRID · NOV 21-22 · 2014
<h1>Contact Us</h1><%= form_for @contact do |f| %> <%= f.label :name %> <%= f.text_field :name %>
<%= f.label :email %> <%= f.email_field :email %>
<%= f.label :message %> <%= f.text_area :message %>
<%= f.submit %><% end %>
MADRID · NOV 21-22 · 2014
4. ActiveSupport::Notifications
¿Qué es?➔ Envio de notificaciones cuando se producen acciones
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.
MADRID · NOV 21-22 · 2014
# config/initializers/notification.rb
ActiveSupport::Notifications.subscribe "process_action.
action_controller" do|name,start,finish,id,payload|
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
MADRID · NOV 21-22 · 2014
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}%") endend
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
MADRID · NOV 21-22 · 2014
5. ActionController::Renderer
¿Qué es?➔ Un hook que expone el método render para
personalizar su comportamiento.
¿Para qué se usa?➔ Nos permite devolver respuestas con formatos
personalizados como csv, pdf, zip, etc.
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
MADRID · NOV 21-22 · 2014
# 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"
end
# config/initializer/mime_types.rb
Mime::Type.register "text/csv", :csv
MADRID · NOV 21-22 · 2014
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
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 endend
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 endend
MADRID · NOV 21-22 · 2014
# lib/flash_responder.rbclass FlashResponder < ActionController::Responder
def to_html 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? endend
MADRID · NOV 21-22 · 2014
# app/controllers/application_controller.rbclass ApplicationController < ActionController::Base self.responder = FlashResponder respond_to :htmlend
MADRID · NOV 21-22 · 2014
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
nueva request.
ActionController::Live::SS
MADRID · NOV 21-22 · 2014
class MessagesController < ApplicationController include ActionController::Live
def events 3.times do |n| response.stream.write "#{n}...\n\n" sleep 2 end ensure response.stream.close endend
MADRID · NOV 21-22 · 2014
class MessagesController < ApplicationController
include ActionController::Live
def create
attributes = params.require(:message).permit(:content, :name)
@message = Message.create!(attributes)
$redis.publish('messages.create', @message.to_json)
end
end
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
MADRID · NOV 21-22 · 2014
# 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
8. ActionView::Resolver
¿Qué es?➔ Es el responsable de encontrar el template que debe
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
MADRID · NOV 21-22 · 2014
# lib/admin/resolver.rb
class Admin::Resolver < ::ActionView::FileSystemResolver
def initialize
super("app/views")
end
def find_templates(name, prefix, partial, details)
super(name, "admin/defaults", partial, details)
end
end
MADRID · NOV 21-22 · 2014
# app/controllers/admin/application_controller.rb
class Admin::ApplicationController < ActionController::Base
append_view_path Admin::Resolver.new
end
MADRID · NOV 21-22 · 2014
class SqlResolver < ActionView::Resolver def find_templates(name, prefix, partial, details) 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 endend
MADRID · NOV 21-22 · 2014
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
class EnvironmentInterceptor
def self.delivering_email(message)
message.to = "[email protected]"
message.subject = "#{Rails.env} #{message.subject}"
end
end
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
MADRID · NOV 21-22 · 2014
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
exceptions, almacenar cookies, etc
MADRID · NOV 21-22 · 2014
Rack
Rack proporciona una interfaz mínima entre servidores web
y frameworks Ruby
MADRID · NOV 21-22 · 2014
$ bin/rake middlewareuse Rack::Lockuse Rack::Runtimeuse Rack::MethodOverrideuse ActionDispatch::RequestIduse Rails::Rack::Loggeruse ActionDispatch::ShowExceptionsuse ActionDispatch::DebugExceptionsuse ActionDispatch::RemoteIpuse ActionDispatch::Reloaderuse ActionDispatch::Callbacksuse ActiveRecord::Migration::CheckPendinguse ActiveRecord::ConnectionAdapters::ConnectionManagementuse ActiveRecord::QueryCache...
MADRID · NOV 21-22 · 2014
class RackMiddleware def initialize(app) @app = app # Rack app end def call(env) status, headers, response = @app.call(env) ... [status, headers, response] end end
Simplest Rack Middleware
MADRID · NOV 21-22 · 2014
require 'digest/md5'module Rack # Automatically sets the ETag header on all String bodies class ETag 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 endend
MADRID · NOV 21-22 · 2014
# Rack::Etagdef 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] endend
MADRID · NOV 21-22 · 2014
# config/application.rb
# Push Rack::ETag at the bottomconfig.middleware.use Rack::ETag
# Add Rack::ETag after ActiveRecord::QueryCache.config.middleware.insert_after ActiveRecord::QueryCache, Rack::ETag
MADRID · NOV 21-22 · 2014
Rails es adaptable a tus necesidades
MADRID · NOV 21-22 · 2014
Domina las herramientas que usas
MADRID · NOV 21-22 · 2014
¡¡Gracias!!
MADRID · NOV 21-22 · 2014
Referencias
MADRID · NOV 21-22 · 2014
ActiveSupport::Concern● Put chubby models on a diet with concerns : https://signalvnoise.com/posts/3372-put-chubby-
models-on-a-diet-with-concerns
ActiveModel::Validator● Seven useful validator: http://viget.com/extend/seven-useful-activemodel-validators
ActiveModel::Model● ActiveModel::Model [Rails 4 Countdown to 2013]: http://blog.remarkablelabs.
com/2012/12/activemodel-model-rails-4-countdown-to-2013
ActionController::Responder● http://blog.plataformatec.com.br/2009/08/embracing-rest-with-mind-body-and-soul/● https://github.com/plataformatec/responders
MADRID · NOV 21-22 · 2014
ActionView::Resolver● Plataformatec: http://blog.plataformatec.com.br/2011/04/default-views-in-rails-3-0-with-custom-
resolvers/
ActionController::Live
● Railscast #401: http://railscasts.com/episodes/401-actioncontroller-live● Tenderlove, Is it live?: http://tenderlovemaking.com/2012/07/30/is-it-live.html
Rack Middlewares● Railscast #151: http://railscasts.com/episodes/151-rack-middleware● Stackoverflow: http://stackoverflow.com/questions/2256569/what-is-rack-middleware● Amberit: https://www.amberbit.com/blog/2011/07/13/introduction-to-rack-middleware/
MADRID · NOV 21-22 · 2014
¿Preguntas?