Skip to content

Latest commit

 

History

History
413 lines (326 loc) · 14.5 KB

vistas-automaticas.md

File metadata and controls

413 lines (326 loc) · 14.5 KB

Vistas automáticas con Msip::Modelo y Msip::ModelosController

En MSIP es posible tener vistas automáticas para un modelo y su controlador, con buen grado de personalización.

Esto requiere que el modelo incluya el módulo Msip::Modelo y que el controlador sea descendiente de Msip::ModelosController

Lo ilustramos a continuación con un ejemplo, supongamos que necesitamos una tabla histórica con tasas de cambio a diversos tipos de moneda (como se tiene en cor1440_cinep). La tabla será tasacambio, con modelo Tasacambio sus campos serán:

  • id
  • Fecha localizada (i.e dia/Mes/año)
  • Tipo de moneda, la cual será llave foránea de la tabla básica Tipomoneda (es decir tendrá campos id y nombre). Sera un decimal localizado
  • Valor en pesos de la moneda a la fecha dada

1. Incluir Msip::Modelo en el modelo

Supongamos que creamos uno inicial con:

rails g migration crea_tasacambio

Y que en la migración que se crea (digamos db/migrate/20170810040012_crea_tasacambio.rb):

class CreaTasacambio < ActiveRecord::Migration[5.1]
  def change
    create_table :tasacambio do |t|
      t.date :fecha
      t.integer :tipomoneda_id
      t.decimal :enpesos
      t.string :observaciones, limit: 5000
    end
    add_foreign_key :tasacambio, :tipomoneda, column: :tipomoneda_id
  end
end

Se podrá crear la tabla en la base de datos con:

rails db:migrate

El modelo en app/models/tasacambio es:

class Tasacambio < ActiveRecord::Base
  include Msip::Modelo
  include Msip::Localizacion

  campofecha_localizado :fecha
  flotante_localizado :enpesos

  belongs_to :tipomoneda, class_name: '::Tipomoneda',
    foreign_key: 'tipomoneda_id'

  validates :fecha, presence: true
  validates :tipomoneda_id, presence: true
  validates :enpesos, numericality: { greater_than: 0}, presence: true

  validates :tipomoneda_id, uniqueness: {
    scope: :fecha,
    message: 'no puede haber dos tasas para una moneda en una misma fecha'
  }
end

Note que es un modelo típico pero incluye Msip::Modelo para facilitar la creación de vistas genéricas y Msip::Localizacion para facilitar localización de fechas.

Administre los permisos para gestionar según el rol en app/models/ability.rb:

  can :manage, Tasacambio

2. El controlador debe ser descendiente de Msip::ModelosController

En este caso como la forma plural de tasa de cambio es tasas de cambio en config/initializers/inflections.rb agregamos:

  inflect.irregular 'tasacambio', 'tasascambio'

Y el contenido de app/controllers/tasascabio_controller es:

class TasascambioController < Msip::ModelosController
  helper ::ApplicationHelper

  before_action :set_tasacambio,
    only: [:show, :edit, :update, :destroy]
  load_and_authorize_resource class: ::Tasacambio

  def clase
    "::Tasacambio"
  end

  def atributos_index
    [ "id",
      "fecha_localizada",
      "tipomoneda_id",
      "enpesos_localizado",
      "observaciones"
    ]
  end

  def index_reordenar(registros)
    return registros.reorder(fecha: :desc, tipomoneda_id: :asc)
  end

  def new_modelo_path(o)
    return new_tasacambio_path()
  end

  def genclase
    return 'F'
  end

  private

  def set_tasacambio
    @registro = @basica = @tasacambio = ::Tasacambio.find(
      Tasacambio.connection.quote_string(params[:id]).to_i
    )
  end

  # No confiar parametros a Internet, sólo permitir lista blanca
  def tasacambio_params
    params.require(:tasacambio).permit([
      :enpesos_localizado,
      :fecha_localizada,
      :observaciones,
      :tipomoneda_id
    ])
  end
end

En este caso supondremos que las rutas serán las típicas de rails (tasascambio, tasascambio/1) aunque preferimos en español las rutas para editar y nuevas. Se crean definiendo en config/routes.rb:

resources :tasascambio,
      path_names: { new: 'nueva', edit: 'edita' }

En caso que prefiera usar singular tanto para el modelo como para el controlador, especifique la misma cadena singular y plural en config/initializers/inflections.rb y cree una ruta post para el modelo en singular con alias crea_modelo_path y que diriga al método create del controlador. Ver por ejemplo como se hace con plantillahcr en https://gitlab.com/pasosdeJesus/heb412_gen/blob/main/config/initializers/inflections.rb y https://gitlab.com/pasosdeJesus/heb412_gen/blob/main/config/routes.rb.

