From Fedora Project Wiki

< FWN‎ | Beats

No edit summary
No edit summary
Line 5: Line 5:
LATAM Fedora is a regular column of Spanish language contributions around open source software.  It is our first expansion into incorporating foreign language content into FWN.   
LATAM Fedora is a regular column of Spanish language contributions around open source software.  It is our first expansion into incorporating foreign language content into FWN.   


This week's contribution is from [[User:gomix|Guillermo Gómez]], a third installment on Ruby.  Enjoy!
This week's contribution is from [[User:gomix|Guillermo Gómez]], a fourth installment on Ruby.  Enjoy!


===Ruby Capítulo 3 : Métodos===
===Ruby Capítulo 4 : Control de Acceso y Duck Typing===


Como ya hemos venido desarrollando en nuestra previas entregas, los métodos sabemos se definen por medio de la palabra clave def. Los nombres de métodos deben comenzar con una letra minúscula. Los métodos que funcionan como consultas suelen ser llamados con el sufijo "?", tal como instance_of? Los métodos peligrosos o que modifican el objeto mismo suelen también llamarse con un el sufijo "!". Por ejemplo String tiene el método chop y chop!. El primero devuelve un string modificado, el segundo modifica el objeto mismo.
Cuando se diseña una interfase de una clase es importante considerar cuánto se expone de la clase al exterior. El permitir mucho acceso a su clase incrementa el riesgo de incrementar el "acoplamiento" en su aplicación, es decir, depender en demasía de los detalles de la implementación en vez de su interfase lógica.


El cuerpo de un método contiene expresiones Ruby normales, excepto que no se puede definir un método de instancia, clase o modulo dentro de un método, si lo intenta obtendrá un error como el siguiente:
En Ruby la única forma fácil de cambiar el estado de un objeto es por medio del uso de uno de sus métodos, controle el acceso a los métodos y controlará el acceso al objeto. Ruby ofrece tres niveles de protección:


<code>
    * Métodos públicos: pueden ser invocados por cualquiera, no se aplica ningún control de acceso. Los métodos son públicos por omisión excepto initiliaze que siempre es privado.
1 SyntaxError: compile error
    * Métodos protegidos: sólo pueden ser invocados por objetos de la misma clase o subclases. El acceso se mantiene a la familia.
2 (irb):2: class definition in method body
     * Métodos privados: no pueden ser invocados con un receptor explícito, el receptor siempre es self. Esto significa que los métodos privados sólo pueden ser llamados en el contexto del objeto actual, no se puede invocar los métodos privados de otro objeto.
3     from (irb):7
</code>


El valor devuelto por los métodos es el valor de la última expresión ejecutada, o el resultado explícito de una expresión return.
Una diferencia importante con otros lenguajes orientados a objeto es que el control de acceso es determinado dinámicamente en la medida que el programa se ejecuta. Obtendrá una violación de acceso sólo cuando intente ejecutar el método restringido.


<code>
====Especificación del control de acceso====
1 >> def resultado
2 >> "Uno"
3 >> end
4 >> p resultado
5 "Uno"
6
7 >> def resultado
8 >> "Uno"
9 >> return true
10 >> end
11 => nil
12 >> p resultado
13 true
</code>


====Parámetros====
Se especifica el nivel de acceso de los métodos dentro de una clase o módulo utilizando una o más de las tres funciones public, protected y private. Puede usar cada función en dos formas diferentes.


Ya que sabe definir un método con su nombre, ahora seguramente se encontrará con la necesidad de invocar el método pasando parámetros.
Si se usan sin argumentos, las tres funciones definen el control de acceso de los métodos subsiguientes.


<code>
<code>
  1 def miMetodo(arg1, arg2, arg3)
  1 class MiClase
  2   # su codigo
  2     def metodo1    # 'public' por omisión
  3 end
  3       #
  4  
  4     end
  5 def miOtroMetodo
  5  
  6  # sin argumentos
  6  protected        # los subsiguientes métodos serán 'protected'
  7  # su codigo
  7  
  8 end
8    def metodo2   
9      #
10    end
11
12  private          # los subsiguientes métodos serán 'private'
13
14    def metodo3
15      #
16    end     
17
18   public          # los subsiguientes métodos serán 'public'
  19
20    def metodo4
21      #
22    end
23 end
</code>
</code>


