Este es el cuarto post de la serie sobre Ruby on Rails desde cero creada por @smarquesz, no dejes de revisar los otros posts.

RoR

En el capítulo anterior, generamos un pequeño módulo usando scaffold, ahora revisaremos y explicaremos el código generado, vamos a partir por las rutas, para lo cual usando su editor favorito (les recomiendo Sublime Text) vamos a abrir el proyecto y navegaremos hasta config/routes.rb, ahí encontraremos el siguiente código:

Rails.application.routes.draw do
  resources :posts

  # mucho código comentado
end

En este archivo se definen todas las rutas que serán soportadas por nuestra aplicación, en este ejemplo el scaffold, nos ha generado todos los recursos para posts, es decir todas las rutas Restful, pero, ¿que pasa si quiero agregar nuevas rutas?, ¿puedo cambiar el home por defecto de mi aplicación?, bueno si miramos un poco el código que se encuentra comentado, encontraremos las respuestas.

  # You can have the root of your site routed with "root"
  root 'welcome#index'

Haciendo uso de root, desactivaremos el home por defecto y crearemos uno personalizado, para eso ubiquen la linea del ejemplo anterior y le quitamos el comentario, tal como se observa en el ejemplo, ahora vamos a la terminal siempre dentro de my-first-app y levantamos el servidor usando:

rails server

Luego abrimos el navegador y nos dirigimos a http://localhost:3000/ y:

Ruby Routing error

¿Qué es eso? ¡Tranquilos! Recuerdan que en el capítulo anterior vimos que cuando se hace match a una ruta, se crea la instancia del controlador y se ejecuta la acción indicada, en este caso Welcome#index no existe. ¿Cómo lo solucionamos? Sencillo, vamos a utilizar nuevamente los maravillosos generadores de Ruby on Rails, para eso nos dirigimos a la terminal, presionamos ctrl + c, para detener nuestro servidor y ejecutamos lo siguiente:

rails g controller welcome index --no-test-framework

Al ejecutar esto, obtendremos la siguiente salida:

create  app/controllers/welcome_controller.rb
route  get 'welcome/index'
invoke  erb
create    app/views/welcome
create    app/views/welcome/index.html.erb
invoke  helper
create    app/helpers/welcome_helper.rb
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.js.coffee
invoke    scss
create      app/assets/stylesheets/welcome.css.scss

¡Excelente! ya hemos creado nuestro home personalizado, para verlo, ejecutamos nuevamente «rails server», vamos a nuestro navegador y nos dirigimos de nuevo a http://localhost:3000/ y listo, ¿Maravilloso, no? Pues sí, pero antes de personalizarlo realmente veremos otro tema importantísimo para poder entender el framework Ruby on Rails, llamado convención sobre configuración.

Este patrón de convención sobre configuración le permite a los frameworks como Ruby on Rails resolver problemas o tomar decisiones automáticamente por nosotros, si yo estoy llamando a la acción index del controlador Welcome, se hará el render de la vista index.html.erb que se encuentra dentro de app/views/welcome, por lo cual naveguemos hasta ahí y escribamos un mensaje personalizado: «Bienvenido a nuestra aplicación super cool».

Nos dirigimos nuevamente al navegador, refrescamos, y voilà! Ya hemos creado nuestro home personalizado.

Controlador

Muy bien es hora de analizar el controlador de posts y ver que encontramos ahí dentro, para lo cual en nuestro editor de texto favorito, abriremos el archivo posts_controller que se encuentra dentro de app/controllers, y veremos el siguiente código:

Lo primero que nos podría parecer extraño es:

before_action :set_post, only: [:show, :edit, :update, :destroy]

def set_post
  @post = Post.find(params[:id])
end

before_action es un método que recibe 2 argumentos: el primero es qué método será ejecutado antes de cada acción y el segundo, para que acciones será ejecutado, en este caso se ejecutará sólo para show, edit, update y destroy. Interesante, pero:

