RubyGems: Patrones (13)

Prácticas comunes para facilitar la vida de los usuarios de gemas y otros desarrolladores.

Denominación consistente

Nombres de archivo

Se coherente con la forma en que se nombran tus gemas: lib y bin . La gema hola de la guía haz tu propia gema es un gran ejemplo:

% tree
.
├── Rakefile
├── bin
│   └── hola
├── hola.gemspec
├── lib
│   ├── hola
│   │   └── translator.rb
│   └── hola.rb
└── test
└── test_hola.rb

El ejecutable y el archivo principal lib tienen el mismo nombre. Un desarrollador puede entrar fácilmente y llamar require 'hola' sin problemas.

Nombres de tu gema

Nombrar tu gema es importante. Antes de elegir un nombre para su gema, haz una búsqueda rápida en RubyGems.org y GitHub para ver si alguien ya lo ha usado.

Cada gema publicada debe tener un nombre único.

Asegúrate de leer nuestras recomendaciones de nombres cuando hayas encontrado un nombre libre que te guste.

Versionado semántico

Una política de control de versiones es simplemente un conjunto de reglas simples que rigen cómo se asignan los números de versión.

Puede ser muy simple (por ejemplo, el número de versión es un solo número que comienza con 1 y se incrementa para cada versión sucesiva) o puede ser realmente extraño (el proyecto TeX de Knuth tenía números de versión: 3, 3.1, 3.14, 3.141, 3.1415; cada versión sucesiva agregaba otro dígito a PI).

El equipo de RubyGems insta a los desarrolladores de gemas a seguir el estándar de las versiones semánticas para las versiones de las gemas.

La biblioteca de RubyGems en sí misma no impone una política estricta de control de versiones, pero el uso de una política “irracional” solo será un perjuicio para aquellos en la comunidad que usen tus gemas.

Supongamos que tienes una gema de ‘pila’ que contiene una clase Stack con funcionalidad push y pop .

Podría CHANGELOG verse así si utilizas el control de versiones semántico:

  • Versión 0.1.0: Se lanza la clase inicial .
  • Versión 0.2.0: Se cambió a una implementación de lista enlazada porque es más genial.
  • Versión 0.3.0: Se agregó un depth método.
  • Versión 1.0.0: Se agregó top y se realizó la pop devolución nil ( pop se usa para devolver el elemento superior anterior).
  • Versión 1.1.0: push ahora devuelve el valor insertado (solía devolver nil ).
  • Versión 1.1.1: Se corrigió un error en la implementación de la lista enlazada.
  • Versión 1.1.2: Se corrigió un error introducido en la última corrección.

El versionado semántico se reduce a:

  • Cambios de nivel de PATCH 0.0.x para cambios de detalle de nivel de implementación, como pequeñas correcciones de errores.
  • Cambios de nivel MENOR 0.x.0 para cualquier cambio de API compatible con versiones anteriores, como nuevas funcionalidades/características.
  • Cambios de nivel x.0.0 : Para cambios de API principales, incompatibles con versiones anteriores, como cambios que romperán el código de los usuarios existentes si se actualizan.

Declaración de dependencias

Las gemas funcionan con otras gemas. Aquí hay algunos consejos para asegurarse de que sean amables el uno con el otro.

Tiempo de ejecución frente a desarrollo

RubyGems proporciona dos “tipos” principales de dependencias: tiempo de ejecución y desarrollo.

Las dependencias de tiempo de ejecución son lo que su gema necesita para funcionar.

Las dependencias de desarrollo son útiles cuando alguien quiere hacer modificaciones a su gema.

Cuando especifica dependencias de desarrollo, otro desarrollador puede ejecutar gem install --dev your_gem y RubyGems tomará ambos conjuntos de dependencias (tiempo de ejecución y desarrollo).

Las dependencias de desarrollo típicas incluyen marcos de prueba y sistemas de compilación.

Establecer dependencias en su gemspec es fácil. Solo usa add_runtime_dependency y add_development_dependency :

Gem::Specification.new do |s|
s.name = "hola"
s.version = "2.0.0"
s.add_runtime_dependency "daemons",
["= 1.1.0"]
s.add_development_dependency "bourne",
[">= 0"]

No uses gem desde dentro de tu gema

Es posible que hayas visto algún código como este para asegurarte de que estás usando una versión específica de una gema:

gem "extlib", ">= 1.0.8"
require "extlib"

Es razonable que las aplicaciones que consumen gemas usen esto (aunque también podrían usar una herramienta como Bundler).

Las gemas en sí mismas no deberían hacer eso.

En su lugar, deberían usar dependencias en gemspec para que RubyGems pueda manejar la carga de la dependencia en lugar del usuario.

Restricción de versión pesimista

Si tu gema sigue correctamente el control de versiones semántico con su esquema de control de versiones, entonces otros desarrolladores de Ruby pueden aprovechar esto al elegir una restricción de versión para bloquear tu gema en su aplicación.

Digamos que existen los siguientes lanzamientos de una gema:

  • Versión 2.1.0: Línea base
  • Versión 2.2.0: Introdujo algunas funciones nuevas (compatibles con versiones anteriores).
  • Versión 2.2.1: Se eliminaron algunos errores
  • Versión 2.2.2: Simplificó su código
  • Versión 2.3.0: Más funciones nuevas (pero aún compatible con versiones anteriores).
  • Versión 3.0.0: Se reelaboró la interfaz. Es posible que el código escrito en la versión 2.x no funcione.

Quieres usar una gema y ha determinado que la versión 2.2.0 funciona con su software, pero la versión 2.1.0 no tiene la función que necesitas.

Agregar una dependencia en tu gema (o Gemfile desde Bundler) podría verse así:

# gemspec
spec.add_runtime_dependency 'library',
'>= 2.2.0'
# bundler
gem 'library', '>= 2.2.0'

Esta es una restricción de versión “optimista”. Está diciendo que todas las versiones superiores o iguales a 2.2.0 funcionarán con el software.

Sin embargo, es posible que sepas que la versión 3.0 introduce un cambio importante y ya no es compatible. La manera de designar esto es siendo “pesimista”.

Esto excluye explícitamente las versiones que podrían romper el código:

# gemspec
spec.add_runtime_dependency 'library',
['>= 2.2.0', '< 3.0']
# bundler
gem 'library', '>= 2.2.0', '< 3.0'

RubyGems proporciona un atajo para eso, comúnmente conocido como twiddle-wakka:

# gemspec
spec.add_runtime_dependency 'library',
'~&gt; 2.2'
# bundler
gem 'library', '~&gt; 2.2'

