Modelo Vista Controlador (MVC) - moiseserg/rubyWebDev GitHub Wiki

El patrón arquitectónico MVC [5] busca la separación entre la lógica de la aplicación y la lógica de la presentación de la información asociada a una interfaz gráfica (GUI).

Al momento de interactuar con una aplicación en Rails el navegador envía una solicitud HTTP que es recibida por el controlador que se encarga de reaccionar de acuerdo a la solicitud. En algunos casos el controlador es sólo una página estática (por ejemplo un index.html para la aplicación) o en la mayoría de los casos se encarga de enviar los parámetros de la consulta al modelo que responde con datos obtenidos de una base de datos. Los datos resultantes son enviados a la Vista que les da formato, regresan al controlador y son enviados en formato HTML al navegador para su posterior presentación.

MVC

Archivos que implementan MVC

Modelo

Dentro de la carpeta app/models se crea el archivo empleado.rb. A este archivo se le agregan comúnmente validaciones para controlar que los valores que se intenten asignar sean correctos basados en el entorno de la aplicación. Por ejemplo, edades no pueden ser negativas, el RFC que debe empezar con letras y tener una fecha válida, etc.

class Empleado < ApplicationRecord
end

En esta caso, suponga la validación de existencia de un nombre para un empleado:

class Empleado < ApplicationRecord
	  validates :nombre, presence: true
end

Si se intenta crear un empleado sin introducir el nombre, genera el siguiente error:

errorEm

Revisar la siguiente referencia para mayor detalle Active Record Validations

Vista

Entre los archivos que se generan para la vista, destaca el archivo _form.html.erb en la carpeta app/views/empleado/, este archivo contiene una forma que se cargará cada que se desee crear un nuevo Empleado o editar (update) su contenido. El formato ERB es interno de Ruby, significa Embedded Ruby sirve para evaluar código Ruby y generar código HTML de manera dinámica - de manera similar a PHP.

<%= form_with(model: empleado, local: true) do |form| %>
  <% if empleado.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(empleado.errors.count, "error") %> prohibited this empleado from being saved:</h2>

      <ul>
      <% empleado.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :nombre %>
    <%= form.text_field :nombre, id: :empleado_nombre %>
  </div>

  <div class="field">
    <%= form.label :direccion %>
    <%= form.text_area :direccion, id: :empleado_direccion %>
  </div>

  <div class="field">
    <%= form.label :edad %>
    <%= form.number_field :edad, id: :empleado_edad %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

Otro archivo generado en esta carpeta es el archivo index.html.erb, éste se ejecuta para listar a todos los empleados (invoca a Empleado.all de manera interna) y muestra en una tabla los datos de los empleados actuales en la tabla. Además genera de manera automática las ligas para poder Crear un nuevo Empleado, Mostrar, Modificar y Eliminar algún Empleado seleccionado. A esto se le llama *CRUD (Create, Read, Update, Remove) por sus siglas en inglés.

<p id="notice"><%= notice %></p>

<h1>Empleados</h1>

<table class="table table-striped">
  <thead>
    <tr class="bg-ligt" >
      <th>Nombre</th>
      <th>Direccion</th>
      <th>Edad</th>
      <th colspan="3" class="text-center">Comandos</th>
    </tr>
  </thead>

  <tbody>
    <% @empleados.each do |empleado| %>
      <tr>
        <td><%= empleado.nombre %></td>
        <td><%= empleado.direccion %></td>
        <td><%= empleado.edad %></td>
        <td><%= link_to ' Mostrar', empleado, :class => 'btn  btn-success btn-sm fa fa-cart-arrow-down'  %>	  </td>
        <td><%= link_to 'Edit', edit_empleado_path(empleado), :class => 'btn  btn-info btn-sm'  %></td>
        <td><%= link_to 'Destroy', empleado, method: :delete, data: { confirm: 'Estas seguro?' }, :class => 'btn  btn-danger btn-sm'  %></td>

      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'Nuevo empleado', new_empleado_path %>

Controlador

El archivo controllers/empleados_controller.rb contiene el controlador para el empleado que consiste en la lógica que manipulará al modelo.

Por ejemplo al momento de crear un empleado se ejecuta la instrucción save de la variable @empleado (@ implica que es una instancia interna de tipo empleado y ejecuta el método save para guardarlo en la tabla), si se puede guardar el empleado genera un mensaje de éxito en este caso 'Empleado was successfully created.', en caso contrario manda un mensaje de error. Algo análogo sucede al momento de actualizar (update) un empleado.

