Resultado de etiquetas “named_scope”

Esta es la segunda parte de una serie de 3 artículos sobre la presentación que realicé en el FLISOL Paraguaná 2009.

Imagen Thumbnail para flisol_todo_borrador_interfaz.png

En la primera parte, explicamos brevemente en qué consistiría la aplicación, generamos el proyecto con Rails, generamos el scaffold para el modelo Tarea y agregamos una que otra validación en el modelo. En esta segunda parte, lo que haremos es darle un poquito de forma, para que parezca al borrador que dibujamos durante la primera parte.

Vemos en el borrador que tenemos claramente separadas las tareas finalizadas de las pendientes, por tanto debemos definir una manera de buscar dichas tareas. ActiveRecord nos brinda muchas opciones para realizar esta labor, en este caso usaremos los named_scopes, aunque esto podría considerarse un overkill es un buen ejemplo de la fantástica semántica que Rails pone a nuestra dispocición. Editamos el modelo Tarea (app/models/tarea.rb), y agregamos al final lo siguiente:

  named_scope :pendientes, :conditions => {:finalizada => false}
  named_scope :finalizadas, :conditions => {:finalizada => true}

Esto nos va a permitir buscar las tareas pendientes y finalizadas de la siguiente manera:

Tarea.pendientes    # => devuelve las tareas pendientes
Tarea.finalizadas   # => devuelve las tareas finalizadas

Sabiendo como buscar las tareas finalizadas y las pendientes, ahora podemos asignarlas tranquilamente a variables de instancia en TareasController#index. Editamos la acción index del controlador TareasController (app/controllers/tareas_controller.rb) para que quede de la siguiente manera:

# GET /tareas
  # GET /tareas.xml
  def index
    @pendientes = Tarea.pendientes
    @finalizadas = Tarea.finalizadas
    @tarea = Tarea.new

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @tareas }
    end
  end

Lo que hicimos fue eliminar la búsqueda original que retornaba todas las tareas, pendientes y finalizadas, y agregamos 2 búsquedas nuevas: Tarea.pendientes y Tarea.finalizadas. Adicionalmente agregamos la variable de instancia @tarea, que contiene una Tarea nueva (vacía), esto se debe a que en nuestra página index (según el borrador), vamos a incluir un formulario en la parte inferior para agregar nuevas tareas.

El siguiente paso es hacerle algunos ajustes a nuestra interfaz para que se parezca mas a nuestro diseño de lápiz y papel. Lo primero, cambiar el markup de la vista, editamos app/views/index.html.erb para que se vea así:

<h1>Mis tareas</h1>

<h2>Pendientes</h2>

<% if @pendientes.any? %>
  <ul class="tareas pendientes">
    <%= render :partial => @pendientes %>
  </ul>
<% else %>
  <p class='sin-tareas'>No hay tareas pendientes</p>
<% end %> 

<% if @finalizadas.any? %>
  <h2>Finalizadas</h2>

  <ul class="tareas finalizadas">
    <%= render :partial => @finalizadas %>
  </ul>
<% end %> 

<hr />

<%= render :partial => "form" %>

¿Qué hicimos? Ya deben haberse dado cuenta que eliminamos la tabla (tag table) para mostrar el listado de tareas, ahora, cada conjunto de tareas se muestran con una "unordered list" (ul). Se agregaron 2 títulos de segundo nivel (h2) para dividir las tareas finalizadas de las pendientes. Adicionalmente, las pendientes y las finalizadas se muestran siempre y cuando existan registros asociados a la misma (if @pendientes.any?, if @finalizadas.any?). Como podrán haberse dado cuenta, en el template aparencen unos fulanos render :partial => algo. El método render, para resumirlo, es uno de los mecanismos con los cuales se envía contenido al usuario (detalles en la api de Rails - Partials).

