edge
Más en rubyonrails.org: Más Ruby on Rails

Extensiones principales de Active Support

Active Support es el componente de Ruby on Rails responsable de proporcionar extensiones y utilidades al lenguaje Ruby.

Ofrece una base más sólida a nivel de lenguaje, dirigida tanto al desarrollo de aplicaciones Rails como al desarrollo de Ruby on Rails en sí.

Después de leer esta guía, sabrás:

1 Cómo cargar las Extensiones Principales

1.1 Active Support independiente

Con el fin de tener la menor huella predeterminada posible, Active Support carga las dependencias mínimas de forma predeterminada. Está dividido en pequeñas piezas para que solo se carguen las extensiones deseadas. También tiene algunos puntos de entrada convenientes para cargar extensiones relacionadas de una sola vez, incluso todo.

Así, después de un simple require como este:

require "active_support"

solo se cargan las extensiones requeridas por el framework Active Support.

1.1.1 Seleccionar una definición

Este ejemplo muestra cómo cargar Hash#with_indifferent_access. Esta extensión permite la conversión de un Hash en un ActiveSupport::HashWithIndifferentAccess que permite acceder a las claves tanto como cadenas o símbolos.

{ a: 1 }.with_indifferent_access["a"] # => 1

Para cada método definido como una extensión principal, esta guía tiene una nota que indica dónde se define dicho método. En el caso de with_indifferent_access, la nota dice:

NOTA: Definido en active_support/core_ext/hash/indifferent_access.rb.

Eso significa que puedes requerirlo de esta manera:

require "active_support"
require "active_support/core_ext/hash/indifferent_access"

Active Support ha sido cuidadosamente revisado para que al seleccionar un archivo solo se carguen las dependencias estrictamente necesarias, si las hay.

1.1.2 Cargar extensiones principales agrupadas

El siguiente nivel es simplemente cargar todas las extensiones de Hash. Como regla general, las extensiones de SomeClass están disponibles de una sola vez cargando active_support/core_ext/some_class.

Así, para cargar todas las extensiones de Hash (incluyendo with_indifferent_access):

require "active_support"
require "active_support/core_ext/hash"

1.1.3 Cargar todas las extensiones principales

Tal vez prefieras simplemente cargar todas las extensiones principales, hay un archivo para eso:

require "active_support"
require "active_support/core_ext"

1.1.4 Cargar todo Active Support

Y finalmente, si quieres tener todo Active Support disponible, simplemente ejecuta:

require "active_support/all"

Esto ni siquiera carga todo Active Support en memoria de antemano, de hecho, algunas cosas están configuradas a través de autoload, por lo que solo se cargan si se utilizan.

1.2 Active Support dentro de una aplicación Ruby on Rails

Una aplicación Ruby on Rails carga todo Active Support a menos que config.active_support.bare sea verdadero. En ese caso, la aplicación solo cargará lo que el propio framework selecciona para sus propias necesidades, y aún puede seleccionar a cualquier nivel de granularidad, como se explica en la sección anterior.

2 Extensiones para todos los objetos

2.1 blank? y present?

Los siguientes valores se consideran en blanco en una aplicación Rails:

  • nil y false,

  • cadenas compuestas solo de espacios en blanco (ver nota a continuación),

  • matrices y hashes vacíos, y

  • cualquier otro objeto que responda a empty? y esté vacío.

El predicado para las cadenas utiliza la clase de caracteres con conocimiento de Unicode [:space:], por lo que, por ejemplo, U+2029 (separador de párrafo) se considera espacio en blanco.

ADVERTENCIA: Ten en cuenta que los números no se mencionan. En particular, 0 y 0.0 no están en blanco.

Por ejemplo, este método de ActionController::HttpAuthentication::Token::ControllerMethods utiliza blank? para verificar si un token está presente:

def authenticate(controller, &login_procedure)
  token, options = token_and_options(controller.request)
  unless token.blank?
    login_procedure.call(token, options)
  end
end

El método present? es equivalente a !blank?. Este ejemplo se toma de ActionDispatch::Http::Cache::Response:

def set_conditional_cache_control!
  return if self["Cache-Control"].present?
  # ...
end

NOTA: Definido en active_support/core_ext/object/blank.rb.

2.2 presence

El método presence devuelve su receptor si present?, y nil en caso contrario. Es útil para idiomatismos como este: ruby host = config[:host].presence || 'localhost'

NOTA: Definido en active_support/core_ext/object/blank.rb.

2.3 duplicable?

A partir de Ruby 2.5, la mayoría de los objetos se pueden duplicar mediante dup o clone:

"foo".dup           # => "foo"
"".dup              # => ""
Rational(1).dup     # => (1/1)
Complex(0).dup      # => (0+0i)
1.method(:+).dup    # => TypeError (allocator undefined for Method)

Active Support proporciona duplicable? para consultar a un objeto sobre esto:

"foo".duplicable?           # => true
"".duplicable?              # => true
Rational(1).duplicable?     # => true
Complex(1).duplicable?      # => true
1.method(:+).duplicable?    # => false

ADVERTENCIA: Cualquier clase puede evitar la duplicación eliminando dup y clone o lanzando excepciones desde ellos. Por lo tanto, solo rescue puede decir si un objeto arbitrario dado es duplicable. duplicable? depende de la lista codificada anteriormente, pero es mucho más rápido que rescue. Úsalo solo si sabes que la lista codificada es suficiente en tu caso de uso.

NOTA: Definido en active_support/core_ext/object/duplicable.rb.

2.4 deep_dup

El método deep_dup devuelve una copia profunda de un objeto dado. Normalmente, cuando haces dup de un objeto que contiene otros objetos, Ruby no los duplica, por lo que crea una copia superficial del objeto. Si tienes un array con un string, por ejemplo, se verá así:

array     = ['string']
duplicate = array.dup

duplicate.push 'another-string'

# el objeto fue duplicado, por lo que el elemento se agregó solo a la copia
array     # => ['string']
duplicate # => ['string', 'another-string']

duplicate.first.gsub!('string', 'foo')

# el primer elemento no fue duplicado, se cambiará en ambos arrays
array     # => ['foo']
duplicate # => ['foo', 'another-string']

Como puedes ver, después de duplicar la instancia de Array, obtuvimos otro objeto, por lo tanto, podemos modificarlo y el objeto original permanecerá sin cambios. Sin embargo, esto no es cierto para los elementos del array. Dado que dup no hace una copia profunda, el string dentro del array sigue siendo el mismo objeto.

Si necesitas una copia profunda de un objeto, debes usar deep_dup. Aquí tienes un ejemplo:

array     = ['string']
duplicate = array.deep_dup

duplicate.first.gsub!('string', 'foo')

array     # => ['string']
duplicate # => ['foo']

Si el objeto no se puede duplicar, deep_dup simplemente lo devuelve:

number = 1
duplicate = number.deep_dup
number.object_id == duplicate.object_id   # => true

NOTA: Definido en active_support/core_ext/object/deep_dup.rb.

2.5 try

Cuando quieres llamar a un método en un objeto solo si no es nil, la forma más sencilla de lograrlo es con declaraciones condicionales, agregando un desorden innecesario. La alternativa es usar try. try es como Object#public_send, excepto que devuelve nil si se envía a nil.

Aquí tienes un ejemplo:

# sin try
unless @number.nil?
  @number.next
end

# con try
@number.try(:next)

Otro ejemplo es este código de ActiveRecord::ConnectionAdapters::AbstractAdapter donde @logger podría ser nil. Puedes ver que el código usa try y evita una verificación innecesaria.

def log_info(sql, name, ms)
  if @logger.try(:debug?)
    name = '%s (%.1fms)' % [name || 'SQL', ms]
    @logger.debug(format_log_entry(name, sql.squeeze(' ')))
  end
end

try también se puede llamar sin argumentos pero con un bloque, que solo se ejecutará si el objeto no es nulo:

@person.try { |p| "#{p.first_name} #{p.last_name}" }

Ten en cuenta que try ignorará los errores de no método, devolviendo nil en su lugar. Si quieres protegerte contra errores tipográficos, usa try! en su lugar:

@number.try(:nest)  # => nil
@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Integer

NOTA: Definido en active_support/core_ext/object/try.rb.

2.6 class_eval(*args, &block)

Puedes evaluar código en el contexto de la clase singleton de cualquier objeto usando class_eval:

class Proc
  def bind(object)
    block, time = self, Time.current
    object.class_eval do
      method_name = "__bind_#{time.to_i}_#{time.usec}"
      define_method(method_name, &block)
      method = instance_method(method_name)
      remove_method(method_name)
      method
    end.bind(object)
  end
end

NOTA: Definido en active_support/core_ext/kernel/singleton_class.rb.

2.7 acts_like?(duck)

El método acts_like? proporciona una forma de verificar si una clase se comporta como otra clase según una convención simple: una clase que proporciona la misma interfaz que String define ruby def acts_like_string? end

que solo es un marcador, su cuerpo o valor de retorno son irrelevantes. Luego, el código del cliente puede consultar si se comporta como un pato de tipo de cadena de esta manera:

some_klass.acts_like?(:string)

Rails tiene clases que se comportan como Date o Time y siguen este contrato.

NOTA: Definido en active_support/core_ext/object/acts_like.rb.

2.8 to_param

Todos los objetos en Rails responden al método to_param, que se supone que devuelve algo que los representa como valores en una cadena de consulta o como fragmentos de URL.

Por defecto, to_param simplemente llama a to_s:

7.to_param # => "7"

El valor de retorno de to_param no debe ser escapado:

"Tom & Jerry".to_param # => "Tom & Jerry"

Varias clases en Rails sobrescriben este método.

Por ejemplo, nil, true y false devuelven ellos mismos. Array#to_param llama a to_param en los elementos y une el resultado con "/":

[0, true, String].to_param # => "0/true/String"

Es importante destacar que el sistema de enrutamiento de Rails llama a to_param en los modelos para obtener un valor para el marcador :id. ActiveRecord::Base#to_param devuelve el id de un modelo, pero puedes redefinir ese método en tus modelos. Por ejemplo, dado

class User
  def to_param
    "#{id}-#{name.parameterize}"
  end
end

obtenemos:

user_path(@user) # => "/users/357-john-smith"

ADVERTENCIA. Los controladores deben ser conscientes de cualquier redefinición de to_param porque cuando llega una solicitud como esa, "357-john-smith" es el valor de params[:id].

NOTA: Definido en active_support/core_ext/object/to_param.rb.

2.9 to_query

El método to_query construye una cadena de consulta que asocia una clave dada con el valor de retorno de to_param. Por ejemplo, con la siguiente definición de to_param:

class User
  def to_param
    "#{id}-#{name.parameterize}"
  end
end

obtenemos:

current_user.to_query('user') # => "user=357-john-smith"

Este método escapa lo que sea necesario, tanto para la clave como para el valor:

account.to_query('company[name]')
# => "company%5Bname%5D=Johnson+%26+Johnson"

por lo que su salida está lista para ser utilizada en una cadena de consulta.

Los arrays devuelven el resultado de aplicar to_query a cada elemento con key[] como clave, y unen el resultado con "&":

[3.4, -45.6].to_query('sample')
# => "sample%5B%5D=3.4&sample%5B%5D=-45.6"

Los hashes también responden a to_query pero con una firma diferente. Si no se pasa ningún argumento, una llamada genera una serie ordenada de asignaciones clave/valor llamando a to_query(key) en sus valores. Luego une el resultado con "&":

{ c: 3, b: 2, a: 1 }.to_query # => "a=1&b=2&c=3"

El método Hash#to_query acepta un espacio de nombres opcional para las claves:

{ id: 89, name: "John Smith" }.to_query('user')
# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"

NOTA: Definido en active_support/core_ext/object/to_query.rb.

2.10 with_options

El método with_options proporciona una forma de factorizar opciones comunes en una serie de llamadas a métodos.

Dado un hash de opciones predeterminado, with_options cede un objeto proxy a un bloque. Dentro del bloque, los métodos llamados en el proxy se reenvían al receptor con sus opciones fusionadas. Por ejemplo, te deshaces de la duplicación en:

class Account < ApplicationRecord
  has_many :customers, dependent: :destroy
  has_many :products,  dependent: :destroy
  has_many :invoices,  dependent: :destroy
  has_many :expenses,  dependent: :destroy
end

de esta manera:

class Account < ApplicationRecord
  with_options dependent: :destroy do |assoc|
    assoc.has_many :customers
    assoc.has_many :products
    assoc.has_many :invoices
    assoc.has_many :expenses
  end
end

Ese idioma también puede transmitir agrupamiento al lector. Por ejemplo, digamos que quieres enviar un boletín cuyo idioma depende del usuario. En algún lugar del mailer podrías agrupar las partes dependientes del idioma de esta manera:

I18n.with_options locale: user.locale, scope: "newsletter" do |i18n|
  subject i18n.t :subject
  body    i18n.t :body, user_name: user.name
