Upgrade to Pro — share decks privately, control downloads, hide ads and more …

10 Cosas de Rails que deberías saber

10 Cosas de Rails que deberías saber

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

Carlos Sánchez Pérez

November 21, 2014
Tweet

More Decks by Carlos Sánchez Pérez

Other Decks in Programming

Transcript

  1. MADRID · NOV 21-22 · 2014 10 cosas de Rails

    que deberías saber Carlos Sánchez & Gabriel Ortuño
  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!
  5. 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
  6. 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
  7. MADRID · NOV 21-22 · 2014 # app/models/message.rb class Message

    < ActiveRecord::Base default_scope -> { where(trashed: false) } scope :trashed, -> { where(trashed: true) } def trash update_attribute :trashed, true end end
  8. 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
  9. 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
  10. 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
  11. 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
  12. MADRID · NOV 21-22 · 2014 # lib/email_validator.rb class 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 end end
  13. MADRID · NOV 21-22 · 2014 class Person < ActiveRecord::Base

    validates :email, presence: true, email: true end
  14. 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]')
  15. 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
  16. 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
  17. 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 end end
  18. 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 %>
  19. 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.
  20. 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
  21. 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
  22. 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
  23. 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.
  24. 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
  25. 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
  26. 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...
  27. 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
  28. 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
  29. MADRID · NOV 21-22 · 2014 # lib/flash_responder.rb class 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? end end
  30. MADRID · NOV 21-22 · 2014 # app/controllers/application_controller.rb class ApplicationController

    < ActionController::Base self.responder = FlashResponder respond_to :html end
  31. 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
  32. 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 end end
  33. 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
  34. 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
  35. 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}"))
  36. 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
  37. 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
  38. 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 end end
  39. 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.
  40. MADRID · NOV 21-22 · 2014 class EnvironmentInterceptor def self.delivering_email(message)

    message.to = "[email protected]" message.subject = "#{Rails.env} #{message.subject}" end end
  41. 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
  42. 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
  43. MADRID · NOV 21-22 · 2014 Rack Rack proporciona una

    interfaz mínima entre servidores web y frameworks Ruby
  44. MADRID · NOV 21-22 · 2014 $ 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 ...
  45. 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
  46. 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 end end
  47. 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
  48. 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
  49. 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
  50. 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/