Rails es lo suficientemente inteligente para determinar que @pendientes y @finalizadas contienen un conjunto de objetos Tarea (ver RecordIdentifier, por tal razón, tanto render :partial => @pendientes como render :partial => @finalizadas lo que hacen es mostar el partial (fragmento de vista) tarea/_tarea.html.erb para cada uno de los elementos dentro de las variables @pendientes y @finalizadas. Nota: los parials en Rails siempre comienzan con "_". Ej: _tarea.html.erb. Rails determina este nombre gracias a RecordIdentifier#partial_path.

render :partial => "form" lo que hace es mostrar el partial _form.html.erb (Nótese que no es necesario especificar el "_").

Hablamos de varios partials (_tarea.html.erb y _form.html.erb), pero no los hemos creado. Eso es lo que vamos hacer ahora mismo. Primero, creamos el archivo _tarea.html.erb en app/views/tareas y agregamos el siguiente contenido:

<li>
  <% form_for(tarea) do |f| %>
    <%= f.check_box :finalizada, :onchange => 'form.submit();' %>
    <%= h tarea.titulo %>
    <span><%= tarea.notas %></span>
    <%= link_to 'Editar', edit_tarea_path(tarea) %>
    <%= link_to 'Eliminar', tarea, :confirm => 'Seguro que desea eliminar la tarea?', :method => :delete %>
  <% end %>
</li>

Este partial se encargará de mostrar cada elemento (o list item li) de la lista ul. La representación de cada tarea en el navegador es un formulario, que incluye un check_box, el cual al hacerle clic realiza el submit de dicho formulario. El resto es el título y las notas de la tarea, con los links de edición y eliminación.

Ahora, creamos el partial del formulario para agregar nuevas tareas (app/views/tareas/_form.html.erb):

<h2>Agregar nueva tarea</h2>

<% form_for(@tarea) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :titulo %><br />
    <%= f.text_field :titulo %>
  </p>
  <p>
    <%= f.label :notas %><br />
    <%= f.text_area :notas %>
  </p>
  <p>
    <%= f.submit 'Agregar' %>
  </p>
<% end %>

Este partial es una versión simplificada del template new.html.erb generado por el scaffold en la parte 1. De hecho, ese template original no va a ser utilizado, así que lo podemos eliminar.

Podríamos probar la aplicación en este momento, pero nos daríamos cuenta de que no se parece a nuestro borrador de lapiz y papel. ¿La razón?, debemos agregar una hoja de estilo simple para darle el "look" que deseamos. Creamos el archivo public/stylesheets/todo.css con el siguiente contenido:

/*Hoja de estilos principal de FLISOL todo*/

h1 {
  background-color: #333;
  padding: 10px;
  color: #FFF;
}

h2 {
  padding-top: 20px;
}

hr { margin-top: 20px; margin-bottom: 20px;}

ul.tareas li {
  padding-left: 0;
  padding-top: 15px;
  list-style: none;
  font-weight: bold;
}

ul.tareas li span {
  display: block;
  font-style: italic;
  font-weight: normal;
  padding-bottom: 5px;
}

ul.tareas li a {
  font-size: 10px;
}

ul.pendientes li {
  font-size: 16px;
  margin-top: 8px;
}

ul.pendientes li span {
  color: #333;
  font-size: 14px;
}

ul.finalizadas li {
  color: #999;
  font-size: 12px;
}

ul.finalizadas li span {
  color: #666;
  font-size: 11px;
}

p.sin-tareas {
  color: #338;
  font-style: italic;
  margin-left: 45px;
}

textarea { height: 50px; }

Sólo nos falta hacer referencia a esta hoja de estilo en nuestro layout. Buscamos la línea:

<%= stylesheet_link_tag 'scaffold' %>

y la reempalzamos por:

<%= stylesheet_link_tag 'scaffold', 'todo' %>

Ahora podemos probar la aplicación en http://localhost:3000/tareas.

Después de navegar, nos daremos cuenta de que hay un comportamiento extraño al momento de editar, eliminar o crear una tarea, esto se debe a que en TareasController, estas acciones deben redirigir a index luego de realizar su trabajo:

class TareasController < ApplicationController

  # GET /tareas
  # GET /tareas.xml
  def index
    @pendientes = Tarea.pendientes
    @finalizadas = Tarea.finalizadas
    @tarea = Tarea.new

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => (@pendientes + @finalizadas) }
    end
  end

  # GET /tareas/1
  # GET /tareas/1.xml
  def show
    @tarea = Tarea.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @tarea }
    end
  end

  # GET /tareas/new
  # GET /tareas/new.xml
  def new
    @tarea = Tarea.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @tarea }
    end
  end

  # GET /tareas/1/edit
  def edit
    @tarea = Tarea.find(params[:id])
  end

  # POST /tareas
  # POST /tareas.xml
  def create
    @tarea = Tarea.new(params[:tarea])

    respond_to do |format|
      if @tarea.save
        flash[:notice] = 'La tarea se creó satisfactoriamente.'
        format.html { redirect_to tareas_path }
        format.xml  { render :xml => @tarea, :status => :created, :location => @tarea }
      else
        @pendientes = Tarea.pendientes
        @finalizadas = Tarea.finalizadas
        format.html { render :action => "index" }
        format.xml  { render :xml => @tarea.errors, :status => :unprocessable_entity }
      end
    end
  end

  # PUT /tareas/1
  # PUT /tareas/1.xml
  def update
    @tarea = Tarea.find(params[:id])

    respond_to do |format|
      if @tarea.update_attributes(params[:tarea])
        flash[:notice] = 'La tarea fue actualizada.'
        format.html { redirect_to tareas_path }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @tarea.errors, :status => :unprocessable_entity }
      end
    end
  end

  # DELETE /tareas/1
  # DELETE /tareas/1.xml
  def destroy
    @tarea = Tarea.find(params[:id])
    @tarea.destroy

    respond_to do |format|
      format.html { redirect_to(tareas_url) }
      format.xml  { head :ok }
    end
  end
end

flisol_todo_con_estilos_aplicados.png

Nótese que las acciones create y update redirigen a index si la operación fue satisfactoria, esto se logra mediante redirect_to tareas_path. Otro pequeño ajuste se realizó para el caso de que la operación de creación de una nueva tarea falla: se debe mostrar el template index.html.erb, pero para evitar un error de que no se encuentran definidas las variables @pendientes y @finalizadas, debemos asignarles los registros tal cual se realizó en la acción index.

Si todo salió bien, debemos ver algo parecido a lo que se muestra en la imagen.

Bueno, eso es todo en esta parte 2, en la siguiente entrega mostraremos las tareas pendientes y finalizadas en el iphone, utilizando mi plugin rails_jqtouch.

Saludos!

1

Sobre COTECSO

Somos un equipo pragmático, desarrollamos software de manera ágil y elegante, nos gusta la tecnología, la simplicidad y las cosas bien hechas.
Cerrar