end

CONSEJO: Dado que with_options reenvía las llamadas a su receptor, se pueden anidar. Cada nivel de anidamiento fusionará los valores predeterminados heredados además de los propios.

NOTA: Definido en active_support/core_ext/object/with_options.rb.

2.11 Soporte JSON

Active Support proporciona una mejor implementación de to_json que la que el gem json proporciona normalmente para los objetos de Ruby. Esto se debe a que algunas clases, como Hash y Process::Status, necesitan un manejo especial para proporcionar una representación JSON adecuada. NOTA: Definido en active_support/core_ext/object/json.rb.

2.12 Variables de instancia

Active Support proporciona varios métodos para facilitar el acceso a las variables de instancia.

2.12.1 instance_values

El método instance_values devuelve un hash que mapea los nombres de las variables de instancia sin "@" a sus valores correspondientes. Las claves son cadenas de texto:

class C
  def initialize(x, y)
    @x, @y = x, y
  end
end

C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}

NOTA: Definido en active_support/core_ext/object/instance_variables.rb.

2.12.2 instance_variable_names

El método instance_variable_names devuelve un array. Cada nombre incluye el signo "@".

class C
  def initialize(x, y)
    @x, @y = x, y
  end
end

C.new(0, 1).instance_variable_names # => ["@x", "@y"]

NOTA: Definido en active_support/core_ext/object/instance_variables.rb.

2.13 Silenciar advertencias y excepciones

Los métodos silence_warnings y enable_warnings cambian el valor de $VERBOSE de acuerdo con la duración de su bloque, y lo restablecen después:

silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }

También es posible silenciar excepciones con suppress. Este método recibe un número arbitrario de clases de excepción. Si se produce una excepción durante la ejecución del bloque y es kind_of? cualquiera de los argumentos, suppress la captura y la devuelve en silencio. De lo contrario, la excepción no se captura:

# Si el usuario está bloqueado, el incremento se pierde, no es gran cosa.
suppress(ActiveRecord::StaleObjectError) do
  current_user.increment! :visits
end

NOTA: Definido en active_support/core_ext/kernel/reporting.rb.

2.14 in?

El predicado in? comprueba si un objeto está incluido en otro objeto. Se generará una excepción ArgumentError si el argumento pasado no responde a include?.

Ejemplos de in?:

1.in?([1, 2])        # => true
"lo".in?("hello")   # => true
25.in?(30..50)      # => false
1.in?(1)            # => ArgumentError

NOTA: Definido en active_support/core_ext/object/inclusion.rb.

3 Extensiones a Module

3.1 Atributos

3.1.1 alias_attribute

Los atributos del modelo tienen un lector, un escritor y un predicado. Puedes crear un alias para un atributo del modelo teniendo los tres métodos correspondientes definidos para ti utilizando alias_attribute. Como en otros métodos de aliasing, el nuevo nombre es el primer argumento y el antiguo nombre es el segundo (una mnemotecnia es que van en el mismo orden que si hicieras una asignación):

class User < ApplicationRecord
  # Puedes referirte a la columna de correo electrónico como "login".
  # Esto puede ser significativo para el código de autenticación.
  alias_attribute :login, :email
end

NOTA: Definido en active_support/core_ext/module/aliasing.rb.

3.1.2 Atributos internos

Cuando defines un atributo en una clase que está destinada a ser subclaseada, existe el riesgo de colisiones de nombres. Esto es especialmente importante para las bibliotecas.

Active Support define las macros attr_internal_reader, attr_internal_writer y attr_internal_accessor. Se comportan como sus contrapartes attr_* integradas en Ruby, excepto que nombran la variable de instancia subyacente de una manera que reduce las posibilidades de colisión.

La macro attr_internal es un sinónimo de attr_internal_accessor:

# biblioteca
class ThirdPartyLibrary::Crawler
  attr_internal :log_level
end

# código del cliente
class MyCrawler < ThirdPartyLibrary::Crawler
  attr_accessor :log_level
end

En el ejemplo anterior, podría suceder que :log_level no pertenezca a la interfaz pública de la biblioteca y solo se use para el desarrollo. El código del cliente, sin conocer el posible conflicto, subclases y define su propio :log_level. Gracias a attr_internal, no hay colisión.

Por defecto, la variable de instancia interna se nombra con un guión bajo inicial, @_log_level en el ejemplo anterior. Sin embargo, esto es configurable a través de Module.attr_internal_naming_format, puedes pasar cualquier cadena de formato similar a sprintf con un @ inicial y un %s en algún lugar, donde se colocará el nombre. El valor predeterminado es "@_%s".

Rails utiliza atributos internos en algunos lugares, por ejemplo, para las vistas:

module ActionView
  class Base
    attr_internal :captures
    attr_internal :request, :layout
    attr_internal :controller, :template
  end
end

NOTA: Definido en active_support/core_ext/module/attr_internal.rb.

3.1.3 Atributos de módulo

Las macros mattr_reader, mattr_writer y mattr_accessor son iguales que las macros cattr_* definidas para las clases. De hecho, las macros cattr_* son simplemente alias de las macros mattr_*. Consulta Atributos de clase. Por ejemplo, la API para el registro de Active Storage se genera con mattr_accessor:

module ActiveStorage
  mattr_accessor :logger
end

NOTA: Definido en active_support/core_ext/module/attribute_accessors.rb.

3.2 Padres

3.2.1 module_parent

El método module_parent en un módulo con nombre anidado devuelve el módulo que contiene su constante correspondiente:

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parent # => X::Y
M.module_parent       # => X::Y

Si el módulo es anónimo o pertenece al nivel superior, module_parent devuelve Object.

ADVERTENCIA: Ten en cuenta que en ese caso module_parent_name devuelve nil.

NOTA: Definido en active_support/core_ext/module/introspection.rb.

3.2.2 module_parent_name

El método module_parent_name en un módulo con nombre anidado devuelve el nombre completamente cualificado del módulo que contiene su constante correspondiente:

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parent_name # => "X::Y"
M.module_parent_name       # => "X::Y"

Para módulos de nivel superior o anónimos, module_parent_name devuelve nil.

ADVERTENCIA: Ten en cuenta que en ese caso module_parent devuelve Object.

NOTA: Definido en active_support/core_ext/module/introspection.rb.

3.2.3 module_parents

El método module_parents llama a module_parent en el receptor y hacia arriba hasta llegar a Object. La cadena se devuelve en un array, de abajo hacia arriba:

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parents # => [X::Y, X, Object]
M.module_parents       # => [X::Y, X, Object]

NOTA: Definido en active_support/core_ext/module/introspection.rb.

3.3 Anónimo

Un módulo puede tener o no un nombre:

module M
end
M.name # => "M"

N = Module.new
N.name # => "N"

Module.new.name # => nil

Puedes verificar si un módulo tiene un nombre con el predicado anonymous?:

module M
end
M.anonymous? # => false

Module.new.anonymous? # => true

Ten en cuenta que ser inaccesible no implica ser anónimo:

module M
end

m = Object.send(:remove_const, :M)

m.anonymous? # => false

aunque un módulo anónimo es inaccesible por definición.

NOTA: Definido en active_support/core_ext/module/anonymous.rb.

3.4 Delegación de Métodos

3.4.1 delegate

La macro delegate ofrece una forma sencilla de reenviar métodos.

Imaginemos que los usuarios en una aplicación tienen información de inicio de sesión en el modelo User, pero el nombre y otros datos en un modelo separado Profile:

class User < ApplicationRecord
  has_one :profile
end

Con esa configuración, se obtiene el nombre de un usuario a través de su perfil, user.profile.name, pero podría ser útil poder acceder directamente a dicho atributo:

class User < ApplicationRecord
  has_one :profile

  def name
    profile.name
  end
end

Esto es lo que hace delegate por ti:

class User < ApplicationRecord
  has_one :profile

  delegate :name, to: :profile
end

Es más corto y la intención es más clara.

El método debe ser público en el objetivo.

La macro delegate acepta varios métodos:

delegate :name, :age, :address, :twitter, to: :profile

Cuando se interpola en una cadena, la opción :to debe convertirse en una expresión que se evalúa en el objeto al que se delega el método. Normalmente una cadena o símbolo. Dicha expresión se evalúa en el contexto del receptor:

# delega a la constante Rails
delegate :logger, to: :Rails

# delega a la clase del receptor
delegate :table_name, to: :class

ADVERTENCIA: Si la opción :prefix es true, esto es menos genérico, ver más abajo.

Por defecto, si la delegación genera un NoMethodError y el objetivo es nil, se propaga la excepción. Puedes pedir que en su lugar se devuelva nil con la opción :allow_nil:

delegate :name, to: :profile, allow_nil: true

Con :allow_nil, la llamada user.name devuelve nil si el usuario no tiene perfil.

La opción :prefix agrega un prefijo al nombre del método generado. Esto puede ser útil, por ejemplo, para obtener un mejor nombre:

delegate :street, to: :address, prefix: true

El ejemplo anterior genera address_street en lugar de street. ADVERTENCIA: En este caso, el nombre del método generado está compuesto por el objeto de destino y los nombres de método de destino, por lo que la opción :to debe ser un nombre de método.

También se puede configurar un prefijo personalizado:

delegate :size, to: :attachment, prefix: :avatar

En el ejemplo anterior, la macro genera avatar_size en lugar de size.

La opción :private cambia el alcance de los métodos:

delegate :date_of_birth, to: :profile, private: true

Los métodos delegados son públicos de forma predeterminada. Pase private: true para cambiar eso.

NOTA: Definido en active_support/core_ext/module/delegation.rb

3.4.2 delegate_missing_to

Imagina que quieres delegar todo lo que falta en el objeto User al objeto Profile. La macro delegate_missing_to te permite implementar esto fácilmente:

class User < ApplicationRecord
  has_one :profile

  delegate_missing_to :profile
end

El objetivo puede ser cualquier cosa llamable dentro del objeto, como variables de instancia, métodos, constantes, etc. Solo se delegan los métodos públicos del objetivo.

NOTA: Definido en active_support/core_ext/module/delegation.rb.

3.5 Redefinición de métodos

Hay casos en los que necesitas definir un método con define_method, pero no sabes si ya existe un método con ese nombre. Si existe, se emite una advertencia si están habilitadas. No es gran cosa, pero tampoco es limpio.

El método redefine_method evita esa advertencia potencial, eliminando el método existente antes si es necesario.

También puedes usar silence_redefinition_of_method si necesitas definir el método de reemplazo tú mismo (porque estás usando delegate, por ejemplo).

NOTA: Definido en active_support/core_ext/module/redefine_method.rb.

4 Extensiones a Class

4.1 Atributos de clase

4.1.1 class_attribute

El método class_attribute declara uno o más atributos de clase heredables que se pueden anular en cualquier nivel de la jerarquía.

class A
  class_attribute :x
end

class B < A; end

class C < B; end

A.x = :a
B.x # => :a
C.x # => :a

B.x = :b
A.x # => :a
C.x # => :b

C.x = :c
A.x # => :a
B.x # => :b

Por ejemplo, ActionMailer::Base define:

class_attribute :default_params
self.default_params = {
  mime_version: "1.0",
  charset: "UTF-8",
  content_type: "text/plain",
  parts_order: [ "text/plain", "text/enriched", "text/html" ]
}.freeze

También se pueden acceder y anular en el nivel de instancia.

A.x = 1

a1 = A.new
a2 = A.new
a2.x = 2

a1.x # => 1, proviene de A
a2.x # => 2, anulado en a2

La generación del método de instancia de escritura se puede evitar configurando la opción :instance_writer en false.

module ActiveRecord
  class Base
    class_attribute :table_name_prefix, instance_writer: false, default: "my"
  end
end

Un modelo puede encontrar útil esa opción como una forma de evitar la asignación masiva de establecer el atributo.

La generación del método de instancia de lectura se puede evitar configurando la opción :instance_reader en false.

class A
  class_attribute :x, instance_reader: false
end

A.new.x = 1
A.new.x # NoMethodError

Para mayor comodidad, class_attribute también define un predicado de instancia que es la doble negación de lo que devuelve el lector de instancia. En los ejemplos anteriores se llamaría x?.

Cuando :instance_reader es false, el predicado de instancia devuelve un NoMethodError al igual que el método lector.

Si no deseas el predicado de instancia, pasa instance_predicate: false y no se definirá.

NOTA: Definido en active_support/core_ext/class/attribute.rb.

4.1.2 cattr_reader, cattr_writer y cattr_accessor

Las macros cattr_reader, cattr_writer y cattr_accessor son análogas a sus contrapartes attr_* pero para clases. Inicializan una variable de clase a nil a menos que ya exista y generan los métodos de clase correspondientes para acceder a ella:

class MysqlAdapter < AbstractAdapter
  # Genera métodos de clase para acceder a @@emulate_booleans.
  cattr_accessor :emulate_booleans