Ruby le permite definir valores por omisión a los argumentos de lo métodos que son obviados durante la invocación.
De forma alternativa puede simplemente listar los métodos en dichas funciones.


<code>
<code>
  1 def trampa(arg1="boba", arg2="tnt", arg3="30s")
  1 class MiClase
  2  "#{arg1}, #{arg2}, #{arg3}"
  2  def metodo1
  3 end
3    #
4  end
# ... y el resto de las definiciones de métodos
6
7  public    :metodo1, :metodo4
8  protected :metodo2
9  private  :metodo3
  10 end
</code>


1 ?> trampa
====Ejemplo private====
2 => "boba, tnt, 30s"
3 >> trampa("lisa")
4 => "lisa, tnt, 30s"
5 >> trampa("lisa","h2o")
6 => "lisa, h2o, 30s"
7 >> trampa("lisa","h2o", "45h")
8 => "lisa, h2o, 45h"
</code>


Si desea pasar una cantidad variable de argumentos, o desea capturar múltiples argumentos en un único parámetro, el colocar un asterisco (*) antes del nombre del último argumento hace justo eso.
El ejemplo abajo muestra un esqueleto para proteger un método peligroso de alteración del estado del objeto por medio del uso de otro método intermediario que se supone asegura el acceso, esto ayuda a mantener el código separado pero simple, uno asegura por ejemplo las credenciales, el otro ajusta el estado del objeto.


<code>
<code>
  1 def varargs(arg1, *resto)
  1 class Banco
  2  "Recibi #{arg1} y #{resto.join(', ')}"  
  2  def initialize
  3 end
3    @tasa="10%"  
 
  end
  1 >> varargs("uno")
5
  2 => "Recibi uno + "
  6  def tasa
  3 >> varargs("uno","dos")
7    @tasa
  4 => "Recibi uno + dos"
8  end
  5 >> varargs("uno","dos","tres")
  9
  6 => "Recibi uno + dos, tres"
  10  def interfase_segura(nueva_tasa)  # Se supone que es "segura", llama al método privado
  7 >> varargs("uno","dos","tres", "cuatro")
11    # Código de seguridad
  8 => "Recibi uno + dos, tres, cuatro"
  12    self.tasa=nueva_tasa
13  end
14
15 private
  16  def tasa=(nueva_tasa)
  17    @tasa=nueva_tasa
  18  end
  19 end
</code>
</code>
El resto de argumentos se apilan en un Array y se pone a disposición del método bajo el nombre especificado.
====Métodos con y sin bloques asociados====
Una técnica poderosa con Ruby es la posibilidad de asociar bloques de código a los métodos, veamos un ejemplo simple para ilustrar la técnica.


