Как правильно обрабатывать измененные атрибуты в хуке Rails before_save? - PullRequest
7 голосов
/ 31 марта 2011

У меня есть модель, которая выглядит так:

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

Это прекрасно работает, как показывают юнит-тесты:

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

Но смешивание с переменной экземпляра @changed_attributes кажется неправильным. Существует ли стандартный Rails-y способ получить старое значение атрибута, который изменяется при сохранении?

Обновление: Благодаря Дугласу Ф. Ширеру и Симоне Карлетти (который явно предпочитает Мерфи Гиннесу), у меня есть более чистое решение:

  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

Спасибо, ребята!

Ответы [ 2 ]

23 голосов
/ 31 марта 2011

Вы хотите ActiveModel::Dirty.

Примеры:

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']

Полная документация: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

1 голос
/ 31 марта 2011

Вы используете правильную функцию, но не тот API.Вы должны #changes и #changed?.

См. эту статью и официальный API .

Два дополнительных замечания о вашем коде:

  1. Никогда не спасайте Exception напрямую, если вы действительно хотите спасти ошибки выполнения.Это стиль Java.Вместо этого вы должны спасти StandardError, потому что более низкие ошибки обычно являются ошибкой компиляции или системной ошибкой.
  2. В этом случае вам не нужен начальный блок.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...