Continuando con el meme "Características ocultas de ...", compartamos las características menos conocidas pero útiles del lenguaje de programación Ruby.
Trate de limitar esta discusión con el núcleo de Ruby, sin nada de Ruby on Rails.
Ver también:
(Por favor, solo one característica oculta por respuesta).
Gracias
Desde Ruby 1.9 Proc # === es un alias para Proc # call, lo que significa que los objetos Proc se pueden usar en declaraciones de casos como las siguientes:
def multiple_of(factor)
Proc.new{|product| product.modulo(factor).zero?}
end
case number
when multiple_of(3)
puts "Multiple of 3"
when multiple_of(7)
puts "Multiple of 7"
end
Peter Cooper tiene una buena lista de trucos de Ruby. Quizás mi favorito es permitir que se enumeren tanto los artículos individuales como las colecciones. (Es decir, trata un objeto que no es de colección como una colección que contiene solo ese objeto). Se ve así:
[*items].each do |item|
# ...
end
No sé qué tan oculto está esto, pero lo he encontrado útil cuando necesito hacer un Hash de una matriz unidimensional:
fruit = ["Apple","red","banana","yellow"]
=> ["Apple", "red", "banana", "yellow"]
Hash[*fruit]
=> {"Apple"=>"red", "banana"=>"yellow"}
Un truco que me gusta es usar el expansor splat (*
) en otros objetos que no sean Arrays. Aquí hay un ejemplo en una coincidencia de expresiones regulares:
match, text, number = *"Something 981".match(/([A-z]*) ([0-9]*)/)
Otros ejemplos incluyen:
a, b, c = *('A'..'Z')
Job = Struct.new(:name, :occupation)
tom = Job.new("Tom", "Developer")
name, occupation = *tom
Wow, nadie mencionó al operador de flip flop:
1.upto(100) do |i|
puts i if (i == 3)..(i == 15)
end
Una de las cosas interesantes de Ruby es que puede llamar a métodos y ejecutar código en lugares que otros idiomas desaprueban, como en el método o las definiciones de clase.
Por ejemplo, para crear una clase que tiene una superclase desconocida hasta el tiempo de ejecución, es decir, es aleatorio, podría hacer lo siguiente:
class RandomSubclass < [Array, Hash, String, Fixnum, Float, TrueClass].sample
end
RandomSubclass.superclass # could output one of 6 different classes.
Esto utiliza el método 1.9 Array#sample
(solo en 1.8.7, vea Array#choice
), y el ejemplo es bastante artificial, pero puede ver el poder aquí.
Otro ejemplo interesante es la capacidad de poner valores de parámetros predeterminados que no son fijos (como lo demandan otros idiomas):
def do_something_at(something, at = Time.now)
# ...
end
Por supuesto, el problema con el primer ejemplo es que se evalúa en el momento de la definición, no en el tiempo de llamada. Entonces, una vez que se ha elegido una superclase, permanece esa superclase durante el resto del programa.
Sin embargo, en el segundo ejemplo, cada vez que llame a do_something_at
, la variable at
será la hora en que se llamó al método (bueno, muy cerca de él)
Otra característica pequeña: convierte una Fixnum
en cualquier base hasta 36:
>> 1234567890.to_s(2)
=> "1001001100101100000001011010010"
>> 1234567890.to_s(8)
=> "11145401322"
>> 1234567890.to_s(16)
=> "499602d2"
>> 1234567890.to_s(24)
=> "6b1230i"
>> 1234567890.to_s(36)
=> "kf12oi"
Y como Huw Walters ha comentado, la conversión a la inversa es igual de simple:
>> "kf12oi".to_i(36)
=> 1234567890
Hashes con valores por defecto! Una matriz en este caso.
parties = Hash.new {|hash, key| hash[key] = [] }
parties["Summer party"]
# => []
parties["Summer party"] << "Joe"
parties["Other party"] << "Jane"
Muy útil en metaprogramación.
Descarga la fuente de Ruby 1.9, y emite make golf
, entonces puedes hacer cosas como esta:
make golf
./goruby -e 'h'
# => Hello, world!
./goruby -e 'p St'
# => StandardError
./goruby -e 'p 1.tf'
# => 1.0
./goruby19 -e 'p Fil.exp(".")'
"/home/manveru/pkgbuilds/Ruby-svn/src/trunk"
Lee el golf_prelude.c
para más cosas interesantes que se esconden.
Otra adición divertida en la funcionalidad 1.9 Proc es Proc # curry, que le permite convertir un Proc aceptando n argumentos en uno aceptando n-1. Aquí se combina con el consejo Proc # === que mencioné anteriormente:
it_is_day_of_week = lambda{ |day_of_week, date| date.wday == day_of_week }
it_is_saturday = it_is_day_of_week.curry[6]
it_is_sunday = it_is_day_of_week.curry[0]
case Time.now
when it_is_saturday
puts "Saturday!"
when it_is_sunday
puts "Sunday!"
else
puts "Not the weekend"
end
Operadores booleanos en valores no booleanos.
&&
y ||
Ambos devuelven el valor de la última expresión evaluada.
Es por eso que el ||=
actualizará la variable con el valor devuelto expresión en el lado derecho si la variable no está definida. Esto no está documentado explícitamente, pero el conocimiento común.
Sin embargo, el &&=
no es tan conocido.
string &&= string + "suffix"
es equivalente a
if string
string = string + "suffix"
end
Es muy útil para operaciones destructivas que no deberían continuar si la variable no está definida.
La función Symbol # to_proc que proporciona Rails es realmente genial.
En lugar de
Employee.collect { |emp| emp.name }
Puedes escribir:
Employee.collect(&:name)
Una última: en Ruby puedes usar cualquier personaje que quieras para delimitar cadenas. Toma el siguiente código:
message = "My message"
contrived_example = "<div id=\"contrived\">#{message}</div>"
Si no desea evitar las comillas dobles dentro de la cadena, simplemente puede usar un delimitador diferente:
contrived_example = %{<div id="contrived-example">#{message}</div>}
contrived_example = %[<div id="contrived-example">#{message}</div>]
Además de evitar tener que escapar de los delimitadores, puede utilizar estos delimitadores para obtener mejores cadenas multilínea:
sql = %{
SELECT strings
FROM complicated_table
WHERE complicated_condition = '1'
}
Me parece que el uso del comando define_method para generar dinámicamente los métodos es bastante interesante y no tan conocido. Por ejemplo:
((0..9).each do |n|
define_method "press_#{n}" do
@number = @number.to_i * 10 + n
end
end
El código anterior utiliza el comando 'define_method' para crear dinámicamente los métodos "press1" a "press9". En lugar de escribir los 10 métodos que esencialmente contienen el mismo código, el comando de definir método se usa para generar estos métodos sobre la marcha, según sea necesario.
Utilice un objeto Range como una lista perezosa infinita:
Inf = 1.0 / 0
(1..Inf).take(5) #=> [1, 2, 3, 4, 5]
Más información aquí: http://banisterfiend.wordpress.com/2009/10/02/wtf-infinite-ranges-in-Ruby/
Los métodos del módulo que se declaran como module_function crearán copias de sí mismos como private instance en la clase que incluye el módulo:
module M
def not!
'not!'
end
module_function :not!
end
class C
include M
def fun
not!
end
end
M.not! # => 'not!
C.new.fun # => 'not!'
C.new.not! # => NoMethodError: private method `not!' called for #<C:0x1261a00>
Si utiliza module_function sin ningún argumento, entonces cualquier método de módulo que venga después de la instrucción module_function se convertirá automáticamente en un módulo de funciones.
module M
module_function
def not!
'not!'
end
def yea!
'yea!'
end
end
class C
include M
def fun
not! + ' ' + yea!
end
end
M.not! # => 'not!'
M.yea! # => 'yea!'
C.new.fun # => 'not! yea!'
Advertencia: este artículo fue votado # 1Hack más horrible de 2008, así que utilícelo con cuidado. En realidad, evítalo como la plaga, pero ciertamente es Hidden Ruby.
¿Alguna vez ha deseado un operador de protocolo de enlace super secreto para alguna operación única en su código? ¿Te gusta jugar al golf de código? Intente operadores como - ~ + ~ - o <--- Este último se usa en los ejemplos para invertir el orden de un elemento.
No tengo nada que ver con el Proyecto de Superators más allá de admirarlo.
Llego tarde a la fiesta, pero:
Puede tomar fácilmente dos matrices de igual longitud y convertirlas en un hash con una matriz que proporciona las claves y la otra los valores:
a = [:x, :y, :z]
b = [123, 456, 789]
Hash[a.Zip(b)]
# => { :x => 123, :y => 456, :z => 789 }
(Esto funciona porque Array # Zip "comprime" los valores de los dos arreglos:
a.Zip(b) # => [[:x, 123], [:y, 456], [:z, 789]]
Y Hash [] puede tomar tal matriz. He visto a la gente hacer esto también:
Hash[*a.Zip(b).flatten] # unnecessary!
Lo que produce el mismo resultado, pero el splat y el aplanamiento son totalmente innecesarios, tal vez no fueron en el pasado?
Hachas auto vivificantes en Ruby
def cnh # silly name "create nested hash"
Hash.new {|h,k| h[k] = Hash.new(&h.default_proc)}
end
my_hash = cnh
my_hash[1][2][3] = 4
my_hash # => { 1 => { 2 => { 3 =>4 } } }
Esto puede ser muy útil.
Destructurando una matriz
(a, b), c, d = [ [:a, :b ], :c, [:d1, :d2] ]
Dónde:
a #=> :a
b #=> :b
c #=> :c
d #=> [:d1, :d2]
Usando esta técnica, podemos usar una asignación simple para obtener los valores exactos que queremos de una matriz anidada de cualquier profundidad.
Class.new()
Crear una nueva clase en tiempo de ejecución. El argumento puede ser una clase de la cual derivarse, y el bloque es el cuerpo de la clase. También es posible que desee consultar const_set/const_get/const_defined?
para registrar correctamente su nueva clase, de modo que inspect
imprima un nombre en lugar de un número.
No es algo que necesites todos los días, pero es bastante útil cuando lo haces.
crear una matriz de números consecutivos:
x = [*0..5]
establece x en [0, 1, 2, 3, 4, 5]
Mucha de la magia que ves en Rubyland tiene que ver con la metaprogramación, que es simplemente escribir código que escribe código para ti. Los attr_accessor
, attr_reader
y attr_writer
de Ruby son metaprogramas simples, ya que crean dos métodos en una línea, siguiendo un patrón estándar. Rails realiza una gran cantidad de metaprogramaciones con sus métodos de gestión de relaciones como has_one
y belongs_to
.
Pero es bastante simple crear sus propios trucos de metaprogramación utilizando class_eval
para ejecutar código escrito de forma dinámica.
El siguiente ejemplo permite que un objeto envoltorio reenvíe ciertos métodos a un objeto interno:
class Wrapper
attr_accessor :internal
def self.forwards(*methods)
methods.each do |method|
define_method method do |*arguments, &block|
internal.send method, *arguments, &block
end
end
end
forwards :to_i, :length, :split
end
w = Wrapper.new
w.internal = "12 13 14"
w.to_i # => 12
w.length # => 8
w.split('1') # => ["", "2 ", "3 ", "4"]
El método Wrapper.forwards
toma símbolos para los nombres de los métodos y los almacena en la matriz methods
. Luego, para cada uno de los dados, usamos define_method
para crear un nuevo método cuyo trabajo es enviar el mensaje, incluidos todos los argumentos y bloques.
Un gran recurso para los problemas de metaprogramación es Por qué el Lucky Stiff "está viendo claramente la metaprogramación" .
use cualquier cosa que responda a ===(obj)
para las comparaciones de casos:
case foo
when /baz/
do_something_with_the_string_matching_baz
when 12..15
do_something_with_the_integer_between_12_and_15
when lambda { |x| x % 5 == 0 }
# only works in Ruby 1.9 or if you alias Proc#call as Proc#===
do_something_with_the_integer_that_is_a_multiple_of_5
when Bar
do_something_with_the_instance_of_Bar
when some_object
do_something_with_the_thing_that_matches_some_object
end
Module
(y por lo tanto Class
), Regexp
, Date
, y muchas otras clases definen un método de instancia: === (otro), y todos pueden ser utilizados.
Gracias a Farrel por el recordatorio de que Proc#call
tiene un alias como Proc#===
en Ruby 1.9.
El binario "Ruby" (al menos MRI) es compatible con muchos de los conmutadores que hicieron a Perl one-liners bastante popular.
Los significativos:
put
s automático al final de cada iteración de bucleAlgunos ejemplos:
# Print each line with its number:
Ruby -ne 'print($., ": ", $_)' < /etc/irbrc
# Print each line reversed:
Ruby -lne 'puts $_.reverse' < /etc/irbrc
# Print the second column from an input CSV (dumb - no balanced quote support etc):
Ruby -F, -ane 'puts $F[1]' < /etc/irbrc
# Print lines that contain "eat"
Ruby -ne 'puts $_ if /eat/i' < /etc/irbrc
# Same as above:
Ruby -pe 'next unless /eat/i' < /etc/irbrc
# Pass-through (like cat, but with possible line-end munging):
Ruby -p -e '' < /etc/irbrc
# Uppercase all input:
Ruby -p -e '$_.upcase!' < /etc/irbrc
# Same as above, but actually write to the input file, and make a backup first with extension .bak - Notice that inplace edit REQUIRES input files, not an input STDIN:
Ruby -i.bak -p -e '$_.upcase!' /etc/irbrc
Siéntase libre de buscar en Google "Ruby one-liners" y "Perl one-liners" para obtener más ejemplos prácticos y útiles. Básicamente, te permite usar Ruby como un reemplazo bastante poderoso para awk y sed.
El método send () es un método de propósito general que se puede usar en cualquier clase u objeto en Ruby. Si no se reemplaza, send () acepta una cadena y llama al nombre del método cuya cadena se pasa. Por ejemplo, si el usuario hace clic en el botón "Clr", la cadena "press_clear" se enviará al método send () y se llamará al método "press_clear". El método send () permite una forma divertida y dinámica de llamar a funciones en Ruby.
%w(7 8 9 / 4 5 6 * 1 2 3 - 0 Clr = +).each do |btn|
button btn, :width => 46, :height => 46 do
method = case btn
when /[0-9]/: 'press_'+btn
when 'Clr': 'press_clear'
when '=': 'press_equals'
when '+': 'press_add'
when '-': 'press_sub'
when '*': 'press_times'
when '/': 'press_div'
end
number.send(method)
number_field.replace strong(number)
end
end
Hablo más sobre esta característica en Blogging Shoes: The Simple-Calc Application
private unless Rails.env == 'test'
# e.g. a bundle of methods you want to test directly
Parece un buen/interesante (y en algunos casos) agradable/útil truco/característica de Ruby.
Fixnum#to_s(base)
puede ser realmente útil en algunos casos. Uno de estos casos es generar tokens aleatorios (pseudo) únicos al convertir un número aleatorio en una cadena usando la base de 36.
Ficha de longitud 8:
Rand(36**8).to_s(36) => "fmhpjfao"
Rand(36**8).to_s(36) => "gcer9ecu"
Rand(36**8).to_s(36) => "krpm0h9r"
Ficha de longitud 6:
Rand(36**6).to_s(36) => "bvhl8d"
Rand(36**6).to_s(36) => "lb7tis"
Rand(36**6).to_s(36) => "ibwgeh"
Engañar a alguna clase o módulo diciéndole que ha requerido algo que realmente no ha requerido:
$" << "something"
Esto es útil, por ejemplo, cuando se requiere A que a su vez requiere B pero no necesitamos B en nuestro código (y A tampoco lo usará a través de nuestro código):
Por ejemplo, el bdrb_test_helper requires
'test/spec'
de Backgroundrb, pero no lo usa en absoluto, así que en su código:
$" << "test/spec"
require File.join(File.dirname(__FILE__) + "/../bdrb_test_helper")
Definir un método que acepte cualquier número de parámetros y simplemente los descarte todos
def hello(*)
super
puts "hello!"
end
El método anterior hello
solo necesita puts
"hello"
en la pantalla y llamar a super
- pero como la superclase hello
define los parámetros, también tiene que hacerlo. ellos un nombre.
Para combinar múltiples expresiones regulares con |
, puede usar
Regexp.union /Ruby\d/, /test/i, "cheat"
para crear un Regexp similar a:
/(Ruby\d|[tT][eE][sS][tT]|cheat)/
Encuentro esto útil en algunos guiones. Posibilita el uso directo de variables de entorno, como en los scripts de Shell y Makefiles. Las variables de entorno se utilizan como reserva para constantes de Ruby no definidas.
>> class <<Object
>> alias :old_const_missing :const_missing
>> def const_missing(sym)
>> ENV[sym.to_s] || old_const_missing(sym)
>> end
>> end
=> nil
>> puts Shell
/bin/zsh
=> nil
>> TERM == 'xterm'
=> true
¿Qué tal abrir un archivo basado en ARGV [0]?
readfile.rb:
$<.each_line{|l| puts l}
Ruby readfile.rb testfile.txt
Es un gran atajo para escribir guiones únicos. Hay una gran cantidad de variables predefinidas que la mayoría de las personas no conocen. Utilícelos sabiamente (lea: no ensucie una base de código que planea mantener con ellos, puede causar problemas).
Soy un fan de:
%w{An Array of strings} #=> ["An", "Array", "of", "Strings"]
Es algo gracioso la frecuencia con la que es útil.
def getCostAndMpg
cost = 30000 # some fancy db calls go here
mpg = 30
return cost,mpg
end
AltimaCost, AltimaMpg = getCostAndMpg
puts "AltimaCost = #{AltimaCost}, AltimaMpg = #{AltimaMpg}"
i = 0
j = 1
puts "i = #{i}, j=#{j}"
i,j = j,i
puts "i = #{i}, j=#{j}"
class Employee < Person
def initialize(fname, lname, position)
super(fname,lname)
@position = position
end
def to_s
super + ", #@position"
end
attr_writer :position
def etype
if @position == "CEO" || @position == "CFO"
"executive"
else
"staff"
end
end
end
employee = Employee.new("Augustus","Bondi","CFO")
employee.position = "CEO"
puts employee.etype => executive
employee.position = "Engineer"
puts employee.etype => staff
(En la mayoría de los idiomas, cuando no se puede encontrar un método y se produce un error y su programa se detiene. En Ruby, realmente puede detectar esos errores y quizás hacer algo inteligente con la situación)
class MathWiz
def add(a,b)
return a+b
end
def method_missing(name, *args)
puts "I don't know the method #{name}"
end
end
mathwiz = MathWiz.new
puts mathwiz.add(1,4)
puts mathwiz.subtract(4,2)
5
No sé restar el método.
nulo
La punta de James A. Rosen es genial ([* items] .each), pero me parece que destruye los hashes:
irb(main):001:0> h = {:name => "Bob"}
=> {:name=>"Bob"}
irb(main):002:0> [*h]
=> [[:name, "Bob"]]
Prefiero esta forma de manejar el caso cuando acepto una lista de cosas para procesar, pero soy indulgente y permito que la persona que llama proporcione una:
irb(main):003:0> h = {:name => "Bob"}
=> {:name=>"Bob"}
irb(main):004:0> [h].flatten
=> [{:name=>"Bob"}]
Esto se puede combinar con una firma de método como muy bien:
def process(*entries)
[entries].flatten.each do |e|
# do something with e
end
end
Acabo de love la palabra clave en línea rescate como esto:
EJEMPLO EDITADO:
@user #=> nil (but I did't know)
@user.name rescue "Unknown"
link_to( d.user.name, url_user( d.user.id, d.user.name)) rescue 'Account removed'
Esto evita romper mi aplicación y es mucho mejor que la característica lanzada en Rails .try ()
Llamar a un método definido en cualquier lugar de la cadena de herencia, incluso si se invalida
Los objetos de ActiveSupport a veces se enmascaran como objetos integrados.
requiere 'active_support' days = 5.days days.class # => Fixnum days.is_a? (Fixnum) # => true Fixnum === days # => false (¿huh? ¿Qué es lo que realmente eres?) Object.instance_method (: class) .bind (days) .call # => ActiveSupport :: Duration (¡aha!) ActiveSupport :: Duration === days # => true
Lo anterior, por supuesto, se basa en el hecho de que active_support no redefine el Object # instance_method, en cuyo caso realmente estaríamos en un arroyo. Una vez más, siempre podríamos guardar el valor de retorno de Object.instance_method (: class) antes de que se cargue cualquier biblioteca de terceros.
Object.instance_method (...) devuelve un UnboundMethod que luego puede enlazar a una instancia de esa clase. En este caso, puede vincularlo a cualquier instancia de Objeto (subclases incluidas).
Si la clase de un objeto incluye módulos, también puede usar UnboundMethod desde esos módulos.
módulo Mod def var_add (más); @ var + más; end end clase Cla incluye Mod def initialize (var); @ var = var; end # override def var_add (más); @ var + más + más; end end cla = Cla.new ('abcdef') cla.var_add ('ghi') # => "abcdefghighi" Mod.instance_method ( : var_add) .bind (cla) .call ('ghi') # => "abcdefghi"
Esto incluso funciona para los métodos singleton que anulan un método de instancia de la clase a la que pertenece el objeto.
clase Foo def mymethod; 'original'; end end foo = Foo.new foo.mymethod # => 'original' def foo.mymethod; 'semifallo'; end foo.mymethod # => 'singleton' Foo.instance_method (: mymethod) .bind (foo) .call # => 'original' # También puede llamar al método #instance en clases singleton: Class << foo; yo; end.instance_method (: mymethod) .bind (foo) .call # => 'singleton'
Hay algunos aspectos de los símbolos literales que la gente debería saber. Un caso resuelto por literales de símbolos especiales es cuando necesita crear un símbolo cuyo nombre cause un error de sintaxis por alguna razón con la sintaxis literal de símbolo normal:
:'class'
También puedes hacer interpolación de símbolos. En el contexto de un accessor, por ejemplo:
define_method :"#{name}=" do |value|
instance_variable_set :"@#{name}", value
end
each_with_index method para cualquier objeto enumerable (array, hash, etc.) ¿quizás?
myarray = ["la", "li", "lu"]
myarray.each_with_index{|v,idx| puts "#{idx} -> #{v}"}
#result:
#0 -> la
#1 -> li
#2 -> lu
Tal vez sea más conocido que otras respuestas, pero no tan conocido por todos los programadores de Ruby :)
class A
private
def my_private_method
puts 'private method called'
end
end
a = A.new
a.my_private_method # Raises exception saying private method was called
a.send :my_private_method # Calls my_private_method and prints private method called'
Ruby tiene un mecanismo call/cc que permite a uno saltar libremente hacia arriba y hacia abajo de la pila.
Un ejemplo simple sigue. Ciertamente, esta no es la forma en que se podría multiplicar una secuencia en Ruby, pero muestra cómo se podría usar call/cc para llegar a la pila y provocar un cortocircuito en un algoritmo. En este caso, estamos multiplicando recursivamente una lista de números hasta que hayamos visto todos los números o veamos cero (los dos casos donde conocemos la respuesta). En el caso cero, podemos ser arbitrariamente profundos en la lista y terminar.
#!/usr/bin/env Ruby
def rprod(k, rv, current, *nums)
puts "#{rv} * #{current}"
k.call(0) if current == 0 || rv == 0
nums.empty? ? (rv * current) : rprod(k, rv * current, *nums)
end
def prod(first, *rest)
callcc { |k| rprod(k, first, *rest) }
end
puts "Seq 1: #{prod(1, 2, 3, 4, 5, 6)}"
puts ""
puts "Seq 2: #{prod(1, 2, 0, 3, 4, 5, 6)}"
Puedes ver la salida aquí:
Para un ejemplo más complejo con continuaciones que se mueven en la otra dirección en la pila, lea la fuente en Generador .
Acabo de leer todas las respuestas ... una omisión notable fue la tarea de desestructuración:
> (a,b),c = [[1,2],3]
=> [[1,2],3]
> a
=> 1
También funciona para parámetros de bloque. Esto es útil cuando tiene matrices anidadas, cada elemento de las cuales representa algo distinto. En lugar de escribir código como "array [0] [1]", puede dividir esa matriz anidada y dar un nombre descriptivo a cada elemento, en una sola línea de código.
@user #=> nil (but I did't know)
@user.name rescue "Unknown"
Mi función favorita de Ruby. La sintaxis es format_string % argument
"%04d" % 1 # => "0001"
"%0.2f" % Math::PI # => "3.14"
Funciona también para matrices (format_string % array_of_arguments
)
"%.2f %.3f %.4f" % ([Math::PI]*3)
# => "3.14 3.142 3.1416"