ActiveRecord::RecordNotFound

Visita este artí­culo en http://www.estadobeta.com/2007/05/06/activerecordrecordnotfound/

Por Ismael en Patrones de diseño, Ruby & Rails, artículos, tips

Aún cuando Ruby on Rails nos facilita enormemente las cosas al proveer una estructura convencional para el desarrollo de aplicaciones web, sigue siendo fácil perderse en los detalles. Por ejemplo, ¿cuál es la mejor forma de mostrar el detalle de un objeto? Sin manejo de errores, la acción de Controlador que busca y muestra un Modelo (una instancia de ActiveRecord) desde la base de datos es trivial:


def show
    @product = Product.find params[:id]
    @category = @product.category
end

El objeto @product queda disponible para las vistas (templates) HTML o de cualquier formato, en el supuesto de que el parámetro id sea provisto en el request al browser.

Pero qué pasa si esa id no existe en la base de datos? El objeto no será encontrado y nuestra página dará error.

Hay muchas forma de validar la existencia del objeto buscado, pero dado que esta es una acción frecuente en cualquier aplicación, definamos una convención (en el mejor espíritu DRY).


def show
    begin
        @product = Product.find params[:id]
    rescue ActiveRecord::RecordNotFound
        flash[:notice] = ‘Ese objeto no existe’
        redirect_to :action => ‘index’
    else
        @category = @product.category
    end
end

¿Qué pasó ahí? Cuando ActiveRecord::find no encuentra resultados en la base de dato, emite una excepción de tipo Activerecord::RecordNotFound (que desciende de la clase Exception, de Ruby). Con este conocimiento, y aprovechando el manejo de excepciones particular de Ruby, podemos reencauzar el flujo del programa en caso de que el objeto solicitado no exista en la base de datos.

En Ruby, luego de las cláusulas begin / rescue podemos, opcionalmente, usar else, normalmente usado junto a if. El código despues de else se ejecutará solamente si begin no emitió excepciones.

Al usar rescue sólo para reaccionar ante excepciones de tipo Activerecord::RecordNotFound, permitimos que otro tipo de excepciones surjan hacia capas superiores del programa y sean procesadas por el manejo de excepciones normall de Rails (que generalmente produce una página de error 500).

¿Cómo interceptar esas otras excepciones para generar páginas de error más amigables? Tema para otro artículo.

ActiveRecord, RubyonRails, Ruby, Web development

14 comentarios para “ActiveRecord::RecordNotFound”

  1. GravatarRodrigo Dice:

    otra opción es usar exists? (hace lo mismo, pero me parece más limpio y legible).
    if Product.exists?(params[:id])
    *busco los datos*
    else
    *aviso que no existe*
    end

    Saludos!

  2. GravatarIsmael Dice:

    Cierto. El problema que veo con eso es que te obliga a hacer 2 queries a la DB.


    if Product.exists?(params[:id]) #1er query
      @product = Product.find(params[:id]) #2do query
    end

    En realidad, la técnica que describo es más útil cuando la usas en conjunto con rescue_action_in_public, de ActionController, para generar páginas 404 si el modelo no existe:


    def rescue_action_in_public( excepcion )
      case excepcion
        when ActiveRecord::RecordNotFound
          render :file => RAILS_ROOT + '/public/404.html',
           :status=> '404 Not Found'
        else
          super( exception )
      end
    end

  3. GravatarHector Vergara Reinoso Dice:

    otra cosa que podrías hacer es un ‘hack’ a AR.


    class ActiveRecord::Base
      def find_safe(*args)
        begin
          find(args)
        rescue ActiveRecord::RecordNotFound
          nil
        end
      end
    end

    Si no encuentra el ‘record’ con ese ID, retorna nil. Lo podrias chequear con:


    if @product
      #lala
    else
      #lolo
    end

    aunque voy mas por usar el rescue_action_in_public si de algo mas serio se trata.

    Saludos!, Héctor.

  4. GravatarIsmael Dice:

    Buena solución H!

  5. GravatarJosé Antonio Silva Dice:

    Me parece que Rails cuando esta en enviroment “production” oculta los mensajes de error y coloca una página que dice “Aplication Error”, es decir los usuarios finales no verán los errores feos por lo qu eno es necesario hacer eso.

  6. GravatarIsmael Dice:

    SI, pero eso por defecto envía un error 500 al browser. Si el usuario no encuentra lo que estás buscando, probablemente quieres envíar un error 404 (”página no encontrada”) y tener más control sobre el diseño de la página de error. por ejemplo: mostrar información relacionada, un buscador, el mapa del sitio, etc.

  7. GravatarBeto Dice:

    Definitivamente el try catch de Ismael es la mejor solucion.

  8. GravatarHector Vergara R. Dice:

    Beto: por qué? Beto ha saber?

  9. GravatarIsmael Dice:

    :)

  10. GravatarBeto Dice:

    Bueno porque considero que es la solución más limpia y eficiente para resolver el problema. Además que extender la clase ActiveRecord para solucionar este problema (pequeño por lo demas) como que no me gusta de onda.. xD.

  11. GravatarIsmael Dice:

    Lo más limpio es no hacer nada en el controlador:


    def show
    @product = Product.find( params[:id] )
    end

    En tu application controller usas rescue_action_in_public para atrapar las excepciones de tipo RecordNotFound y presentar la página de error correspondiente.

    
    def rescue_action_in_public(exception)
      case exception
        when ActiveRecord::RecordNotFound
          # .. redirección, pag, de error, etc.
        else
          super
      end
    end
    
    
  12. GravatarIsmael Dice:

    Lo anterior sólo funciona si usas ActiveRecord:.Base#find, pasándole una ID como único argumento. Si quieres usar otro campo como (como el nombre o “permalink”) y mantener este patrón de diseño, puedes usar mi plugin sluggable_finder :)

  13. GravatarEstadoBeta » Archivo » Rails 2.0 Preview Release Dice:

    […] con excepciones como ActiveRecord::RecordNotFound, desde interceptarlas en cada acción hasta capturarlas en un sólo punto con rescue_action_in_public. Ahora, ActionController provee una macro para definir métodos […]

  14. GravatarEstadoBeta » Archivo » Plugin Rails: sluggable_finder Dice:

    […] Las ID’s en una base de datos debieran ser únicas; Rails aprovecha esto y por convención emite una excepción ActiveRecord::RecordNotFound si el objeto con esa ID no existe. Esto es útil porque podemos usar esa particularidad del método find para normalizar nuestro manejo de errores. […]

Deja un comentario

XHTML: puedes usar estas etiquetas: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>