2010-01-21 25 views
6

Esto es más una cuestión de estilo, me pregunto qué hacen otras personas.Ruby Style Pregunta: almacenar hash constante con diferentes valores posibles

Digamos que tengo un campo en mi base de datos llamado "estado" para una publicación de blog. Y quiero que tenga varios valores posibles, como "borrador", "en espera de revisión" y "publicado", solo como un ejemplo.

Obviamente, no queremos "código duro" en estos valores mágicos cada vez, eso no sería SECO.

Así que lo que a veces lo que hago es algo como esto:

class Post 
    STATUS = { 
    :draft => "draft", 
    :awaiting_review => "awaiting review", 
    :posted => "posted" 
    } 

    ... 

end 

Entonces puedo escribir código que hace referencia a ella más tarde como STATUS[:draft] o Post::STATUS[:draft] etc.

Esto funciona bien, pero hay algunas cosas No me gusta sobre eso

  1. Si usted tiene un error tipográfico y llamar algo así como STATUS[:something_that_does_not_exist] no generará un error, simplemente devuelve nulo, y puede llegar a establecer esto en la base de datos, etc antes de que usted nota un error
  2. Se no se ve limpia o rubí-ish a escribir cosas como if some_var == Post::STATUS[:draft] ...

no sé, algo me dice que hay una manera mejor, pero sólo quería ver lo que hacen los demás. ¡Gracias!

Respuesta

6

Este es un problema común. Considerar algo como esto:

class Post < ActiveRecord::Base 
    validates_inclusion_of :status, :in => [:draft, :awaiting_review, :posted] 
    def status 
    read_attribute(:status).to_sym 
    end 
    def status= (value) 
    write_attribute(:status, value.to_s) 
    end 
end 

Puede utilizar un ActiveRecord plug-in de terceros llamado symbolize a hacer esto aún más fácil:

class Post < ActiveRecord::Base 
    symbolize :status 
end 
+0

Nice John, thanks! –

2

Se puede usar un método de clase para elevar una excepción en una clave que falta:

class Post 
    def self.status(key) 
    statuses = { 
     :draft => "draft", 
     :awaiting_review => "awaiting review", 
     :posted => "posted" 
    } 
    raise StatusError unless statuses.has_key?(key) 
    statuses[key] 
    end 
end 

class StatusError < StandardError; end 

Potencialmente, también se puede utilizar este método para almacenar los estados como números enteros en la base de datos cambiando sus cadenas en enteros (en el hash), convertir sus tipos de columnas y agregar un getter y un setter.

1

lo hago de esta manera:

class Post 
    DRAFT = "draft" 
    AWAITING_REPLY = "awaiting reply" 
    POSTED = "posted" 
    STATUSES = [DRAFT, AWAITING_REPLY, POSTED] 

    validates_inclusion_of :status, :in => STATUSES 
    ... 
end 

Este forma de obtener errores si escribe mal uno. Si tengo varios conjuntos de constantes, podría hacer algo como DRAFT_STATUS para distinguir.

+0

Me gusta, excepto que luego tendrá que referirse a él más tarde como 'ESTADO [0]' ¿correcto? En ese caso, no es tan legible. –

8

Puede utilizar Hash.new y darle un argumento bloque que se llama si una clave es desconocida.

class Post 
    STATUS = Hash.new{ |hash, key| raise("Key #{ key } is unknown")}.update(
    :draft => "draft", 
    :awaiting_review => "awaiting review", 
    :posted => "posted") 
end 

Es un poco complicado pero funciona.

irb(main):007:0> Post::STATUS[ :draft ] 
=> "draft" 
irb(main):008:0> Post::STATUS[ :bogus ] 
RuntimeError: Key bogus is unknown 
    from (irb):2 
    from (irb):8:in `call' 
    from (irb):8:in `default' 
    from (irb):8:in `[]' 
    from (irb):8 
+0

¡Interesante, gracias por publicarlo! –

+0

Me gusta este método. Sin dependencias externas. –

1

Eche un vistazo a la gema attribute_mapper.

Hay una related article que muestra cómo se puede manejar el problema de forma declarativa, como esto (tomado del artículo):

class Post < ActiveRecord::Base 
    include AttributeMapper 

    map_attribute :status, :to => { 
    :draft => 1, 
    :reviewed => 2, 
    :published => 3 
    } 
end 

... que se parece bastante elegante.

+0

Bastante bien. Es una lástima que no los agrupe en caso de que tenga múltiples atributos, como status [: draft] vs something_else [: draft] pero en general esta podría ser la mejor solución que he visto –

0

A pesar de que esta es una entrada antigua, para alguien tropezarse con esto, se puede utilizar el método fetch en Hash, que genera un error (cuando se pasa ningún defecto) si no se encuentra la clave dada.

STATUS = { 
    :draft => "draft", 
    :awaiting_review => "awaiting review", 
    :posted => "posted" 
} 

STATUS.fetch(:draft) #=> "draft" 
STATUS.fetch(:invalid_key) #=> KeyError: key not found: invalid_key 
Cuestiones relacionadas