end

Además, puedes pasar un bloque a cattr_* para configurar el atributo con un valor predeterminado:

class MysqlAdapter < AbstractAdapter
  # Genera métodos de clase para acceder a @@emulate_booleans con un valor predeterminado de true.
  cattr_accessor :emulate_booleans, default: true
end

Los métodos de instancia también se crean por conveniencia, son solo proxies para el atributo de clase. Por lo tanto, las instancias pueden cambiar el atributo de clase, pero no pueden anularlo como sucede con class_attribute (ver arriba). Por ejemplo, dado

module ActionView
  class Base
    cattr_accessor :field_error_proc, default: Proc.new { ... }
  end
end

podemos acceder a field_error_proc en las vistas.

La generación del método de instancia lector se puede evitar configurando :instance_reader en false y la generación del método de instancia escritor se puede evitar configurando :instance_writer en false. La generación de ambos métodos se puede evitar configurando :instance_accessor en false. En todos los casos, el valor debe ser exactamente false y no cualquier valor falso.

module A
  class B
    # No se genera el lector de instancia first_name.
    cattr_accessor :first_name, instance_reader: false
    # No se genera el escritor de instancia last_name=.
    cattr_accessor :last_name, instance_writer: false
    # No se genera el lector de instancia surname ni el escritor surname=.
    cattr_accessor :surname, instance_accessor: false
  end
end

Un modelo puede encontrar útil configurar :instance_accessor en false como una forma de evitar la asignación masiva de atributos.

NOTA: Definido en active_support/core_ext/module/attribute_accessors.rb.

4.2 Subclases y descendientes

4.2.1 subclasses

El método subclasses devuelve las subclases del receptor:

class C; end
C.subclasses # => []

class B < C; end
C.subclasses # => [B]

class A < B; end
C.subclasses # => [B]

class D < C; end
C.subclasses # => [B, D]

El orden en el que se devuelven estas clases no está especificado.

NOTA: Definido en active_support/core_ext/class/subclasses.rb.

4.2.2 descendants

El método descendants devuelve todas las clases que son < que su receptor:

class C; end
C.descendants # => []

class B < C; end
C.descendants # => [B]

class A < B; end
C.descendants # => [B, A]

class D < C; end
C.descendants # => [B, A, D]

El orden en el que se devuelven estas clases no está especificado.

NOTA: Definido en active_support/core_ext/class/subclasses.rb.

5 Extensiones a String

5.1 Seguridad de la salida

5.1.1 Motivación

Insertar datos en plantillas HTML requiere cuidado adicional. Por ejemplo, no se puede simplemente interpolar @review.title literalmente en una página HTML. Por un lado, si el título de la reseña es "¡Flanagan & Matz rules!", la salida no será válida porque un ampersand debe escaparse como "&amp;". Además, dependiendo de la aplicación, esto puede ser un gran agujero de seguridad porque los usuarios pueden inyectar HTML malicioso estableciendo un título de reseña personalizado. Consulta la sección sobre scripting entre sitios en la guía de seguridad para obtener más información sobre los riesgos.

5.1.2 Cadenas seguras

Active Support tiene el concepto de cadenas (html) seguras. Una cadena segura es aquella que se marca como insertable en HTML tal cual. Se considera confiable, sin importar si se ha escapado o no.

Las cadenas se consideran inseguras de forma predeterminada:

"".html_safe? # => false

Puedes obtener una cadena segura a partir de una cadena dada con el método html_safe:

s = "".html_safe
s.html_safe? # => true

Es importante entender que html_safe no realiza ninguna escapada en absoluto, es solo una afirmación:

s = "<script>...</script>".html_safe
s.html_safe? # => true
s            # => "<script>...</script>"

Es tu responsabilidad asegurarte de que llamar a html_safe en una cadena en particular sea seguro.

Si agregas contenido a una cadena segura, ya sea directamente con concat/<<, o con +, el resultado es una cadena segura. Los argumentos inseguros se escapan:

"".html_safe + "<" # => "&lt;"

Los argumentos seguros se agregan directamente:

"".html_safe + "<".html_safe # => "<"

Estos métodos no deben usarse en vistas normales. Los valores inseguros se escapan automáticamente:

<%= @review.title %> <%# bien, escapado si es necesario %>

Para insertar algo textualmente, utiliza el ayudante raw en lugar de llamar a html_safe:

<%= raw @cms.current_template %> <%# inserta @cms.current_template tal cual %>

o, de manera equivalente, utiliza <%==:

<%== @cms.current_template %> <%# inserta @cms.current_template tal cual %>

El ayudante raw llama a html_safe por ti:

def raw(stringish)
  stringish.to_s.html_safe
end

NOTA: Definido en active_support/core_ext/string/output_safety.rb.

5.1.3 Transformación

Como regla general, excepto quizás para la concatenación como se explicó anteriormente, cualquier método que pueda cambiar una cadena te devuelve una cadena insegura. Estos son downcase, gsub, strip, chomp, underscore, etc.

En el caso de las transformaciones en su lugar, como gsub!, el receptor en sí mismo se vuelve inseguro.

La parte de seguridad se pierde siempre, sin importar si la transformación realmente cambió algo.

5.1.4 Conversión y coerción

Llamar a to_s en una cadena segura devuelve una cadena segura, pero la coerción con to_str devuelve una cadena insegura.

5.1.5 Copia

Llamar a dup o clone en cadenas seguras produce cadenas seguras.

5.2 remove

El método remove eliminará todas las ocurrencias del patrón:

"Hello World".remove(/Hello /) # => "World"

También existe la versión destructiva String#remove!.

NOTA: Definido en active_support/core_ext/string/filters.rb.

5.3 squish

El método squish elimina los espacios en blanco al principio y al final, y sustituye las secuencias de espacios en blanco por un solo espacio:

" \n  foo\n\r \t bar \n".squish # => "foo bar"

También existe la versión destructiva String#squish!.

Ten en cuenta que maneja tanto espacios en blanco ASCII como Unicode.

NOTA: Definido en active_support/core_ext/string/filters.rb.

5.4 truncate

El método truncate devuelve una copia de su receptor truncado después de una longitud dada:

"Oh dear! Oh dear! I shall be late!".truncate(20)
# => "Oh dear! Oh dear!..."

Elipsis se puede personalizar con la opción :omission:

"Oh dear! Oh dear! I shall be late!".truncate(20, omission: '&hellip;')
# => "Oh dear! Oh &hellip;"

Ten en cuenta en particular que la truncación tiene en cuenta la longitud de la cadena de omisión.

Pasa un :separator para truncar la cadena en una ruptura natural:

"Oh dear! Oh dear! I shall be late!".truncate(18)
# => "Oh dear! Oh dea..."
"Oh dear! Oh dear! I shall be late!".truncate(18, separator: ' ')
# => "Oh dear! Oh..."

La opción :separator puede ser una expresión regular:

"Oh dear! Oh dear! I shall be late!".truncate(18, separator: /\s/)
# => "Oh dear! Oh..."

En los ejemplos anteriores, "dear" se corta primero, pero luego :separator lo evita.

NOTA: Definido en active_support/core_ext/string/filters.rb.

5.5 truncate_bytes

El método truncate_bytes devuelve una copia de su receptor truncado a un máximo de bytesize bytes:

"👍👍👍👍".truncate_bytes(15)
# => "👍👍👍…"

Elipsis se puede personalizar con la opción :omission:

"👍👍👍👍".truncate_bytes(15, omission: "🖖")
# => "👍👍🖖"

NOTA: Definido en active_support/core_ext/string/filters.rb.

5.6 truncate_words

El método truncate_words devuelve una copia de su receptor truncado después de un número dado de palabras:

"Oh dear! Oh dear! I shall be late!".truncate_words(4)
# => "Oh dear! Oh dear!..."

Elipsis se puede personalizar con la opción :omission:

"Oh dear! Oh dear! I shall be late!".truncate_words(4, omission: '&hellip;')
# => "Oh dear! Oh dear!&hellip;"

Pasa un :separator para truncar la cadena en una ruptura natural:

"Oh dear! Oh dear! I shall be late!".truncate_words(3, separator: '!')
# => "Oh dear! Oh dear! I shall be late..."

La opción :separator puede ser una expresión regular:

"Oh dear! Oh dear! I shall be late!".truncate_words(4, separator: /\s/)
# => "Oh dear! Oh dear!..."

NOTA: Definido en active_support/core_ext/string/filters.rb.

5.7 inquiry

El método inquiry convierte una cadena en un objeto StringInquirer, lo que hace que las comprobaciones de igualdad sean más bonitas.

"production".inquiry.production? # => true
"active".inquiry.inactive?       # => false

NOTA: Definido en active_support/core_ext/string/inquiry.rb.

5.8 starts_with? y ends_with?

Active Support define alias de tercera persona de String#start_with? y String#end_with?:

"foo".starts_with?("f") # => true
"foo".ends_with?("o")   # => true

NOTA: Definido en active_support/core_ext/string/starts_ends_with.rb.

5.9 strip_heredoc

El método strip_heredoc elimina la sangría en los heredocs.

Por ejemplo, en

if options[:usage]
  puts <<-USAGE.strip_heredoc
    This command does such and such.

    Supported options are:
      -h         This message
      ...
  USAGE
end

el usuario vería el mensaje de uso alineado con el margen izquierdo.

Técnicamente, busca la línea con menos sangría en toda la cadena y elimina esa cantidad de espacios en blanco al principio.

NOTA: Definido en active_support/core_ext/string/strip.rb.

5.10 indent

El método indent sangra las líneas en el receptor:

<<EOS.indent(2)
def some_method
  some_code
end
EOS
# =>
  def some_method
    some_code
  end

El segundo argumento, indent_string, especifica qué cadena de sangría usar. El valor predeterminado es nil, lo que indica al método que haga una suposición educada mirando la primera línea sangrada y que use un espacio si no hay ninguna.

"  foo".indent(2)        # => "    foo"
"foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
"foo".indent(2, "\t")    # => "\t\tfoo"

Si bien indent_string suele ser un espacio o una tabulación, puede ser cualquier cadena.

El tercer argumento, indent_empty_lines, es una bandera que indica si las líneas vacías deben sangrarse. El valor predeterminado es falso.

"foo\n\nbar".indent(2)            # => "  foo\n\n  bar"
"foo\n\nbar".indent(2, nil, true) # => "  foo\n  \n  bar"

El método indent! realiza la sangría en su lugar.

NOTA: Definido en active_support/core_ext/string/indent.rb.

5.11 Acceso

5.11.1 at(position)

El método at devuelve el carácter de la cadena en la posición position:

"hello".at(0)  # => "h"
"hello".at(4)  # => "o"
"hello".at(-1) # => "o"
"hello".at(10) # => nil

NOTA: Definido en active_support/core_ext/string/access.rb.

5.11.2 from(position)

El método from devuelve la subcadena de la cadena que comienza en la posición position:

"hello".from(0)  # => "hello"
"hello".from(2)  # => "llo"
"hello".from(-2) # => "lo"
"hello".from(10) # => nil

NOTA: Definido en active_support/core_ext/string/access.rb.

5.11.3 to(position)

El método to devuelve la subcadena de la cadena hasta la posición position:

"hello".to(0)  # => "h"
"hello".to(2)  # => "hel"
"hello".to(-2) # => "hell"
"hello".to(10) # => "hello"

NOTA: Definido en active_support/core_ext/string/access.rb.

5.11.4 first(limit = 1)

El método first devuelve una subcadena que contiene los primeros limit caracteres de la cadena.

La llamada str.first(n) es equivalente a str.to(n-1) si n > 0, y devuelve una cadena vacía para n == 0.

NOTA: Definido en active_support/core_ext/string/access.rb.

5.11.5 last(limit = 1)

El método last devuelve una subcadena que contiene los últimos limit caracteres de la cadena.

La llamada str.last(n) es equivalente a str.from(-n) si n > 0, y devuelve una cadena vacía para n == 0.

NOTA: Definido en active_support/core_ext/string/access.rb.

5.12 Inflections

5.12.1 pluralize

El método pluralize devuelve el plural de su receptor:

"table".pluralize     # => "tables"
"ruby".pluralize      # => "rubies"
"equipment".pluralize # => "equipment"

Como muestra el ejemplo anterior, Active Support conoce algunos plurales irregulares y sustantivos incontables. Las reglas incorporadas se pueden ampliar en config/initializers/inflections.rb. Este archivo se genera de forma predeterminada con el comando rails new y tiene instrucciones en comentarios.

pluralize también puede tomar un parámetro opcional count. Si count == 1, se devolverá la forma singular. Para cualquier otro valor de count, se devolverá la forma plural:

"dude".pluralize(0) # => "dudes"
"dude".pluralize(1) # => "dude"
"dude".pluralize(2) # => "dudes"