<code>
<code>
  1 >> def con_y_sin_bloque(p1)
  1 >> banco = Banco.new
  2 >>   if block_given?
  2 => #<Banco:0xb747e318 @tasa="10%">
  3 >>     yield(p1)
  3 >> banco.tasa
  4 >>  else
  4 => "10%"
  5 ?>     p1
  5 >> banco.interfase_segura("20%")
  6 >>  end
  6 => "20%"
  7 >> end
  7 >> banco.tasa
  8 => nil
  8 => "20%"
  9 >> con_y_sin_bloque(10) { |p1| (1..5).each { |m| p m + p1 } }
  9 >> banco.tasa="30%"
  10 11
  10 NoMethodError: private method `tasa=' called for #<Banco:0xb747e318 @tasa="20%">
11 12
  11    from (irb):63
12 13
  12
13 14
14 15
15 => 1..5
  16 >> con_y_sin_bloque(10)
  17 => 10
</code>
</code>


Para determinar la presencia del bloque hemos usado el método block_given? para condicionar el procesamiento en la ejecución del método. Luego yield permite ejecutar dicho bloque de código ruby con el argumento que deseemos, en este caso p1.
====Ejemplo protected====


Si en la definición de método, el último parámetro es precedido por un ampersand "&", cualquier bloque asociado a la llamada es convertido a un objeto Proc y dicho objeto es asignado a dicho parámetro, veamos un ejemplo para visualizar su uso.
Por definición este control de acceso limita el acceso a la familia, objetos de la misma clase, y objetos de clases derivadas de la clase que define el método protegido. Abajo un ejemplo simple para comparar manzanas con manzanas, no con peras.


<code>
<code>
  1 class CalculadoraDeVentas
  1 class Manzana
  2  def initialize(nombre, &bloque)
  2  def initialize(peso)
  3    @nombre, @bloque = nombre, bloque
  3    @peso = peso
  4  end
  4  end
  5  
  5  
  6  def precio_de_venta(costo)
  6  def <=>(otra_manzana)
  7    @bloque.call(costo)
  7    self.peso <=> otra_manzana.peso
  8  end
  8  end
  9 end
  9  
10  protected
11
12  def peso
13    @peso
14  end
15 end
16
17 class Pera
18  def initialize(peso)
19    @peso = peso
20  end
21
22  def <=>(otra_pera)
23    self.peso <=> otra_pera.peso
24  end
25
26  protected
27
28  def peso
29    @peso
30  end
31 end
</code>


  1 >> cpd = CalculadoraDeVentas.new("Distribuidor1") { |costo| p "Precio de venta #{(1.5*costo).to_s} BsF." }
<code>
  2 => #<CalculadoraDeVentas:0xb735ccc8 @nombre="Distribuidor1", @bloque=#<Proc:0xb735cd18@(irb):75>
  1 >> m1 = Manzana.new(100)
  3 >> cpm = CalculadoraDeVentas.new("Minorista1") { |costo| p "Precio de venta #{(1.8*costo).to_s} BsF." }
  2 => #<Manzana:0xb74838e0 @peso=100>
  4 => #<CalculadoraDeVentas:0xb7347008 @nombre="Minorista1", @bloque=#<Proc:0xb7347058@(irb):77>
  3 >> m2 = Manzana.new(200)
  5  
  4 => #<Manzana:0xb747dda0 @peso=200>
  6 >> cpd.precio_de_venta(100)
  5 >> p1 = Pera.new(50)
  7 "Precio de venta 150.0 BsF."
  6 => #<Pera:0xb74783c8 @peso=50>
  8 => nil
  7 >> m1 <=> m2
  9 >> cpm.precio_de_venta(100)
  8 => -1
  10 "Precio de venta 180.0 BsF."
  9 >> m1 <=> p1
  11 => nil
  10 NoMethodError: protected method `peso' called for #<Pera:0xb74783c8 @peso=50>
11    from ./manzanas.rb:7:in `<=>'
  12    from (irb):11
13
</code>
</code>


En el ejemplo hemos introducido el uso del método call de Proc para invocar la ejecución correspondiente y con los argumentos necesarios. Tendremos más que decir acerca de Proc en futuras ediciones.
Al intentar hacer la comparación entre manzana y pera, se invoca pera.peso en el contexto de las manzanas, y ya que el método está protegido, entonces da el error.


Por supuesto que esta funcionalidad puede implementarse de otras maneras, muchas otras maneras, el ejemplo lo que demuestra sutilmente es que también es posible reusar bloques de código por medio del uso de los bloques asociados a las llamadas de los métodos, es decir, puede por ejemplo compartir un bloque de código con más de una definición de método en su aplicación de hecho reduciendo y reusando el código por medio de funcionalidad común no representaba en clases o módulos. Esto igualmente puede hacerse en tiempo de ejecución, es decir, código que genera código y que lo pasa otro código para que lo ejecute. Si está confundido es porque está comenzando a experimentar el fuerte componente de dinamismo que tiene Ruby, no se asuste y abrace el poder que de esto se deriva.
Ahora bien, esta idea de uso para protected en realidad está en franco desuso por muchos ya que va en contra de la flexibilidad y dinamismo natural de Ruby y del concepto de Duck Typing que explicaremos a continuación.


====Invocando los métodos====
====Duck Typing====


Usted puede invocar o "llamar" un método especificando un "receptor" (objeto), el nombre del método, y opcionalmente algunos parámetros y un bloque asociado. Para el caso de los métodos de clase y módulo, el receptor sería el nombre de clase o módulo.
En Ruby no declaramos tipos de variables o tipos para los métodos, todo es simplemente alguna encarnación, un objeto de una clase, y las clases no son tipos. Si deseamos programar con la filosofía Duck Typing lo único que necesita recordar es que el "tipo" de objeto está determinado por lo que puede hacer, no por su clase.
 
En la práctica esto significa que hay pocas pruebas de valores de objeto. Por ejemplo digamos que estamos programando un método para agregar información de dos objetos de cierta clase y obtener un resultado String. Los programadores con conocmientos de C# o Java estarían tentados a hacer algo como lo siguiente:


<code>
<code>
File.size("archivo_grande.mpeg")
1 def anexar(obj1,obj2)
2   Math.sin(Math::PI/3)
2  # probar que se nos han dado lo parámetros correctos
3   unless obj1.kind_of?(String)
4    fail TypeError.new("Se espera String")
5  end
6   unless obj2.kind_of?(String)
7    fail TypeError.new("Se espera String")
8  end
9  obj1 << obj2
10 end
</code>
</code>


Si se omite el receptor, se usa self, el objeto actual:
<code>
1 >> anexar("Hola", " Mundo")
2 => "Hola Mundo"
3 >> anexar("Hola", 1)
4 TypeError: Se espera String
5    from ./dt.rb:7:in `anexar'
6
</code>
 