¿Para qué sirve el método before_action?

La respuesta es sencilla, DRY (don’t repeat yourself), no te repitas, se imaginan que el día de mañana viene nuestro jefe y nos dice, «muchachos, ya no vamos a usar el id para buscar los posts, ahora vamos a usar el slug», si no usamos DRY, tendríamos que cambiar el código en 4 métodos, lo cual nos puede llevar a cometer errores; en cambio, en nuestro ejemplo, sólo cambiaríamos el código del método «set_post» que está al final del archivo posts_controller.

Listar todos los posts

Al seguir visualizando el código de nuestro controlador, encontraremos el método index, el cual contiene los siguiente:

def index
  @posts = Post.all
end

Como podemos intuir, estamos pidiendo todos los posts a nuestro modelo, para luego mostrarlos a los usuarios en pantalla. Recuerden que Ruby on Rails, se basa en convención sobre configuración, por lo tanto cuando se ejecute este método, se hará el render del archivo index.html.erb, que podemos encontrar dentro app/views/posts.

En dicha vista, recibimos la colección de @posts y los mostramos en una tabla. Para ejecutar código Ruby dentro de una vista se deben utilizar las etiquetas <%= %> (aunque también tenemos otros sistemas de plantillas como veremos más adelante)

Visualizando un post

def show
end

Se preguntarán qué puede hacer un método vacío, pero recuerden el before_action que explicamos el principio de este apartado, antes de cada una de esas acciones entre ellas show, se ejecuta un método llamado set_post, que pide al modelo el post que coincide con el parámetro id que le estamos enviando en la petición y lo guarda en la variable @post, por ejemplo, al visitar /posts/2, el «2» corresponde al parámetro id, por lo cual al llamarse a la vista app/views/posts/show.html.erb, veremos en pantalla el registro que contenga ese id.

<%= notice %>

Title: <%= @post.title %>

Como podemos observar, estamos recibiendo un objeto del tipo Post almacenado en la variable de instancia @post y mostrando en pantalla sus atributos.

Creando posts

Seguimos avanzando en nuestro controlador, es hora de ver como se crea un nuevo post.

# GET /posts/new
def new
  @post = Post.new
end

Como se muestra en el ejemplo anterior, podríamos indicar que este proceso se compone de 2 partes, primero, al hacer una petición GET a /posts/new, veremos en pantalla la vista app/views/posts/new.html.erb.

New post

<%= render 'form' %> <%= link_to 'Back', posts_path %>

Si ya observaron el código se habrán dado cuenta que en vez de un formulario HTML, nos encontramos con esto. Como ya hemos visto en este artículo, Ruby on Rails, hace mucho énfasis en DRY (don’t repeat yourself), por lo cual lo único que estamos haciendo es separar el formulario en otro archivo, para que luego podamos reutilizarlo, esto se hace a través de un partial.

Los partials son sub-plantillas que pueden reutilizarse dentro de otra plantilla, su nombre debe comenzar con un _ y para incluirlos en nuestras vistas, lo hacemos utilizando el método render, el cual le pasamos como parámetro, el nombre de nuestro archivo, sin el _ ni la extensión (.html.erb). Ahora veamos que contiene este archivo:

<%= form_for(@post) do |f| %>
 <% if @post.errors.any? %>
 

<%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:

    <% @post.errors.full_messages.each do |message| %>
  • <%= message %>
  • <% end %>
<% end %>
<%= f.label :title %>
<%= f.text_field :title %>

Al fin hemos encontrado el formulario que nos permitirá crear un nuevo post, para ello, hacemos uso de un helper llamado form_for, al cual le estamos pasando como argumentos, el objeto @post y un bloque de código que contiene el cuerpo del formulario

Dentro del cuerpo del form, lo primero que encontramos es:

 if @post.errors.any?

Al existir errores de validación de nuestro formulario, éstos serán mostrados en pantalla, para que el usuario los pueda corregir, luego podemos ver que usando:

<%= f.label :title %>
<%= f.text_field :title %>

Se crean los label y los input, por último podemos ver que con f.submit, se creará el botón de submit que enviará nuestro formulario con la información que será guardada en nuestra base de datos, el submit del form es manejado por el método create de nuestro controlador.

# POST /posts
def create
  @post = Post.new(post_params)

  respond_to do |format|
    if @post.save
      format.html { redirect_to @post, notice: 'Post was successfully created.' }
      format.json { render :show, status: :created, location: @post }
    else
      format.html { render :new }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end

Vemos acá, que estamos creando una nueva instancia de nuestro modelo Post y la inicializamos con algo llamado post_params

¿Qué hace el método post_params?

Si vamos al final de nuestro controlador, encontraremos parte de la respuesta.

def post_params
  params.require(:post).permit(:title, :body, :author, :publication_date)
end

¿Para qué me sirve esto?. Con strong parameters, no podemos asignar parámetros de forma masiva a nuestro modelo, hasta que estos son incluidos en una especie de «whitelist», que es lo que estamos haciendo en el método post_params, en él sólo estamos permitiendo, los parámetros title, body, author y publication_date.

Bien ahora que ya entendemos lo que hace strong_parameters, podemos seguir viendo que hace nuestro método create, bueno, en realidad no mucho, sólo estamos llamando al método save del objeto @post, el cual nos devuelve true/false, dependiendo si se creó o no el registro, en caso de que se haya creado correctamente, seremos redireccionados al detalle del post, de lo contrario seremos enviados nuevamente al formulario para que corrijamos los errores.

Editando un post

Es hora de que veamos qué sucede cuando queremos editar la información de un post en particular.

# GET /posts/1/edit
def edit
end

A esta altura ya no nos debemos sorprender que exista un método «vacío», ya que recuerden, que antes de la acción edit, se ejecuta el método, set_post, el cual busca el registro con el id que estamos enviando en la petición GET /posts/1/edit, lo almacena en la variable @post, para luego ser enviada a nuestra vista que se encuentra en app/views/posts/edit.html.erb.

Editing post

<%= render 'form' %> <%= link_to 'Show', @post %> | <%= link_to 'Back', posts_path %>

Si se fijan nuevamente estamos llamando el mismo partial «form», eso quiere decir que estamos reutilizando el mismo formulario tanto para crear como para editar un registro, ahora se estarán preguntando como Rails sabe que acción debe ejecutar, como les dije anteriormente: Ruby on Rails, hace énfasis en la convención sobre configuración, por lo tanto al usar rutas Restful, el helper form_for, al recibir como argumento un recurso (@post), puede inferir a qué url y con que métodos HTTP, realizar la petición, pueden encontrar más información referente a esto en Estilo orientado a recursos.

Si seguimos las convenciones de Ruby on Rails, el framework nos ayudará en muchas tareas triviales y que si queremos hacerlas por nuestra cuenta, nos consumirán mucho de nuestro valioso tiempo.

Es hora de ver que sucede cuando el usuario edita la información y hace click en el botón Update post.

def update
  respond_to do |format|
    if @post.update(post_params)
      format.html { redirect_to @post, notice: 'Post was successfully updated.' }
      format.json { render :show, status: :ok, location: @post }
    else
      format.html { render :edit }
      format.json { render json: @post.errors, status: :unprocessable_entity }
    end
  end
end

Como podemos observar el método update luce similar a create, sin embargo podemos apreciar algunas diferencias, primero que nada, antes de la acción update, se ejecuta el método set_post, que recuerden que busca el registro de acuerdo al id y lo almacena en la variable @post, luego se llama el método update del objeto @post que recibe como argumentos los parámetros del «whitelist» que pueden ser asignados al modelo, el cual nos devolverá true/false, dependiendo si se actualizó correctamente el post o no.

Eliminado un post

