Comunidad de diseño web y desarrollo en internet online

Módulo de usuarios (VI): Editar un registro con Laravel

Falta poco para terminar nuestro módulo. Si es tu primera vez en esta serie de tutoriales, todavía estás a tiempo de aprender a crear un módulo con Laravel, paso a paso. Hoy veremos cómo editar registros con Laravel.

Este tutorial es muy trivial, lo haremos basado en el progreso que hemos hecho hasta ahora, copiando, no, más bien reusando parte del código que ya producimos para crear la acción “create”.

Primero regresemos a la lista de usuarios:

app/views/admin/users/list.blade.php

Allí vamos a agregar una columna adicional para agregar un botón a la acción editar usuarios:

Código :

  <table class="table table-striped" style="width: 900px">
    <tr>
        <th>Nombre completo</th>
        <th>Correo electr&oacute;nico</th>
        <th>Acciones</th>
    </tr>
    @foreach ($users as $user)
    <tr>
        <td>{{ $user->full_name }}</td>
        <td>{{ $user->email }}</td>
        <td>
          <a href="{{ route('admin.users.show', $user->id) }}" class="btn btn-info">
              Ver
          </a>
          <a href="{{ route('admin.users.edit', $user->id) }}" class="btn btn-primary">
            Editar
          </a>
        </td>
    </tr>
    @endforeach
  </table>




Esto nos permitirá acceder a la opción de editar fácilmente. Ahora, de vuelta en:

app/controllers/admin/UsersController.php

Código :

   /**
    * Show the form for editing the specified resource.
    *
    * @param  int  $id
    * @return Response
    */
   public function edit($id)
   {
      return 'Aqui editamos el usuario: ' . $id;
   }


Lo vamos a reemplazar por:

Código :

public function edit($id)
{
$user = User::find($id);
if (is_null ($user))
{
App::abort(404);
}

return View::make('admin/users/form')->with('user', $user);
}


Vean lo simple del código:

Código :

$user = User::find($id);


Nos trae el usuario que corresponde a la id numérica almacenada en la variable $id

Noten que antes le pasamos dicha ID a la ruta edit:

Código :

<a href="{{ route('admin.users.edit', $user->id) }}">


En caso de que la ID no corresponda a ningún usuario válido, User::find devolverá NULL, por lo tanto:

Código :

if (is_null ($user))
{
App::abort(404);
}


Si el usuario no existe se aborta la operación con un error 404. Por último se le pasa el usuario a la vista, como ya hemos hecho antes.

Al darle clic a editar en cualquiera de sus usuarios, verán el mismo formulario usado para crear pero con los campos llenos…

A mí me gusta evitar tener 2 vistas con casi el mismo HTML repetido, en este caso se puede hacer que el mismo formulario sirva tanto para create como para edit de esta forma:

Código :

@extends ('admin/layout')

<?php

    if ($user->exists):
        $form_data = array('route' => array('admin.users.update', $user->id), 'method' => 'PATCH');
        $action    = 'Editar';
    else:
        $form_data = array('route' => 'admin.users.store', 'method' => 'POST');
        $action    = 'Crear';        
    endif;

?>

@section ('title') {{ $action }} Usuarios @stop

@section ('content')

  <h1>{{ $action }} Usuarios</h1>

  <p>
    <a href="{{ route('admin.users.index') }}" class="btn btn-info">Lista de usuarios</a>
  </p>

{{ Form::model($user, $form_data, array('role' => 'form')) }}

  @include ('admin/errors', array('errors' => $errors))

  <div class="row">
    <div class="form-group col-md-4">
      {{ Form::label('email', 'Dirección de E-mail') }}
      {{ Form::text('email', null, array('placeholder' => 'Introduce tu E-mail', 'class' => 'form-control')) }}
    </div>
    <div class="form-group col-md-4">
      {{ Form::label('full_name', 'Nombre completo') }}
      {{ Form::text('full_name', null, array('placeholder' => 'Introduce tu nombre y apellido', 'class' => 'form-control')) }}        
    </div>
  </div>
  <div class="row">
    <div class="form-group col-md-4">
      {{ Form::label('password', 'Contraseña') }}
      {{ Form::password('password', array('class' => 'form-control')) }}
    </div>
    <div class="form-group col-md-4">
      {{ Form::label('password_confirmation', 'Confirmar contraseña') }}
      {{ Form::password('password_confirmation', array('class' => 'form-control')) }}
    </div>
  </div>
  {{ Form::button($action . ' usuario', array('type' => 'submit', 'class' => 'btn btn-primary')) }}    
  
{{ Form::close() }}

@stop


En las primeras líneas definimos el título “Crear” o “Editar” y la ruta y método que se le va a pasar al form, dependiendo si el usuario existe o no.

También se puede lograr lo mismo pasando la data desde el controlador a la vista:

Código :

   public function edit($id)
   {
        $user = User::find($id);
        if (is_null ($user))
        {
            App::abort(404);
        }
        
        $form_data = array('route' => array('admin.users.update', $user->id), 'method' => 'PATCH');
        $action    = 'Editar';
        
        return View::make('admin/users/form', compact('user', 'form_data', 'action'));
   }


Lo importante es evitar duplicar código de acuerdo al principio DRY (Don’t repeat yourself), por ejemplo, para el HTML de errores, dado que va a ser muy común a todos los módulos, lo separé en otra vista y ahora sólo hay que incluirlo:

Código :

  @include ('admin/errors', array('errors' => $errors)) 


De esta manera se vuelve más reusable:

app/views/admin/errors.blade.php:

Código :

  @if ($errors->any())
    <div class="alert alert-danger">
      <button type="button" class="close" data-dismiss="alert">&times;</button>
      <strong>Por favor corrige los siguentes errores:</strong>
      <ul>
      @foreach ($errors->all() as $error)
        <li>{{ $error }}</li>
      @endforeach
      </ul>
    </div>
  @endif