Si abraza la filosofía Duck Typing de Ruby podrá escribir este método de una forma mucho más simple.


<code>
<code>
  1 >> self.class
  1 def anexar(obj1,obj2)
  2 => Object
  2   obj1 << obj2
  3 >> self.hash
  3 end
4 => -608576338
5 >> hash
6 => -608576338
7 >> self.object_id
8 => -608576338
9 >> object_id
10 => -608576338
</code>
</code>


Note que he evitado intencionalmente llamar a class omitiendo el receptor, y ello es porque provoca un error, class es ambiguo, es una palabra clave (keyword) y Ruby cree que usted está tratando de definir una clase, no intenta self.class. En estos casos, use self.class, en general es mejor explicitar el receptor, es más legible y potencialmente evitará estas ambigüedades difíciles de depurar.
Usted no necesita verificar el tipo de los argumentos en tanto que se soporte el método << en obj1 simplemente todo funcionará bien. Si no es así, se lanzará una excepción de todas maneras, el mismo resultado de que si usted hubiera implementado la verificación. Pero de pronto su método es mucho más flexible, puede pasarle otros objetos no necesariamente String, tal vez un Fixnum.
 
También observe con cuidado que no es igual Object.hash a self.hash ya que self es una instancia de Object en el ejemplo y Object.hash es un método de clase, referencian a hash diferentes, uno a la instancia (self), y el otro a la clase Object.


<code>
<code>
  1 >> self.class
  1 >> anexar("Hola", 1)
  2 => Object
  2 => "Hola\001"
3 >> self.object_id
4 => -608576338
5 >> Object.object_id
6 => -608573798
</code>
</code>


Los paréntesis en la llamada al método para listar los argumentos son opcionales, sin embargo para evitar ambigüedades, le recomendamos fuertemente que siempre use los paréntesis, tal vez sólo en los casos más simples puede evitarse.
¿Qué pasa si obj1 no soporta el método << entonces?


====Expandiendo arreglos en las llamadas====
<code>
1 >> anexar(1.2, " dos ")
2 NoMethodError: undefined method `<<' for 1.2:Float
3    from (irb):6:in `anexar'
4    from (irb):13
5
</code>


De forma similar a la invocación con asterisco, en reversa, al invocar un método es posible "expandir" un arreglo para que cada elemento sea tomado como elemento individual.
Obtenemos una excepción NoMethodError para el método << para la clase Float en este ejemplo. Una forma de prevenir posible es usar el método respond_to?:


<code>
<code>
  1   def cinco(a,b,c,d,e)
  1 def anexar(obj1,obj2)
  2     "Argumentos #{a} #{b} #{c} #{d} #{e}"
  2   # probar que se nos han dado lo par ámetros correctos
  3  end
  3  unless obj1.respond_to?(:<<)
 
  4    fail TypeError.new("'obj1' necesita la capacidad <<")
1 >> cinco("un", *["dos", "tres" , "cuatro", "cinco"])
  5   end
  2 => "Argumentos un dos tres cuatro cinco"
  6  
3 >> cinco(*["un", "dos", "tres" , "cuatro", "cinco"])
  7   obj1 << obj2
4 => "Argumentos un dos tres cuatro cinco"
  8 end
  5 >> cinco("un", *["dos", "tres" , "cuatro", "cinco"])
  6 => "Argumentos un dos tres cuatro cinco"
  7 >> cinco("un", *["dos", "tres"] , "cuatro", "cinco")    # Debe ser el último argumento
  8 SyntaxError: compile error
