Ruby

Absence validator

Validador de ausencia (absence) En Ruby on Rails existe un validador con el nombre de absence, este la primera vez que lo vi pensé "¿Para que quiere comprobar que un valor no existe?" Pero luego leyendo este post me ha parecido útil a la hora de definir campos en el modelo que se encuentran en la base de datos y esta los genera automáticamente. Como por ejemplo un ID class User include ActiveModel::Validations attr_accessor :id, :name validates :id, absence: true end De esta forma modelamos que el usuario tiene un ID pero no puede ser impuesto desde el la aplicación, lo dejamos para la base de datos. De tal manera que si comprobamos si el objeto que hemos creado es válido o no, uno de los puntos que tendrá en cuenta será que tiene que tener el ID en este caso vacío. user = User.new user.valid? # => true user.id = 1 user.valid? # => false Este ejemplo no es bueno ya que el ID lo genera rails automáticamente y debe de estar presente siempre en la base de datos. Esto generará un problema si hacemos lo siguiente. user = User.new user.name = 'Pepe' user.save # save on database user = User.last # id: 1 name: Pepe user.name = 'Antonio' user.save # error id should be blank user.id = nil # error from database, id can't be null Al tener la restricción en el modelo de que el ID tiene que ser vacío en el momento de la creación y el primer guardado en la base de datos no hay problema pero si cargamos un usuario para modificarlo nos obliga la aplicación a que sea vacío el ID y al guardarlo en la base de datos no puede ejecutar la sentencia SQL. Referencias Documentación oficial sobre absence Post aclarativo en español.

Ejecutar un solo test

Ejecutar solo un test En nuestros test podemos definir etiquetas, si queremos ejecutar un solo test podemos definir una que sea :focus de manera que en el test que queramos que se ejecute hacemos :focus => true describe "group with tagged specs" do it "example I'm working now", :focus => true do; end it "special example", :type => 'special' do; end it "slow example", :skip => true do; end it "ordinary example", :speed => 'slow' do; end it "untagged example" do; end end Si ahora cuando ejecutamos nuestros test hacemos rspec . --tag focus nos ejecutara solamente el test con el :focus => true en nuestro caso el primero solamente. Si hacemos rspec . --tag type:special en ese caso solo nos ejecutara el segundo test. Para este ejemplo estamos usando rspec toda la documentación sobre etiquetas aquí

Test en ruby

Test en Ruby Artículo original en rspec-info Instalar la gema Primero creamos un archivo Gemfile en la raiz de nuestro proyecto con al menos el siguiente contenido source "https://rubygems.org" gem 'rspec', '~> 3.0' Luego instalamos las gemas que hemos indicado en nuestro fichero Gemfile mediante bundle install --binstubs Con todo instalado iniciamos la suite de test bin/rspec --init Creamos nuestro primer test require 'rspec' describe 'My first test' do it 'should do works fine' do expect(true).to be_truthy expect(true).to be true end end Y lo ejecutamos desde la raiz del proyecto añadiendo la opción --format doc hacemos que sea mas amigable, mostrando los nombres de los test que han pasado en verde y los que no en rojo. Si no ponemos esta opción se mostrará una línea de puntos verdes y rojos para indicar los test que han pasado y los que no. bin/rspec --format doc

Yield

Yield Artículo completo en ruby-doc.org Una sentencia yield es similar a un callback en JavaScript. Defines un bloque de código y lo pasas como argumento de un método, este segundo que lo recibe lo ejecuta internamente donde quiere mediante el uso de yield. Como se verá en el ejemplo siguiente defines tu método y luego a este le envías un bloque de código que dentro ejecutas dos veces. def twice yield yield end twice {puts("Hello World!")} # Hello World! # Hello World! Además se le pueden pasar parámetros, si lo quisiéramos. En este segundo ejemplo como se trata de un bloque de código de varias líneas lo hacemos con do end en lugar de usar las llaves { }. Para indicarle los parámetros usamos las barras verticales | parametro |. def names yield("Joe") yield("Sandy") yield("Melissa") end names do |name| puts "Hello " + name + ", how are you?" end # Hello Joe, how are you? # Hello Sandy, how are you? # Hello Melissa, how are you?