Bueno, hemos llegado a la última parte del CRUD, ya hemos visto, Create, Read y Update, ahora veremos Delete. Cuando hacemos una petición al posts/1, usando el método HTTP DELETE, se ejecutará el código del método delete de nuestro controlador.

# DELETE /posts/1
def destroy
  @post.destroy
  respond_to do |format|
    format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
    format.json { head :no_content }
  end
end

Al igual que edit, update y show, antes de ejecutar el método, destroy, se ejecuta el método, set_post, para que dentro del método destroy, podamos hacer uso de dicho @post y poder ejecutar su método @destroy, que eliminará el registro de la base de datos.

Layouts

En las vistas que hemos estudiado hasta ahora sólo hemos encontramos código inherente a dicha vista en particular y ningún código que contenga el header, footer, menús de navegación y otros. Esto es porque Ruby on Rails hace el uso de Layouts:

# codigo dentro de views/layouts/layouts.html.erb




 MyFirstApp
 <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
 <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
 <%= csrf_meta_tags %>



<%= yield %>



Como dije anteriormente todo el código común a todas las vistas, debe ir dentro de los layouts, como podemos observar acá, está el título, que por ahora será el mismo para todas las páginas, sin embargo en la práctica esto no debe ser así, pero la idea por ahora es mantenerlo sencillo, están además las llamadas a los archivos js y css y por último dentro del body encontramos la instrucción <%= yield %> que es en donde será puesto el contenido de nuestras vistas. Los layouts siguen convenciones similares a las vistas, por lo cual si queremos un layout especial para un controlador en particular, basta sólo con colocar un archivo con el nombre de nuestro controlador dentro de app/views/layouts.

Modelo

Hasta este punto, hemos visto, las Rutas, las Vistas y los Controladores, es hora de ver que sucede con los Modelos, para eso vamos abrir el archivo post.rb, que se encuentra dentro de app/models.

class Post < ActiveRecord::Base
end

Cómo podemos observar, nuestro modelo hereda de ActiveRecord::Base, lo cual nos entrega todas las herramientas necesarias para poder leer y escribir la información en nuestra base de datos, ahora podrá parecer algo mágico, pero a medida que vayan avanzando en el aprendizaje del framework y el lenguaje Ruby, entenderán como es que sucede la magia detrás de escena :).

Como les dije en el post anterior, el modelo tiene 2 responsabilidades, manipular la data y encapsular la lógica de negocios, para ilustrar esto último, supongamos que el título y el autor, de ahora en adelante deben ser requeridos, es decir no se permitirá crear un post, si no contiene esos datos, para lo cual vamos a ir a nuestro modelo de Post y agregar lo siguiente:

validates :title, :author, presence: true

Para probarel código recuerden dirigirse a la terminal siempre dentro de la carpeta de nuestro proyecto my-first-app y ejecutar "rails server"

Luego vamos a ir a nuestro navegador a crear un nuevo post, http://localhost:3000/posts/new y vamos a presionar el botón Create post, dejando en blanco en título y el autor.

rails-error-postComo se pueden dar cuenta con una simple línea hemos agregado lógica de validación a nuestro modelo. Maravilloso ¿No?.

Conclusión
Este post es la continuación de nuestro post anterior, en donde se generó todo este código, usando los generadores de rails, sin embargo me quise dar el tiempo de explicarles que hace el código, ya que es la base para poder aprender como realmente funciona el framework, que sucede cuando se recibe una petición del cliente (usuario), como se manejan las rutas, etc.

Es cierto que en el mundo real, la programación no es tan sencilla como hacer un CRUD, sin embargo si entienden esto y siguen la filosofía de Ruby on Rails, les será mucho más simple abordar problemas más complejos.

Los espero en nuestra próxima entrega, en donde comenzaremos a ver temas más orientados a problemas del mundo real.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección anterior MVC, Rutas y Scaffold en Ruby on Rails Lección siguiente Creando un sitio multi idioma con Ruby on Rails (i18n)