ruby - una - plan de clase por competencias pdf
¿Es posible dar a un submódulo el mismo nombre que una clase de nivel superior? (3)
Aquí hay otro ejemplo divertido:
module SomeName
class Client
end
end
module Integrations::SomeName::Importer
def perform
...
client = ::SomeName::Client.new(...)
...
end
end
Eso produce:
block in load_missing_constant'': uninitialized constant Integrations::SomeName::Importer::SomeName (NameError)
Ruby (2.3.4) solo va a la primera aparición de "SomeName" que puede encontrar, no al nivel superior.
Una forma de Kernel.const_get(''SomeName'')
es usar un mejor anidamiento de módulos / clases (!!), o usar Kernel.const_get(''SomeName'')
Fondo:
- Ruby cree que estoy haciendo referencia a una constante de nivel superior incluso cuando especifico el espacio de nombres completo
- ¿Cómo me refiero al "camino completo" de un submódulo en ruby?
Aquí está el problema, reducido a un ejemplo mínimo:
# bar.rb
class Bar
end
# foo/bar.rb
module Foo::Bar
end
# foo.rb
class Foo
include Foo::Bar
end
# runner.rb
require ''bar''
require ''foo''
➔ ruby runner.rb ./foo.rb:2: warning: toplevel constant Bar referenced by Foo::Bar ./foo.rb:2:in `include'': wrong argument type Class (expected Module) (TypeError) from ./foo.rb:2 from runner.rb:2:in `require'' from runner.rb:2
Aquí hay un ejemplo más mínimo para demostrar este comportamiento:
class Bar; end
class Foo
include Foo::Bar
end
Salida:
warning: toplevel constant Bar referenced by Foo::Bar
TypeError: wrong argument type Class (expected Module)
Y aquí es aún más mínimo:
Bar = 0
class Foo; end
Foo::Bar
Salida:
warning: toplevel constant Bar referenced by Foo::Bar
La explicación es simple, no hay ningún error: no hay una Bar
en Foo
, y Foo::Bar
aún no está definido. Para que se defina Foo::Bar
, Foo
debe definirse primero. El siguiente código funciona bien:
class Bar; end
class Foo
module ::Foo::Bar; end
include Foo::Bar
end
Sin embargo, hay algo que es inesperado para mí. Los siguientes dos bloques se comportan de manera diferente:
Bar = 0
class Foo; end
Foo::Bar
produce una advertencia:
warning: toplevel constant Bar referenced by Foo::Bar
pero
Bar = 0
module Foo; end
Foo::Bar
produce un error:
uninitialized constant Foo::Bar (NameError)
Excelente; Su ejemplo de código es muy claro. Lo que tiene allí es una dependencia circular de jardín, oscurecida por las peculiaridades del operador de resolución de alcance de Ruby.
Cuando ejecutas el código de Ruby require ''foo''
, ruby encuentra foo.rb
y lo ejecuta, y luego encuentra foo/bar.rb
y lo ejecuta. Así que cuando Ruby encuentra su clase de Foo
y ejecuta include Foo::Bar
, busca una constante llamada Bar
en la clase de Foo
, porque eso es lo que Foo::Bar
denota. Cuando no encuentra uno, busca constantes denominadas Bar
otros ámbitos que lo encierran y, finalmente, lo encuentra en el nivel superior. Pero ese Bar
es una clase, y por eso no se puede include
d.
Incluso si pudieras persuadirte de que foo/bar.rb
antes de foo.rb
, no sería útil; module Foo::Bar
significa "encuentra la constante Foo
, y si es una clase o un módulo, comienza a definir un módulo dentro de él llamado Bar
". Foo
no se habrá creado aún, por lo que la demanda seguirá fallando.
Cambiar el nombre de Foo::Bar
a Foo::UserBar
tampoco servirá de nada, ya que el choque de nombres no tiene la culpa definitiva.
Entonces, ¿qué puedes hacer? En un nivel alto, tienes que romper el ciclo de alguna manera. Lo más simple es definir Foo
en dos partes, así:
# bar.rb
class Bar
A = 4
end
# foo.rb
class Foo
# Stuff that doesn''t depend on Foo::Bar goes here.
end
# foo/bar.rb
module Foo::Bar
A = 5
end
class Foo # Yep, we re-open class Foo inside foo/bar.rb
include Bar # Note that you don''t need Foo:: as we automatically search Foo first.
end
Bar::A # => 4
Foo::Bar::A # => 5
Espero que esto ayude.