Blocks procs lambda

Diferencia entre Block, Proc y Lambda Los proc se crean de la siguiente forma: p = Proc.new {|bla| puts "I'm a proc that says #{bla}!" } p = proc {|bla| puts "I'm a proc that says #{bla}!" } Las lambda se crean de la siguiente forma: lmb = lambda {|bla| "I'm also a proc, and I say #{bla}" } also_lmb = ->(bla) { "I'm also a proc, and I say #{bla}" } Diferencias Procs son como blocks, lambdas son como métodos anónimos. Lambdas son estrictas en sus argumentos, si le pasas muchos o pocos argumentos devuelve una excepción. En el caso de blocks y procs si pasas muchos los ignora y si pasas pocos pone los restantes a nil Los return y break en procs y lambdas se comportan diferentes. El return en un proc se ejecuta en el scope donde se definió el bloque. En lambdas return y break devuelven el control al que lo ha llamado. next se comporta igual en los tres casos. Usar la abreviatura de invocación de proc cuando el método invocado es la única operación de un bloque. Ejemplo sacado de la guía de estilos Rubocop names = ["Pepe", "Juan", "Rosa", "Violeta"] # BAD upper_names = names.map { |name| name.upcase } # ["PEPE", "JUAN", "ROSA", "VIOLETA"] # GOOD upper_names = names.map(&:upcase) # ["PEPE", "JUAN", "ROSA", "VIOLETA"]

Filtros

Cuando estamos implementando nuestro controlador en Ruby es posible que en algunos casos tengamos que comprobar antes de cada acción (método) algo como por el ejemplo el caso de la sesión. Es decir, comprobar que el usuario está logeado para poder realizar esa acción concreta o ver las credenciales del mismo para comprobar si tiene permisos para ello. Esto se puede hacer en Ruby de manera sencilla con los filtros (filters). Siguiendo con el ejemplo anterior tendríamos que hacer algo parecido a lo siguiente. class ApplicationController < ActionController::Base before_action :require_login, only: :createItem def index # end def createItem # end private def require_login unless logged_in? flash[:error] = "You must be logged in to access this section" redirect_to new_login_url # halts request cycle end end end De esta forma cada vez que que la aplicación acceda a createItem antes pasará por require_login comprobando que el usuario ha iniciado sesión antes de hacer la acción. Como se ve en el ejemplo también existen modificadores si quieres que solo sea en una acción concreta como en el ejemplo con only:, para que NO se ejecute en ciertas acciones solamente except:. Además del before_action existen también el after_action y el arround_action y como es de esperar en el after se ejecutará después de cada acción. El caso del around es más especial y este se ejecuta entre el before y el after, según he podido leer en este post de stackoverflow.

Constantes

Constantes Las constantes se definen escribiendo la primera letra en mayúsculas, pero normalmente se escriben todas ellas. Si despues de definir esta constante tratas de modificarla te salta un aviso de que estás modificando una constante. MAX_SPEED = 100 MAX_SPEED = 1000 # warning: already initialized constant MAX_SPEED # warning: previous definition of MAX_SPEED was here En cambio si la constante es un objeto y tratas de modificarlo no dice nada (dado que está guardando la referencia a memoria), para poder evitar que nos modifiquen una constante objeto existe el método freeze. De tal manera que si creamos un array por ejemplo podemos inicializarlo y evitar que lo modifiquen la consante pero no el objeto que tenga dentro. TYPES = [] TYPES << "freighter" # can be modified TYPES.freeze TYPES << "freighter" # RuntimeError: can't modify frozen Array TYPES[0].upcase! # FREIGHTER

Variables

