doc/vistas-automaticas.md
# 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:
```sh
rails g migration crea_tasacambio
```
Y que en la migración que se crea (digamos
`db/migrate/20170810040012_crea_tasacambio.rb`):
```ruby
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:
```sh
rails db:migrate
```
El modelo en ```app/models/tasacambio``` es:
```ruby
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:
```ruby
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:
```ruby
inflect.irregular 'tasacambio', 'tasascambio'
```
Y el contenido de `app/controllers/tasascabio_controller` es:
```ruby
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`:
```ruby
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:
```ruby
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](https://gitlab.com/pasosdeJesus/msip/wiki/Autorizaci%C3%B3n-por-omisi%C3%B3n-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
```sh
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:
```yaml
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,
```ruby
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:
```ruby
scope :filtro_fechaini, lambda { |f|
where('fecha >= ?', f)
}
scope :filtro_fechafin, lambda { |f|
where('fecha <= ?', f)
}
```
Para campos tipo cadena recomendamos algo como:
```ruby
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:
```erb
<%= 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`:
```erb
<% 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 `msip`en 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`):
```ruby
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:
```erb
<% 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`