</code>
</code>


==== Argumentos opcionales====
Pero nuevamente esto es más código que mantener y hay que evaluar si realmente quiere tomar ese trabajo ya que igualmente podría manejar las excepciones más arriba.


Usando el mismo ejemplo de arriba, intente pasar menos de cinco argumentos:
Si ahora pasa un Array como obj2 obtendrá un error de tipo ya que Array no es un String pero si podemos invocar el método to_s para obtener una representación razonable. En última instancia lo que queremos es que nuestro método si nos devuelva un String.


<code>
<code>
  1 >> cinco
  1 >> arreglo = [0,1,2]
  2 ArgumentError: wrong number of arguments (0 for 5)
  2 => [0, 1, 2]
3 >> anexar("Hola", arreglo.to_s)
4 => "Hola012"
</code>
</code>


Los cinco argumentos son obligatorios, una forma de convertirlos en opcionales es asignarles un valor por omisión nil.
Esto nos lleva a una nueva versión del método anexar, usando to_s para "convertir" los objetos en String, ahora ya tampoco necesitamos la verificación respond_to? ya que String responde a << . Tal vez ahora lo que necesita es validar es que tengan una representación en String, es decir, que respondan a to_s.
 
<code>
<code>
  1   def cinco(a=nil,b=nil,c=nil,d=nil,e=nil)
  1 def anexar(obj1, obj2)
  2    "Argumentos #{a} #{b} #{c} #{d} #{e}"  
  2   # probar que se nos han dado lo parámetros correctos
  3   end
3  unless obj1.respond_to?(:to_s)
 
4     fail TypeError.new("Se espera que responda a to_s")
  1 >> cinco
  5   end
  2 => "Argumentos     "
  6  unless obj2.respond_to?(:to_s)
3 >> cinco("uno")
  7     fail TypeError.new("Se espera que responda a to_s")
  4 => "Argumentos uno    "
  8  end
9
10  obj1.to_s << obj2.to_s
11 end
</code>
</code>


Sin embargo note que no puede especificar el tercer argumento sin especificar el segundo, debe respetar la secuencialidad en la definición de los argumentos. Veamos la siguiente versión revisada para visualizar los argumentos pasados al método.
El código de prueba puede llevarle nuevamente a situaciones indeseables. ¿Soporta obj1 << pero no to_s? Es mejor lidiar con las excepciones que ponerse a probar los tipos y/o los métodos a los que responde. Con esta nueva versión, podemos por ejemplo pasarle dos arreglos, mejor dicho, cualquier par de objetos que tenga una representación String (to_s) y concatenarlos y obtener una salida en String.


<code>
<code>
  1   def cinco(a=nil,b=nil,c=nil,d=nil,e=nil)
  1 >> arreglo = [0,1,2]
  2    "Argumentos 1:#{a} 2:#{b} 3:#{c} 4:#{d} 5:#{e}"  
2 => [0, 1, 2]
3  end
3 >> anexar(arreglo, arreglo)
  4 => "012012"  
</code>
</code>


Use nil para saltar al próximo argumento en la lista cuanda haga la invocación del método.
Ahora veamos nuestro método en acción con varios objetos involucrados:


<code>
<code>
  1 >> cinco
  1 >> anexar("Hola", " Mundo")                          ; dos String
  2 => "Argumentos 1: 2: 3: 4: 5:"  
  2 => "Hola Mundo"
  3 >> cinco("A",nil,nil,"D")
3 >> anexar(1, " Mundo")                                ; Fixnum + String
  4 => "Argumentos 1:A 2: 3: 4:D 5:"  
4 => "1 Mundo"
5 >> anexar(1, 1)                                      ; dos Fixnum
6 => "11"  
  7 >> anexar(1, 1.1)                                    ; Fixnum + Float
8 => "11.1"  
9 >> anexar(1, [1,2," :) "])                           ; Fixnum + Array
  10 => "112 :) "
11 >> anexar({1=> "mundo", 2 => "hola"}, [1,2," :) "])  ; Hash + Array
12 => "1mundo2hola12 :) "  
</code>
</code>


==== Simulando argumentos por nombres: hash ====
De lo único que debe preocuparse en su clase particular es que soporte to_s para tomar ventaja de nuestro método, si no fallará con una excepción. Veamos:
 
