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ónico</th> <th>Acciones</th>
</tr>
@foreach ($users as $user)
<tr>
<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);
{
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' =>
</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';
'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">×</button>
<strong>Por favor corrige los siguentes errores:</strong>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</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
$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
// 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);
}
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!