2.1 Lo clásico de un controlador

Notará que tiene varios métodos típicos de un controlador de rails: tasacambio_params, set_tasacambio y al comienzo las declaraciones:

   before_action :set_tasacambio,
    only: [:show, :edit, :update, :destroy]
  load_and_authorize_resource class: ::Tasacambio

La primera para establecer la variable @tasacambio antes de llamar métodos show, edit, update y destroy con la función set_tasacambio. Notará que esa función establece @ŧasacambio como es típico en rails, pero también @registros que es requerida por las vistas automáticas.

Por su parte load_and_authorize... asegura que se cumplan las reglas de autorización que se definan sobre ::TasaCambio. Sin embargo el controladore y las vistas automáticas lo obligan como se explica en Autorización por omisión con Msip::ModelosController

2.2 Lo nuevo en un controlador

El método clase retorna una cadena con el módelo de la clase que principalmente modificaran las vistas automáticas.

El método atributos_index retorna un vector con los campos que deben presentarse en la vista index (también sería posible especificar atributos por presentar en el formulario y en la vista show, pero si no se especifican mediante los métodos atributos_form y atributos_show por omisión serán los mismos de atributos_index.)

El método index_reordenar reordena los registros que se presentarán en la vista index.

El método new_modelo_path retorna la ruta para crear un nuevo registro de la tabla tasacambio.

El método genclase retorna el genero en español del módelo, por ejemplo como decimos "la tasa de cambio" (femenino) se retorna 'F', (si fueses "el proyecto" sería 'M').

Notará que las vistas automáticas usan bootstrap para el diseño y tom-select para los campos de texto

3. Prueba inicial

Con esto basta para usar las vistas creadas automáticamente. Inicie el servidor de prueba

rails s

Y en el navegador puede dirigirse a la ruta /tasascambio

Verá el listado con diseño estilo bootstrap y listo para agregar nuevos registros. Cuando agregue suficientes verá el paginado.

Notará un primer filtro en la columna id, podrá definir otros personalizando un poco más el modelo y el controlador o creando vistas parciales.

Con el botón Nueva ingresará al formulario de edición, la cual tendrá como campos de selección (agradables con tom-select) los campos que en el modelo sean belongs_to y has_many.

4. Personalización de las vistas

Puede usar sus propias vistas creando los archivos app/views/tiposcambio/index.html.erb, app/views/tiposcambio/show.html.erb, app/views/tiposcambio/edit.html.erb y app/views/tiposcambio/new.html.erb.

Como haría en una aplicación rails típica, sin embargo la infraestructura de Msip permite hacer pequeñas modificaciones a las vistas genéricas que genera para Msip::Modelo, como se explica en esta sección.

4.1 Listado (vista index)

4.1.1 Título y nombres de campos

El título se presenta en la parte superior del listado y los nombres de campos son los títulos de las columnas (así como los nombres de campo en el formulario) se modifican en config/locale/es.yml allí dentro de es.attributes.activerecord.attributes cree una sección como:

      tasacambio:
        Tasacambio: Tasa de cambio
        Tasascambio: Tasas de cambio
        tipomoneda: Tipo de moneda

Si necesita que el título de una columna (digamos con atributo pmindicador) sea diferente sólo en el listado, puede crear una vista parcial de nombre _index_titulo_tabla_pmindicador.

4.1.2 Filtro en listado

Usted puede modificar por completo el filtro estándar que se presenta definiendo en el directorio de la vista el archivo _index_filtro.html.erb que recibe el formulario en la variable f.

Si prefiere también puede personalizar el filtro por omisión de una vista index, el cual inicialmente sólo tiene la identificación (para buscar por esta). Para agregar otros criterios basta agregar scopes en el modelo cuyo nombre sea de la forma :filtro_campo donde campo es alguno de los campos de atributos_index.

Por ejemplo para filtrar por tipo de moneda,

scope :filtro_tipomoneda_id, lambda { |t|
  where(tipomoneda_id: t)
}

Para campos de fecha, enteros y flotantes es fácil que el filtro maneje un rango usando dos scope, uno con posfijo ini y otro con posfijo fin. Por ejemplo para filtrar en un rango de fechas:

scope :filtro_fechaini, lambda { |f|
  where('fecha >= ?', f)
}


scope :filtro_fechafin, lambda { |f|
  where('fecha <= ?', f)
}

Para campos tipo cadena recomendamos algo como:

scope :filtro_observaciones, lambda {|o|
    where("unaccent(observaciones) ILIKE '%' || unaccent(?) || '%'", o)
} 

Que permite encontrar parte de la cadena, sin tener en cuenta capitalización, ni acentos.

Note que en el caso de fecha, a menos que cambie el control para el filtro, por omisión el control que Msip pone es un campo HTML estándar para fechas.
Este control depende del navegador y siempre retorna la fecha en formato aaaa-mm-dd, por eso el filtro no emplea fecha_localizada (sino fecha) aún cuando la columna especificada en el controlador sea esa.

4.1.2.1 Control para un campo del filtro

Al igual que en formulario se generará un control para el filtro con base en el tipo del campo y su relación con el modelo que se presenta. Si prefiere especificar un control particular para un campo cree una plantilla app/views/tasascambio/_filtro_tipomoneda_id.html.erb con:

<%= f.input :bustipomonedad_id,
  collection: ::Tipomoneda.where("nombre!='PESO'"), 
  include_blank: true,
  label_method: :presenta_nombre,
  value_method: :id,
  label: false,
  selected: Msip::ModeloHelper.poromision(params, :bustipomoneda_id),
  input_html: {
    class: 'tom-select',
    'data-enviarautomatico' => ''
  }
%>

También puede emplear otros nombres en los campos de filtro (no incluido en atributos_index del controlador), en tal caso puede detectar y filtrar por estos en un scop de nombre filtrar_alterno en el modelo.
Puede ver un ejemplo de campos de filtro no definidos en atributos_index en https://gitlab.com/pasosdeJesus/sivel2_gen/blob/main/app/views/sivel2_gen/victimas/_filtro_pconsolidado.html.erb y su scope filtrar_alterno en https://gitlab.com/pasosdeJesus/sivel2_gen/blob/main/lib/sivel2_gen/concerns/models/victima.rb

4.1.2.2 Otros controles de un filtro

A la derecha del filtro debajo de la columna acciones por omisión se presentará el botón "Filtrar". Si necesita agregar otro o modificarlo defina en el directorio de la vista el archivo _index_filtro_controles.html.erb que recibe el formulario en la variable f.

4.1.3 Información de cada campo dentro de la tabla

Es posible configurar la presentación de un campo de dos maneras (1) con una vista parcial de nombre _index_campo_nombrecampo.html.erb o (2) mediante la función presenta del modelo.

4.1.3.1 Usando una vista parcial

Un ejemplo puede verse en cor1440_cinep, donde los anexos de un efecto se presentan agregando el archivo app/views/efectos/_index_campo_anexo_efecto.html.erb con un contenido que utiliza la variable registro:

<% if registro %>
  <% registro.msip_anexo.each do |i| %>
    <%= link_to(
      image_tag("cor1440_gen/clip.svg", size: "16"),
      msip.descarga_anexo_path(i.id),
      {"data-turbo" => false }
    ) %>
  <% end %>
<% end %>

4.1.3.2 Usando función presenta del modelo

Un ejemplo está en las fuentes de msipen el modelo usuario que tiene un campo entero rol, cuya representación en cadena no está en tabla alguna sino en la aplicación (se trata de un vector Ability::ROLES):

def presenta(atr)
  case atr.to_s
  when 'rol'
    a = Ability::ROLES.select { |v| v[1] = rol }                                                                                         
    a.first[0]                                                                                                                           
  else                                                                                                                                   
    presenta_gen(atr)                                                                                                                    
  end                                                                                                                                    
end             

4.1.4 Epilogo de la tabla

Después de la tabla es posible configurar un epilogo definiendo en el directorio de la vista el archivo _index_post_tabla que recibe el formulario en la variable f.

Si no define uno diferente el epilogo por omisión permite paginar y presenta un botón "Nuevo" que permite añadir registros.

4.2 Formulario (vista _form)

4.2.1 Remplazar un sólo campo

Si prefiere tener un control no estándar para algún campo del formulario basta que cree un directorio para las vistas y un archivo con nombre de la forma _campo_nombre.html.erb.

Suponiendo que en tasacambio quisiera emplear un campo diferente para el tipo de moneda que no incluya PESOS, lo que debe hacer es crear el archivo app/views/tasascambio/_campo_tipomoneda_id.html.erb con contenido:

<% tm = ::Tipomoneda.where("nombre!='PESO'") %>
<%= f.association :tipomoneda,
   collection: tm,
   label_method: :presenta_nombre,
   value_method: :id,
   input_html: { class: 'tom-select' }
%>

4.3 Resumen (vista show)

El contenido de un campo puede presentarse de forma personalizada bien con la función presenta del modelo o bien con una vista parcial de nombre _show_campo_nombrecampo. Ambas situaciones son análogas en la vista index por lo que lo referenciamos a ver la sección 4.1.

Los botones de esta vista puede configurarse por completo para un modelo con una vista parcial _show_acciones o pueden agregarse botones antes del botón Eliminar en _show_mas_acciones. Estos parciales deben ubicarse en una ruta de la forma app/views/modelo/_show_acciones