Soy nuevo en Ruby y trato de descubrir cómo funciona la declaración de clase a continuación de Sequel gem detrás de escena.
class Enumeration < Sequel::Model(DB[:enumerations]); endTras una rápida investigación del código de Sequel gem, me parece que el método del módulo Sequel::Model devuelve una instancia de clase con un atributo de clase configurado. La instancia de retorno de Class se usa luego en la jerarquía de herencia, así que traté de probar mi comprensión a través del código;
module MySequel class MyModel module ClassMethods attr_accessor :table_name def model(table_name) klass = Class.new(self) klass.table_name = table_name puts klass.table_name # prints table_name, it does get set for the first class Class object klass klass end end extend ClassMethods end end class Enumeration < MySequel::MyModel.model(:enumerations); end class EnumerationValue < MySequel::MyModel.model(:enumeration_values); end p Enumeration.table_name # prints nil p EnumerationValue.table_name # prints nilSegún mi comprensión, la variable de clase table_name se establece al crear una instancia de Class y se propaga a su clase secundaria. Sin embargo, ese no parece ser el caso.
¿Puede alguien explicar el concepto detrás de Sequel::Model y el problema con mi implementación de muestra para lograr el mismo resultado?
El comportamiento en este ejemplo es similar al uso de las clases anónimas anteriores.
class TestModel @@table_name = 'test' def TestModel.table_name @@table_name end def TestModel.table_name=(value) @@table_name = value end end TestModel.table_name = 'posts' p TestModel.table_name # prints posts class ChildTestModel < TestModel; end puts Enumeration.table_name # prints nilLa biblioteca Sequel aparentemente está propagando la información sobre el conjunto de datos a través del método de llamada al módulo a la clase secundaria (a través de la clase anónima).
Sin embargo, no puedo entender; qué construcción del lenguaje Ruby permite que la clase de estado a continuación sepa que está restringida a un registro en las enumeraciones de tablas con el nombre establecido en 'estado'.
class Status < Sequel::Model(DB[:enumerations].where(name: 'status')); endLa biblioteca Sequel aparentemente está propagando la información sobre el conjunto de datos a través del método de llamada al módulo a la clase secundaria
Sí, de hecho. Sequel usa la devolución de llamada inherited para copiar las variables de instancia de clase a la subclase (ver lib/sequel/model/base.rb#843 ).
Aquí hay una versión muy básica para su clase de ejemplo:
module MySequel class MyModel module ClassMethods attr_accessor :table_name def model(table_name) klass = Class.new(self) klass.table_name = table_name klass end def inherited(subclass) instance_variables.each do |var| value = instance_variable_get(var) subclass.instance_variable_set(var, value) end end end extend ClassMethods end end Como resultado, las subclases tendrán sus variables de instancia de clase (aquí @table_name ) establecidas en los mismos valores que la clase principal (anónima):
class Enumeration < MySequel::MyModel.model(:enumerations); end class EnumerationValue < MySequel::MyModel.model(:enumeration_values); end p Enumeration.table_name #=> :enumerations p EnumerationValue.table_name #=> :enumeration_valuesEntonces, la mayoría del código es correcto y su suposición sobre cómo funciona todo es correcta. ¡Lo cual es realmente genial y definitivamente no es un nivel de principiante de Ruby!
Entonces, lo que no funciona. Cuando define una subclase de alguna clase, la subclase hereda todos los "métodos de clase" (entre comillas, ya que en realidad no existe tal cosa), pero las variables de instancia no se heredan (es decir, la variable de instancia de una instancia de una clase Clase ):
class A @foo = 1 end A.instance_variables #=> [:@foo] class B < A; end B.instance_variables #=> [] Eso significa que si invoca un setter en la clase anónima que creó en su método de model , establece la variable de instancia solo en esa clase. Y las subclases de esa clase anónima no heredan eso. Podrías ver que esto funcionaría:
Enumeration = MySequel::MyModel.model(:enumerations) EnumerationValue = MySequel::MyModel.model(:enumeration_values) Enumeration.table_name #=> :enumerations EnumerationValue.table_name #=> :enumeration_valuesY para ser honesto, realmente no hay mucha ganancia subclasificando sus clases anónimas aquí. Esto solo tendría sentido si también hubiera estado almacenando en caché esas clases y cuando llamadas separadas a ese método hubieran devuelto el mismo objeto Class dos veces.
Si aún desea que table_name sea heredable, debe ser muy explícito al respecto y, en lugar de usar attr_accessor , debe buscar directamente en una superclase:
attr_setter :table_name def table_name @table_name || (superclass.table_name if superclass.respond_to?(:table_name)) endHay algunos ajustes que tal vez quieras considerar aquí. La versión anterior
El nombre de la tabla se establece al definir Enumeration o EnumerationValue y se mantiene dentro de la clase principal (que es una clase anónima).
Es mucho más simple comprender el concepto, para mí personalmente, cuando lo comparo con la herencia prototípica de javascript.
Se puede acceder a través de la superclase de la clase Enumeration y EnumerationValue.
# frozen_string_literal: true module MySequel class MyModel module ClassMethods attr_accessor :table_name def model(table_name) klass = Class.new(self) klass.table_name = table_name klass end end extend ClassMethods end end class Enumeration < MySequel::MyModel.model(:enumerations); end class EnumerationValue < MySequel::MyModel.model(:enumeration_values); end p Enumeration.superclass.table_name # prints :enumerations p EnumerationValue.superclass.table_name # prints :enumeration_values