2011-03-31 4 views
6

que tienen un modelo que se parece a esto:¿Cómo manejar apropiadamente los atributos cambiados en un gancho antes de guardar?

class StopWord < ActiveRecord::Base 
    UPDATE_KEYWORDS_BATCH_SIZE = 1000 

    before_save :update_keywords 

    def update_keywords 
    offset = 0 
    max_id = ((max_kw = Keyword.first(:order => 'id DESC')) and max_kw.id) || 0 
    while offset <= max_id 
     begin 
     conditions = ['id >= ? AND id < ? AND language = ? AND keyword RLIKE ?', 
      offset, offset + UPDATE_KEYWORDS_BATCH_SIZE, language] 

     # Clear keywords that matched the old stop word 
     if @changed_attributes and (old_stop_word = @changed_attributes['stop_word']) and not @new_record 
      Keyword.update_all 'stopword = 0', conditions + [old_stop_word] 
     end 

     Keyword.update_all 'stopword = 1', conditions + [stop_word] 

     rescue Exception => e 
     logger.error "Skipping batch of #{UPDATE_KEYWORDS_BATCH_SIZE} keywords at offset #{offset}" 
     logger.error "#{e.message}: #{e.backtrace.join "\n "}" 

     ensure 
     offset += UPDATE_KEYWORDS_BATCH_SIZE 
     end 
    end 
    end 
end 

Esto funciona muy bien, ya que las pruebas unitarias muestran:

class KeywordStopWordTest < ActiveSupport::TestCase 
    def test_stop_word_applied_on_create 
    kw = Factory.create :keyword, :keyword => 'foo bar baz', :language => 'en' 
    assert !kw.stopword, 'keyword is not a stop word by default' 

    sw = Factory.create :stop_word, :stop_word => kw.keyword.split(' ')[1], :language => kw.language 
    kw.reload 
    assert kw.stopword, 'keyword is a stop word' 
    end 

    def test_stop_word_applied_on_save 
    kw = Factory.create :keyword, :keyword => 'foo bar baz', :language => 'en', :stopword => true 
    sw = Factory.create :keyword_stop_word, :stop_word => kw.keyword.split(' ')[1], :language => kw.language 

    sw.stop_word = 'blah' 
    sw.save 

    kw.reload 
    assert !kw.stopword, 'keyword is not a stop word' 
    end 
end 

Pero limpiando con la variable @changed_attributes ejemplo, sólo se siente mal. ¿Existe una forma estándar de Rails-y de obtener el valor anterior de un atributo que se está modificando en un guardado?

Actualización: Gracias aDouglas F Shearer y Simone Carletti (que al parecer prefiere de Murphy a Guinness), tengo una solución más limpia:

def update_keywords 
    offset = 0 
    max_id = ((max_kw = Keyword.first(:order => 'id DESC')) and max_kw.id) || 0 
    while offset <= max_id 
     begin 
     conditions = ['id >= ? AND id < ? AND language = ? AND keyword RLIKE ?', 
      offset, offset + UPDATE_KEYWORDS_BATCH_SIZE, language] 

     # Clear keywords that matched the old stop word 
     if stop_word_changed? and not @new_record 
      Keyword.update_all 'stopword = 0', conditions + [stop_word_was] 
     end 

     Keyword.update_all 'stopword = 1', conditions + [stop_word] 

     rescue StandardError => e 
     logger.error "Skipping batch of #{UPDATE_KEYWORDS_BATCH_SIZE} keywords at offset #{offset}" 
     logger.error "#{e.message}: #{e.backtrace.join "\n "}" 

     ensure 
     offset += UPDATE_KEYWORDS_BATCH_SIZE 
     end 
    end 
    end 

Gracias, chicos!

Respuesta

23

Quiere ActiveModel::Dirty.

Ejemplos:

person = Person.find_by_name('Uncle Bob') 
person.changed?  # => false 

person.name = 'Bob' 
person.changed?  # => true 
person.name_changed? # => true 
person.name_was  # => 'Uncle Bob' 
person.name_change # => ['Uncle Bob', 'Bob'] 

documentación completa: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

+0

La documentación dice que tienes que 'include' el módulo, y luego unos pasos más, pero no es necesario - que sólo puede llamar a los métodos según lo escrito por [Douglas] (http://stackoverflow.com/users/13831/douglas-f-shearer) – sameers

+0

bastante interesante; algunos detalles jóvenes no pensarán que están disponibles al principio. Pero parece funcionar sin incluir nada – Ben

1

Usted está utilizando la función correcta, pero la API mal. Debe #changes y #changed?.

Ver this article y official API.

Dos notas adicionales acerca de su código:

  1. Nunca rescate de excepción cuando realmente quieren rescatar a los errores de ejecución. Esto es estilo Java. Debería rescatar StandardError en su lugar porque los errores menores normalmente son error de compilación o error del sistema.
  2. No necesita el bloque de inicio en este caso.

    def update_keywords 
        ... 
        rescue => e 
        ... 
        ensure 
        ... 
    end 
    
+0

Gracias por la nota sobre el manejo de excepciones. Ahora estoy rescatando StandardError. Sin embargo, parece que necesito el bloque begin. 'Mientras ... rescatar ... asegurar ... fin' no funciona para mí. ¿O quisiste decir algo más? –

+0

Vaya, lo siento. No vi que el bloque begin/rescue/end estuviera dentro del tiempo. En este caso necesitas el comienzo. Lo siento. –

Cuestiones relacionadas