Active Record utiliza este método para calcular el nombre de tabla predeterminado que corresponde a un modelo:

# active_record/model_schema.rb
def undecorated_table_name(model_name)
  table_name = model_name.to_s.demodulize.underscore
  pluralize_table_names ? table_name.pluralize : table_name
end

NOTA: Definido en active_support/core_ext/string/inflections.rb.

5.12.2 singularize

El método singularize es el inverso de pluralize:

"tables".singularize    # => "table"
"rubies".singularize    # => "ruby"
"equipment".singularize # => "equipment"

Las asociaciones calculan el nombre de la clase asociada predeterminada correspondiente utilizando este método:

# active_record/reflection.rb
def derive_class_name
  class_name = name.to_s.camelize
  class_name = class_name.singularize if collection?
  class_name
end

NOTA: Definido en active_support/core_ext/string/inflections.rb.

5.12.3 camelize

El método camelize devuelve su receptor en formato camel case:

"product".camelize    # => "Product"
"admin_user".camelize # => "AdminUser"

Como regla general, puedes pensar en este método como aquel que transforma rutas en nombres de clases o módulos de Ruby, donde las barras diagonales separan los espacios de nombres:

"backoffice/session".camelize # => "Backoffice::Session"

Por ejemplo, Action Pack utiliza este método para cargar la clase que proporciona un determinado almacenamiento de sesión:

# action_controller/metal/session_management.rb
def session_store=(store)
  @@session_store = store.is_a?(Symbol) ?
    ActionDispatch::Session.const_get(store.to_s.camelize) :
    store
end

camelize acepta un argumento opcional, que puede ser :upper (predeterminado) o :lower. Con este último, la primera letra se convierte en minúscula:

"visual_effect".camelize(:lower) # => "visualEffect"

Esto puede ser útil para calcular nombres de métodos en un lenguaje que sigue esa convención, como JavaScript.

Como regla general, puedes pensar en camelize como el inverso de underscore, aunque hay casos en los que esto no se cumple: "SSLError".underscore.camelize devuelve "SslError". Para admitir casos como este, Active Support te permite especificar acrónimos en config/initializers/inflections.rb:

ActiveSupport::Inflector.inflections do |inflect|
  inflect.acronym 'SSL'
end

"SSLError".underscore.camelize # => "SSLError"

camelize está aliasado como camelcase.

NOTA: Definido en active_support/core_ext/string/inflections.rb.

5.12.4 underscore

El método underscore hace lo contrario, convierte de camel case a rutas:

"Product".underscore   # => "product"
"AdminUser".underscore # => "admin_user"

También convierte "::" en "/":

"Backoffice::Session".underscore # => "backoffice/session"

y entiende cadenas que comienzan con minúscula:

"visualEffect".underscore # => "visual_effect"

underscore no acepta argumentos.

Rails utiliza underscore para obtener un nombre en minúscula para las clases de controladores:

# actionpack/lib/abstract_controller/base.rb
def controller_path
  @controller_path ||= name.delete_suffix("Controller").underscore
end

Por ejemplo, ese valor es el que obtienes en params[:controller].

Como regla general, puedes pensar en underscore como el inverso de camelize, aunque hay casos en los que esto no se cumple. Por ejemplo, "SSLError".underscore.camelize devuelve "SslError".

NOTA: Definido en active_support/core_ext/string/inflections.rb.

5.12.5 titleize

El método titleize capitaliza las palabras en el receptor:

"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize     # => "Fermat's Enigma"

titleize está aliasado como titlecase.

NOTA: Definido en active_support/core_ext/string/inflections.rb.

5.12.6 dasherize

El método dasherize reemplaza los guiones bajos en el receptor por guiones:

"name".dasherize         # => "name"
"contact_data".dasherize # => "contact-data"

El serializador XML de los modelos utiliza este método para convertir los nombres de los nodos en formato guionizado:

# active_model/serializers/xml.rb
def reformat_name(name)
  name = name.camelize if camelize?
  dasherize? ? name.dasherize : name
end

NOTA: Definido en active_support/core_ext/string/inflections.rb.

5.12.7 demodulize

Dada una cadena con un nombre de constante calificado, demodulize devuelve el nombre de la constante en sí, es decir, la parte más a la derecha:

"Product".demodulize                        # => "Product"
"Backoffice::UsersController".demodulize    # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
"::Inflections".demodulize                  # => "Inflections"
"".demodulize                               # => ""

Active Record, por ejemplo, utiliza este método para calcular el nombre de una columna de caché de contador:

# active_record/reflection.rb
def counter_cache_column
  if options[:counter_cache] == true
    "#{active_record.name.demodulize.underscore.pluralize}_count"
  elsif options[:counter_cache]
    options[:counter_cache]
  end
end

NOTA: Definido en active_support/core_ext/string/inflections.rb.

5.12.8 deconstantize

Dada una cadena con una expresión de referencia a una constante calificada, deconstantize elimina el segmento más a la derecha, dejando generalmente el nombre del contenedor de la constante:

"Product".deconstantize                        # => ""
"Backoffice::UsersController".deconstantize    # => "Backoffice"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"

NOTA: Definido en active_support/core_ext/string/inflections.rb.

5.12.9 parameterize

El método parameterize normaliza su receptor de una manera que se puede utilizar en URLs legibles.

"John Smith".parameterize # => "john-smith"
"Kurt Gödel".parameterize # => "kurt-godel"

Para preservar el caso de la cadena, establece el argumento preserve_case en true. Por defecto, preserve_case se establece en false.

"John Smith".parameterize(preserve_case: true) # => "John-Smith"
"Kurt Gödel".parameterize(preserve_case: true) # => "Kurt-Godel"

Para utilizar un separador personalizado, sobrescribe el argumento separator. ruby "Employee Salary".downcase_first # => "employee Salary" "".downcase_first # => ""

NOTA: Definido en active_support/core_ext/numeric/conversions.rb.

6 Extensiones a Integer

6.1 multiple_of?

El método multiple_of? verifica si un entero es múltiplo del argumento:

2.multiple_of?(1) # => true
1.multiple_of?(2) # => false

NOTA: Definido en active_support/core_ext/integer/multiple.rb.

6.2 ordinal

El método ordinal devuelve el sufijo ordinal correspondiente al entero receptor:

1.ordinal    # => "º"
2.ordinal    # => "º"
53.ordinal   # => "º"
2009.ordinal # => "º"
-21.ordinal  # => "º"
-134.ordinal # => "º"

NOTA: Definido en active_support/core_ext/integer/inflections.rb.

6.3 ordinalize

El método ordinalize devuelve el string ordinal correspondiente al entero receptor. En comparación, el método ordinal devuelve solo el sufijo.

1.ordinalize    # => "1º"
2.ordinalize    # => "2º"
53.ordinalize   # => "53º"
2009.ordinalize # => "2009º"
-21.ordinalize  # => "-21º"
-134.ordinalize # => "-134º"

NOTA: Definido en active_support/core_ext/integer/inflections.rb.

6.4 Time

Los siguientes métodos:

permiten declaraciones y cálculos de tiempo, como 4.months + 5.years. Sus valores de retorno también se pueden sumar o restar a objetos de tipo Time.

Estos métodos se pueden combinar con from_now, ago, etc., para cálculos precisos de fechas. Por ejemplo:

# equivalente a Time.current.advance(months: 1)
1.month.from_now

# equivalente a Time.current.advance(years: 2)
2.years.from_now

# equivalente a Time.current.advance(months: 4, years: 5)
(4.months + 5.years).from_now

ADVERTENCIA. Para otras duraciones, consulte las extensiones de tiempo a Numeric.

NOTA: Definido en active_support/core_ext/integer/time.rb.

7 Extensiones a BigDecimal

7.1 to_s

El método to_s proporciona un especificador predeterminado de "F". Esto significa que una llamada simple a to_s dará como resultado una representación de punto flotante en lugar de notación científica:

BigDecimal(5.00, 6).to_s       # => "5.0"

También se admite la notación científica:

BigDecimal(5.00, 6).to_s("e")  # => "0.5E1"

8 Extensiones a Enumerable

8.1 sum

El método sum suma los elementos de un enumerable:

[1, 2, 3].sum # => 6
(1..100).sum  # => 5050

La suma asume que los elementos responden a +:

[[1, 2], [2, 3], [3, 4]].sum    # => [1, 2, 2, 3, 3, 4]
%w(foo bar baz).sum             # => "foobarbaz"
{ a: 1, b: 2, c: 3 }.sum          # => [:a, 1, :b, 2, :c, 3]

La suma de una colección vacía es cero por defecto, pero esto se puede personalizar:

[].sum    # => 0
[].sum(1) # => 1

Si se proporciona un bloque, sum se convierte en un iterador que devuelve los elementos de la colección y suma los valores devueltos:

(1..5).sum { |n| n * 2 } # => 30
[2, 4, 6, 8, 10].sum    # => 30

La suma de un receptor vacío también se puede personalizar en esta forma:

[].sum(1) { |n| n**3 } # => 1

NOTA: Definido en active_support/core_ext/enumerable.rb.

8.2 index_by

El método index_by genera un hash con los elementos de un enumerable indexados por alguna clave.

Itera a través de la colección y pasa cada elemento a un bloque. El elemento se indexará por el valor devuelto por el bloque:

invoices.index_by(&:number)
# => {'2009-032' => <Invoice ...>, '2009-008' => <Invoice ...>, ...}

ADVERTENCIA. Las claves normalmente deben ser únicas. Si el bloque devuelve el mismo valor para diferentes elementos, no se construirá una colección para esa clave. El último elemento ganará.

NOTA: Definido en active_support/core_ext/enumerable.rb.

8.3 index_with

El método index_with genera un hash con los elementos de un enumerable como claves. El valor es un valor predeterminado pasado o devuelto en un bloque.

post = Post.new(title: "hey there", body: "what's up?")

%i( title body ).index_with { |attr_name| post.public_send(attr_name) }
# => { title: "hey there", body: "what's up?" }

WEEKDAYS.index_with(Interval.all_day)
# => { monday: [ 0, 1440 ], … }

NOTA: Definido en active_support/core_ext/enumerable.rb.

8.4 many?

El método many? es una forma abreviada de collection.size > 1:

<% if pages.many? %>
  <%= pagination_links %>
<% end %>

Si se proporciona un bloque opcional, many? solo tiene en cuenta aquellos elementos que devuelven verdadero:

@see_more = videos.many? { |video| video.category == params[:category] }

NOTA: Definido en active_support/core_ext/enumerable.rb.

8.5 exclude?

El predicado exclude? prueba si un objeto dado no pertenece a la colección. Es la negación de include? incorporado:

to_visit << node if visited.exclude?(node)

NOTA: Definido en active_support/core_ext/enumerable.rb.

8.6 including

El método including devuelve un nuevo enumerable que incluye los elementos pasados:

[ 1, 2, 3 ].including(4, 5)                    # => [ 1, 2, 3, 4, 5 ]
["David", "Rafael"].including %w[ Aaron Todd ] # => ["David", "Rafael", "Aaron", "Todd"]

NOTA: Definido en active_support/core_ext/enumerable.rb.

8.7 excluding

El método excluding devuelve una copia de un enumerable con los elementos especificados eliminados:

["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]

excluding es un alias de without.

NOTA: Definido en active_support/core_ext/enumerable.rb.

8.8 pluck

El método pluck extrae la clave dada de cada elemento:

[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) # => ["David", "Rafael", "Aaron"]
[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) # => [[1, "David"], [2, "Rafael"]]

NOTA: Definido en active_support/core_ext/enumerable.rb.

8.9 pick

El método pick extrae la clave dada del primer elemento:

[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name) # => "David"
[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name) # => [1, "David"]

NOTA: Definido en active_support/core_ext/enumerable.rb.

9 Extensiones para Array

9.1 Acceso

Active Support amplía la API de los arrays para facilitar ciertas formas de acceso. Por ejemplo, to devuelve el subarray de elementos hasta el índice pasado:

%w(a b c d).to(2) # => ["a", "b", "c"]
[].to(7)          # => []

De manera similar, from devuelve la cola desde el elemento en el índice pasado hasta el final. Si el índice es mayor que la longitud del array, devuelve un array vacío.

%w(a b c d).from(2)  # => ["c", "d"]
%w(a b c d).from(10) # => []
[].from(0)           # => []

El método including devuelve un nuevo array que incluye los elementos pasados:

[ 1, 2, 3 ].including(4, 5)          # => [ 1, 2, 3, 4, 5 ]
[ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ]

El método excluding devuelve una copia del Array excluyendo los elementos especificados. Esta es una optimización de Enumerable#excluding que utiliza Array#- en lugar de Array#reject por razones de rendimiento.

["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]
[ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ])                  # => [ [ 0, 1 ] ]

Los métodos second, third, fourth y fifth devuelven el elemento correspondiente, al igual que second_to_last y third_to_last (first y last son incorporados). Gracias a la sabiduría social y la constructividad positiva en todas partes, también está disponible forty_two.

%w(a b c d).third # => "c"
%w(a b c d).fifth # => nil

NOTA: Definido en active_support/core_ext/array/access.rb.

9.2 Extracción

El método extract! elimina y devuelve los elementos para los cuales el bloque devuelve un valor verdadero. Si no se proporciona un bloque, en su lugar se devuelve un Enumerator.

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]