Observe que bajamos el nivel de PATCH del número de versión. Si se hubiese dicho ~&gt; 2.2.0 , eso hubiera sido equivalente a `[‘>= 2.2.0’, ‘ 2.2’, ‘>= 2.2.1’

# bundler
gem 'library', '~&gt; 2.2', '&gt;= 2.2.1'

La nota importante aquí, es tener en cuenta que otros usarán tus gemas, así que debes protegerte de posibles errores/fallas en versiones futuras usando ~&gt; en lugar de &gt;= si es posible.

Si está lidiando con muchas dependencias de gemas en tu aplicación, es recomendable que eches un vistazo a Bundler o Isolate. Que hacen un gran trabajo al administrar un manifiesto de versión complejo para muchas gemas.

Si deseas permitir lanzamientos preliminares y lanzamientos regulares, usa el requisito compuesto:

# gemspec
spec.add_runtime_dependency 'library', '>= 2.0.0.a', '< 3'

El uso ~&gt; con versiones preliminares restringirá únicamente a las versiones preliminares.

También es importante saber que si especificas solo una versión principal, así:

# gemspec
spec.add_runtime_dependency 'library', '~&gt; 2'

Solo se utilizará la última versión de la serie 2.x, es decir, la 2.3.0, y no la 3.0.0.

Este comportamiento puede sorprender a algunas personas, pero permitir automáticamente cualquier versión principal más allá de la versión 2 es un comportamiento más sorprendente.

También puedes excluir versiones específicas usando != .

Digamos que la versión 2.2.1 tiene un error que detiene la inicialización o un cambio que accidentalmente rompe la compatibilidad con versiones anteriores y por lo tanto, rompe tu gema.

Puede excluirlo de la siguiente manera:

# gemspec
spec.add_runtime_dependency 'library', '~&gt; 2', '!= 2.2.1'

Puedes agregar versiones adicionales agregándolas como un argumento adicional a add_runtime_dependency ; después de todo, tu último argumento es solo una matriz.

Requerir RubyGems

Resumen: no.

Esta línea…

require 'rubygems'

…no debería ser necesario en el código de tu gema, ya que RubyGems ya está cargado cuando se requiere una gema.

No tener require 'rubygems' en tu código significa que la gema se puede usar fácilmente sin necesidad de que se ejecute el cliente RubyGems.

Para obtener más información, consulta la publicación original de Ryan Tomayko sobre el tema.

Código de carga

En esencia, RubyGems existe para ayudarte a administrar Ruby’s $LOAD_PATH que es cómo la declaración require recoge el nuevo código.

Hay varias cosas que puedes hacer para asegurarte de que estás cargando el código de la manera correcta.

Respetar la ruta de carga global

Al empaquetar tus archivos de gemas, debes tener cuidado con lo que hay en tu directorio lib .

Cada gema que has instalado obtiene el directorio lib adjunto a su archivo $LOAD_PATH . Eso significa que cualquier archivo en el nivel superior del directorio lib podría ser requerido.

Por ejemplo, digamos que tenemos una gema denominada foo con la siguiente estructura:

.
└── lib
├── foo
│   └── cgi.rb
├── erb.rb
├── foo.rb
└── set.rb

Esto puede parecer inofensivo ya que tus archivos personalizados erb y set están dentro de tu gema.

Sin embargo, esto no es inofensivo, cualquiera que requiera esta gema no puede incorporar las clases ERB o Set proporcionadas por la biblioteca estándar de Ruby.

La mejor manera de evitar esto es mantener los archivos en un directorio diferente bajo lib .

La convención habitual es ser consistente y ponerlos en el mismo nombre de carpeta que el nombre de tu gema, por ejemplo lib/foo/cgi.rb .

Requerir archivos relativos entre sí

Las gemas no deberían tener que usarse __FILE__ para traer otros archivos de Ruby a tu gema.

El código como este es sorprendentemente común en las gemas:

require File.join(
File.dirname(__FILE__),
"foo", "bar")

O:

require File.expand_path(File.join(
File.dirname(__FILE__),
"foo", "bar"))

La solución es simple, solo requiere el archivo relativo a la ruta de carga:

require 'foo/bar'

O usar require_relative:

require_relative 'foo/bar'

La guía para hacer tu propia gema tiene un excelente ejemplo de este comportamiento en la práctica, incluido un conjunto de pruebas en funcionamiento.

El código de esa gema también está en GitHub .

Destrozando la ruta de carga

Las gemas no deben cambiar la variable $LOAD_PATH . RubyGems gestiona esto por ti. Un código como este no debería ser necesario:

lp = File.expand_path(File.dirname(__FILE__))
unless $LOAD_PATH.include?(lp)
$LOAD_PATH.unshift(lp)
end

O:

__DIR__ = File.dirname(__FILE__)
$LOAD_PATH.unshift __DIR__ unless
$LOAD_PATH.include?(__DIR__) ||
$LOAD_PATH.include?(File.expand_path(__DIR__))

Cuando RubyGems activa una gema, se agrega la carpeta lib de tu paquete a la lista $LOAD_PATH para que otra biblioteca o aplicación la requiera normalmente.

Es seguro asumir que puedes aplicar require sobre cualquier archivo de tu carpeta lib .

Gemas de presentación

Muchos desarrolladores de gemas tienen versiones de sus gema listas para ser probadas o realizar lanzamientos “perimetrales” antes del lanzamiento de una versión final de su gema.

RubyGems admite el concepto de versiones “prelanzamiento” que pueden ser betas, alfas o cualquier otra cosa que no esté lista como versión regular.

Aprovechar esto es fácil. Lo que necesitas es una o más letras en la versión de la gema.

Por ejemplo, así es como se vería el campo de una especificación de una versión preliminar de la gema:

Gem::Specification.new do |s|
s.name = "hola"
s.version = "1.0.0.pre"

Otros números de versiones preliminares pueden incluir 2.0.0.rc1 , o 1.5.0.beta.3 . Solo tienes que tener una letra y listo.

Estas gemas se pueden instalar con la bandera --pre así:

% gem list factory_girl -r --pre
*** REMOTE GEMS ***
factory_girl (2.0.0.beta2, 2.0.0.beta1)
factory_girl_rails (1.1.beta1)
% gem install factory_girl --pre
Successfully installed factory_girl-2.0.0.beta2
1 gem installed

Relacionados

Deja un comentario