Si bien Ruby no tiene un verdadero soporte para "keyword arguments", es decir, pasar argumentos con su nombre y valor, nada nos impide simular dicho comportamiento usando un hash. Ruby incluso tiene un pequeño atajo que nos evita la sintáxis Hash {} para evitarnos confundir con un bloque asociado. El hash debe estar especificado de último en la lista de argumentos.


<code>
<code>
  1 def hash_params(params)
  1 >> class Miclase
  2  p "ID: #{params[:id]}"  
2 >> end
  3  p "Clave: #{params[:clave]}"  
3 => nil
4 end
4 >> miobj = Miclase.new
5 => #<Miclase:0x7fb2a1b2c770>
6 >> anexar("Uh?", miobj)
  7 => "Uh?#<Miclase:0x7fb2a1b2c770>"           ; to_s ya existe!
  8 >> anexar(miobj, "¡Ah!")
9 => "#<Miclase:0x7fb2a1b2c770>\302\241Ah!"   ; to_s ya existe...
</code>


code class="ruby">
Por supuesto puede sobrescribir el método to_s heredado de Object.
>> hash_params(:id => "XXX" , :clave => "Z A Z Z")
"ID: XXX"
"Clave: Z A Z Z"
=> nil
>> hash_params(:clave => "1 2 3 4", :id => "007")
"ID: 007"
"Clave: 1 2 3 4"
=> nil
>> hash_params({:clave => "1 2 3 4", :id => "007"})  # Sintaxis estricta con las {}
"ID: 007"
"Clave: 1 2 3 4"
 
Loading...
Fudcon-panama-2011_728x90_leaderboard_rotated
Powered by Redmine Poder_fedora Poder_ruby Haproxy Powered_by_apache
</code>

Revision as of 14:53, 22 May 2011

LATAM Fedora!

LATAM Fedora is a regular column of Spanish language contributions around open source software. It is our first expansion into incorporating foreign language content into FWN.

This week's contribution is from Guillermo Gómez, a fourth installment on Ruby. Enjoy!

Ruby Capítulo 4 : Control de Acceso y Duck Typing

Cuando se diseña una interfase de una clase es importante considerar cuánto se expone de la clase al exterior. El permitir mucho acceso a su clase incrementa el riesgo de incrementar el "acoplamiento" en su aplicación, es decir, depender en demasía de los detalles de la implementación en vez de su interfase lógica.

En Ruby la única forma fácil de cambiar el estado de un objeto es por medio del uso de uno de sus métodos, controle el acceso a los métodos y controlará el acceso al objeto. Ruby ofrece tres niveles de protección:

   * Métodos públicos: pueden ser invocados por cualquiera, no se aplica ningún control de acceso. Los métodos son públicos por omisión excepto initiliaze que siempre es privado.
   * Métodos protegidos: sólo pueden ser invocados por objetos de la misma clase o subclases. El acceso se mantiene a la familia.
   * Métodos privados: no pueden ser invocados con un receptor explícito, el receptor siempre es self. Esto significa que los métodos privados sólo pueden ser llamados en el contexto del objeto actual, no se puede invocar los métodos privados de otro objeto.

Una diferencia importante con otros lenguajes orientados a objeto es que el control de acceso es determinado dinámicamente en la medida que el programa se ejecuta. Obtendrá una violación de acceso sólo cuando intente ejecutar el método restringido.

Especificación del control de acceso

Se especifica el nivel de acceso de los métodos dentro de una clase o módulo utilizando una o más de las tres funciones public, protected y private. Puede usar cada función en dos formas diferentes.

Si se usan sin argumentos, las tres funciones definen el control de acceso de los métodos subsiguientes.

1 class MiClase
2     def metodo1    # 'public' por omisión
3       #
4     end
5 
6   protected        # los subsiguientes métodos serán 'protected'
7 
8     def metodo2    
9       #
10     end
11 
12   private          # los subsiguientes métodos serán 'private'
13 
14     def metodo3
15       #
16     end      
17 
18   public           # los subsiguientes métodos serán 'public'
19 
20     def metodo4
21       #
22     end
23 end

De forma alternativa puede simplemente listar los métodos en dichas funciones.