NOTA: Definido en active_support/core_ext/array/extract.rb.

9.3 Extracción de opciones

Cuando el último argumento en una llamada a un método es un hash, excepto tal vez por un argumento &block, Ruby permite omitir los corchetes:

User.exists?(email: params[:email])

Este azúcar sintáctico se utiliza mucho en Rails para evitar argumentos posicionales cuando habría demasiados, ofreciendo en su lugar interfaces que emulan parámetros nombrados. En particular, es muy idiomático usar un hash al final para las opciones.

Sin embargo, si un método espera un número variable de argumentos y utiliza * en su declaración, ese hash de opciones termina siendo un elemento del array de argumentos, donde pierde su función.

En esos casos, puedes darle un tratamiento distinguido a un hash de opciones con extract_options!. Este método verifica el tipo del último elemento de un array. Si es un hash, lo extrae y lo devuelve, de lo contrario devuelve un hash vacío.

Veamos por ejemplo la definición de la macro del controlador caches_action:

def caches_action(*actions)
  return unless cache_configured?
  options = actions.extract_options!
  # ...
end

Este método recibe un número arbitrario de nombres de acciones y un hash opcional de opciones como último argumento. Con la llamada a extract_options! obtienes el hash de opciones y lo eliminas de actions de una manera simple y explícita.

NOTA: Definido en active_support/core_ext/array/extract_options.rb.

9.4 Conversiones

9.4.1 to_sentence

El método to_sentence convierte un array en una cadena que enumera sus elementos:

%w().to_sentence                # => ""
%w(Earth).to_sentence           # => "Earth"
%w(Earth Wind).to_sentence      # => "Earth and Wind"
%w(Earth Wind Fire).to_sentence # => "Earth, Wind, and Fire"

Este método acepta tres opciones:

  • :two_words_connector: Lo que se utiliza para arrays de longitud 2. El valor predeterminado es " and ".
  • :words_connector: Lo que se utiliza para unir los elementos de arrays con 3 o más elementos, excepto los dos últimos. El valor predeterminado es ", ".
  • :last_word_connector: Lo que se utiliza para unir los últimos elementos de un array con 3 o más elementos. El valor predeterminado es ", and ".

Los valores predeterminados para estas opciones se pueden localizar, sus claves son:

Opción Clave I18n
:two_words_connector support.array.two_words_connector
:words_connector support.array.words_connector
:last_word_connector support.array.last_word_connector

NOTA: Definido en active_support/core_ext/array/conversions.rb.

9.4.2 to_fs

El método to_fs actúa como to_s de forma predeterminada.

Sin embargo, si el array contiene elementos que responden a id, se puede pasar el símbolo :db como argumento. Esto se utiliza típicamente con colecciones de objetos Active Record. Las cadenas devueltas son:

[].to_fs(:db)            # => "null"
[user].to_fs(:db)        # => "8456"
invoice.lines.to_fs(:db) # => "23,567,556,12"

Se supone que los enteros en el ejemplo anterior provienen de las respectivas llamadas a id.

NOTA: Definido en active_support/core_ext/array/conversions.rb.

9.4.3 to_xml

El método to_xml devuelve una cadena que contiene una representación XML de su receptor:

Contributor.limit(2).order(:rank).to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors type="array">
#   <contributor>
#     <id type="integer">4356</id>
#     <name>Jeremy Kemper</name>
#     <rank type="integer">1</rank>
#     <url-id>jeremy-kemper</url-id>
#   </contributor>
#   <contributor>
#     <id type="integer">4404</id>
#     <name>David Heinemeier Hansson</name>
#     <rank type="integer">2</rank>
#     <url-id>david-heinemeier-hansson</url-id>
#   </contributor>
# </contributors>

Para hacer esto, envía to_xml a cada elemento por turno y recopila los resultados bajo un nodo raíz. Todos los elementos deben responder a to_xml, de lo contrario se genera una excepción.

De forma predeterminada, el nombre del elemento raíz es el plural en minúsculas y con guiones del nombre de la clase del primer elemento, siempre que el resto de los elementos pertenezcan a ese tipo (comprobado con is_a?) y no sean hashes. En el ejemplo anterior, eso es "contributors".

Si hay algún elemento que no pertenece al tipo del primero, el nodo raíz se convierte en "objects": ```ruby [Contributor.first, Commit.first].to_xml

=>

<?xml version="1.0" encoding="UTF-8"?>

4583

Aaron Batalion

53

aaron-batalion

Joshua Peek

2009-09-02T16:44:36Z

origin/master

2009-09-02T16:44:36Z

Joshua Peek

190316

false

Kill AMo observing wrap_with_notifications since ARes was only using it

723a47bfb3708f968821bc969a9a3fc873a3ed58


Si el receptor es un array de hashes, el elemento raíz por defecto también es "objects":

```ruby
[{ a: 1, b: 2 }, { c: 3 }].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <objects type="array">
#   <object>
#     <b type="integer">2</b>
#     <a type="integer">1</a>
#   </object>
#   <object>
#     <c type="integer">3</c>
#   </object>
# </objects>

ADVERTENCIA. Si la colección está vacía, el elemento raíz por defecto es "nil-classes". Esto puede ser confuso, por ejemplo, el elemento raíz de la lista de contribuyentes anterior no sería "contributors" si la colección estuviera vacía, sino "nil-classes". Puede utilizar la opción :root para asegurar un elemento raíz consistente.

El nombre de los nodos hijos es, por defecto, el nombre del nodo raíz en singular. En los ejemplos anteriores hemos visto "contributor" y "object". La opción :children te permite establecer estos nombres de nodos.

El constructor XML por defecto es una nueva instancia de Builder::XmlMarkup. Puedes configurar tu propio constructor a través de la opción :builder. El método también acepta opciones como :dasherize y otros, que se envían al constructor:

Contributor.limit(2).order(:rank).to_xml(skip_types: true)
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors>
#   <contributor>
#     <id>4356</id>
#     <name>Jeremy Kemper</name>
#     <rank>1</rank>
#     <url-id>jeremy-kemper</url-id>
#   </contributor>
#   <contributor>
#     <id>4404</id>
#     <name>David Heinemeier Hansson</name>
#     <rank>2</rank>
#     <url-id>david-heinemeier-hansson</url-id>
#   </contributor>
# </contributors>

NOTA: Definido en active_support/core_ext/array/conversions.rb.

9.5 Envoltura

El método Array.wrap envuelve su argumento en un array a menos que ya sea un array (o similar a un array).

Específicamente:

  • Si el argumento es nil, se devuelve un array vacío.
  • De lo contrario, si el argumento responde a to_ary, se invoca, y si el valor de to_ary no es nil, se devuelve.
  • De lo contrario, se devuelve un array con el argumento como su único elemento.
Array.wrap(nil)       # => []
Array.wrap([1, 2, 3]) # => [1, 2, 3]
Array.wrap(0)         # => [0]

Este método es similar en propósito a Kernel#Array, pero hay algunas diferencias:

  • Si el argumento responde a to_ary, se invoca el método. Kernel#Array pasa a intentar to_a si el valor devuelto es nil, pero Array.wrap devuelve de inmediato un array con el argumento como su único elemento.
  • Si el valor devuelto de to_ary no es nil ni un objeto Array, Kernel#Array lanza una excepción, mientras que Array.wrap no lo hace, simplemente devuelve el valor.
  • No llama a to_a en el argumento, si el argumento no responde a to_ary, devuelve un array con el argumento como su único elemento.

El último punto es particularmente importante al comparar algunos enumerables:

Array.wrap(foo: :bar) # => [{:foo=>:bar}]
Array(foo: :bar)      # => [[:foo, :bar]]

También hay un idioma relacionado que utiliza el operador splat:

[*object]

NOTA: Definido en active_support/core_ext/array/wrap.rb.

9.6 Duplicación

El método Array#deep_dup duplica el array y todos los objetos en su interior de forma recursiva con el método Object#deep_dup de Active Support. Funciona como Array#map, enviando el método deep_dup a cada objeto en su interior.

array = [1, [2, 3]]
dup = array.deep_dup
dup[1][2] = 4
array[1][2] == nil   # => true

NOTA: Definido en active_support/core_ext/object/deep_dup.rb.

9.7 Agrupamiento

9.7.1 in_groups_of(number, fill_with = nil)

El método in_groups_of divide un array en grupos consecutivos de un tamaño determinado. Devuelve un array con los grupos:

[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]]

o los devuelve a su vez si se pasa un bloque:

<% sample.in_groups_of(3) do |a, b, c| %>
  <tr>
    <td><%= a %></td>
    <td><%= b %></td>
    <td><%= c %></td>
  </tr>
<% end %>

El primer ejemplo muestra cómo in_groups_of llena el último grupo con tantos elementos nil como sea necesario para tener el tamaño solicitado. Puede cambiar este valor de relleno utilizando el segundo argumento opcional:

[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]]

Y puede indicarle al método que no llene el último grupo pasando false:

[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]]

Como consecuencia, false no se puede utilizar como valor de relleno.

NOTA: Definido en active_support/core_ext/array/grouping.rb.

9.7.2 in_groups(number, fill_with = nil)

El método in_groups divide una matriz en un cierto número de grupos. El método devuelve una matriz con los grupos:

%w(1 2 3 4 5 6 7).in_groups(3)
# => [["1", "2", "3"], ["4", "5", nil], ["6", "7", nil]]

o los devuelve a su vez si se pasa un bloque:

%w(1 2 3 4 5 6 7).in_groups(3) { |group| p group }
["1", "2", "3"]
["4", "5", nil]
["6", "7", nil]

Los ejemplos anteriores muestran que in_groups llena algunos grupos con un elemento nil adicional según sea necesario. Un grupo puede tener como máximo uno de estos elementos adicionales, el más a la derecha si lo hay. Y los grupos que los tienen siempre son los últimos.

Puede cambiar este valor de relleno utilizando el segundo argumento opcional:

%w(1 2 3 4 5 6 7).in_groups(3, "0")
# => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]]

Y puede indicarle al método que no llene los grupos más pequeños pasando false:

%w(1 2 3 4 5 6 7).in_groups(3, false)
# => [["1", "2", "3"], ["4", "5"], ["6", "7"]]

Como consecuencia, false no se puede utilizar como valor de relleno.

NOTA: Definido en active_support/core_ext/array/grouping.rb.

9.7.3 split(value = nil)

El método split divide una matriz por un separador y devuelve los fragmentos resultantes.

Si se pasa un bloque, los separadores son aquellos elementos de la matriz para los cuales el bloque devuelve verdadero:

(-5..5).to_a.split { |i| i.multiple_of?(4) }
# => [[-5], [-3, -2, -1], [1, 2, 3], [5]]

De lo contrario, el valor recibido como argumento, que por defecto es nil, es el separador:

[0, 1, -5, 1, 1, "foo", "bar"].split(1)
# => [[0], [-5], [], ["foo", "bar"]]

CONSEJO: Observa en el ejemplo anterior que los separadores consecutivos resultan en matrices vacías.

NOTA: Definido en active_support/core_ext/array/grouping.rb.

10 Extensiones a Hash

10.1 Conversiones

10.1.1 to_xml

El método to_xml devuelve una cadena que contiene una representación XML de su receptor:

{ foo: 1, bar: 2 }.to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <hash>
#   <foo type="integer">1</foo>
#   <bar type="integer">2</bar>
# </hash>

Para hacer esto, el método recorre los pares y construye nodos que dependen de los valores. Dado un par clave, valor:

  • Si valor es un hash, hay una llamada recursiva con clave como :root.

  • Si valor es una matriz, hay una llamada recursiva con clave como :root, y clave singularizado como :children.

  • Si valor es un objeto invocable, debe esperar uno o dos argumentos. Dependiendo de la aridad, se invoca al objeto invocable con el hash de opciones como primer argumento con clave como :root, y clave singularizado como segundo argumento. Su valor de retorno se convierte en un nuevo nodo.

  • Si valor responde a to_xml, se invoca al método con clave como :root.

  • De lo contrario, se crea un nodo con clave como etiqueta y una representación en cadena de valor como nodo de texto. Si valor es nil, se agrega un atributo "nil" establecido en "true". A menos que exista la opción :skip_types y sea verdadera, también se agrega un atributo "type" según la siguiente asignación: ruby XML_TYPE_NAMES = { "Symbol" => "símbolo", "Integer" => "entero", "BigDecimal" => "decimal", "Float" => "flotante", "TrueClass" => "booleano", "FalseClass" => "booleano", "Date" => "fecha", "DateTime" => "fecha_hora", "Time" => "fecha_hora" }

