2008-12-30 10 views
21

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.Cómo implementar un modelo singleton

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?

¿Todavía necesita crear métodos get y set para cada atributo de este modo:

def self.attribute=(param) 
    Model.first.attribute = param 
end 

def self.attribute 
    Model.first.attribute 
end 

es que acaso no las mejores prácticas para utilizar Model.attribute directamente, sino siempre crear una instancia del mismo y el uso que se ?

¿Qué debo hacer aquí?

Respuesta

7

No estoy seguro de que desperdicie la base de datos/la sobrecarga 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, recomendaría 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 mucha complejidad. Las constantes están disponibles en todas partes, y no es necesario inicializarlas ni crear instancias. Si es absolutamente crítico que usted utiliza una clase como un conjunto unitario, recomiendo hacer estas dos cosas:.

  1. undef el nuevo método/initialize
  2. sólo definen auto * métodos de esa manera, no es posible para Para mantener un estado
+8

Usando una. El archivo yml es inflexible y no se presta bien a un usuario (en lugar del autor del sitio) que puede actualizar la configuración. – Undistraction

3

Las probabilidades son buenas, no necesita 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 utilizar 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 monoestacionario. 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.

1

El uso de has_many :contacts no significa que necesite 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 has_many :contacts pero que no use un modelo (o modelo) ActiveRecord para Contact.

1

Es posible que también echa un vistazo a Configatron:

http://configatron.mackframework.com/

Configatron hace que la configuración de las aplicaciones y scripts 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!

23

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.

+1

Estoy leyendo porque tengo un desafío similar, pero estoy de acuerdo en que un archivo YAML es una mala elección. Mi sitio tendrá múltiples usuarios con derechos de administrador que pueden editar la configuración del sitio, y no puedo esperar que editen los archivos de configuración. – Jeff

46

(Estoy de acuerdo con @ user43685 y no estoy de acuerdo con @Derek P; hay 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 toda la web servidores (si tiene varios servidores web); los cambios a su configuración serán ACID; no tiene que perder tiempo implementando un contenedor YAML, etc.)

En rieles, esto es bastante fácil de implementar, usted 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:

  1. Añadir un nuevo modelo, con una columna para cada propiedad que necesita
  2. Añadir una columna especial llamado "singleton_guard", y validar que siempre es igual a "0", y lo marca como únicos (esto va a hacer cumplir que sólo hay una fila en la base de datos para esta tabla)
  3. Añadir un método de ayuda estática a la clase de modelo para cargar la fila Singleton

Así que la migración debe ser algo como esto:

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 del modelo debería ser algo como esto:

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 debe ser capaz de reemplazar el cuerpo de el getter de "instancia" con una llamada a "first_or_create!"

+3

En caso de que le ahorre a alguien un par de minutos, recomendaría contra un modelo llamado "Config": http://oldwiki.rubyonrails.org/rails/pages/ReservedWords – Ricky

+0

Gracias, @Ricky, he cambiado el nombre del modelo – Rich

+3

¡Esas palabras reservadas te atraparán todo el tiempo! ¿Por qué no – Ricky

6

Sé que este es un hilo viejo, pero solo necesitaba lo mismo y descubrí que hay una gema para esto: acts_as_singleton.

Las instrucciones de instalación son para Rails 2, pero también funcionan muy bien con Rails 3.

9

También podría cumplir 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 reemplaza el método ActiveRecord de modo que va a explotar si intenta crear una nueva instancia de la clase cuando ya existe.

A continuación, puede pasar a definir sólo los métodos de la clase que actúan sobre la discográfica:

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 
+0

¿La referencia a la clase 'ShopGlobalSetting' es intencional? ¿La cuenta de 'AppConfig' en realidad le mostraría que esta clase tiene una instancia? –

+0

No, debería ser 'AppConfig'. Buena atrapada; Lo cambiaré. Y sí, la llamada de conteo debería funcionar. – hoffm

0
class Constant < ActiveRecord::Base 
    after_initialize :readonly! 

    def self.const_missing(name) 
    first[name.to_s.downcase] 
    end 
end 

constante :: FIELD_NAME

1

simple:

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 
Cuestiones relacionadas