ruby on rails - patron - Cómo implementar un modelo singleton
singleton c# (10)
Las probabilidades son buenas, no necesitas un singleton. Es desafortunado que uno de los peores hábitos de diseño para salir de la locura de los patrones sea también uno de los más comúnmente adoptados. Culpo a la desafortunada apariencia de simplicidad, pero estoy divagando. Si lo hubieran llamado el patrón "Global estático", estoy seguro de que la gente habría sido más tímida al usarlo.
Sugiero usar una clase contenedora con una instancia privada estática de la clase que desea usar para el singleton. No introducirás una pareja estrecha en todo tu código como lo harás con el singleton.
Algunas personas lo llaman un patrón monostate. Tiendo a pensar que es simplemente otro giro en el concepto de estrategia / agente ya que puede permitir una mayor flexibilidad mediante la implementación de diferentes interfaces para exponer / ocultar la funcionalidad.
Tengo un sitio en rieles y quiero tener una configuración para todo el sitio. Una parte de mi aplicación puede notificar al administrador por SMS si ocurre un evento específico. Este es un ejemplo de una característica que quiero configurable a través de la configuración de todo el sitio.
Así que estaba pensando que debería tener un modelo de configuración o algo así. Tiene que ser un modelo porque quiero poder has_many: contactos para la notificación por SMS.
El problema es que solo puede haber una publicación en la base de datos para el modelo de configuración. Entonces, estaba pensando en usar un modelo de Singleton, pero eso solo evita que se creen nuevos objetos, ¿verdad?
¿Debo seguir necesitando crear métodos getter y setter para cada atributo así:
def self.attribute=(param)
Model.first.attribute = param
end
def self.attribute
Model.first.attribute
end
¿Acaso no es la mejor práctica usar el atributo Modelo directamente, pero siempre crear una instancia y usar eso?
¿Qué debería hacer aquí?
No estoy seguro de que desperdicie la base de datos / gastos generales de ActiveRecord / Model para una necesidad tan básica. Estos datos son relativamente estáticos (supongo) y sobre la marcha los cálculos no son necesarios (incluidas las búsquedas en la base de datos).
Habiendo dicho eso, recomiendo que defina un archivo YAML con la configuración de su sitio y defina un archivo de inicializador que cargue las configuraciones en una constante. No tendrá casi tantas partes móviles innecesarias.
No hay ninguna razón por la que los datos no puedan simplemente quedarse en la memoria y ahorrarle una tonelada de complejidad. Las constantes están disponibles en todas partes, y no es necesario inicializarlas ni crear instancias. Si es absolutamente crítico que utilices una clase como singleton, te recomendaría hacer estas dos cosas:
- undef el método initialize / new
- definir solo los métodos self. * de esa manera no es posible para usted mantener un estado
Usar has_many :contacts
no significa que necesites un modelo. has_many
hace algo de magia, pero al final solo agrega un método con un contrato específico. No hay ninguna razón por la que no pueda implementar esos métodos (o algún subconjunto que necesite) para hacer que su modelo se comporte como si has_many :contacts
pero que realmente no utilizara un modelo (o modelo) ActiveRecord para el Contacto.
También puede consultar Configatron:
http://configatron.mackframework.com/
Configatron hace que la configuración de sus aplicaciones y scripts sea increíblemente fácil. Ya no es necesario utilizar constantes o variables globales. Ahora puede usar un sistema simple e indoloro para configurar su vida. Y, como es todo Ruby, ¡puedes hacer cualquier cosa loca que quieras!
No estoy de acuerdo con la opinión común: no hay nada de malo en leer una propiedad fuera de la base de datos. Puede leer el valor de la base de datos y congelarlo si lo desea, sin embargo, podría haber alternativas más flexibles a la congelación simple.
¿En qué se diferencia YAML de la base de datos? .. mismo ejercicio: externo a la configuración persistente del código de la aplicación.
Lo bueno del enfoque de la base de datos es que se puede cambiar sobre la marcha de forma más o menos segura (sin abrir y sobrescribir archivos directamente). Otra cosa agradable es que se puede compartir a través de la red entre los nodos del clúster (si se implementa correctamente).
Sin embargo, la pregunta sigue siendo cuál sería la forma correcta de implementar dicha configuración utilizando ActiveRecord.
Sé que este es un hilo viejo, pero solo necesitaba lo mismo y descubrí que hay una joya para esto: acts_as_singleton .
Las instrucciones de instalación son para Rails 2, pero también funciona muy bien con Rails 3.
También podría aplicar un máximo de un registro de la siguiente manera:
class AppConfig < ActiveRecord::Base
before_create :confirm_singularity
private
def confirm_singularity
raise Exception.new("There can be only one.") if AppConfig.count > 0
end
end
Esto anula el método ActiveRecord
para que explote si intenta crear una nueva instancia de la clase cuando ya existe.
A continuación, puede definir solo los métodos de clase que actúan en un registro:
class AppConfig < ActiveRecord::Base
attr_accessible :some_boolean
before_create :confirm_singularity
def self.some_boolean?
settings.some_boolean
end
private
def confirm_singularity
raise Exception.new("There can be only one.") if AppConfig.count > 0
end
def self.settings
first
end
end
class Constant < ActiveRecord::Base
after_initialize :readonly!
def self.const_missing(name)
first[name.to_s.downcase]
end
end
Constant :: FIELD_NAME
(Estoy de acuerdo con @ user43685 y no estoy de acuerdo con @Derek P; existen muchas buenas razones para mantener los datos de todo el sitio en la base de datos en lugar de un archivo yaml. Por ejemplo, su configuración estará disponible en todos los servidores web (si tener varios servidores web), los cambios a su configuración serán ACID, no tiene que perder tiempo implementando un contenedor YAML, etc.
En los rieles, esto es bastante fácil de implementar, solo tiene que recordar que su modelo debe ser un "singleton" en términos de base de datos, no en términos de objetos ruby.
La forma más fácil de implementar esto es:
- Agregue un nuevo modelo, con una columna por cada propiedad que necesite
- Agregue una columna especial llamada "singleton_guard" y valide que siempre es igual a "0", y márquela como única (esto exigirá que haya solo una fila en la base de datos para esta tabla)
- Agregue un método de ayuda estático a la clase de modelo para cargar la fila singleton
Entonces, la migración debería verse más o menos así:
create_table :app_settings do |t|
t.integer :singleton_guard
t.datetime :config_property1
t.datetime :config_property2
...
t.timestamps
end
add_index(:app_settings, :singleton_guard, :unique => true)
Y la clase de modelo debería verse más o menos así:
class AppSettings < ActiveRecord::Base
# The "singleton_guard" column is a unique column which must always be set to ''0''
# This ensures that only one AppSettings row is created
validates_inclusion_of :singleton_guard, :in => [0]
def self.instance
# there will be only one row, and its ID must be ''1''
begin
find(1)
rescue ActiveRecord::RecordNotFound
# slight race condition here, but it will only happen once
row = AppSettings.new
row.singleton_guard = 0
row.save!
row
end
end
end
En Rails> = 3.2.1 deberías poder reemplazar el cuerpo del getter "instancia" con una llamada a " first_or_create! "
Sencillo:
class AppSettings < ActiveRecord::Base
before_create do
self.errors.add(:base, "already one setting object existing") and return false if AppSettings.exists?
end
def self.instance
AppSettings.first_or_create!(...)
end
end