Por defecto, el nodo raíz es "hash", pero esto se puede configurar mediante la opción :root.

El constructor XML predeterminado es una nueva instancia de Builder::XmlMarkup. Puede configurar su propio constructor con la opción :builder. El método también acepta opciones como :dasherize y otras, que se envían al constructor.

NOTA: Definido en active_support/core_ext/hash/conversions.rb.

10.2 Combinación

Ruby tiene un método incorporado Hash#merge que combina dos hashes:

{ a: 1, b: 1 }.merge(a: 0, c: 2)
# => {:a=>0, :b=>1, :c=>2}

Active Support define algunas formas más de combinar hashes que pueden ser convenientes.

10.2.1 reverse_merge y reverse_merge!

En caso de colisión, la clave en el hash del argumento gana en merge. Puede admitir hashes de opciones con valores predeterminados de manera compacta con esta expresión:

options = { length: 30, omission: "..." }.merge(options)

Active Support define reverse_merge en caso de que prefiera esta notación alternativa:

options = options.reverse_merge(length: 30, omission: "...")

Y una versión con exclamación reverse_merge! que realiza la combinación en el lugar:

options.reverse_merge!(length: 30, omission: "...")

ADVERTENCIA. Tenga en cuenta que reverse_merge! puede cambiar el hash en el llamador, lo cual puede ser o no una buena idea.

NOTA: Definido en active_support/core_ext/hash/reverse_merge.rb.

10.2.2 reverse_update

El método reverse_update es un alias de reverse_merge!, explicado anteriormente.

ADVERTENCIA. Tenga en cuenta que reverse_update no tiene exclamación.

NOTA: Definido en active_support/core_ext/hash/reverse_merge.rb.

10.2.3 deep_merge y deep_merge!

Como se puede ver en el ejemplo anterior, si una clave se encuentra en ambos hashes, el valor en el hash del argumento gana.

Active Support define Hash#deep_merge. En una combinación profunda, si una clave se encuentra en ambos hashes y sus valores son hashes a su vez, entonces su combinación se convierte en el valor en el hash resultante:

{ a: { b: 1 } }.deep_merge(a: { c: 2 })
# => {:a=>{:b=>1, :c=>2}}

El método deep_merge! realiza una combinación profunda en el lugar.

NOTA: Definido en active_support/core_ext/hash/deep_merge.rb.

10.3 Duplicación Profunda

El método Hash#deep_dup duplica a sí mismo y a todas las claves y valores dentro de forma recursiva con el método Object#deep_dup de Active Support. Funciona como Enumerator#each_with_object enviando el método deep_dup a cada par dentro.

hash = { a: 1, b: { c: 2, d: [3, 4] } }

dup = hash.deep_dup
dup[:b][:e] = 5
dup[:b][:d] << 5

hash[:b][:e] == nil      # => true
hash[:b][:d] == [3, 4]   # => true

NOTA: Definido en active_support/core_ext/object/deep_dup.rb.

10.4 Trabajando con Claves

10.4.1 except y except!

El método except devuelve un hash con las claves de la lista de argumentos eliminadas, si están presentes:

{ a: 1, b: 2 }.except(:a) # => {:b=>2}

Si el receptor responde a convert_key, se llama al método en cada uno de los argumentos. Esto permite que except funcione correctamente con hashes de acceso indiferente, por ejemplo:

{ a: 1 }.with_indifferent_access.except(:a)  # => {}
{ a: 1 }.with_indifferent_access.except("a") # => {}

También existe la variante con exclamación except! que elimina las claves en su lugar.

NOTA: Definido en active_support/core_ext/hash/except.rb.

10.4.2 stringify_keys y stringify_keys!

El método stringify_keys devuelve un hash que tiene una versión en forma de cadena de las claves en el receptor. Lo hace enviando to_s a ellas:

{ nil => nil, 1 => 1, a: :a }.stringify_keys
# => {"" => nil, "1" => 1, "a" => :a}

En caso de colisión de claves, el valor será el más recientemente insertado en el hash:

{ "a" => 1, a: 2 }.stringify_keys
# El resultado será
# => {"a"=>2}

Este método puede ser útil, por ejemplo, para aceptar fácilmente tanto símbolos como cadenas como opciones. Por ejemplo, ActionView::Helpers::FormHelper define:

def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
  options = options.stringify_keys
  options["type"] = "checkbox"
  # ...
end

La segunda línea puede acceder de forma segura a la clave "type" y permitir al usuario pasar tanto :type como "type".

También existe la variante con exclamación stringify_keys! que convierte las claves en cadenas en su lugar.

Además, se puede utilizar deep_stringify_keys y deep_stringify_keys! para convertir en cadenas todas las claves del hash dado y todos los hashes anidados en él. Un ejemplo del resultado es:

{ nil => nil, 1 => 1, nested: { a: 3, 5 => 5 } }.deep_stringify_keys
# => {""=>nil, "1"=>1, "nested"=>{"a"=>3, "5"=>5}}

NOTA: Definido en active_support/core_ext/hash/keys.rb.

10.4.3 symbolize_keys y symbolize_keys!

El método symbolize_keys devuelve un hash que tiene una versión simbolizada de las claves en el receptor, cuando es posible. Lo hace enviando to_sym a ellas:

{ nil => nil, 1 => 1, "a" => "a" }.symbolize_keys
# => {nil=>nil, 1=>1, :a=>"a"}

ADVERTENCIA. Observa que en el ejemplo anterior solo se simbolizó una clave.

En caso de colisión de claves, el valor será el más recientemente insertado en el hash:

{ "a" => 1, a: 2 }.symbolize_keys
# => {:a=>2}

Este método puede ser útil, por ejemplo, para aceptar fácilmente tanto símbolos como cadenas como opciones. Por ejemplo, ActionText::TagHelper define:

def rich_text_area_tag(name, value = nil, options = {})
  options = options.symbolize_keys

  options[:input] ||= "trix_input_#{ActionText::TagHelper.id += 1}"
  # ...
end

La tercera línea puede acceder de forma segura a la clave :input y permitir al usuario pasar tanto :input como "input".

También existe la variante con exclamación symbolize_keys! que simboliza las claves en su lugar.

Además, se puede utilizar deep_symbolize_keys y deep_symbolize_keys! para simbolizar todas las claves del hash dado y todos los hashes anidados en él. Un ejemplo del resultado es:

{ nil => nil, 1 => 1, "nested" => { "a" => 3, 5 => 5 } }.deep_symbolize_keys
# => {nil=>nil, 1=>1, nested:{a:3, 5=>5}}

NOTA: Definido en active_support/core_ext/hash/keys.rb.

10.4.4 to_options y to_options!

Los métodos to_options y to_options! son alias de symbolize_keys y symbolize_keys!, respectivamente.

NOTA: Definido en active_support/core_ext/hash/keys.rb.

10.4.5 assert_valid_keys

El método assert_valid_keys recibe un número arbitrario de argumentos y verifica si el receptor tiene alguna clave fuera de esa lista. Si lo hace, se lanza un ArgumentError.

{ a: 1 }.assert_valid_keys(:a)  # pasa
{ a: 1 }.assert_valid_keys("a") # ArgumentError

Active Record no acepta opciones desconocidas al construir asociaciones, por ejemplo. Implementa ese control a través de assert_valid_keys.

NOTA: Definido en active_support/core_ext/hash/keys.rb.

10.5 Trabajando con Valores

10.5.1 deep_transform_values y deep_transform_values!

El método deep_transform_values devuelve un nuevo hash con todos los valores convertidos por la operación del bloque. Esto incluye los valores del hash raíz y de todos los hashes y arrays anidados.

hash = { person: { name: 'Rob', age: '28' } }

hash.deep_transform_values { |value| value.to_s.upcase }
# => {person: {name: "ROB", age: "28"}}

También existe la variante con exclamación deep_transform_values! que convierte destructivamente todos los valores utilizando la operación del bloque.

NOTA: Definido en active_support/core_ext/hash/deep_transform_values.rb.

10.6 Slicing

El método slice! reemplaza el hash con solo las claves dadas y devuelve un hash que contiene los pares clave/valor eliminados.

hash = { a: 1, b: 2 }
rest = hash.slice!(:a) # => {:b=>2}
hash                   # => {:a=>1}

NOTA: Definido en active_support/core_ext/hash/slice.rb.

10.7 Extracción

El método extract! elimina y devuelve los pares clave/valor que coinciden con las claves dadas.

hash = { a: 1, b: 2 }
rest = hash.extract!(:a) # => {:a=>1}
hash                     # => {:b=>2}

El método extract! devuelve la misma subclase de Hash que el receptor. ```ruby hash = { a: 1, b: 2 }.with_indifferent_access rest = hash.extract!(:a).class

=> ActiveSupport::HashWithIndifferentAccess


NOTA: Definido en `active_support/core_ext/hash/slice.rb`.


### Acceso indiferente

El método [`with_indifferent_access`][Hash#with_indifferent_access] devuelve un [`ActiveSupport::HashWithIndifferentAccess`][ActiveSupport::HashWithIndifferentAccess] a partir de su receptor:

```ruby
{ a: 1 }.with_indifferent_access["a"] # => 1

NOTA: Definido en active_support/core_ext/hash/indifferent_access.rb.

11 Extensiones a Regexp

11.1 multiline?

El método multiline? indica si una expresión regular tiene la bandera /m establecida, es decir, si el punto coincide con saltos de línea.

%r{.}.multiline?  # => false
%r{.}m.multiline? # => true

Regexp.new('.').multiline?                    # => false
Regexp.new('.', Regexp::MULTILINE).multiline? # => true

Rails utiliza este método en un solo lugar, también en el código de enrutamiento. Las expresiones regulares de varias líneas no están permitidas para los requisitos de ruta y esta bandera facilita la aplicación de esa restricción.

def verify_regexp_requirements(requirements)
  # ...
  if requirement.multiline?
    raise ArgumentError, "La opción multiline de la expresión regular no está permitida en los requisitos de enrutamiento: #{requirement.inspect}"
  end
  # ...
end

NOTA: Definido en active_support/core_ext/regexp.rb.

12 Extensiones a Range

12.1 to_fs

Active Support define Range#to_fs como una alternativa a to_s que entiende un argumento de formato opcional. Hasta la fecha de esta escritura, el único formato no predeterminado compatible es :db:

(Date.today..Date.tomorrow).to_fs
# => "2009-10-25..2009-10-26"

(Date.today..Date.tomorrow).to_fs(:db)
# => "BETWEEN '2009-10-25' AND '2009-10-26'"

Como muestra el ejemplo, el formato :db genera una cláusula SQL BETWEEN. Esto es utilizado por Active Record en su soporte para valores de rango en condiciones.

NOTA: Definido en active_support/core_ext/range/conversions.rb.

12.2 === y include?

Los métodos Range#=== y Range#include? indican si un valor se encuentra entre los extremos de una instancia dada:

(2..3).include?(Math::E) # => true

Active Support extiende estos métodos para que el argumento pueda ser otro rango a su vez. En ese caso, se prueba si los extremos del rango del argumento pertenecen al receptor en sí:

(1..10) === (3..7)  # => true
(1..10) === (0..7)  # => false
(1..10) === (3..11) # => false
(1...9) === (3..9)  # => false

(1..10).include?(3..7)  # => true
(1..10).include?(0..7)  # => false
(1..10).include?(3..11) # => false
(1...9).include?(3..9)  # => false

NOTA: Definido en active_support/core_ext/range/compare_range.rb.

12.3 overlap?

El método Range#overlap? indica si dos rangos dados tienen una intersección no vacía:

(1..10).overlap?(7..11)  # => true
(1..10).overlap?(0..7)   # => true
(1..10).overlap?(11..27) # => false

NOTA: Definido en active_support/core_ext/range/overlap.rb.

13 Extensiones a Date

13.1 Cálculos

Los siguientes métodos de cálculo tienen casos especiales en octubre de 1582, ya que los días 5..14 simplemente no existen. Esta guía no documenta su comportamiento en torno a esos días por brevedad, pero es suficiente decir que hacen lo que esperarías. Es decir, Date.new(1582, 10, 4).tomorrow devuelve Date.new(1582, 10, 15) y así sucesivamente. Consulta test/core_ext/date_ext_test.rb en el conjunto de pruebas de Active Support para conocer el comportamiento esperado.

13.1.1 Date.current

Active Support define Date.current como la fecha de hoy en la zona horaria actual. Es similar a Date.today, excepto que respeta la zona horaria del usuario, si está definida. También define Date.yesterday y Date.tomorrow, y los predicados de instancia past?, today?, tomorrow?, next_day?, yesterday?, prev_day?, future?, on_weekday? y on_weekend?, todos ellos en relación a Date.current.