Completando la acción “update”


Si se detienen a pensar unos momentos, este método “update” se completa de manera similar a “store”, la diferencia es que acá no crearemos un nuevo usuario, sino que cargaremos uno tal como hicimos en el método “edit” hace un momento, además tenemos que cambiar la redirección en caso de datos no válidos a la ruta admin.users.edit. El código resultante quedaría así:

Código :

public function update($id)
{
        // Creamos un nuevo objeto para nuestro nuevo usuario
        $user = User::find($id);
        
        // Si el usuario no existe entonces lanzamos un error 404 :(
        if (is_null ($user))
        {
            App::abort(404);
        }
        
        // Obtenemos la data enviada por el usuario
        $data = Input::all();
        
        // Revisamos si la data es válido
        if ($user->isValid($data))
        {
            // Si la data es valida se la asignamos al usuario
            $user->fill($data);
            // Guardamos el usuario
            $user->save();
            // Y Devolvemos una redirección a la acción show para mostrar el usuario
            return Redirect::route('admin.users.show', array($user->id));
        }
        else
        {
            // En caso de error regresa a la acción edit con los datos y los errores encontrados
            return Redirect::route('admin.users.edit', $user->id)->withInput()->withErrors($user->errors);
        }
}


Con esto ya debería funcionar “a medias” la función de actualizar, sin embargo, si intento actualizar mi propio usuario recibo un error:

Código :

El email ya ha sido utilizado


Esto es por la regla “unique” definida en app/models/User.php, además que editar la clave debería ser opcional.

Corrijamos el método isValid:

Código :

    public function isValid($data)
    {
        $rules = array(
            'email'     => 'required|email|unique:users',
            'full_name' => 'required|min:4|max:40',
            'password'  => 'min:8|confirmed'
        );
        
        // Si el usuario existe:
        if ($this->exists)
        {
            //Evitamos que la regla “unique” tome en cuenta el email del usuario actual
$rules['email'] .= ',email,' . $this->id;
        }
        else // Si no existe...
        {
            // La clave es obligatoria:
            $rules['password'] .= '|required';
        }
        
        $validator = Validator::make($data, $rules);
        
        if ($validator->passes())
        {
            return true;
        }
        
        $this->errors = $validator->errors();
        
        return false;
    }


Noten el IF extra para configurar algunas reglas dependiendo si el usuario ya existe o no…

De esta forma, gracias a la propiedad $this->exists de los modelos de Laravel y un poco de astucia, podemos reusar tanto el método isValid como nuestro formulario.

Guardar contraseñas con Laravel


Como hemos visto, al actualizar un registro la contraseña queda como campo opcional (para no obligar al usuario a cambiar su contraseña cada vez que quiera editar otro dato), sin embargo tenemos que prevenir que Laravel grabe una contraseña vacía en la base de datos, además, también tenemos que encriptar las contraseñas no vacías. Todo este lío se soluciona muy fácil, dentro del modelo (app/models/User.php) agregando el siguiente método:

Código :

    public function setPasswordAttribute($value)
    {
        if ( ! empty ($value))
        {
            $this->attributes['password'] = Hash::make($value);
        }
    }


Cada vez que se intenta acceder a un atributo, Laravel intenta ver si el modelo tiene un método llamado get[nombre del atributo en camelCase]Attribute, así mismo cada vez que se intenta guardar un atributo dentro del modelo, se intenta llamar set[nombre del atributo en camelCase]Attribute, que permiten modificar el comportamiento por defecto. ¿No me creen? Agreguen lo siguiente al modelo, temporalmente:

Código :

    public function getFullNameAttribute()
    {
        return strtoupper($this->attributes['full_name']);
    }


Ahora todos, todos, TODOS los atributos full_name de todos los usuarios se imprimirán siempre en mayúsculas, al menos que borremos o cambiemos el método getFullNameAttribute.

Eloquent tiene muchos métodos interesantes que se escapan, lamentablemente, del alcance de este tutorial


El método validAndSave (haciendo que todo se vea aún más limpio y mejor)


Aún:

Código :

        // Revisamos si la data es válido
        if ($user->isValid($data))
        {
            // Si la data es valida se la asignamos al usuario
            $user->fill($data);
            // Guardamos el usuario
            $user->save();
        }


Parece mucho código, además hay el mismo código repetido en store y update, ¿Cierto?

Que tal si en el modelo (app/models/User.php) agregamos un nuevo método:

Código :

    public function validAndSave($data)
    {
        // Revisamos si la data es válida
        if ($this->isValid($data))
        {
            // Si la data es valida se la asignamos al usuario
            $this->fill($data);
            // Guardamos el usuario
            $this->save();
            
            return true;
        }
        
        return false;
    }


Ahora en la acción update podríamos escribir, solamente:

Código :

        // Revisamos si la data es válida y guardamos en ese caso
        if ($user->validAndSave($data))
        {
            // Y Devolvemos una redirección a la acción show para mostrar el usuario
            return Redirect::route('admin.users.show', array($user->id));
        }
        else
        {
            // En caso de error regresa a la acción create con los datos y los errores encontrados
            return Redirect::route('admin.users.edit', $user->id)->withInput()->withErrors($user->errors);
        }


La acción “store” podríamos simplificarla también de la misma manera ¿Cómo quedaría?



Con esto finalizamos la acción de editar, espero les haya gustado.

P.D. ¿Terminaron la acción “show”? Si la respuesta es no, todavía tienen un poco de tiempo antes que se publique el próximo y último tutorial, donde veremos la acción “destroy” y algo de ¡AJAX!

Stay Tuned!

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate