Gastón Ramos

"La mayoría de las gaviotas no se molesta en aprender sino las normas de vuelo más elementales: como ir y volver entre playa y comida. Para la mayoría de las gaviotas, no es volar lo que importa, sino comer. Para esta gaviota, sin embargo, no era comer lo que le importaba, sino volar. Más que nada en el mundo, Juan Salvador Gaviota amaba volar".

Category: modulos

Acts as date Module

Esta es una solución que se me ocurrió para hacerme más cómodo el trabajo con
el atributo created_at de un modelo, por ej:

Para saber el año de creación de un post, haríamos lo siguiente:

>> p = Post.find(1)
>> p.created_at.month
=> 6

como podemos observar retorna 6, pero resulta que yo lo necesito necesito con el siguiente formato: “06”. Lo mismo pasa con el día:

>> p = Post.find(1)
>> p.created_at.day
=> 4
module ActsAsDate

  def year
    created_at.strftime("%Y")
  end

  def month
    created_at.strftime("%m")
  end

  def day
    created_at.strftime("%d")
  end

  def year_month
    [year, month]
  end

  # Armo un string para usarlo en un find
  #    ej:
  #   params = {:year => "2008", :month => "06"}
  #   Post.all(:conditions => Post.created_at_conditions(params))
  #
  def self.included(klass)
    klass.class_eval do
      def self.created_at_conditions(params)
        return nil if params[:year].blank? or params[:month].blank?
        "created_at like '#{params[:year]}-#{params[:month]}%'"
      end
    end
  end

end

Entonces tenemos que incluir este módulo en modelo que lo queremos usar:

class Post < ActiveRecord::Base

  include ActsAsDate

end

Ahora con esto directamente hago p.month, p.day, p.year y obtengo el mes, día y año de creación del post con el formato que necesito y escribiendo menos código :). Además agregué un método que me arma el param conditions, que se podría un usar así por ej:


class PostsController < ApplicationController

def list
    @posts = Post.paginate(
         :page => params[:page],
         :order => 'created_at DESC',
         :conditions => Post.created_at_conditions(params)
    )

    respond_to do |format|
      format.html
      format.xml  { render :xml => @posts }
    end
  end

end

De esta manera no tengo que estar preguntando en el controller si vienen (year y month) como parámetros, por ahora este modulo sólo funciona con el atributo created_at por que está hardcodeado, pero con pocas modificaciones se podría hacer que funciona para cualquier atributo.
Ahora lo que me pregunto es si Rails ya tiene alguna solución mejor para este problema, si alguien la conoce que me lo haga saber por favor (yo no la encontré), además se aceptan modificaciones, sugerencias y comentarios.

Advertisements

Módulos parte IV – Callbacks

Callbacks y hooks

Para continuar, vamos a ver el tema “callbacks” relacionado con los módulos, los callbacks y los hooks son una técnica de metaprogramación bastante común. Estos métodos son invocados cada vez que ocurre un evento particular durante la ejecución del programa, por ejemplo:

  • Un método inexistente es llamado en un objeto
  • Una clase es “mixineada” en un módulo
  • El “subclaseado” de una clase
  • Un método de instancia es agregado a una clase

Atrapando las operaciones de include con Module#included

Cuando un módulo es incluido (Mixed in) dentro de una clase, si se define un método llamado included para ese módulo, entonces este método se ejecuta. El método recibe el nombre de la clase como argumento.

module A
  def self.included(clase)
    puts "He sido incluido en #{clase}."
  end
end

class B
  include A
end

salida:

=> He sido incluido en la clase B.

Usando el callback included para agregar métodos de clase

Podemos atrapar la operación de include para agregar métodos de clase a la clase que estamos incluyendo el módulo:

module A
  def self.included(clase)
    def clase.metodo_de_clase
      puts "Agrego un metodo de clase."
    end
  end

  def metodo_de_instancia
    puts "Agrego un metodo de instancia."
  end

end

class B
  include A
end
b = B.new
b.metodo_de_instancia
B.metodo_de_clase

salida:

=> Agrego un metodo de instancia.
=> Agrego un metodo de clase

Bueno como vemos Module#included es una manera muy útil para agregar cosas a las clases/módulos de nuestros programas.
Esto fue todo, hasta el próximo post.

Modulos parte III

Nuevamente amigos seguimos con el tema de los módulos en ruby, para continuar comenzaremos con un ejmeplo del uso de “super” entre módulos y clases:

module A
  def imprimir
    puts "Imprimo desde el --modulo-- A"
  end
end

class B
  include A
  def imprimir
    puts "Imprimo desde la --clase-- B"
    puts "Trigger para ejecutar el inmediato mas alto 'imprimir'"
    super
    puts "Vuelvo de la llamada a super."
  end
end
b = B.new
b.imprimir

salida:

=> Imprimo desde la --clase-- B
=> Trigger para ejecutar el inmediato mas alto 'imprimir'
=> Imprimo desde el --modulo-- A
=> Vuelvo de la llamada a super.

La instancia de B (b) recibe el mensaje “imprimir”, como vimos anteriormente comienza mirando la propia clase y encuentra el método “imprimir” , por otro lado dentro del método hay una llamada a super, esto significa que cuando encuentre un método “imprimir” debe continuar buscando hacia arriba al siguiente, la siguiente “ocurrencia” en este caso es la del módulo “A”. Hasta acá todo genial, pero que pasa si el método en cuestión tiene argumentos?
super maneja los argumentos de la siguiente forma:

  • Invocado sólo, super automáticamente redirige los argumentos pasado al método desde dónde se invocó.
  • Invocado con una lista vacía de argumentos –super()– no envía ningún argumento al método de arriba
  • Invocado con una lista de argumentos –super(a,b,c)– este envía exactamente estos argumentos