Cuando se hacen comparaciones de fechas utilizando métodos que respetan la zona horaria del usuario, asegúrate de usar Date.current y no Date.today. Hay casos en los que la zona horaria del usuario puede estar en el futuro en comparación con la zona horaria del sistema, que es la que utiliza Date.today de forma predeterminada. Esto significa que Date.today puede ser igual a Date.yesterday.

NOTA: Definido en active_support/core_ext/date/calculations.rb.

13.1.2 Fechas nombradas

13.1.2.1 beginning_of_week, end_of_week

Los métodos beginning_of_week y end_of_week devuelven las fechas del inicio y fin de la semana, respectivamente. Se asume que las semanas comienzan el lunes, pero eso se puede cambiar pasando un argumento, estableciendo Date.beginning_of_week en el hilo local o config.beginning_of_week.

d = Date.new(2010, 5, 8)     # => Sat, 08 May 2010
d.beginning_of_week          # => Mon, 03 May 2010
d.beginning_of_week(:sunday) # => Sun, 02 May 2010
d.end_of_week                # => Sun, 09 May 2010
d.end_of_week(:sunday)       # => Sat, 08 May 2010

beginning_of_week se asigna a at_beginning_of_week y end_of_week se asigna a at_end_of_week.

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.2.2 monday, sunday

Los métodos monday y sunday devuelven las fechas del lunes anterior y del domingo siguiente, respectivamente.

d = Date.new(2010, 5, 8)     # => Sat, 08 May 2010
d.monday                     # => Mon, 03 May 2010
d.sunday                     # => Sun, 09 May 2010

d = Date.new(2012, 9, 10)    # => Mon, 10 Sep 2012
d.monday                     # => Mon, 10 Sep 2012

d = Date.new(2012, 9, 16)    # => Sun, 16 Sep 2012
d.sunday                     # => Sun, 16 Sep 2012

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.2.3 prev_week, next_week

El método next_week recibe un símbolo con el nombre del día en inglés (por defecto es el Date.beginning_of_week local del hilo, o config.beginning_of_week, o :monday) y devuelve la fecha correspondiente a ese día.

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.next_week              # => Mon, 10 May 2010
d.next_week(:saturday)   # => Sat, 15 May 2010

El método prev_week es análogo:

d.prev_week              # => Mon, 26 Apr 2010
d.prev_week(:saturday)   # => Sat, 01 May 2010
d.prev_week(:friday)     # => Fri, 30 Apr 2010

prev_week se asigna a last_week.

Tanto next_week como prev_week funcionan como se espera cuando Date.beginning_of_week o config.beginning_of_week están configurados.

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.2.4 beginning_of_month, end_of_month

Los métodos beginning_of_month y end_of_month devuelven las fechas del principio y fin del mes:

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.beginning_of_month     # => Sat, 01 May 2010
d.end_of_month           # => Mon, 31 May 2010

beginning_of_month se asigna a at_beginning_of_month, y end_of_month se asigna a at_end_of_month.

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.2.5 quarter, beginning_of_quarter, end_of_quarter

El método quarter devuelve el trimestre del año calendario del receptor:

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.quarter                # => 2

Los métodos beginning_of_quarter y end_of_quarter devuelven las fechas del principio y fin del trimestre del año calendario del receptor:

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.beginning_of_quarter   # => Thu, 01 Apr 2010
d.end_of_quarter         # => Wed, 30 Jun 2010

beginning_of_quarter se asigna a at_beginning_of_quarter, y end_of_quarter se asigna a at_end_of_quarter.

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.2.6 beginning_of_year, end_of_year

Los métodos beginning_of_year y end_of_year devuelven las fechas del principio y fin del año:

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.beginning_of_year      # => Fri, 01 Jan 2010
d.end_of_year            # => Fri, 31 Dec 2010

beginning_of_year se asigna a at_beginning_of_year, y end_of_year se asigna a at_end_of_year.

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.3 Otras Computaciones de Fecha

13.1.3.1 years_ago, years_since

El método years_ago recibe un número de años y devuelve la misma fecha hace tantos años:

date = Date.new(2010, 6, 7)
date.years_ago(10) # => Wed, 07 Jun 2000

years_since se mueve hacia adelante en el tiempo:

date = Date.new(2010, 6, 7)
date.years_since(10) # => Sun, 07 Jun 2020

Si ese día no existe, se devuelve el último día del mes correspondiente:

Date.new(2012, 2, 29).years_ago(3)     # => Sat, 28 Feb 2009
Date.new(2012, 2, 29).years_since(3)   # => Sat, 28 Feb 2015

last_year es una forma abreviada de #years_ago(1).

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.3.2 months_ago, months_since

Los métodos months_ago y months_since funcionan de manera análoga para los meses:

Date.new(2010, 4, 30).months_ago(2)   # => Sun, 28 Feb 2010
Date.new(2010, 4, 30).months_since(2) # => Wed, 30 Jun 2010

Si ese día no existe, se devuelve el último día del mes correspondiente:

Date.new(2010, 4, 30).months_ago(2)    # => Sun, 28 Feb 2010
Date.new(2009, 12, 31).months_since(2) # => Sun, 28 Feb 2010

last_month es una forma abreviada de #months_ago(1). NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.3.3 weeks_ago

El método weeks_ago funciona de manera análoga para semanas:

Date.new(2010, 5, 24).weeks_ago(1)    # => Mon, 17 May 2010
Date.new(2010, 5, 24).weeks_ago(2)    # => Mon, 10 May 2010

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

13.1.3.4 advance

La forma más genérica de saltar a otros días es advance. Este método recibe un hash con las claves :years, :months, :weeks, :days, y devuelve una fecha avanzada tanto como indiquen las claves presentes:

date = Date.new(2010, 6, 6)
date.advance(years: 1, weeks: 2)  # => Mon, 20 Jun 2011
date.advance(months: 2, days: -2) # => Wed, 04 Aug 2010

Observa en el ejemplo anterior que los incrementos pueden ser negativos.

NOTA: Definido en active_support/core_ext/date/calculations.rb.

13.1.4 Cambiar Componentes

El método change te permite obtener una nueva fecha que es igual a la original excepto por el año, mes o día especificado:

Date.new(2010, 12, 23).change(year: 2011, month: 11)
# => Wed, 23 Nov 2011

Este método no tolera fechas que no existen, si el cambio es inválido se lanzará un ArgumentError:

Date.new(2010, 1, 31).change(month: 2)
# => ArgumentError: invalid date

NOTA: Definido en active_support/core_ext/date/calculations.rb.

13.1.5 Duraciones

Los objetos Duration se pueden sumar y restar a fechas:

d = Date.current
# => Mon, 09 Aug 2010
d + 1.year
# => Tue, 09 Aug 2011
d - 3.hours
# => Sun, 08 Aug 2010 21:00:00 UTC +00:00

Se traducen en llamadas a since o advance. Por ejemplo, aquí obtenemos el salto correcto en la reforma del calendario:

Date.new(1582, 10, 4) + 1.day
# => Fri, 15 Oct 1582

13.1.6 Marcas de tiempo

Los siguientes métodos devuelven un objeto Time si es posible, de lo contrario un DateTime. Si se establece, respetan la zona horaria del usuario.

13.1.6.1 beginning_of_day, end_of_day

El método beginning_of_day devuelve una marca de tiempo al comienzo del día (00:00:00):

date = Date.new(2010, 6, 7)
date.beginning_of_day # => Mon Jun 07 00:00:00 +0200 2010

El método end_of_day devuelve una marca de tiempo al final del día (23:59:59):

date = Date.new(2010, 6, 7)
date.end_of_day # => Mon Jun 07 23:59:59 +0200 2010

beginning_of_day se aliasa a at_beginning_of_day, midnight, at_midnight.

NOTA: Definido en active_support/core_ext/date/calculations.rb.

13.1.6.2 beginning_of_hour, end_of_hour

El método beginning_of_hour devuelve una marca de tiempo al comienzo de la hora (hh:00:00):

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.beginning_of_hour # => Mon Jun 07 19:00:00 +0200 2010

El método end_of_hour devuelve una marca de tiempo al final de la hora (hh:59:59):

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.end_of_hour # => Mon Jun 07 19:59:59 +0200 2010

beginning_of_hour se aliasa a at_beginning_of_hour.

NOTA: Definido en active_support/core_ext/date_time/calculations.rb.

13.1.6.3 beginning_of_minute, end_of_minute

El método beginning_of_minute devuelve una marca de tiempo al comienzo del minuto (hh:mm:00):

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.beginning_of_minute # => Mon Jun 07 19:55:00 +0200 2010

El método end_of_minute devuelve una marca de tiempo al final del minuto (hh:mm:59):

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.end_of_minute # => Mon Jun 07 19:55:59 +0200 2010

beginning_of_minute se aliasa a at_beginning_of_minute.

beginning_of_hour, end_of_hour, beginning_of_minute y end_of_minute están implementados para Time y DateTime pero no para Date, ya que no tiene sentido solicitar el comienzo o el final de una hora o minuto en una instancia de Date.

NOTA: Definido en active_support/core_ext/date_time/calculations.rb.

13.1.6.4 ago, since

El método ago recibe un número de segundos como argumento y devuelve una marca de tiempo que corresponde a esa cantidad de segundos antes de la medianoche:

date = Date.current # => Fri, 11 Jun 2010
date.ago(1)         # => Thu, 10 Jun 2010 23:59:59 EDT -04:00

De manera similar, since avanza en el tiempo:

date = Date.current # => Fri, 11 Jun 2010
date.since(1)       # => Fri, 11 Jun 2010 00:00:01 EDT -04:00

NOTA: Definido en active_support/core_ext/date/calculations.rb.

14 Extensiones a DateTime

ADVERTENCIA: DateTime no es consciente de las reglas de DST, por lo que algunos de estos métodos tienen casos especiales cuando ocurre un cambio de DST. Por ejemplo, seconds_since_midnight puede no devolver la cantidad real en ese día.

14.1 Cálculos

La clase DateTime es una subclase de Date, por lo que al cargar active_support/core_ext/date/calculations.rb heredas estos métodos y sus alias, excepto que siempre devolverán datetimes.

Los siguientes métodos se vuelven a implementar para que no necesites cargar active_support/core_ext/date/calculations.rb para estos:

Por otro lado, advance y change también están definidos y admiten más opciones, se documentan a continuación.

Los siguientes métodos solo se implementan en active_support/core_ext/date_time/calculations.rb ya que solo tienen sentido cuando se usan con una instancia de DateTime:

14.1.1 Datetimes Nombrados

14.1.1.1 DateTime.current

Active Support define DateTime.current como Time.now.to_datetime, excepto que respeta la zona horaria del usuario, si está definida. Los predicados de instancia past? y future? se definen en relación a DateTime.current.

NOTA: Definido en active_support/core_ext/date_time/calculations.rb.

14.1.2 Otras Extensiones

14.1.2.1 seconds_since_midnight

El método seconds_since_midnight devuelve el número de segundos desde la medianoche:

now = DateTime.current     # => Mon, 07 Jun 2010 20:26:36 +0000
now.seconds_since_midnight # => 73596

NOTA: Definido en active_support/core_ext/date_time/calculations.rb.

14.1.2.2 utc

El método utc te da el mismo datetime en el receptor expresado en UTC.

now = DateTime.current # => Mon, 07 Jun 2010 19:27:52 -0400
now.utc                # => Mon, 07 Jun 2010 23:27:52 +0000

Este método también se aliasa como getutc.

NOTA: Definido en active_support/core_ext/date_time/calculations.rb.

14.1.2.3 utc?

El predicado utc? indica si el receptor tiene UTC como su zona horaria:

now = DateTime.now # => Mon, 07 Jun 2010 19:30:47 -0400
now.utc?           # => false
now.utc.utc?       # => true

NOTA: Definido en active_support/core_ext/date_time/calculations.rb.

14.1.2.4 advance

La forma más genérica de saltar a otro datetime es advance. Este método recibe un hash con las claves :years, :months, :weeks, :days, :hours, :minutes y :seconds, y devuelve un datetime avanzado tanto como indiquen las claves presentes.

d = DateTime.current
# => Thu, 05 Aug 2010 11:33:31 +0000
d.advance(years: 1, months: 1, days: 1, hours: 1, minutes: 1, seconds: 1)
# => Tue, 06 Sep 2011 12:34:32 +0000

Este método primero calcula la fecha de destino pasando :years, :months, :weeks y :days a Date#advance documentado anteriormente. Después de eso, ajusta la hora llamando a since con el número de segundos a avanzar. Este orden es relevante, un orden diferente daría diferentes datetimes en algunos casos especiales. Se aplica el ejemplo en Date#advance, y podemos extenderlo para mostrar la relevancia del orden relacionado con los bits de tiempo.

