2009-11-22 11 views
5

que he tenido este modelo, que estaba trabajando bien:validates_presence_of causa after_initialize para ser llamado con un auto extraño

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    self.measured_on ||= Date.today 
    end 
end 

he añadido esta línea

validates_uniqueness_of :measured_on, :scope => :user_id 

y comenzaron a arrojar un error en validación. No es un error de validación, pero un error de Ruby:

>> w.valid? 
ActiveRecord::MissingAttributeError: missing attribute: measured_on 
    from /Users/pupeno/Projects/sano/app/models/weight.rb:8:in `after_initialize' 

He puesto una declaración depurador en after_initialize y me he dado cuenta de algo inesperado. Cuando creo un nuevo peso funciona como se esperaba y el objeto mismo en after_initialize es el peso esperado:

>> w = Weight.new 
/Users/pupeno/Projects/sano/app/models/weight.rb:9 
self.measured_on ||= Date.today 
(rdb:1) p self 
#<Weight id: nil, user_id: nil, weight: nil, measured_on: nil, created_at: nil, updated_at: nil> 
(rdb:1) c 
=> #<Weight id: nil, user_id: nil, weight: nil, measured_on: "2009-11-22", created_at: nil, updated_at: nil> 

Cuando corro w.valid? se pone raro after_initialize se llama de nuevo, no estoy seguro de por qué, y el objeto mismo es nada de lo que esperaba:

>> w.valid? 
/Users/pupeno/Projects/sano/app/models/weight.rb:9 
self.measured_on ||= Date.today 
(rdb:1) p self 
#<Weight id: 1> 
(rdb:1) p self.inspect 
"#<Weight id: 1>" 
(rdb:1) p self.class 
Weight(id: integer, user_id: integer, weight: float, measured_on: date, created_at: datetime, updated_at: datetime) 
(rdb:1) p self.measured_on 
ActiveRecord::MissingAttributeError Exception: missing attribute: measured_on 
(rdb:1) 

Parece que otro objeto de peso fue creada sin ningún atributo pero el conjunto de Identificación. ¿Alguna idea de por qué? ¿Es esto un error o el comportamiento esperado? ¿Estoy haciendo algo mal al configurar el medido encendido after_initialize?

Mi solución actual, en caso de que alguien está teniendo el mismo problema, es

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    validates_uniqueness_of :measured_on, :scope => :user_id 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    if self.has_attribute? :measured_on 
     self.measured_on ||= Date.today 
    end 
    end 
end 

pero me gustaría tener una solución adecuada.

Respuesta

6

Creo que está golpeando un error carriles recientemente he luchado con. Consulte This blog entry que enlaza con el error de faro relacionado.

Según tengo entendido, lo que está sucediendo es que algunos códigos de piezas anteriores hacen una "identificación de selección de nombre de tabla" para ver si existe una entrada o coincide. A continuación, el objeto almacena en caché que el único campo que existe para la tabla es "id". Su código se ejecuta y el valor de "atributos" es incorrecto, reflejando solo el campo de identificación.

Por lo que pude encontrar, esto solo sucedió cuando se golpeó esta ruta de código particular, y generalmente no alteró las cosas, excepto cuando se realizaban validaciones.

Lo que hice para evitarlo fue ajustar el código after_initialise en un comienzo/rescate del bloque ActiveRecord :: MissingAttributeError. Luego escribí una nota importante en la aplicación y encima de cada elemento que indica cuándo se lanza una nueva versión de los rieles, podríamos eliminar esto.

Sí, estoy seguro de que hay soluciones más elegantes.

def after_initialize 
    begin 
    # ... updates here 
    # self.unique_reference = UUIDTools::UUID.random_create.to_s 
    rescue ActiveRecord::MissingAttributeError 
    end 
end 

O también se podría hacer:

def after_initialize 
    if self.has_attribute? :measured_on 
    self.measured_on ||= Date.today 
    end 
end 
+0

El OP editó mi respuesta y creó la sección "has_attribute?: Measured_on" - No estoy 100% seguro de estar de acuerdo con ella después de mirar la fuente de los rieles - Sospecho que funciona debido a un efecto secundario, en lugar de por -diseño). Sin embargo, no estoy retrasando esa parte de la respuesta, ya que, quién sabe, puede ayudar a alguien. – oskarpearson

+0

Hoy me han mordido esto. Esta publicación y tu respuesta salvó mi tocino. Había olvidado que una de las desventajas de los lenguajes dinámicos es que no siempre tienes los mismos atributos en un objeto ... lo que podría ser bueno, pero también malo :) –

+0

Hoy lo he mordido. Wow, esto fue publicado aquí en 2009? Estoy impresionado de que no se haya corregido durante tanto tiempo. – Trejkaz

0

validates_uniqueness_of necesita escanear su base de datos para las entradas. ActiveRecord está cargando todas esas otras entradas como instancias de su modelo. Pero para reducir el uso del procesador/memoria, no está agregando métodos para los atributos, porque no debería necesitarlos para una verificación de existencia rápida. Sin embargo, sigue creando instancias de todos los registros existentes como instancias de su modelo, por lo que se llama after_initialize.

La solución es modificar las @attributes picadillo directamente en lugar de depender de descriptores de acceso:

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    validates_uniqueness_of :measured_on, :scope => :user_id 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    @attributes["measured_on"] ||= Date.today 
    end 
end 
+0

Estoy bastante seguro de que validates_uniqueness simplemente genera una instrucción SQL para verificar la existencia. No causa la creación de una instancia de todas las entradas de la tabla. – Mike

+0

Así es como se ve, ¿pero existe? en el extremo posterior de la validación \ _cada uno de los bloques en la fuente de validates \ _uniqueness \ _of aún crea una instancia de registro activa. – EmFi

2

Este problema está relacionado con el boleto carriles # 3165. Lea aquí, si está interesado en cómo y por qué ocurre esto

Acabo de pasar medio día en esto, antes de encontrar el ticket. Aunque estoy triste de que ha sido casi exactamente un año desde que se informó de esto, y no se ha fijado todavía, aquí está mi trabajo sencillo alrededor de mi escenario:

validates_uniqueness_of :email, :scope => :library_id 

def after_initialize 
    self.status ||= "Invited" 
end 

Esto provocará un 'MissingAttributeError' a se arrojará, si hay registros devueltos por la validación_unidad_de consulta. Mi solución sencilla es la siguiente:

def after_initialize 
    self.status ||= "Invited" if new_record? 
end 

Mientras que otras personas están teniendo problemas más complejos, esto debería resolver el caso simple, hasta que una solución real está comprometida en los rieles.

+0

Tenga en cuenta que si también necesita esto para los registros que no son nuevos, esta solución no es buena (en mi caso, rompió las pruebas). – Trejkaz

Cuestiones relacionadas