Cómo siempre cortito y simple, nos vemos en el próximo capítulo :)

Modulos parte II

Bueno el post anterior vimos como usar módulo con métodos de instancia, métodos de módulo y variables de instancia de forma simple, ahora vamos a ver que pasa con la herencia.
Vamos a refrescar algunos conceptos. Algunas diferencias entre módulos y clases:
Cuando escribimos una clase, podemos tener instancias de esa clase (objetos) estas instancias pueden ejecutar los métodos de instancia de esa clase, por otro lado los módulos no pueden tener instancias, los módulos son “Mixed In” en las clases. Cuando esto sucede la instancia de la clase tiene la habilidad de llamar métodos de instancia definidos en el módulo (esto lo vimos en los ejemplos anteriores). Se podría decir que cuando hacemos un Mixin, por ejmeplo dentro de la clase A hacemos un include del Módulo B, es como que A hereda de B, las instancias de A pueden llamar a los métodos de instancia del módulo B. La principal diferencia entre la herencia de clases y la de los Mixins es que una clase puede heredar de una sóla superclase pero en una clase puede “Mixinear” muchos módulos (esto vendría a ser algo así como herencia múltiple). Cuando estamos diseñando un programa y detectamos que un comportamiento o conjunto de comportamientos es aplicable a más de una entidad u objeto entonces es un claro candidato a Módulo. Vamos directo al ejemplo:

Module A
  def imprimir
    puts "Imprimo desde el Modulo A"
  end
end
class B
  include A
end
class C < B
end
obj = C.new
obj.imprimir

Salida:

=> Imprimo desde el Modulo A

El método de instancia “imprimir” está definido en el Módulo A que luego es “Mixed In” en la clase B , y la clase C es una subclase de B. obj es una instancia de C. A través de esta “cascada’ el objeto (ibj) tiene acceso al método “imprimir”, veamos como es esto un poco más en detalle:
1- El objeto recibe el mensaje “imprimir”
2- La clase C define un método de clase llamado “impirmir” ?
3- NO
4- C tiene algún módulo Mixed in?
5- NO
6- La superclase de C es decir (B) define un método de instancia llamado “imprimir”
7- NO
8- B tiene algún módulo Mixed In?
9- SI: A
10- A define un método de instancia llamado “imprimir” ?
11- SI, entonces lo ejecuta

Esto es todo por este post, nos vemos en el próximo :)

Modulos

Bueno en este post voy a mostrar un ejemplo sencillo con módulos de ruby, muy básicamente un módulo es una colección de métodos y constanstes, los métodos en un módulo pueden ser métodos de instancia o métodos de módulo. Los métodos de instancia aparecen como métodos en una clase cuando el módulo es incluido, los métodos de módulo no. Al contrario los métodos de módulo pueden ser llamados sin crear un objeto encapsulado, mientras que los métodos de instanacia no. (ref: http://www.ruby-doc.org/core/classes/Module.html)

Veamos un ejemplo dónde tenemos algunos métodos de instancia:

module Saludo
  attr_accessor :saludo

  def agregar_nombre
    @saludo << " Pepe"
  end
end

class SaludoNuevo
  include Saludo

  def saludo= value
    @saludo = value
  end
end

s =  SaludoNuevo.new
s.saludo = 'Hola'
puts s.saludo
s.agregar_nombre
puts s.saludo

La salida de este programa es la siguiente:

=> Hola
=> Hola Pepe

Cómo podemos ver los métodos declarados dentro del Módulo Saludo aparecen cómo métodos de instancia de la clase SaludoNuevo, dado que Saludable “Mixed in” en SaludadorNuevo. Los Mixins son la manera en que ruby resuelve la Herencia Múltiple (Ya veremos algunos ejemplos más adelante).

Ahora agregamos un método de Módulo:

module Saludo
  attr_accessor :saludo

  def agregar_nombre
    @saludo << " Pepe"
  end

  def Saludo::general
    "Hola a todos"
  end
end

class SaludoNuevo
  include Saludo

  def saludo= value
    @saludo = value
  end
end

puts Saludo::general

s =  SaludoNuevo.new
s.saludo = 'Hola'
puts s.saludo
s.agregar_nombre
puts s.saludo

Y la salida es:

=> Hola a todos
=> Hola
=> Hola Pepe

Ahora vamos a agregar una variable de instancia en el módulo Y la vamos a usar y modificar desde la clase cliente:

module Saludo
  attr_accessor :saludo, :nombres

  def initialize
    @nombres = ["Pepe", "Gaston", "David"]
  end

  def agregar_nombre
    @saludo << " Pepe"
  end

  def Saludo::general
    "Hola a todos"
  end

end

class SaludoNuevo
  include Saludo

  def saludo= value
    @saludo = value
  end

  def saludo_general_con_nombres
    "Hola " + @nombres.join(', ')
  end

end

s = SaludoNuevo.new
puts s.saludo_general_con_nombres

# Modifico la variable de instancia @nombres Heredada del módulo "Saludo"
s.nombres << "Dave"
puts s.saludo_general_con_nombres

# Creo un nuevo objeto de Saludo Nuevo y modifico @nombres para
# ver que es una variable de instancia
s2 = SaludoNuevo.new
s2.nombres = ["Matz", "Kent", "Bruce"]
puts s2.saludo_general_con_nombres

puts s.saludo_general_con_nombres

la salida es:

=> Hola Pepe, Gaston, David
=> Hola Pepe, Gaston, David, Dave
=> Hola Matz, Kent, Bruce
=> Hola Pepe, Gaston, David, Dave

Bueno esto fue todo por ahora, cortito y sencilllo.