Asociaciones entre modelos ... - moiseserg/rubyWebDev GitHub Wiki
Las asociaciones entre modelos (Active Record Associations, ARA) en Ruby on Rails (RoR) permiten establecer relaciones entre diversos modelos, por ejemplo que un empleado tiene muchas tareas, un libro tiene muchos capítulos, un médico tiene muchos pacientes, pero también cada paciente puede tener más de un médico asignado, o que al paciente se le puede asignar uno o más medicamentos.
Rails soporta las siguientes asociaciones:
- belongs_to
- has_one
- has_many
- has_many :through
- has_one :through
- has_and_belongs_to_many
Se realizará la siguiente asociación mencionada al inicio de este documento un empleado tiene muchas tareas. Para cada tarea tendrá como atributos:
- descripcion: string
- duracion:numerico
- lugar: text
Para ver una lista completa de tipos de datos soportados por RoR vea . http://overooped.com/post/100354794/ruby-script-generate-scaffold-types
Se creará el modelo Empleado que podrá tener una o más tareas.
rails generate scaffold Empleado nombre:string direccion:text edad:integer
rails generate scaffold Tarea descripcion:string duracion:decimal lugar:text empleado:references
La ejecución de ambas líneas es la siguiente:
$ rails generate scaffold Empleado nombre:string direccion:text edad:integer
Running via Spring preloader in process 18833
invoke active_record
create db/migrate/20180130162814_create_empleados.rb
create app/models/empleado.rb
invoke test_unit
create test/models/empleado_test.rb
create test/fixtures/empleados.yml
invoke resource_route
route resources :empleados
invoke scaffold_controller
create app/controllers/empleados_controller.rb
invoke erb
create app/views/empleados
create app/views/empleados/index.html.erb
create app/views/empleados/edit.html.erb
create app/views/empleados/show.html.erb
create app/views/empleados/new.html.erb
create app/views/empleados/_form.html.erb
invoke test_unit
create test/controllers/empleados_controller_test.rb
invoke helper
create app/helpers/empleados_helper.rb
invoke test_unit
invoke jbuilder
create app/views/empleados/index.json.jbuilder
create app/views/empleados/show.json.jbuilder
create app/views/empleados/_empleado.json.jbuilder
invoke test_unit
create test/system/empleados_test.rb
invoke assets
invoke coffee
create app/assets/javascripts/empleados.coffee
invoke scss
create app/assets/stylesheets/empleados.scss
invoke scss
identical app/assets/stylesheets/scaffolds.scss
$ rails g scaffold Tarea descripcion:string duracion:decimal lugar:text empleado:references
Running via Spring preloader in process 18902
invoke active_record
create db/migrate/20180130162826_create_tareas.rb
create app/models/tarea.rb
invoke test_unit
create test/models/tarea_test.rb
create test/fixtures/tareas.yml
invoke resource_route
route resources :tareas
invoke scaffold_controller
create app/controllers/tareas_controller.rb
invoke erb
create app/views/tareas
create app/views/tareas/index.html.erb
create app/views/tareas/edit.html.erb
create app/views/tareas/show.html.erb
create app/views/tareas/new.html.erb
create app/views/tareas/_form.html.erb
invoke test_unit
create test/controllers/tareas_controller_test.rb
invoke helper
create app/helpers/tareas_helper.rb
invoke test_unit
invoke jbuilder
create app/views/tareas/index.json.jbuilder
create app/views/tareas/show.json.jbuilder
create app/views/tareas/_tarea.json.jbuilder
invoke test_unit
create test/system/tareas_test.rb
invoke assets
invoke coffee
create app/assets/javascripts/tareas.coffee
invoke scss
create app/assets/stylesheets/tareas.scss
invoke scss
identical app/assets/stylesheets/scaffolds.scss
Es importante que identifique en las salidas anteriores las migraciones a realizar:
Tanto para Empleados
create db/migrate/20180130162814_create_empleados.rb
como para Tareas
create db/migrate/20180130162826_create_tareas.rb
El archivo 20180130162814_create_empleados.rb
tiene la información del modelo Empleados
class CreateEmpleados < ActiveRecord::Migration[5.1]
def change
create_table :empleados do |t|
t.string :nombre
t.text :direccion
t.integer :edad
t.timestamps
end
end
end
En el archivo de 20180130162826_create_tareas.rb
note que aparece ya la clave foránea.
class CreateTareas < ActiveRecord::Migration[5.1]
def change
create_table :tareas do |t|
t.string :descripcion
t.decimal :duracion
t.text :lugar
t.references :empleado, foreign_key: true
t.timestamps
end
end
end
Los archivos para los modelos generados están en: app/models/
class Empleado < ApplicationRecord
end
Note la relación de Tareas hacia Empleados:
class Tarea < ApplicationRecord
belongs_to :empleado
end
De manera automática, se genera *app/models/tarea.rb con la relación belongs_to :empleado, indicando que una tarea pertenece a un empleado. Por otro lado, hay que agregar has_many: tareas
al archivo app/models/empleado.rb, para completar la relación:
class Empleado < ApplicationRecord
has_many :tareas
validates :nombre, presence: true
end
En el archivo anterior, puede aprovechar para agregar las validaciones que considere necesarias.
Recordar que aunque los archivos han sido creados, falta ejecutar los cambios en la base de datos:
$ rake db:migrate
== 20180130162814 CreateEmpleados: migrating ==================================
-- create_table(:empleados)
-> 0.0038s
== 20180130162814 CreateEmpleados: migrated (0.0040s) =========================
== 20180130162826 CreateTareas: migrating =====================================
-- create_table(:tareas)
-> 0.0044s
== 20180130162826 CreateTareas: migrated (0.0046s) ============================
Si desea deshacer cierta cantidad de migraciones puede usar la siguiente sintaxis:
rake db:rollback STEP=2
Donde el valor indicado por STEP* es el número de pasos a ejecutar hacia atrás.
El archivo config/routes.rb también ha cambiado, puede contener algo como lo siguiente:
Rails.application.routes.draw do
resources :tareas
resources :empleados
end
haga lo análogo en la interfaz web y podrá verificar que se han agregado las rutas correspondientes.
Los controladores pueden verse en:
app/controllers/
├── application_controller.rb
├── concerns
├── empleados_controller.rb
└── tareas_controller.rb
Así mismo los archivos que pueden modificarse para las vistas están en:
$ tree app/views/
app/views/
├── empleados
│ ├── edit.html.erb
│ ├── _empleado.json.jbuilder
│ ├── _form.html.erb
│ ├── index.html.erb
│ ├── index.json.jbuilder
│ ├── new.html.erb
│ ├── show.html.erb
│ └── show.json.jbuilder
├── layouts
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
└── tareas
├── edit.html.erb
├── _form.html.erb
├── index.html.erb
├── index.json.jbuilder
├── new.html.erb
├── show.html.erb
├── show.json.jbuilder
└── _tarea.json.jbuilder
Suponga que se tienen los siguientes Empleados en la BD
Al momento de agregar un empleado se solicita que se introduzca el identificador del empleado:
Para averiguar el id del empleado puede usarse la consola (o la interfaz de phpMyAdmin)
$ rails c
Running via Spring preloader in process 22479
Loading development environment (Rails 5.1.4)
irb(main):001:0> Empleado.all
Empleado Load (2.5ms) SELECT "empleados".* FROM "empleados" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation
[#<Empleado id: 1, nombre: "Juan Osorio", direccion: "Calle Benito Juárez #33", edad: 23, created_at: "2018-01-30 17:18:50", updated_at: "2018-01-30 17:19:45">,
#<Empleado id: 2, nombre: "María Espino", direccion: "Sagrario #22", edad: 31, created_at: "2018-01-30 17:20:10", updated_at: "2018-01-30 17:20:10">]>
Si se ingresa el identificador 1, es posible ingresar la tarea:
Al listar la información aparece lo siguiente:
Sin embargo, esto implica tener abierta la página de empleados para conocer el id. El siguiente código genera una lista desplegable (combo) para poder asignar la tarea a un usuario ingresado.
Al final de app/views/tareas/_form.html.erb se tiene el siguiente código:
<div class="field">
<%= 'Empleado' %>
<%= form.text_field :empleado_id, id: :tarea_empleado_id %>
</div>
Si es reemplazado por el siguiente código
<div class="field">
<%= form.label :empleado_id %>
<%= form.select :empleado_id, options_from_collection_for_select(Empleado.all, 'id', 'nombre', @tarea.empleado_id) %>
</div>
Y se obtiene una lista desplegable con el nombre de los empleados:
Al mostrar un empleado, aún se aprecia la referencia al empleado, para mostrar el id del empleado puede modificar el archivo /app/views/tareas/show.html.erb
<p>
<strong>Empleado:</strong>
<%= @tarea.empleado %>
</p>
y colocar el siguiente código:
<p>
<strong>Empleado:</strong>
<%= @tarea.empleado_id %>
</p>
Para mostrar las tareas se ingresa a http://localhost:3000/tareas
Pero se aprecia que no aparecen las personas, sino una referencia al objeto. Para mostrar las personas según la siguiente figura:
Agregue una referencia a empleados con la siguiente línea:
<% @empleados = Empleado.all %>
Cambiar:
<td><%= tarea.empleado %></td>
por
<td><%= @empleados.find(tarea.empleado_id).nombre %></td>
El código final queda con el siguiente contenido:
<p id="notice"><%= notice %></p>
<h1>Tareas</h1>
<table>
<thead>
<tr>
<th>Descripcion</th>
<th>Duracion</th>
<th>Lugar</th>
<th>Empleado</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @empleados = Empleado.all %>
<% @tareas.each do |tarea| %>
<tr>
<td><%= tarea.descripcion %></td>
<td><%= tarea.duracion %></td>
<td><%= tarea.lugar %></td>
<td><%= @empleados.find(tarea.empleado_id).nombre %></td>
<td><%= link_to 'Show', tarea %></td>
<td><%= link_to 'Edit', edit_tarea_path(tarea) %></td>
<td><%= link_to 'Destroy', tarea, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Tarea', new_tarea_path %>
Mayores detalles en: http://guides.rubyonrails.org/association_basics.html