Variables en las clases de Ruby Ruby permite cuatro tipos de variables: Variable local, las variables locales son variables que están definidas en un método. Estas variables no están disponible fuera del método. Las variables locales empiezan con letra minúscula o guión bajo (_) variable_name def method foo = Array.new end # foo no esta definida aqui, no existe Variables de instancia, están disponible en todos los métodos de cualquier de cualquier instancia u objeto. Esto quiere decir que las variables de instancia cambian de un objeto a otro. Estas variables están precedidas por un @ seguidas del nombre de la variable @variable_name def method @foo = "Hola!" end puts @foo Variables de clase, están disponible en los diferentes objetos. Una variable de clase pertenece a la clase y es una característica de la clase. Están precedidas por @@ seguidas del nombre de la variable @@variable_name class Foo @@variable = 0 def counter @@variable += 1 puts("Count: #@@variable") end end foo1 = Foo.new foo2 = Foo.new foo1.counter # 1 foo1.counter # 2 foo2.counter # 3 Variables globales, están disponible en todas las diferentes clases. Se definen con $ seguido del nombre de la variable $variable_name class Foo $global_variable = "Global" end class Any def method puts($global_variable) end end any = Any.new any.method # "Global" Las variables creadas con @ crean instancias de variables en cada objeto, esto puede generar problemas si creamos dos módulos que tengan el mismo nombre en algunas variables, estas entrarán en colisión. module MajorScales def majorNum @numNotes = 7 if @numNotes.nil? @numNotes # Return 7 end end module PentatonicScales def pentaNum @numNotes = 5 if @numNotes.nil? @numNotes # Return 5? end end class ScaleDemo include MajorScales include PentatonicScales def initialize puts majorNum # Should be 7 puts pentaNum # Should be 5 end end ScaleDemo.new # output # 7 # 7 Como podemos ver tanto en MajorScales como en PentatonicScales hemos creado una variable con el mismo nombre lo que hace que en nuestra nueva clase ScaleDemo compartan esta variable en ambos, de manera que tienen el mismo valor ambas. Ejemplo completo en phrogz Más infromación sobre variables en tutorialspoint Más información sobre clases en tutorialspoint

Curiosidades de ruby

