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

Usando scaffold para crear Empleados y Tareas

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

Migraciones generadas

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

Modelos generados

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.

Rutas

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.

Controladores

Los controladores pueden verse en:

app/controllers/
├── application_controller.rb
├── concerns
├── empleados_controller.rb
└── tareas_controller.rb

Vistas

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

Creación de una tarea que está asociada a un empleado

Suponga que se tienen los siguientes Empleados en la BD

Empleados

Al momento de agregar un empleado se solicita que se introduzca el identificador del empleado:

Empleados

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:

t1

Al listar la información aparece lo siguiente:

t1

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:

t1

Forma Show de tareas

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

t01

Pero se aprecia que no aparecen las personas, sino una referencia al objeto. Para mostrar las personas según la siguiente figura:

t01

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

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