1 class MiClase
2   def metodo1
3     #
4   end
5   # ... y el resto de las definiciones de métodos
6 
7   public    :metodo1, :metodo4
8   protected :metodo2
9   private   :metodo3
10 end

Ejemplo private

El ejemplo abajo muestra un esqueleto para proteger un método peligroso de alteración del estado del objeto por medio del uso de otro método intermediario que se supone asegura el acceso, esto ayuda a mantener el código separado pero simple, uno asegura por ejemplo las credenciales, el otro ajusta el estado del objeto.

1 class Banco
2   def initialize
3     @tasa="10%" 
4   end
5 
6   def tasa
7     @tasa
8   end
9 
10   def interfase_segura(nueva_tasa)  # Se supone que es "segura", llama al método privado
11     # Código de seguridad
12     self.tasa=nueva_tasa
13   end
14 
15 private
16   def tasa=(nueva_tasa)
17     @tasa=nueva_tasa
18   end
19 end

1 >> banco = Banco.new
2 => #<Banco:0xb747e318 @tasa="10%">
3 >> banco.tasa
4 => "10%" 
5 >> banco.interfase_segura("20%")
6 => "20%" 
7 >> banco.tasa
8 => "20%" 
9 >> banco.tasa="30%" 
10 NoMethodError: private method `tasa=' called for #<Banco:0xb747e318 @tasa="20%">
11     from (irb):63
12 

Ejemplo protected

Por definición este control de acceso limita el acceso a la familia, objetos de la misma clase, y objetos de clases derivadas de la clase que define el método protegido. Abajo un ejemplo simple para comparar manzanas con manzanas, no con peras.

1 class Manzana
2   def initialize(peso)
3     @peso = peso
4   end
5 
6   def <=>(otra_manzana)
7     self.peso <=> otra_manzana.peso
8   end
9 
10   protected
11 
12   def peso
13     @peso
14   end
15 end
16 
17 class Pera
18   def initialize(peso)
19     @peso = peso
20   end
21 
22   def <=>(otra_pera)
23     self.peso <=> otra_pera.peso
24   end
25 
26   protected 
27 
28   def peso
29     @peso
30   end
31 end

1 >> m1 = Manzana.new(100)
2 => #<Manzana:0xb74838e0 @peso=100>
3 >> m2 = Manzana.new(200)
4 => #<Manzana:0xb747dda0 @peso=200>
5 >> p1 = Pera.new(50)
6 => #<Pera:0xb74783c8 @peso=50>
7 >> m1 <=> m2
8 => -1
9 >> m1 <=> p1
10 NoMethodError: protected method peso' called for #<Pera:0xb74783c8 @peso=50>
11     from ./manzanas.rb:7:in <=>'
12     from (irb):11
13 

Al intentar hacer la comparación entre manzana y pera, se invoca pera.peso en el contexto de las manzanas, y ya que el método está protegido, entonces da el error.

Ahora bien, esta idea de uso para protected en realidad está en franco desuso por muchos ya que va en contra de la flexibilidad y dinamismo natural de Ruby y del concepto de Duck Typing que explicaremos a continuación.

Duck Typing

En Ruby no declaramos tipos de variables o tipos para los métodos, todo es simplemente alguna encarnación, un objeto de una clase, y las clases no son tipos. Si deseamos programar con la filosofía Duck Typing lo único que necesita recordar es que el "tipo" de objeto está determinado por lo que puede hacer, no por su clase.

En la práctica esto significa que hay pocas pruebas de valores de objeto. Por ejemplo digamos que estamos programando un método para agregar información de dos objetos de cierta clase y obtener un resultado String. Los programadores con conocmientos de C# o Java estarían tentados a hacer algo como lo siguiente:

1 def anexar(obj1,obj2)
2   # probar que se nos han dado lo parámetros correctos
3   unless obj1.kind_of?(String)
4     fail TypeError.new("Se espera String")
5   end
6   unless obj2.kind_of?(String)
7     fail TypeError.new("Se espera String")
8   end
9   obj1 << obj2
10 end

1 >> anexar("Hola", " Mundo")
2 => "Hola Mundo" 
3 >> anexar("Hola", 1)
4 TypeError: Se espera String
5     from ./dt.rb:7:in anexar'
6 

Si abraza la filosofía Duck Typing de Ruby podrá escribir este método de una forma mucho más simple.

1 def anexar(obj1,obj2)
2   obj1 << obj2
3 end

Usted no necesita verificar el tipo de los argumentos en tanto que se soporte el método << en obj1 simplemente todo funcionará bien. Si no es así, se lanzará una excepción de todas maneras, el mismo resultado de que si usted hubiera implementado la verificación. Pero de pronto su método es mucho más flexible, puede pasarle otros objetos no necesariamente String, tal vez un Fixnum.

1 >> anexar("Hola", 1)
2 => "Hola\001" 

¿Qué pasa si obj1 no soporta el método << entonces?

1 >> anexar(1.2, " dos ")
2 NoMethodError: undefined method <<' for 1.2:Float
3     from (irb):6:in `anexar'
4     from (irb):13
5 