Manejo de array Para acceder a los arrays podemos hacerlo de varias formas, una de ellas es mediante los corchetes. Estos son muy flexibles, podemos hacer muchas cosas con ellos. Podemos acceder con [0..2] elementos desde 0 hasta 2 [0], [1], [2] [0, 2] 2 elementos desde el 0 [0], [1] [-1] el último elemento [0..2] = 'A' elementos desde el 0 hasta el 2 sustituye por ‘A’ deja un solo elemento, une los tres seleccionados en uno solo a = Array.new a[4] = "4"; #=> [nil, nil, nil, nil, "4"] a[0, 3] = [ 'a', 'b', 'c' ] #=> ["a", "b", "c", nil, "4"] a[1..2] = [ 1, 2 ] #=> ["a", 1, 2, nil, "4"] a[0, 2] = "?" #=> ["?", 2, nil, "4"] a[0..2] = "A" #=> ["A", "4"] a[-1] = "Z" #=> ["A", "Z"] a[1..-1] = nil #=> ["A", nil] a[1..-1] = [] #=> ["A"] a[0, 0] = [ 1, 2 ] #=> [1, 2, "A"] a[3, 0] = "B" #=> [1, 2, "A", "B"] Diferencia entre dup & clone Articulo completo en coderwall. Ambos métodos crean una copia superficial (shalow copy), lo que quiere decir que hace una copia directa de los campos que el objeto tenga dentro. Por lo tanto si tiene primitivos los copia y si tiene objetos copia la referencia a la zona de memoria, con lo que si modificamos un campo de la copia el cual es un objeto modificara el valor del original. items = [ Item.new('irrelevant'), Item.new('irrelevant'), Item.new('irrelevant') ] cart = ShoppingCart.new(items) clone = cart.clone puts cart #<ShoppingCart:0x000055d2f0d41bb0> puts clone #<ShoppingCart:0x000055d2f0a421d0> puts cart.getList.size #3 clone.delete 1 puts clone.getList.size #2 puts cart.getList.size #2 La diferencia entre ambos es que clone hace dos cosas que no hace dup estas son: Copia la clase singleton del objeto copiado Mantiene el estado congelado del objeto copiado a = Object.new # creamos la instacia del objeto def a.foo; :foo end # le incluimos un método nuevo p a.foo # => :foo # vemos que el método existe b = a.dup # hacemos la copia p b.foo # => undefined method `foo' for #<Object:0x007f8bc395ff00> (NoMethodError) # el método nuevo no existe en la copia con dup b = a.clone p b.foo # => :foo # el método nuevo si existe en la copia con clone Estado congelado: a = Object.new a.freeze p a.frozen? # => true b = a.dup p b.frozen? # => false c = a.clone p c.frozen? # => true Como podemos ver el estado se mantiene si usamos el clone. for VS each Artículo completo en freecodecamp-leandrotk por LeandroTK en el apartado de Looping/Iterator. En el ejemplo siguiente veremos porque nos recomiendan que usemos each en lugar de for, esto es debido a que el bucle foor una vez termina su ejecución mantiene la variable que usa para iterar. En cambio esto no ocurre si usamos un iterador each. # for looping for num in 1...5 puts num end puts num # => 5 # each iterator [1, 2, 3, 4, 5].each do |num| puts num end puts num # => undefined local variable or method `n' for main:Object (NameError) Objetos En ruby no tenemos porque declarar los campos de un objeto, basta con incluirlos con un @. Estos campos por defecto no son accesibles desde fuera, para decirdir el nivel de acceso que le queremos dar existen tres etiquetas: attr_accesor, permites lectura y escritura en el campo attr_reader, permites lectura en el campo attr_writter, permites escritura en el campo Para usarlo sería de la siguiente forma. class Item attr_reader :price attr_accessor :amount attr_writer :name def initialize(name, amount, price, foo) @name = name @amount = amount @price = price @foo = foo end end De esta forma price solo se puede leer, amount permite lectura y escritura, name solo escritura y al campo foo no se puede acceder. item = Item.new('raton', 15, 40, 'foo') puts item.price # 40 puts item.amount # 15 puts item.name # Error puts item.foo # Error item.amount = 10 puts item.amount # 10 item.name = 'teclado' # se modifica el valor a teclado Múltiples campos en un método Articulo completo en codementor punto 3 En caso de tener un método con una cantidad de campos indefinidos existe un operador * def mood(name, *feelings) feelings.each do |feeling| puts "#{name} is feeling #{feeling} today." end end feeling("Suzie", "happy", "excited", "nervous") # Suzie is feeling happy today. # Suzie is feeling excited today. # Suzie is feeling nervous today. Usar doble exclamación (!!) para comprobar si existe un valor Articulo completo en codementor punto 5 Puede sonar raro, usar una doble negación? No se supone que se anulan al usarla. Esto es un truco que se usa en ruby para evitar que nos devuelvan nil. Si devolvemos directamente un valor que esperamos que sea verdadero o falso, puede darse el caso de que este no esté inicializado. Es por ello que si devolvemos una doble negación, con la primera obtenemos verdadero en caso de que no exista y en la seguda negación falso, lo que nosotros queríamos. class Name attr_reader :middle_name def initialize(first_name, last_name, middle_name=nil) @first_name = first_name @middle_name = middle_name @last_name = last_name end def has_middle_name? !!@middle_name end end george = Name.new("George", "Washington") george.has_middle_name? #=> false puts george.middle_name # nil puts !george.middle_name # true puts !!george.middle_name # false obama = Name.new("Barack", "Obama", "Hussein") obama.has_middle_name? #=> true puts obama.middle_name #=> "Hussein" puts !obama.middle_name #=> false puts !!obama.@middle_name #=> true Legibilidad Métodos true false Articulo completo en sitepoint Por convenio en Ruby los métodos que devuelvan verdadero o faso, como puede ser is_empty se terminan con un signo de interrogación para dar más semántica al código. De tal manera que quedan is_empty?. Condiciones if !true Articulo completo en sitepoint Existe una cláusula que hace lo mismo y queda más claro (a mi parecer), además te recomiendan que la uses. La cláusula es unless if !true do_this end unless true do_this end Y nunca debe de tener un else, se le puede poner pero no tiene sentido. Si tienes un else en un unless con cambiar el sentido de la condición queda igual. # NEVER do this unless success #throw error else #return success end # This is the correct form if success #return success else #throw error end