class EmpleadosController < ApplicationController
  before_action :set_empleado, only: [:show, :edit, :update, :destroy]

  # GET /empleados
  # GET /empleados.json
  def index
    @empleados = Empleado.all
  end

  # GET /empleados/1
  # GET /empleados/1.json
  def show
  end

  # GET /empleados/new
  def new
    @empleado = Empleado.new
  end

  # GET /empleados/1/edit
  def edit
  end

  # POST /empleados
  # POST /empleados.json
  def create
    @empleado = Empleado.new(empleado_params)

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

  # PATCH/PUT /empleados/1
  # PATCH/PUT /empleados/1.json
  def update
    respond_to do |format|
      if @empleado.update(empleado_params)
        format.html { redirect_to @empleado, notice: 'Empleado was successfully updated.' }
        format.json { render :show, status: :ok, location: @empleado }
      else
        format.html { render :edit }
        format.json { render json: @empleado.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /empleados/1
  # DELETE /empleados/1.json
  def destroy
    @empleado.destroy
    respond_to do |format|
      format.html { redirect_to empleados_url, notice: 'Empleado was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_empleado
      @empleado = Empleado.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def empleado_params
      params.require(:empleado).permit(:nombre, :direccion, :edad)
    end
end

Validaciones

Las validaciones son métodos que ayudan a asegurarse que los datos que se van a introducir a la base de datos son correctos bajo ciertas especificaciones. Son una forma conveniente de probar y mantener la base de datos consistente. Rails implementa a nivel del modelo una cantidad que se puede considera que cumple con la mayoría de las circunstancias 7

Los modelos contienen información acerca de las características que pueden ser valores de distintos tipos. Por ejemplo las edades son enteros, por sentido común no se manejan con decimales y tampoco se manejan edades negativas. Dentro del modelo es posible agregar lo que se conoce como validaciones. Algunas validaciones frecuentes son para direcciones de correo (que contengan una arroba), las letras la inicio y posteriormente una fecha válida en el formato YYYYMMDD y una posible homoclave para el RFC, una estructura específica para la CURP, longitudes mínimas o máximas de una cadena de texto, inclusive valores mínimos o máximos para alguna cantidad numérica ingresada.

Las validaciones pueden ser ejecutadas al momento de crear instancias de un objeto en memoria (new) o al momento de insertar o actualizar un registro en la base de datos. Las validaciones son ejecutadas antes de insertar los objetos a la base de datos, para evitar la inserción de objetos inválidos (create, create!, save, save!, update, update!).

Existen algunos métodos que ignoran las validaciones y pueden introducir un estado inconsistente a la base de datos, algunos de ellos son [7] :

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

save también puede ser invocado con el parámetro validate: false para que ignore las validaciones.

save(validate: false)

El método valid? puede ser usado para verificar la validez de un objeto, este método regresa true o false como resultado de la validación de validez del objeto.

class Empleado < ActiveRecord::Base
  validates :nombre, presence: true
end

Si se ejecuta en consola, el método valid? arrojará :

irb(main):001:0> Empleado.create(nombre: "Juan").valid?
   (0.5ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
   (0.4ms)  BEGIN
  SQL (0.6ms)  INSERT INTO `empleados` (`nombre`, `created_at`, `updated_at`) VALUES ('Juan', '2018-01-26 18:52:39', '2018-01-26 18:52:39')
   (57.9ms)  COMMIT
=> true
irb(main):002:0> Empleado.all
  Empleado Load (0.8ms)  SELECT  `empleados`.* FROM `empleados` LIMIT 11
=> #<ActiveRecord::Relation [
#<Empleado id: 5, nombre: "Maria", direccion: "Calle Rev. Mex. #22", edad: 20, created_at: "2018-01-26 18:30:45", updated_at: "2018-01-26 18:30:45">, 
#<Empleado id: 6, nombre: "Juan", direccion: nil, edad: nil, created_at: "2018-01-26 18:52:39", updated_at: "2018-01-26 18:52:39">]>
irb(main):003:0> 
Empleado.create(nombre: nil).valid? 
   (0.4ms)  BEGIN
   (0.4ms)  ROLLBACK
=> false

Las validaciones pueden ser ejecutadas después de crear una instancia del objeto en memoria con new en el siguiente ejemplo se puede apreciar la validez del objeto en p. Después de crear el objeto (tiene nombre = nil) no es válido, pero al asignarle un nombre, pasa a ser válido. Al momento de crear un objeto q, igualmente no es válido y si se intenta almacenar en la tabla no se podrá completar la transacción. Por otro lado, al guardar p en la tabla, éste si se inserta.

irb(main):007:0> p= Empleado.new
=> #<Empleado id: nil, nombre: nil, direccion: nil, edad: nil, created_at: nil, updated_at: nil>
irb(main):008:0> p.errors
=> #<ActiveModel::Errors:0x00007f9d9e456d00 @base=#<Empleado id: nil, nombre: nil, direccion: nil, edad: nil, created_at: nil, updated_at: nil>, @messages={}, @details={}>
irb(main):009:0> p.errors.messages
=> {}
irb(main):011:0> p.valid?
=> false
irb(main):012:0>  p.nombre='Pedro'  
=> "Pedro"
irb(main):013:0> p.valid?  
=> true
irb(main):014:0> q = Empleado.new  
=> #<Empleado id: nil, nombre: nil, direccion: nil, edad: nil, created_at: nil, updated_at: nil>
irb(main):015:0> p.save
   (0.4ms)  BEGIN
  SQL (0.6ms)  INSERT INTO `empleados` (`nombre`, `created_at`, `updated_at`) VALUES ('Pedro', '2018-01-26 18:56:30', '2018-01-26 18:56:30')
   (30.1ms)  COMMIT
=> true
 

Helpers para validaciones

Rails provee métodos para ayudar a validar la mayor cantidad posible de escenarios (helpers). Cada uno de estos helpers provee un mensaje de error por default que permite identificar el error en el atributo asociado.

Para el caso del empleado, se utiliza presence, pero también se puede pedir una longitud mínima en el texto del nombre y de la dirección y que la edad sea un valor entero y mayor que 18. El código que hace dichas validaciones es el siguiente:

class Empleado < ApplicationRecord
	  validates :nombre, length: {minimum: 5}, presence: true
	  validates :direccion, length: {minimum: 10}, presence: true
	  validates :edad, numericality: {only_integer: true, greater_than_or_equal_to: 18}
end

El siguiente fragmento muestra algunas validaciones y el uso del método valid?

irb(main):001:0> p = Empleado.new
   (0.6ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<Empleado id: nil, nombre: nil, direccion: nil, edad: nil, created_at: nil, updated_at: nil>
irb(main):002:0> p.valid?
=> false
irb(main):003:0> p.nombre="Karla"
=> "Karla"
irb(main):004:0> p.valid?
=> false
irb(main):005:0> p.edad=18
=> 18
irb(main):006:0> p.direccion="Reforma #23"
=> "Reforma #23"
irb(main):007:0> p.valid?
=> true
irb(main):008:0> p.edad=17
=> 17
irb(main):009:0> p.valid?
=> false

Si se hacen pruebas similares en la página Web pueden verse mensajes de error como:

error02

Algunas validaciones importantes son:

  • acceptance Este método valida que un checkbox en la interfaz haya sido habilitada. Se usa típicamente cuando se requiere que el usuario esté de acuerdo con los términos de servicio o un concepto análogo. En este caso la aceptación no requiere ser almacenada explícitamente en la base de datos.
  • validates_associated Se utiliza cuando el modelo tiene asociaciones con otros modelos y éstos también deben ser validados.
  • confirmation Se usa cuando dos campos deben contener exactamente el mismo texto. Por ejemplo cuando hay que validar el correo electrónico en dos cajas de texto distintas.
  • exclusion Valida que un atributo no esté incluido dentro de otro conjunto, por ejemplo puede ser utilizado para que cierto texto no encaje dentro de otro, por decir que no acepte alguna url de cierto dominio.
  • inclusion el opuesto de exclusion.
  • format Valida que el valor de entrada cumpla con algún formato, por ejemplo para validar alguna dirección de correo o el RFC.
  • length Valida que la longitud del atributo cumpla con alguna propiedad como:
    • Máxima longitud
    • Mínima longitudes
    • Intervalo
	class Empleado < ActiveRecord::Base
	  validates :nombre, length: { minimum: 2 }
	  validates :direccion, length: { maximum: 500 }
	  validates :password, length: { in: 6..20 }
	end
  • numericality Valida que el atributo contenga exclusivamente valores numéricos ya sean enteros (:only_integer) o de punto flotante. Algunas validaciones útiles son:

    • :greater_than
    • :greater_than_or_equal_to
    • :equal_to
    • :less_than
    • :less_than_or_equal_to
    • :odd
    • :even
  • presence valida que el atributo especificado no esté vacío, usa el método blank?

  • uniqueness valida que el valor ingresado sea único antes de que sea almacenado en la tabla. Cabe mencionar que no crea la propiedad de unicidad en la tabla por lo que pueden ocurrir dos conexiones al mismo tiempo que puedan violar esta validación. Se puede utilizar para crear usuarios únicos, que no se haya registrado el mismo correo anteriormente, entre otros. La validación está implementada haciendo una consulta SQL para validar la unicidad solicitada.

Internacionalización

Rails provee la gema I18n para traducción de la aplicación hacia diferentes lenguajes. Los archivos de traducción están localizados en config/locales. En la carpeta hay un archivo en.yml que tiene un ejemplo que puede ser modificado directamente para hacer cambios.

en:
  hello: "Hello world"

puede cambiarse a:

en:
  hello: "Hello world"

  helpers:
    submit:
      create: "Crear un %{model}"
      update: "Confirmar cambios al %{model} creado"

Esto permitirá que las etiquetas a español.

transl

Sin embargo, esto es bastante limitado. I18n provee funciones para cargar páginas en diferentes lenguas.

Instalación de la gema

Agregar al Gemfile dependiendo de la versión de rails:

gem 'rails-i18n', '~> 5.0.0' # For 5.0.x and 5.1.x
gem 'rails-i18n', '~> 4.0.0' # For 4.0.x
gem 'rails-i18n', '~> 3.0.0' # For 3.x
gem 'rails-i18n', github: 'svenfuchs/rails-i18n', branch: 'master' # For 5.x
gem 'rails-i18n', github: 'svenfuchs/rails-i18n', branch: 'rails-4-x' # For 4.x
gem 'rails-i18n', github: 'svenfuchs/rails-i18n', branch: 'rails-3-x' # For 3.x

O ejecutar el comando:

gem install rails-i18n -v '~> 5.0.0' # For 5.0.x and 5.1.x
gem install rails-i18n -v '~> 4.0.0' # For 4.0.x
gem install rails-i18n -v '~> 3.0.0' # For 3.x

En rails-i18n puede ver archivos de traducción para diferentes lenguas - ubicar: es-MX.yml y descargarlo a la carpeta ./config/locale/

Configuración de locale

Agregar a config/application.rb lo siguiente para cargar los archivos de traducción.

	config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]

	# Whitelist locales available for the application
	I18n.available_locales = [:es, :en]
	 
	# Set default locale to something other than :en
	I18n.default_locale = :es

El siguiente es un fragmento de algunos cambios para poder mostrar el texto Categorias en lugar de Categorium

---
es:
  hello: "Hola Mundo"
  site_title: 'Esta es la versión en español'
  site_name: 'Mi sitio'
  welcome: 'Bienvenido a nuestro maravilloso sitio web'
  categorium: "Categorias"

  activerecord:
    
    models:
      categorium: "Categorias"
      categoria: 
        one: "Categoria"
        other: "Categorias"
      attributes:
        categoria:
          nombre: "nombre"
    errors:
      messages:
        record_invalid: "La validación falló: %{errors}"
        restrict_dependent_destroy:
          has_one: El registro no puede ser eliminado pues existe un %{record} dependiente
          has_many: El registro no puede ser eliminado pues existen %{record} dependientes

Para esto, el archivo edit.html.erb tiene el siguiente código

<h1>Editing <%=t :categorium  %> </h1>

<%=t :hello  %></h1>

<%= render 'form', categorium: @categorium %>

<%= link_to 'Show', @categorium %> |
<%= link_to 'Back', categoria_path %>

El resultado se muestra en la siguiente imagen

locES

Note que para entrar a las páginas se usa en el url la abreviación del lenguaje: http://localhost:3000/es/categoria/1/edit

El único texto que queda en inglés es el siguiente pero puede ser cambiado usando la misma idea en el archivo es.yml

<h1>Editing <%=t :categorium  %> </h1>

Referencias para este tema:

⚠️ **GitHub.com Fallback** ⚠️