Obtenemos una excepción NoMethodError para el método << para la clase Float en este ejemplo. Una forma de prevenir posible es usar el método respond_to?:

1 def anexar(obj1,obj2)
2   # probar que se nos han dado lo par ámetros correctos
3   unless obj1.respond_to?(:<<)
4     fail TypeError.new("'obj1' necesita la capacidad <<")
5   end
6 
7   obj1 << obj2
8 end

Pero nuevamente esto es más código que mantener y hay que evaluar si realmente quiere tomar ese trabajo ya que igualmente podría manejar las excepciones más arriba.

Si ahora pasa un Array como obj2 obtendrá un error de tipo ya que Array no es un String pero si podemos invocar el método to_s para obtener una representación razonable. En última instancia lo que queremos es que nuestro método si nos devuelva un String.

1 >> arreglo = [0,1,2]
2 => [0, 1, 2]
3 >> anexar("Hola", arreglo.to_s)
4 => "Hola012" 

Esto nos lleva a una nueva versión del método anexar, usando to_s para "convertir" los objetos en String, ahora ya tampoco necesitamos la verificación respond_to? ya que String responde a << . Tal vez ahora lo que necesita es validar es que tengan una representación en String, es decir, que respondan a to_s.

1 def anexar(obj1, obj2)
2   # probar que se nos han dado lo parámetros correctos
3   unless obj1.respond_to?(:to_s)
4     fail TypeError.new("Se espera que responda a to_s")
5   end
6   unless obj2.respond_to?(:to_s)
7     fail TypeError.new("Se espera que responda a to_s")
8   end
9 
10   obj1.to_s << obj2.to_s
11 end

El código de prueba puede llevarle nuevamente a situaciones indeseables. ¿Soporta obj1 << pero no to_s? Es mejor lidiar con las excepciones que ponerse a probar los tipos y/o los métodos a los que responde. Con esta nueva versión, podemos por ejemplo pasarle dos arreglos, mejor dicho, cualquier par de objetos que tenga una representación String (to_s) y concatenarlos y obtener una salida en String.

1 >> arreglo = [0,1,2]
2 => [0, 1, 2]
3 >> anexar(arreglo, arreglo)
4 => "012012" 

Ahora veamos nuestro método en acción con varios objetos involucrados:

1 >> anexar("Hola", " Mundo")                           ; dos String
2 => "Hola Mundo" 
3 >> anexar(1, " Mundo")                                ; Fixnum + String
4 => "1 Mundo" 
5 >> anexar(1, 1)                                       ; dos Fixnum
6 => "11" 
7 >> anexar(1, 1.1)                                     ; Fixnum + Float
8 => "11.1"    
9 >> anexar(1, [1,2," :) "])                            ; Fixnum + Array
10 => "112 :) " 
11 >> anexar({1=> "mundo", 2 => "hola"}, [1,2," :) "])   ; Hash + Array
12 => "1mundo2hola12 :) " 

De lo único que debe preocuparse en su clase particular es que soporte to_s para tomar ventaja de nuestro método, si no fallará con una excepción. Veamos:

1 >> class Miclase
2 >> end
3 => nil
4 >> miobj = Miclase.new
5 => #<Miclase:0x7fb2a1b2c770>
6 >> anexar("Uh?", miobj)
7 => "Uh?#<Miclase:0x7fb2a1b2c770>"           ; to_s ya existe!
8 >> anexar(miobj, "¡Ah!")
9 => "#<Miclase:0x7fb2a1b2c770>\302\241Ah!"   ; to_s ya existe...

Por supuesto puede sobrescribir el método to_s heredado de Object.