Si primero movemos los bits de fecha (que también tienen un orden relativo de procesamiento, como se documentó anteriormente), y luego los bits de tiempo, obtenemos, por ejemplo, el siguiente cálculo:

d = DateTime.new(2010, 2, 28, 23, 59, 59)
# => Sun, 28 Feb 2010 23:59:59 +0000
d.advance(months: 1, seconds: 1)
# => Mon, 29 Mar 2010 00:00:00 +0000

pero si los calculamos al revés, el resultado sería diferente:

d.advance(seconds: 1).advance(months: 1)
# => Thu, 01 Apr 2010 00:00:00 +0000

ADVERTENCIA: Como DateTime no es consciente de DST, puedes terminar en un punto en el tiempo que no existe sin ninguna advertencia o error que te lo indique.

NOTA: Definido en active_support/core_ext/date_time/calculations.rb.

14.1.3 Cambiar Componentes

El método change te permite obtener un nuevo datetime que es igual al receptor excepto por las opciones dadas, que pueden incluir :year, :month, :day, :hour, :min, :sec, :offset, :start:

now = DateTime.current
# => Tue, 08 Jun 2010 01:56:22 +0000
now.change(year: 2011, offset: Rational(-6, 24))
# => Wed, 08 Jun 2011 01:56:22 -0600

Si las horas se establecen en cero, entonces los minutos y segundos también se establecen en cero (a menos que tengan valores dados):

now.change(hour: 0)
# => Tue, 08 Jun 2010 00:00:00 +0000

De manera similar, si los minutos se establecen en cero, entonces los segundos también se establecen en cero (a menos que se haya dado un valor):

now.change(min: 0)
# => Tue, 08 Jun 2010 01:00:00 +0000

Este método no es tolerante a fechas que no existen, si el cambio es inválido se genera un ArgumentError:

DateTime.current.change(month: 2, day: 30)
# => ArgumentError: fecha inválida

NOTA: Definido en active_support/core_ext/date_time/calculations.rb.

14.1.4 Duraciones

Los objetos Duration se pueden sumar y restar a datetimes:

now = DateTime.current
# => Mon, 09 Aug 2010 23:15:17 +0000
now + 1.year
# => Tue, 09 Aug 2011 23:15:17 +0000
now - 1.week
# => Mon, 02 Aug 2010 23:15:17 +0000

Se traducen en llamadas a since o advance. Por ejemplo, aquí obtenemos el salto correcto en la reforma del calendario:

DateTime.new(1582, 10, 4, 23) + 1.hour
# => Fri, 15 Oct 1582 00:00:00 +0000

15 Extensiones a Time

15.1 Cálculos

Son análogos. Por favor, consulte su documentación anterior y tenga en cuenta las siguientes diferencias:

  • change acepta una opción adicional :usec.
  • Time entiende DST, por lo que se obtienen cálculos DST correctos como en
Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>

# En Barcelona, el 28/03/2010 02:00 +0100 se convierte en 28/03/2010 03:00 +0200 debido a DST.
t = Time.local(2010, 3, 28, 1, 59, 59)
# => Sun Mar 28 01:59:59 +0100 2010
t.advance(seconds: 1)
# => Sun Mar 28 03:00:00 +0200 2010
  • Si since o ago salta a un tiempo que no se puede expresar con Time, se devuelve un objeto DateTime en su lugar.

15.1.1 Time.current

Active Support define Time.current como hoy en la zona horaria actual. Es como Time.now, excepto que respeta la zona horaria del usuario, si está definida. También define los predicados de instancia past?, today?, tomorrow?, next_day?, yesterday?, prev_day? y future?, todos ellos relativos a Time.current.

Cuando se realizan comparaciones de tiempo utilizando métodos que respetan la zona horaria del usuario, asegúrese de usar Time.current en lugar de Time.now. Hay casos en los que la zona horaria del usuario puede estar en el futuro en comparación con la zona horaria del sistema, que Time.now utiliza de forma predeterminada. Esto significa que Time.now.to_date puede ser igual a Date.yesterday.

NOTA: Definido en active_support/core_ext/time/calculations.rb.

15.1.2 all_day, all_week, all_month, all_quarter y all_year

El método all_day devuelve un rango que representa todo el día del tiempo actual.

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now.all_day
# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Mon, 09 Aug 2010 23:59:59 UTC +00:00

De manera análoga, all_week, all_month, all_quarter y all_year sirven para generar rangos de tiempo.

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now.all_week
# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Sun, 15 Aug 2010 23:59:59 UTC +00:00
now.all_week(:sunday)
# => Sun, 16 Sep 2012 00:00:00 UTC +00:00..Sat, 22 Sep 2012 23:59:59 UTC +00:00
now.all_month
# => Sat, 01 Aug 2010 00:00:00 UTC +00:00..Tue, 31 Aug 2010 23:59:59 UTC +00:00
now.all_quarter
# => Thu, 01 Jul 2010 00:00:00 UTC +00:00..Thu, 30 Sep 2010 23:59:59 UTC +00:00
now.all_year
# => Fri, 01 Jan 2010 00:00:00 UTC +00:00..Fri, 31 Dec 2010 23:59:59 UTC +00:00

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

15.1.3 prev_day, next_day

prev_day y next_day devuelven el tiempo en el día anterior o siguiente:

t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900
t.prev_day               # => 2010-05-07 00:00:00 +0900
t.next_day               # => 2010-05-09 00:00:00 +0900

NOTA: Definido en active_support/core_ext/time/calculations.rb.

15.1.4 prev_month, next_month

prev_month y next_month devuelven el tiempo con el mismo día en el mes anterior o siguiente: ruby t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900 t.prev_month # => 2010-04-08 00:00:00 +0900 t.next_month # => 2010-06-08 00:00:00 +0900

Si ese día no existe, se devuelve el último día del mes correspondiente:

Time.new(2000, 5, 31).prev_month # => 2000-04-30 00:00:00 +0900
Time.new(2000, 3, 31).prev_month # => 2000-02-29 00:00:00 +0900
Time.new(2000, 5, 31).next_month # => 2000-06-30 00:00:00 +0900
Time.new(2000, 1, 31).next_month # => 2000-02-29 00:00:00 +0900

NOTA: Definido en active_support/core_ext/time/calculations.rb.

15.1.5 prev_year, next_year

prev_year y next_year devuelven una fecha con el mismo día/mes en el año anterior o siguiente:

t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900
t.prev_year              # => 2009-05-08 00:00:00 +0900
t.next_year              # => 2011-05-08 00:00:00 +0900

Si la fecha es el 29 de febrero de un año bisiesto, se obtiene el 28:

t = Time.new(2000, 2, 29) # => 2000-02-29 00:00:00 +0900
t.prev_year               # => 1999-02-28 00:00:00 +0900
t.next_year               # => 2001-02-28 00:00:00 +0900

NOTA: Definido en active_support/core_ext/time/calculations.rb.

15.1.6 prev_quarter, next_quarter

prev_quarter y next_quarter devuelven la fecha con el mismo día en el trimestre anterior o siguiente:

t = Time.local(2010, 5, 8) # => 2010-05-08 00:00:00 +0300
t.prev_quarter             # => 2010-02-08 00:00:00 +0200
t.next_quarter             # => 2010-08-08 00:00:00 +0300

Si ese día no existe, se devuelve el último día del mes correspondiente:

Time.local(2000, 7, 31).prev_quarter  # => 2000-04-30 00:00:00 +0300
Time.local(2000, 5, 31).prev_quarter  # => 2000-02-29 00:00:00 +0200
Time.local(2000, 10, 31).prev_quarter # => 2000-07-31 00:00:00 +0300
Time.local(2000, 11, 31).next_quarter # => 2001-03-01 00:00:00 +0200

prev_quarter se define como un alias de last_quarter.

NOTA: Definido en active_support/core_ext/date_and_time/calculations.rb.

15.2 Constructores de Tiempo

Active Support define Time.current como Time.zone.now si hay una zona horaria de usuario definida, con fallback a Time.now:

Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
Time.current
# => Fri, 06 Aug 2010 17:11:58 CEST +02:00

Análogamente a DateTime, los predicados past? y future? son relativos a Time.current.

Si el tiempo a construir está fuera del rango soportado por Time en la plataforma de ejecución, los microsegundos se descartan y se devuelve un objeto DateTime en su lugar.

15.2.1 Duraciones

Se pueden sumar y restar objetos Duration a objetos de tiempo:

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now + 1.year
# => Tue, 09 Aug 2011 23:21:11 UTC +00:00
now - 1.week
# => Mon, 02 Aug 2010 23:21:11 UTC +00:00

Se traducen en llamadas a since o advance. Por ejemplo, aquí obtenemos el salto correcto en la reforma del calendario:

Time.utc(1582, 10, 3) + 5.days
# => Mon Oct 18 00:00:00 UTC 1582

16 Extensiones a File

16.1 atomic_write

Con el método de clase File.atomic_write se puede escribir en un archivo de una manera que evite que cualquier lector vea contenido medio escrito.

El nombre del archivo se pasa como argumento y el método cede un manejador de archivo abierto para escribir. Una vez que el bloque se completa, atomic_write cierra el manejador de archivo y completa su trabajo.

Por ejemplo, Action Pack utiliza este método para escribir archivos de caché de activos como all.css:

File.atomic_write(joined_asset_path) do |cache|
  cache.write(join_asset_file_contents(asset_paths))
end

Para lograr esto, atomic_write crea un archivo temporal. Ese es el archivo al que el código en el bloque realmente escribe. Al completarse, el archivo temporal se renombra, lo cual es una operación atómica en sistemas POSIX. Si el archivo de destino existe, atomic_write lo sobrescribe y mantiene los propietarios y permisos. Sin embargo, hay algunos casos en los que atomic_write no puede cambiar la propiedad o los permisos del archivo, este error se captura y se salta, confiando en el usuario/sistema de archivos para asegurarse de que el archivo sea accesible para los procesos que lo necesiten.

NOTA. Debido a la operación chmod que realiza atomic_write, si el archivo de destino tiene un ACL establecido en él, este ACL se recalculará/modificará. `` ADVERTENCIA. Tenga en cuenta que no se puede agregar conatomic_write`.

El archivo auxiliar se escribe en un directorio estándar para archivos temporales, pero puede pasar un directorio de su elección como segundo argumento.

NOTA: Definido en active_support/core_ext/file/atomic.rb.

17 Extensiones a NameError

Active Support agrega missing_name? a NameError, que prueba si la excepción se produjo debido al nombre pasado como argumento.

El nombre puede ser dado como un símbolo o una cadena. Un símbolo se prueba contra el nombre constante sin formato, una cadena se prueba contra el nombre constante completamente calificado.

CONSEJO: Un símbolo puede representar un nombre constante completamente calificado como en :"ActiveRecord::Base", por lo que el comportamiento para los símbolos está definido por conveniencia, no porque tenga que ser así técnicamente.

Por ejemplo, cuando se llama a una acción de ArticlesController, Rails intenta usar optimistamente ArticlesHelper. Está bien que el módulo de ayuda no exista, por lo que si se genera una excepción para ese nombre constante, debe ser silenciada. Pero podría ser el caso de que articles_helper.rb genere un NameError debido a una constante desconocida real. Eso debería ser relanzado. El método missing_name? proporciona una forma de distinguir ambos casos:

def default_helper_module!
  module_name = name.delete_suffix("Controller")
  module_path = module_name.underscore
  helper module_path
rescue LoadError => e
  raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
  raise e unless e.missing_name? "#{module_name}Helper"
end

NOTA: Definido en active_support/core_ext/name_error.rb.

18 Extensiones a LoadError

Active Support agrega is_missing? a LoadError.

Dado un nombre de ruta, is_missing? prueba si la excepción se generó debido a ese archivo en particular (excepto tal vez por la extensión ".rb").

Por ejemplo, cuando se llama a una acción de ArticlesController, Rails intenta cargar articles_helper.rb, pero ese archivo puede no existir. Eso está bien, el módulo de ayuda no es obligatorio, por lo que Rails silencia un error de carga. Pero podría ser el caso de que el módulo de ayuda exista y, a su vez, requiera otra biblioteca que falte. En ese caso, Rails debe relanzar la excepción. El método is_missing? proporciona una forma de distinguir ambos casos:

def default_helper_module!
  module_name = name.delete_suffix("Controller")
  module_path = module_name.underscore
  helper module_path
rescue LoadError => e
  raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
  raise e unless e.missing_name? "#{module_name}Helper"
end

NOTA: Definido en active_support/core_ext/load_error.rb.

19 Extensiones a Pathname

19.1 existence

El método existence devuelve el receptor si el archivo con el nombre especificado existe, de lo contrario devuelve nil. Es útil para idiomatismos como este:

content = Pathname.new("file").existence&.read

NOTA: Definido en active_support/core_ext/pathname/existence.rb.

Comentarios

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.

También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.

Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.

Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.